6.2 KiB
6.2 KiB
Plan: Photopea-Style Inline Text Tool
Goal
Replace the current modal/separate text dialog with a direct-on-canvas inline text editor that is dedicated to the Text tool and behaves like Photopea/Photoshop.
Design Decisions (resolved)
| Topic | Decision |
|---|---|
| Activation | Text tool active → click on canvas opens an editable text box at the clicked point. |
| Box position | Clicked point is the top-left corner of the text box; alignment controls internal line alignment only. |
| Box size | Box grows/shrinks dynamically with the typed text; height expands for multi-line content. |
| Visual style | Thin-framed editable box with caret, rendered on top of the canvas. |
| Live preview | Every keystroke updates the underlying Text layer through the engine API (set_text_layer_text). |
| Accept | Ctrl/Cmd+Enter or the toolbar Accept ✓ button finalizes the edit. |
| Cancel | Escape or the toolbar Cancel ✕ button discards the draft layer (ToolTextCancelled). |
| Toolbar | Accept/Cancel buttons are shown only in the top menu bar and only while the Text tool is active and an edit is in progress. |
| Tool switching guard | While a text edit is active, switching to another tool is blocked until Accept or Cancel is pressed. |
| Re-edit existing layer | Double-click a text layer, or click it while the Text tool is active, enters edit mode for that layer. |
| Non-text layers | Clicking a non-text layer while editing cancels the draft. |
Affected code boundaries
hcie-egui-app/crates/hcie-gui-egui/src/canvas/text_overlay.rs— inline editor widget.hcie-egui-app/crates/hcie-gui-egui/src/canvas/mod.rs— hook overlay, double-click detection, Text-tool click on existing text layer.hcie-egui-app/crates/hcie-gui-egui/src/app/panels.rs— add Accept/Cancel to the top menu bar when Text tool + edit active.hcie-egui-app/crates/hcie-gui-egui/src/app/mod.rs— event handling, tool-change guard, Accept/Cancel events.hcie-egui-app/crates/hcie-gui-egui/src/app/text_editor.rs— keep properties panel live-preview controls.
Implementation tasks
-
Inline text box (
canvas/text_overlay.rs)- Use
egui::TextEdit::multilinebound tostate.text_edit.draft_text. - Make the box auto-sized: remove fixed width/height and let egui measure content.
- Add a visible frame (thin border + subtle fill) so the user sees the edit area.
- Place top-left at
(canvas_rect.min.x + edit.canvas_x * zoom, canvas_rect.min.y + edit.canvas_y * zoom). - On change, call
doc.engine.set_text_layer_text(layer_id, text)and pushRenderRequested. - Keyboard:
Enter→ new line.Ctrl/Cmd+Enter→ pushToolTextAccepted.Escape→ already handled globally, pushesToolTextCancelled.
- Use
-
Top toolbar Accept/Cancel (
app/panels.rs/app/mod.rs)- In
show_menu_bar, after the right window-controls cluster or in a dedicated center/right slot, render two small buttons only whenstate.active_tool == Tool::Text && state.text_edit.active. - Accept: green checkmark / “Accept” label; pushes
ToolTextAccepted. - Cancel: red cross / “Cancel” label; pushes
ToolTextCancelled. - Keep existing layout stable when buttons are hidden.
- In
-
Event handling updates (
app/mod.rs)ToolTextStart { x, y }: already creates a draft Text layer and opens edit. Keep behavior.ToolTextAccepted: finalize, clear edit state.ToolTextCancelled: delete draft layer, clear edit state.TextLayerSelected(id): load existing text layer into edit state.ToolChanged(new_tool)/LayerSelected(id):- If
state.text_edit.active, do not apply the change; optionally show a status message like “Finish text edit first”. - Only allow the change after
ToolTextAcceptedorToolTextCancelled.
- If
-
Canvas interaction updates (
canvas/mod.rs)- Detect double-click on the canvas.
- If double-clicked point is over a text layer (use engine
get_pixelor hit-test via layer bounds), pushTextLayerSelected(layer_id). - If Text tool active and single-click lands on an existing text layer, also push
TextLayerSelectedinstead of creating a new draft. - If Text tool active and single-click lands on empty canvas, push
ToolTextStart(already implemented).
-
Properties panel (
app/text_editor.rs)- Keep current live-preview controls (text content, font, size, color, angle, alignment, orientation).
- Remove Accept/Cancel from here because they now live in the top toolbar.
- Ensure changes while editing update the draft layer and request a render.
-
State consistency
- When
TextLayerSelectedloads an existing layer, populateTextEditStatefromLayerData::Text. - When Accept is pressed, ensure the final text is written to the layer and the edit state is cleared.
- When Cancel is pressed, delete the draft layer only if it was newly created; if editing an existing layer, Cancel should revert to the original text.
- To support safe Cancel for existing layers, store an
original_textsnapshot inTextEditStatewhen entering edit mode.
- When
Validation plan
- Build:
cargo build -p hcie-gui-egui - Regression tests:
cargo test -p hcie-engine-api --test visual_regression --test performance_stroke_4k -- --nocapture - Manual GUI checks:
- Select Text tool, click canvas → inline box appears and typing shows live text on canvas.
- Press Ctrl+Enter or toolbar Accept → edit closes, layer stays visible.
- Click canvas again → new draft. Press Escape or toolbar Cancel → draft disappears.
- While editing, press B/Brush shortcut or click another tool → nothing happens, status message appears.
- Create a text layer, accept it, then double-click it → edit re-opens with original text.
- Edit existing layer, change text, press Cancel → text reverts to original.
- Change font, size, color, angle, alignment, orientation in Properties panel → canvas updates live.
Open / out-of-scope
- Warp, 3D and other non-destructive text effects remain metadata-only and will be rasterized in a follow-up phase.
- PSD/XCF/Krita text-layer round-trip import is out of scope for this plan; the data structures are already prepared for it.