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::EditorApp Class Reference

Top-level editor shell. More...

#include "donner/editor/EditorApp.h"

Classes

struct  CompletedTransformWriteback
 Payload describing a completed DOM-side transform mutation that needs to be spliced into the source text. target is a stable path-based reference captured while the source was still in sync with the DOM; transform is the local (parent-space) transform that should appear in the element's transform= attribute. More...
struct  CompletedElementRemoveWriteback

Public Types

enum class  ZOrder : std::uint8_t {
  BringToFront ,
  SendToBack ,
  BringForward ,
  SendBackward
}
 Paint-order ("z-order") move direction for reorderSelectedElement. SVG paints in document order - later siblings paint on top - so "forward"/"front" move the element later among its siblings. More...

Public Member Functions

 EditorApp (const EditorApp &)=delete
EditorApp & operator= (const EditorApp &)=delete
 EditorApp (EditorApp &&)=delete
EditorApp & operator= (EditorApp &&)=delete
bool loadFromString (std::string_view svgBytes)
 Load an SVG document from a string. Replaces any current document and clears the current selection. Returns true on parse success.
bool hasDocument () const
 Whether a document has been loaded.
AsyncSVGDocumentdocument ()
 Direct access to the wrapped AsyncSVGDocument. Used by the main loop for flushFrame() and currentFrameVersion(), and by tests.
const AsyncSVGDocumentdocument () const
const std::optional< std::string > & currentFilePath () const
 The file path this document was loaded from, or std::nullopt if it was created from scratch. Populated by the main loop via setCurrentFilePath after a successful File → Open / argv load.
void setCurrentFilePath (std::string path)
 Set the path associated with the current document. Called by the main loop when a file is loaded. Clears the dirty flag.
void clearCurrentFilePath ()
 Clear the backing path for an untitled or built-in document.
bool isDirty () const
 Whether the document has unsaved changes. Set automatically on every mutation via applyMutation; cleared by setCurrentFilePath / markClean.
void markClean ()
 Mark the document as clean (e.g. after a successful save).
void markDirty ()
 Mark the document as dirty. The main loop calls this when the user types in the source pane, since text-pane edits don't go through applyMutation.
void setCleanSourceText (std::string_view sourceText)
 Record the current source text as the "clean" baseline. Used after loading or saving a document so later source edits can determine whether the in-memory text has diverged from the last persisted bytes.
std::string_view cleanSourceText () const
 The last source text known to be in sync with the DOM.
bool revertToCleanSource ()
 Reload the last clean source baseline and clear transient document state.
void syncDirtyFromSource (std::string_view currentSourceText)
 Recompute the dirty flag from the current source text. This allows the editor to clear the dirty indicator when the user undoes or edits back to the last clean baseline.
void applyMutation (EditorCommand command)
 The single entry point for editor-initiated DOM writes. Tools and the text pane both flow through here. Pushes the command onto the document's command queue; nothing is applied until flushFrame().
void setElementVisible (const svg::SVGElement &element, bool visible)
 Set the visibility of element by toggling its display presentation attribute. Hiding captures the element's current author display value (if any) before overwriting it with display="none". Showing restores that captured value when it is a genuine, non-none author value (e.g. display="block"), so a hide/show round trip does not clobber it; otherwise (no captured value, or the element was never hidden through this toggle this session) it writes display="inline" (a definitively visible value, observable through the computed-style visibility check). Routes through applyMutation so the Layers panel eye button and context menu share one code path. Visibility toggles are intentionally NOT lock-gated.
void setElementLocked (const svg::SVGElement &element, bool locked)
 Lock or unlock element by toggling the data-donner-locked marker attribute ("true" to lock, "false" to unlock). Routes through applyMutation. The lock toggle itself is NOT lock-gated, so a locked layer can always be unlocked.
void restoreSelectionAfterNextDocumentReplace (std::vector< AttributeWritebackTarget > targets)
 Restore these selection targets after the next source-backed document replacement.
bool flushFrame ()
 Drain and apply any pending mutations. Called once per frame at the start of the main loop. Returns true if any commands were applied.
bool deleteSelectionWithUndo (std::string_view currentSourceText)
 Delete the current selection and record a source-level undo entry.
bool reorderSelectedElement (ZOrder direction)
 Reorder the single selected element among its siblings (paint/z-order), recording one undoable structural edit.
bool reorderElementBeforeSibling (svg::SVGElement element, std::optional< svg::SVGElement > referenceSibling)
 Move element so it sits immediately before referenceSibling among its current parent's children (or to the end when referenceSibling is std::nullopt), recording one undoable structural edit.
bool moveElementBefore (svg::SVGElement element, svg::SVGElement parent, std::optional< svg::SVGElement > referenceElement, std::string_view undoLabel="Move element")
 Move element into parent before referenceElement as one undoable DOM edit.
GroupOperationAvailability groupSelectionAvailability () const
 Return whether the current selection can be wrapped in one lossless structural group.
bool groupSelection ()
 Wrap the current contiguous same-parent selection in an attribute-free <g>.
GroupOperationAvailability ungroupSelectionAvailability () const
 Return whether the selected attribute-free <g> can be removed losslessly.
bool ungroupSelection ()
 Lift the selected attribute-free group's children into its parent.
bool renameSelectedElement (std::string_view newId)
 Rename the single selected element's id to newId, updating every internal reference so the document keeps rendering the same - one undoable structural edit.
const std::vector< svg::SVGElement > & selectedElements () const
 All currently-selected elements, in selection order. Empty when nothing is selected. Multi-element selections come from shift+click and marquee-drag (Milestone 4 of the editor UX design doc).
const std::optional< svg::SVGElement > & selectedElement () const
 Single-element accessor for back-compat with single-select call sites (overlay chrome, source-pane highlight, drag writeback, inspector, etc.). Returns the first selected element, or std::nullopt if nothing is selected. Cached so the const optional& reference stays stable across calls.
bool hasSelection () const
 Whether anything is selected.
void setSelection (std::optional< svg::SVGElement > element)
 Replace the current selection with a single element. Pass std::nullopt to clear.
void setSelection (std::vector< svg::SVGElement > elements)
 Replace the current selection with the given list. Use this for marquee-resolved multi-selects.
void toggleInSelection (const svg::SVGElement &element)
 Add element to the current selection if it isn't already selected; remove it if it is. The natural Shift+click handler.
void addToSelection (const svg::SVGElement &element)
 Append element to the current selection without disturbing existing entries. No-op if element is already selected.
bool enterGroupEdit (const svg::SVGElement &group)
bool exitGroupEdit ()
 Exit one isolated editing level and select the group that was being edited.
const std::optional< svg::SVGElement > & editingScope () const
 Current isolated group, or null when editing the full document.
bool isElementInEditingScope (const svg::SVGElement &element) const
 Whether p element is selectable within the current isolated group.
bool setAttributeOnSelection (std::string_view attrName, std::string_view attrValue)
 Queue an attribute write for every selected element.
bool setStylePropertyOnSelection (std::string_view propertyName, std::string_view propertyValue)
 Merge a single CSS declaration into each selected element's style attribute.
bool setStrokeWidthOnSelection (double strokeWidth)
 Queue a stroke-width style-property write for every selected element.
const ActivePaintStyleactivePaintStyle () const
 Active paint settings used by path-authoring tools for newly-created elements.
void setActiveFill (std::string_view fill)
 Set the active fill attribute for newly-created elements.
void setActiveStroke (std::string_view stroke)
 Set the active stroke attribute for newly-created elements.
void setActiveStrokeWidth (double strokeWidth)
 Set the active stroke width for newly-created elements.
void clearSelection ()
 Drop every entry from the selection. Equivalent to setSelection(std::nullopt) but reads better at clear sites.
PathOperationAvailability pathOperationAvailability (PathOperationKind operation) const
 Return whether a path operation is available for the current selection.
bool applyPathOperation (PathOperationKind operation)
 Queue a destructive path operation over the current selection.
PathOperationAvailability compoundPathUnbundleAvailability (std::optional< svg::SVGElement > target=std::nullopt) const
 Return whether a compound path can be unbundled into separate path elements.
bool unbundleCompoundPath (std::optional< svg::SVGElement > target=std::nullopt)
 Queue an unbundle operation for one compound path.
std::optional< svg::SVGGraphicsElementhitTest (const Vector2d &documentPoint)
 Find the topmost geometry element at the given document-space point, or std::nullopt if no element is hit. Coordinates are in the SVG canvas space (the same space as the root <svg> viewBox).
std::vector< svg::SVGGraphicsElementhitTestRect (const Box2d &documentRect)
 Find every geometry element whose painted shape intersects documentRect. Used by marquee selection. Returns elements in document order (root-to-leaf depth-first), so callers that care about z-order can rely on a stable sequence.
std::vector< svg::SVGElementselectableElements ()
 Return every selectable geometry element in the document, in document order (root-to-leaf depth-first). This is the canonical "Select All" set: the same elements hitTestRect would return for a marquee covering the whole canvas, minus the rectangle filter. Non-geometry nodes (<defs>, gradients, plain containers, XML text nodes) are excluded, so it matches what marquee selection treats as selectable. Empty when there is no document.
UndoTimelineundoTimeline ()
 Access the underlying UndoTimeline. Tools record begin/commit transactions on it directly; EditorApp::undo() below is the canonical way to apply undo entries because it routes them through the command queue so the mutation seam is preserved.
const UndoTimelineundoTimeline () const
void recordDocumentSourceUndoOnNextFlush (std::string label, svg::SVGElement anchorElement, std::string beforeSource)
 Defer a single document-source undo entry to the next flushFrame().
bool canUndo () const
 Whether there is an entry to undo.
bool canRedo () const
 Whether the most recently undone entry can be redone.
void undo ()
 Undo the most recent entry. Pops the timeline's next entry and pushes the restored transform onto the command queue as a SetTransformCommand - the actual DOM mutation happens on the next flushFrame(), keeping every DOM write on the same path. No-op if there is nothing to undo.
void redo ()
 Redo the most recently undone entry.
void setStructuredEditingEnabled (bool enabled)
 Enable or disable the structured-editing incremental path (M5). When enabled, text edits that land inside a known attribute value dispatch to SetAttributeCommand instead of ReplaceDocumentCommand, preserving tree identity. Defaults to true; the flag remains as a runtime escape hatch while the structured-editing rollout settles.
bool structuredEditingEnabled () const
 Whether the structured-editing incremental path is active.
void enqueueTransformWriteback (CompletedTransformWriteback writeback)
 Queue a transform writeback that main.cc will splice into the source on its next applyPendingTransformWriteback() call. SelectTool calls this when a drag completes; undo() / redo() call it so undoing a canvas drag restores both the DOM transform and the source text in lock-step. Multiple entries are preserved so grouped multi-selection undo/redo updates every participant.
std::optional< CompletedTransformWritebackconsumeTransformWriteback ()
 Drain the oldest queued transform writeback, if any.
std::vector< CompletedTransformWritebackconsumeTransformWritebacks ()
 Drain all queued transform writebacks. Called once per frame by DocumentSyncController.
void enqueueElementRemoveWriteback (CompletedElementRemoveWriteback writeback)
 Queue an element-removal writeback that main.cc will splice into the source on its next drain.
std::vector< CompletedElementRemoveWritebackconsumeElementRemoveWritebacks ()
 Drain any queued element-removal writebacks.

Detailed Description

Top-level editor shell.

Lifetime: typically one per window. All public methods are UI-thread only.


Class Documentation

◆ donner::editor::EditorApp::CompletedTransformWriteback

struct donner::editor::EditorApp::CompletedTransformWriteback

Payload describing a completed DOM-side transform mutation that needs to be spliced into the source text. target is a stable path-based reference captured while the source was still in sync with the DOM; transform is the local (parent-space) transform that should appear in the element's transform= attribute.

Class Members
bool restoreSourceTransformAttributeValue = false
optional< RcString > sourceTransformAttributeValue
AttributeWritebackTarget target
Transform2d transform

◆ donner::editor::EditorApp::CompletedElementRemoveWriteback

struct donner::editor::EditorApp::CompletedElementRemoveWriteback
Class Members
AttributeWritebackTarget target

Member Enumeration Documentation

◆ ZOrder

enum class donner::editor::EditorApp::ZOrder : std::uint8_t
strong

Paint-order ("z-order") move direction for reorderSelectedElement. SVG paints in document order - later siblings paint on top - so "forward"/"front" move the element later among its siblings.

Enumerator
BringToFront 

Move to the last sibling (paints on top of all siblings).

SendToBack 

Move to the first sibling (paints behind all siblings).

BringForward 

Move one position later (up one in paint order).

SendBackward 

Move one position earlier (down one in paint order).

Member Function Documentation

◆ applyPathOperation()

bool donner::editor::EditorApp::applyPathOperation ( PathOperationKind operation)

Queue a destructive path operation over the current selection.

Inputs are sorted by SVG paint order before dispatching to PathOps so selection click order cannot change Subtract Front / Subtract Back semantics. The result is rejected if the operation is over the editor's complexity limits or produces geometry outside the selected inputs' union bounds.

Parameters
operationOperation to apply.
Returns
true if commands were queued.

◆ compoundPathUnbundleAvailability()

PathOperationAvailability donner::editor::EditorApp::compoundPathUnbundleAvailability ( std::optional< svg::SVGElement > target = std::nullopt) const
nodiscard

Return whether a compound path can be unbundled into separate path elements.

If target is provided, availability is checked for that element. Otherwise the current single-element selection is used.

Parameters
targetOptional explicit path element to inspect.
Returns
Availability and a user-facing disabled reason.

◆ deleteSelectionWithUndo()

bool donner::editor::EditorApp::deleteSelectionWithUndo ( std::string_view currentSourceText)

Delete the current selection and record a source-level undo entry.

Parameters
currentSourceTextThe source-pane text that is in sync with the current document.
Returns
true if there was a selection to delete.

◆ enterGroupEdit()

bool donner::editor::EditorApp::enterGroupEdit ( const svg::SVGElement & group)
 Enter isolated editing for a group in the current document.

 Selection and hit testing are constrained to strict descendants until

ef exitGroupEdit is called. Nested groups may become the new scope.

◆ moveElementBefore()

bool donner::editor::EditorApp::moveElementBefore ( svg::SVGElement element,
svg::SVGElement parent,
std::optional< svg::SVGElement > referenceElement,
std::string_view undoLabel = "Move element" )

Move element into parent before referenceElement as one undoable DOM edit.

Unlike reorderElementBeforeSibling, this supports cross-parent moves. It rejects roots, locked subtrees, non-container parents, cycles, foreign elements, invalid references, and no-op positions. Structured editing reflects the committed DOM move into source.

Parameters
elementElement to move.
parentDestination container in the current document.
referenceElementDestination sibling, or std::nullopt to append.
undoLabelLabel for the single undo entry.
Returns
True when the move was queued.

◆ pathOperationAvailability()

PathOperationAvailability donner::editor::EditorApp::pathOperationAvailability ( PathOperationKind operation) const
nodiscard

Return whether a path operation is available for the current selection.

Parameters
operationOperation to test.
Returns
Availability and a user-facing disabled reason.

◆ recordDocumentSourceUndoOnNextFlush()

void donner::editor::EditorApp::recordDocumentSourceUndoOnNextFlush ( std::string label,
svg::SVGElement anchorElement,
std::string beforeSource )

Defer a single document-source undo entry to the next flushFrame().

The "before" source is captured now (by the caller, while the document is still in sync); the "after" source is captured during the next flushFrame() once queued geometry has been applied, and one undo entry is recorded only if the source actually changed. This lets a multi-step authoring gesture (e.g. a whole Pen-tool session) collapse into one undoable command without the tool having to flush the frame itself, so the normal per-frame source-sync path stays intact.

Parameters
labelHuman-readable undo label.
anchorElementElement used to anchor the source snapshot.
beforeSourceDocument source captured before the gesture began.

◆ redo()

void donner::editor::EditorApp::redo ( )

Redo the most recently undone entry.

Like undo(), the restored transform is routed through the command queue so the mutation seam is preserved. No-op unless the most recent timeline action was an undo.

◆ renameSelectedElement()

bool donner::editor::EditorApp::renameSelectedElement ( std::string_view newId)

Rename the single selected element's id to newId, updating every internal reference so the document keeps rendering the same - one undoable structural edit.

All work is DOM-level (per CLAUDE.md "DOM-Level Editing Only"): the element's id and every referencing attribute value (url(#oldId) in fill / stroke / clip-path / mask / filter / markers / inline style, and href / xlink:href="#oldId") are changed via SetAttributeCommand, and the structured-editing reflection rewrites the source. No source-text surgery.

Refuses (returns false) when there is no single selection, the element is locked, newId is empty or already used by another element, or newId equals the current id.

CSS #oldId selectors inside <style> blocks are rewritten as well, so renames never silently break the style cascade.

Parameters
newIdThe new element id.
Returns
true if the rename was applied.

◆ reorderElementBeforeSibling()

bool donner::editor::EditorApp::reorderElementBeforeSibling ( svg::SVGElement element,
std::optional< svg::SVGElement > referenceSibling )

Move element so it sits immediately before referenceSibling among its current parent's children (or to the end when referenceSibling is std::nullopt), recording one undoable structural edit.

This is the arbitrary-position generalization of reorderSelectedElement used by the Layers-panel drag-to-reorder affordance.

Like the z-order moves it is a pure DOM SVGDocument::insertElement and the structured-editing reflection rewrites the source (no source-text surgery).

Refuses (returns false) when element is locked or the document root, when referenceSibling is not a child of the same parent (cross-parent moves are unsupported here), when referenceSibling is element, or when the element is already in the requested position.

Parameters
elementThe element to move (selection is left to the caller).
referenceSiblingInsert element before this sibling, or append when std::nullopt.
Returns
true if the element moved.

◆ reorderSelectedElement()

bool donner::editor::EditorApp::reorderSelectedElement ( ZOrder direction)

Reorder the single selected element among its siblings (paint/z-order), recording one undoable structural edit.

This is a pure DOM move - a single SVGDocument::insertElement of the already-attached element to a new position before a computed reference sibling - and the structured-editing reflection rewrites the source from the DOM change. No source-text surgery (see CLAUDE.md "DOM-Level Editing Only").

Parameters
directionWhich way to move the element in paint order.
Returns
true if the element moved; false if there is no single selection, the selection is the document root, or it is already at the requested extreme.

◆ setActiveFill()

void donner::editor::EditorApp::setActiveFill ( std::string_view fill)
inline

Set the active fill attribute for newly-created elements.

Parameters
fillSVG fill attribute value.

◆ setActiveStroke()

void donner::editor::EditorApp::setActiveStroke ( std::string_view stroke)
inline

Set the active stroke attribute for newly-created elements.

Parameters
strokeSVG stroke attribute value.

◆ setActiveStrokeWidth()

void donner::editor::EditorApp::setActiveStrokeWidth ( double strokeWidth)

Set the active stroke width for newly-created elements.

Parameters
strokeWidthStroke width in user units. Negative values clamp to zero.

◆ setAttributeOnSelection()

bool donner::editor::EditorApp::setAttributeOnSelection ( std::string_view attrName,
std::string_view attrValue )

Queue an attribute write for every selected element.

Parameters
attrNameAttribute name to set, e.g. "fill".
attrValueAttribute value to write.
Returns
true if commands were queued.

◆ setStrokeWidthOnSelection()

bool donner::editor::EditorApp::setStrokeWidthOnSelection ( double strokeWidth)

Queue a stroke-width style-property write for every selected element.

Parameters
strokeWidthStroke width in user units. Negative values clamp to zero.
Returns
true if commands were queued.

◆ setStylePropertyOnSelection()

bool donner::editor::EditorApp::setStylePropertyOnSelection ( std::string_view propertyName,
std::string_view propertyValue )

Merge a single CSS declaration into each selected element's style attribute.

Parameters
propertyNameCSS property name, e.g. "fill".
propertyValueCSS property value, e.g. "#112233".
Returns
true if commands were queued.

◆ unbundleCompoundPath()

bool donner::editor::EditorApp::unbundleCompoundPath ( std::optional< svg::SVGElement > target = std::nullopt)

Queue an unbundle operation for one compound path.

The source path is split into one path per contour, including hole/counter contours such as the center of a letter D. New path elements inherit the original path's non-geometry attributes except id, and the original path is removed after the replacement paths are inserted at the same paint-order position.

Parameters
targetOptional explicit path element to unbundle. If omitted, the current single-element selection is used.
Returns
true if commands were queued.

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