|
|
Donner SVG Editor & Engine
SVG-native editor and embeddable SVG2 + CSS3 engine in C++20, with GPU (WebGPU) and compact CPU renderers, built for correctness, security, and performance.
|
In-canvas text authoring with a live caret. More...
#include "donner/editor/TextTool.h"
Classes | |
| struct | EditingChrome |
| Caret + frame chrome for the active editing session, in document space. More... | |
| struct | DragPreviewChrome |
| Drag-to-create preview chrome (document space): the live box being dragged out, the first text baseline it would create (one default font size below the box top, clamped into the box), and an I-beam marker at the future caret position. Distinct from the selection marquee so the gesture reads as "creating a text box". More... | |
Public Types | |
| enum class | CaretMove { Left , Right , Up , Down , LineStart , LineEnd } |
| Caret movement gestures. | |
Public Member Functions | |
| void | onMouseDown (EditorApp &editor, const Vector2d &documentPoint, MouseModifiers modifiers) override |
| Begin a gesture: a press on the session frame's transform handles starts a frame resize (reflow - glyphs never scale) or a rotate; a click inside the session's text moves the caret (clicks inside the frame but off every glyph land the caret at the end); otherwise commit any active session, then either open an editing session on the existing <text> under the click (caret at the clicked character) or start a box drag on empty canvas. | |
| void | onMouseMove (EditorApp &editor, const Vector2d &documentPoint, bool buttonHeld) override |
| Extend the box drag or the active frame gesture. | |
| void | onMouseUp (EditorApp &editor, const Vector2d &documentPoint) override |
| Finish the gesture: a drag creates box text and a double-click places point text, opening the editing session with the caret at the start; a plain click on empty canvas creates nothing. Releasing a frame gesture keeps the editing session open. | |
| bool | isEditing () const |
| True while an in-canvas editing session (with caret) is active. | |
| bool | isDraggingBox () const |
| True while a text-box drag is in progress. | |
| bool | isAdjustingFrame () const |
| True while a frame handle gesture (frame resize or rotate) is active. The editing session stays open underneath it. | |
| bool | isRotatingFrame () const |
| True while the active frame gesture is a rotate (subset of isAdjustingFrame); drives the rotate cursor during the drag. | |
| bool | isResizingFrame () const |
| True while the active frame gesture is a resize. | |
| SelectionTransformCorner | frameCorner () const |
| Corner of the active frame gesture, for cursor feedback. | |
| SelectionTransformHandleIntent | frameHandleIntentAt (const Vector2d &documentPoint, double pixelsPerDocUnit, bool includeRotate) const |
Hit-test the session frame's transform handles at documentPoint for hover cursor feedback: Resize over a corner handle, Rotate in the ring outside it, None elsewhere or when no session is editing. The test runs in the text's local space, so on a rotated frame the handle zones track the ORIENTED corners, not the axis-aligned envelope. Reads the session's computed ink bounds for point text - call only while the async render worker is idle. Hidden point-text chrome is not hit-testable. | |
| const std::optional< Box2d > & | dragBoxDoc () const |
| The box being dragged, for drag-preview chrome. | |
| std::optional< DragPreviewChrome > | dragPreviewChrome () const |
| Text-box drag preview chrome (frame + first baseline + I-beam marker), or nullopt when no box drag has produced a live rectangle yet. | |
| void | notifyPointerMoved (const Vector2d &documentPoint) |
| Reveal point-text frame chrome after pointer movement. Box-text frames are always visible, so this is a no-op for them. | |
| void | insertCodepoint (EditorApp &editor, char32_t codepoint) |
| Insert one code point at the caret. | |
| void | insertCodepoints (EditorApp &editor, std::span< const char32_t > codepoints) |
| Insert a frame's queued code points with one DOM synchronization. | |
| void | insertNewline (EditorApp &editor) |
| Insert a hard line break at the caret. | |
| void | backspace (EditorApp &editor) |
| Delete the code point before the caret. | |
| void | deleteForward (EditorApp &editor) |
| Delete the code point after the caret. | |
| void | moveCaret (EditorApp &editor, CaretMove move) |
| Move the caret. | |
| void | toggleBold (EditorApp &editor) |
| Toggle font-weight: bold on the session's text element. | |
| void | toggleItalic (EditorApp &editor) |
| Toggle font-style: italic on the session's text element. | |
| void | toggleUnderline (EditorApp &editor) |
| Toggle text-decoration: underline on the session's text element. | |
| bool | commit (EditorApp &editor) |
| Commit the active session as one undoable operation. An empty session deletes the created element, restoring the pre-session document and selection. | |
| void | cancel () |
| Reset tool state without touching the document (tool destruction). | |
| std::optional< EditingChrome > | editingChrome (EditorApp &editor) const |
| Caret + frame chrome for the active session, or nullopt when not editing. Reads the element's computed character geometry - call only while the async render worker is idle. | |
| bool | caretBlinkVisible () const |
| True while the blinking caret is currently in its visible phase. Always true right after a caret-affecting edit (typing, caret movement, session open) because those reset the blink phase. | |
| std::optional< float > | nextCaretBlinkWakeSeconds () const |
| Seconds until the caret's blink phase next flips, or nullopt when no editing session is active. Feeds the shell's idle-wake schedule so blink redraws happen via overlay-chrome recapture only - the timed wake never forces a full-scene re-render. | |
| std::optional< float > | nextPointFrameFadeWakeSeconds () const |
| Wake cadence while a point-text frame is fading after input. | |
| const std::u32string & | sessionContent () const |
| Logical content of the active session (hard breaks as ' '); for tests. | |
| std::size_t | caretIndex () const |
| Caret position in code points; for tests. | |
| Public Member Functions inherited from donner::editor::Tool | |
| Tool (const Tool &)=delete | |
| Tool & | operator= (const Tool &)=delete |
| Tool (Tool &&)=delete | |
| Tool & | operator= (Tool &&)=delete |
Static Public Attributes | |
| static constexpr std::string_view | kDefaultFontFamily = "sans-serif" |
| Default font-family attribute applied to new text. | |
| static constexpr double | kDefaultFontSize = 32.0 |
| Default font-size attribute applied to new text (document units). | |
| static constexpr std::string_view | kDefaultFill = "black" |
| Default fill applied to new text, written as style="fill: ...". | |
| static constexpr double | kLineHeightFactor = 1.2 |
| Line height as a multiple of font size for wrapped/multi-line text. | |
| static constexpr double | kBoxDragScreenTolerance = 4.0 |
| Minimum drag extent (document units, at zoom 1) that turns a click into a text-box drag instead of point text. | |
| static constexpr double | kCaretBlinkHalfPeriodSeconds = 0.5 |
| Caret blink cadence: visible for this long, then hidden for the same, repeating. The phase resets to visible on every caret-affecting edit so the caret never blinks away right after input. | |
| static constexpr double | kPointFrameFadeSeconds = 0.18 |
| Point-text frames fade out over this interval after text input. | |
In-canvas text authoring with a live caret.
| struct donner::editor::TextTool::EditingChrome |
Caret + frame chrome for the active editing session, in document space.
| Class Members | ||
|---|---|---|
| Vector2d | caretBottomDoc | |
| Vector2d | caretTopDoc | Caret line endpoints (top, bottom). |
| optional< array< Vector2d, 4 > > | frameCornersDoc | Session frame corners (local TL, TR, BR, BL mapped through the text's transform): the authored box for box text, the laid-out ink bounds for point text. An ORIENTED quad - after a rotate it stays aligned to the text's rotation, never the axis-aligned envelope. Absent when the session has no frame yet (empty point text). |
| float | frameOpacity = 1.0f | Opacity for the point-text frame and handles. Box-text frames remain fully opaque. New point text starts at zero, pointer movement reveals it, and text input fades it back to zero. |
| struct donner::editor::TextTool::DragPreviewChrome |
Drag-to-create preview chrome (document space): the live box being dragged out, the first text baseline it would create (one default font size below the box top, clamped into the box), and an I-beam marker at the future caret position. Distinct from the selection marquee so the gesture reads as "creating a text box".
| Class Members | ||
|---|---|---|
| Vector2d | baselineEndDoc | |
| Vector2d | baselineStartDoc | First-baseline segment endpoints (left, right - inset from the frame). |
| Box2d | boxDoc | The live drag rectangle. |
| Vector2d | ibeamBottomDoc | |
| Vector2d | ibeamTopDoc | I-beam bar endpoints at the future caret position (top, bottom). |
| bool donner::editor::TextTool::commit | ( | EditorApp & | editor | ) |
Commit the active session as one undoable operation. An empty session deletes the created element, restoring the pre-session document and selection.
| void donner::editor::TextTool::insertCodepoints | ( | EditorApp & | editor, |
| std::span< const char32_t > | codepoints ) |
Insert a frame's queued code points with one DOM synchronization.
ImGui may deliver multiple characters in one input frame (paste, key repeat, or an input method). Batching them avoids rebuilding and flushing the text subtree once per code point.
|
overridevirtual |
Begin a gesture: a press on the session frame's transform handles starts a frame resize (reflow - glyphs never scale) or a rotate; a click inside the session's text moves the caret (clicks inside the frame but off every glyph land the caret at the end); otherwise commit any active session, then either open an editing session on the existing <text> under the click (caret at the clicked character) or start a box drag on empty canvas.
Implements donner::editor::Tool.
|
overridevirtual |
Extend the box drag or the active frame gesture.
Implements donner::editor::Tool.
|
overridevirtual |
Finish the gesture: a drag creates box text and a double-click places point text, opening the editing session with the caret at the start; a plain click on empty canvas creates nothing. Releasing a frame gesture keeps the editing session open.
Implements donner::editor::Tool.