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>
This commit is contained in:
@@ -4,8 +4,10 @@ import { useEffect, useRef, useState } from 'react';
|
||||
import { useCanvasStore, useLayerStore, useToolStore } from '@/store';
|
||||
import { useHistoryStore } from '@/store/history-store';
|
||||
import { useSelectionStore } from '@/store/selection-store';
|
||||
import { useTextStore } from '@/store/text-store';
|
||||
import { drawMarchingAnts } from '@/lib/selection-utils';
|
||||
import { getContext, drawGrid, drawCheckerboard } from '@/lib/canvas-utils';
|
||||
import { renderText } from '@/lib/text-utils';
|
||||
import { DrawCommand } from '@/core/commands';
|
||||
import {
|
||||
PencilTool,
|
||||
@@ -25,6 +27,7 @@ import {
|
||||
} from '@/tools';
|
||||
import type { PointerState } from '@/types';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { OnCanvasTextEditor } from './on-canvas-text-editor';
|
||||
|
||||
// Tool instances
|
||||
const tools: Record<string, BaseTool> = {
|
||||
@@ -66,6 +69,7 @@ export function CanvasWithTools() {
|
||||
const { activeTool, settings } = useToolStore();
|
||||
const { executeCommand } = useHistoryStore();
|
||||
const { activeSelection, selectionType, isMarching } = useSelectionStore();
|
||||
const { textObjects, editingTextId } = useTextStore();
|
||||
const [marchingOffset, setMarchingOffset] = useState(0);
|
||||
|
||||
const [isPanning, setIsPanning] = useState(false);
|
||||
@@ -129,6 +133,27 @@ export function CanvasWithTools() {
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
|
||||
// Draw text objects (skip the one being edited)
|
||||
textObjects.forEach((textObj) => {
|
||||
// Don't render text that's currently being edited
|
||||
if (editingTextId && textObj.id === editingTextId) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderText(ctx, textObj.x, textObj.y, {
|
||||
text: textObj.text,
|
||||
fontFamily: textObj.fontFamily,
|
||||
fontSize: textObj.fontSize,
|
||||
fontStyle: textObj.fontStyle,
|
||||
fontWeight: textObj.fontWeight,
|
||||
color: textObj.color,
|
||||
align: textObj.align,
|
||||
baseline: textObj.baseline,
|
||||
lineHeight: textObj.lineHeight,
|
||||
letterSpacing: textObj.letterSpacing,
|
||||
});
|
||||
});
|
||||
|
||||
// Draw grid if enabled (only within canvas bounds)
|
||||
if (showGrid) {
|
||||
drawGrid(ctx, gridSize, 'rgba(0, 0, 0, 0.15)', width, height);
|
||||
@@ -141,7 +166,7 @@ export function CanvasWithTools() {
|
||||
|
||||
// Restore context state
|
||||
ctx.restore();
|
||||
}, [layers, width, height, zoom, offsetX, offsetY, showGrid, gridSize, backgroundColor, selection, pointer, activeSelection, isMarching, marchingOffset]);
|
||||
}, [layers, width, height, zoom, offsetX, offsetY, showGrid, gridSize, backgroundColor, selection, pointer, activeSelection, isMarching, marchingOffset, textObjects, editingTextId]);
|
||||
|
||||
// Marching ants animation
|
||||
useEffect(() => {
|
||||
@@ -353,6 +378,9 @@ export function CanvasWithTools() {
|
||||
ref={canvasRef}
|
||||
className="absolute inset-0"
|
||||
/>
|
||||
|
||||
{/* On-canvas text editor */}
|
||||
<OnCanvasTextEditor />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user