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.
Loading...
Searching...
No Matches
donner::editor::TextTool Class Referencefinal

In-canvas text authoring with a live caret. More...

#include "donner/editor/TextTool.h"

Inheritance diagram for donner::editor::TextTool:
[legend]

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< DragPreviewChromedragPreviewChrome () 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< EditingChromeeditingChrome (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 Member Functions

static bool CaretBlinkVisibleAtPhase (double secondsSincePhaseReset)
 Pure blink-phase math: true while the caret is in a visible half-period at secondsSincePhaseReset since the last caret-affecting edit.
static double SecondsUntilCaretBlinkFlip (double secondsSincePhaseReset)
 Pure blink-phase math: seconds from secondsSincePhaseReset until the caret's visibility next flips. Always in (0, kCaretBlinkHalfPeriodSeconds].

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.

Detailed Description

In-canvas text authoring with a live caret.


Class Documentation

◆ donner::editor::TextTool::EditingChrome

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.

◆ donner::editor::TextTool::DragPreviewChrome

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).

Member Function Documentation

◆ commit()

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.

Returns
true if a session was committed (or discarded-as-empty).

◆ insertCodepoints()

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.

◆ onMouseDown()

void donner::editor::TextTool::onMouseDown ( EditorApp & editor,
const Vector2d & documentPoint,
MouseModifiers modifiers )
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.

◆ onMouseMove()

void donner::editor::TextTool::onMouseMove ( EditorApp & editor,
const Vector2d & documentPoint,
bool buttonHeld )
overridevirtual

Extend the box drag or the active frame gesture.

Implements donner::editor::Tool.

◆ onMouseUp()

void donner::editor::TextTool::onMouseUp ( EditorApp & editor,
const Vector2d & documentPoint )
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.


The documentation for this class was generated from the following file: