Files
paint-ui/tools/text-tool.ts
Sebastian Krüger 69a468141c feat(text-tool): implement Photoshop-style on-canvas text editor
- Replaced modal dialog with inline on-canvas text editor
- Text objects stored as editable entities (non-rasterized)
- Live preview with transparent textarea overlay
- Click on existing text to re-edit
- Drag transform handles to move text
- Auto-commit on click outside (via overlay)
- Text selection with visible highlight
- Hidden original text during editing to prevent double vision
- Position alignment fixes for editing existing text
- Keyboard shortcuts: Ctrl+Enter to commit, Escape to cancel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 14:31:47 +01:00

85 lines
2.2 KiB
TypeScript

import { BaseTool } from './base-tool';
import type { PointerState } from '@/types';
import { useTextStore } from '@/store/text-store';
import { useLayerStore } from '@/store/layer-store';
import { useHistoryStore } from '@/store/history-store';
import { DrawCommand } from '@/core/commands/draw-command';
import { renderText } from '@/lib/text-utils';
/**
* Text tool - Click to place text on canvas
*/
export class TextTool extends BaseTool {
constructor() {
super('Text');
}
onPointerDown(pointer: PointerState): void {
const layer = this.getActiveLayer();
if (!layer) return;
const { activateOnCanvasEditor, getTextObjectAt } = useTextStore.getState();
// Check if clicking on existing text object
const existingText = getTextObjectAt(pointer.x, pointer.y, layer.id);
if (existingText) {
// Edit existing text
activateOnCanvasEditor(pointer.x, pointer.y, existingText.id);
} else {
// Create new text
activateOnCanvasEditor(pointer.x, pointer.y);
}
}
onPointerMove(): void {
// No-op for text tool
}
onPointerUp(): void {
// No-op for text tool
}
getCursor(): string {
return 'text';
}
/**
* Render text on canvas (called from dialog)
*/
static renderTextOnCanvas(x: number, y: number): void {
const layer = TextTool.getActiveLayerStatic();
if (!layer?.canvas) return;
const ctx = layer.canvas.getContext('2d');
if (!ctx) return;
const { settings } = useTextStore.getState();
// Create draw command for history
const drawCommand = new DrawCommand(layer.id, 'Add Text');
// Render text
renderText(ctx, x, y, settings);
// Add to history
drawCommand.captureAfterState();
const { executeCommand } = useHistoryStore.getState();
executeCommand(drawCommand);
// Update layer to trigger re-render
const { updateLayer } = useLayerStore.getState();
updateLayer(layer.id, { updatedAt: Date.now() });
}
private getActiveLayer() {
const { activeLayerId, layers } = useLayerStore.getState();
return layers.find((l) => l.id === activeLayerId);
}
private static getActiveLayerStatic() {
const { activeLayerId, layers } = useLayerStore.getState();
return layers.find((l) => l.id === activeLayerId);
}
}