feat: text tool UX, image crop, text format toolbar, double-click focus
- Text tool: click-to-place immediately opens inline editor, auto-switches back to select tool. Empty text cleanup on save. - Crop: select image + press C (or right-click > Crop) to enter crop mode. 8 drag handles with rule-of-thirds grid, dimmed outside area. Enter confirms, Escape cancels. Non-destructive (stored as normalized rect). - Text format toolbar: appears when text items selected, with font size +/-, font family dropdown, and color picker with presets. - Double-click image: zoom-to-fit (PureRef-style focus) - Entrance animation: replaced bounce with simple fade-in (no delay before items become interactive) - TextEditor: allow empty text (caller handles cleanup)
This commit is contained in:
@@ -13,6 +13,7 @@ import type { SelectionManager } from './SelectionManager';
|
||||
import { Text, TextStyle } from 'pixi.js';
|
||||
import { DrawingSprite } from './sprites/DrawingSprite';
|
||||
import type { DrawingObject } from './scene-format';
|
||||
import { TextEditor } from './TextEditor';
|
||||
|
||||
export enum ToolType {
|
||||
SELECT = 'SELECT',
|
||||
@@ -44,6 +45,10 @@ export interface ToolContext {
|
||||
onChange: () => void;
|
||||
/** Broadcast only specific changed elements (lightweight, for live drawing). */
|
||||
broadcastElements?: (ids: string[]) => void;
|
||||
/** Text editor instance for inline editing. */
|
||||
textEditor?: TextEditor;
|
||||
/** Switch back to select tool after placing text. */
|
||||
switchToSelect?: () => void;
|
||||
}
|
||||
|
||||
export function activateTool(
|
||||
@@ -95,7 +100,7 @@ export function activateTool(
|
||||
locked: false,
|
||||
name: '',
|
||||
visible: true,
|
||||
text: 'Type here',
|
||||
text: ' ', // placeholder — will be replaced by user input
|
||||
fontSize: opts.fontSize!,
|
||||
fill: opts.color!,
|
||||
fontFamily: 'sans-serif',
|
||||
@@ -106,6 +111,33 @@ export function activateTool(
|
||||
ctx.broadcastElements?.([textData.id]);
|
||||
ctx.onChange();
|
||||
|
||||
// Immediately open inline editor on the new text item
|
||||
const item = scene.getById(textData.id);
|
||||
if (item && ctx.textEditor) {
|
||||
// Find the canvas DOM container
|
||||
const canvasEl = container.querySelector('canvas');
|
||||
const domContainer = canvasEl?.parentElement ?? container;
|
||||
|
||||
// Small delay to let PixiJS render the text sprite
|
||||
setTimeout(() => {
|
||||
ctx.textEditor!.startEditing(item, viewport, domContainer, () => {
|
||||
// If user saved empty text, remove the item
|
||||
const text = (item.data as any).text?.trim();
|
||||
if (!text) {
|
||||
scene.removeItem(item.id, true);
|
||||
}
|
||||
ctx.broadcastElements?.([item.id]);
|
||||
ctx.onChange();
|
||||
});
|
||||
// Clear placeholder so user types from scratch
|
||||
const ta = document.querySelector('textarea');
|
||||
if (ta) { ta.value = ''; }
|
||||
}, 50);
|
||||
}
|
||||
|
||||
// Switch back to select tool after placing
|
||||
ctx.switchToSelect?.();
|
||||
|
||||
// Remove handler after placing text
|
||||
container.removeEventListener('pointerdown', onClick);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user