メインコンテンツへスキップ

Tool

The tool is the editor's answer to "what does the next canvas pointer sequence mean?" With the cursor tool it means select/translate (the surface gestures); with an authoring tool it means create content. This document specifies the tool taxonomy, how tools are activated, and the per-tool interaction contracts — grounded in the production web editor's tool system, which is the semantic source of truth for what each tool feels like.

Position in the system

  • The active tool is instance UI state (per the golden state model): not part of the document, not undoable, not persisted, not replicated (at most surfaced as presence).
  • The tool machine is editor-core, headless: it consumes normalized pointer events in canvas space and emits mutation batches through the editor's dispatch — never touching a renderer. The shell only routes events to it and reflects its outcome (cursor icon, toolbar highlight).
  • Authoring previews ride the existing gesture frame (history HISB-2): a drag-insert is begin_gesture → insert + size previews (silent) → commit_gesture. Escape aborts the frame; the inserted node vanishes because gesture abort is already specified to restore the pre-gesture document (HISB-4). The tool system introduces no new preview mechanism.

Taxonomy

Tools form a closed set. "Tool" is reserved for modes that change what content-directed pointer input does; affordances that merely change navigation (hand, zoom) are virtual tools — cursor modes to the human, not tools in the taxonomy.

ToolKeyClassProduces
cursorVpointerselection / surface gestures (the default)
rectangleRinsertRectangle node
ellipseOinsertEllipse node
polygonYinsertRegularPolygon node
containerA, FinsertContainer node (+ adoption)
trayShift+FinsertTray node (+ adoption)
textTinsertTextSpan node + edit session
lineLdrawLine node
arrowShift+LdrawLine node with an end marker
pencilShift+PdrawVector node (polyline network)
penPmode-scopedvector network editing (vector-edit)
scaleKtransformuniform scale of the selection about a pivot

Web-parity notes, decided here as spec:

  • Arrow is not a node kind. Web models arrow as the same vector polyline as line plus a marker_end_shape; the engine's Line node carries marker_start_shape/marker_end_shape natively. Arrow = line tool that sets the end marker. One node kind, one property difference (TOOL-9).
  • Virtual tools (hand H, zoom Z) and the selection-adjacent tools (lasso Q) are out of the authoring taxonomy; hand/zoom are navigation bindings the shell already owns.
  • Deferred tools are named, with study notes below, and reserved keys — pressing a deferred tool's key is a no-op, never a misbinding.

Activation & lifecycle

  • Activation: toolbar click or shortcut key. Keys route to tools only when no widget has keyboard focus and no text-edit session is active (the ui UI-3 focus ladder and the text session win first).
  • The cursor tool is the home state. After an insert tool completes one insertion (click or drag), the tool reverts to cursor — except pencil, which stays active so consecutive strokes need no re-arming (web parity; TOOL-8). The scale tool likewise stays active across scales — it is a transform, not an insertion, and reverting after each drag would defeat repeated tuning (TOOL-10).
  • Escape steps down one rung at a time: abort an in-flight authoring drag → revert a non-cursor tool to cursor → (then the surface's own Escape semantics: deselect).
  • While a non-cursor tool is armed, canvas pointer events go to the tool machine — the surface's select/translate gestures do not run (the arbitration ladder of SURF-1 gains one rung: panel → chrome → tool → content).

Insertion gestures (shapes, container, tray, text)

One state machine serves every insert tool:

idle --pointer-down--> armed(anchor)
armed --pointer-up (< threshold)--> CLICK INSERT, done
armed --pointer-move (≥ threshold)--> dragging: insert 1×1 at anchor (gesture opens)
dragging --pointer-move--> size preview = rect(anchor, current)
dragging --pointer-up--> commit (adoption for container/tray), done
dragging --Escape--> abort gesture (document as before), tool stays armed
  • Click insert: a default-size node (100×100, web parity) centered on the point. Drag insert: the node is created at the anchor once the pointer travels the threshold, then its position/size track the rect spanned by anchor and pointer (normalized so dragging up-left works). The threshold is an explicit constant in screen px — the web editor inherits its host's implicit drag threshold (~5 px); this spec makes it a named number rather than an accident.
  • Either path yields exactly one history entry whose undo removes the node (TOOL-3); the entry is endpoint-shaped, not a replay of every size preview (gesture framing's endpoint minimality — see history, HISB-2).
  • The insert targets the scene root level at the top of the z-order. Inserting into a hovered container is a later refinement and must not change the entry shape when it lands.

Container & tray adoption

Drag-inserting a container (or tray) over existing content adopts it — this is what makes the container tool a hierarchy-authoring tool and not just a rect with children:

  • Candidates are the container's would-be siblings (children of the parent the container is inserted into). The predicate is full containment: a sibling whose world bounds lie entirely inside the drawn rect is adopted; intersection is not enough (web parity: cmath.rect.contains).
  • Adopted nodes become children of the new container, preserving document order among themselves, and their positions are re-anchored to the container's space so their world position does not move (TOOL-5).
  • Adoption is part of the same gesture — the insert, the moves, and the re-anchoring patches are one history entry; undo restores the original parent, order, and positions in one step.
  • Click-inserting a container adopts nothing (there is no drawn region).
  • M1 restriction, stated honestly: candidates whose bounds the editor cannot compute without a renderer (auto-sized kinds) are skipped; the contract tests pin the concrete-size kinds.

Text

The text tool composes insertion with the engine's text-edit session (the dedicated text editor — see the golden text editing docs):

  • Click: insert an auto-sized TextSpan at the point and enter the edit session. Drag: insert with the dragged width, then enter the edit session on release.
  • The session is a scoped sub-editor: while it is active, keys go to it (its own caret, selection, and internal undo); the document is not patched per keystroke. On exit-commit, the final text becomes one Patch — insert + typed text are one history entry (TOOL-6).
  • Exiting a fresh text node with no content aborts the whole frame: no node, no entry (TOOL-7's click-inserts-nothing spirit — empty authoring leaves no trace).
  • Entering edit mode on an existing text node (double-click with the cursor tool) uses the same session and the same one-entry commit contract, with the entry containing only the text patch.

Pencil

  • Drag-only: pointer-down arms, movement past the threshold begins a stroke, pointer-up commits it. A click inserts nothing (TOOL-7).
  • A stroke is a Vector node whose network is the sampled polyline (one vertex per pointer move, straight segments — no smoothing or simplification in M1; the network representation leaves room for both later).
  • The stroke previews live (the growing polyline is a silent patch per sample inside the gesture frame) and commits as one endpoint-shaped entry: undo removes the stroke.
  • Pencil stays active after a stroke (TOOL-8 exception).

Deferred tools — study notes

Recorded so deferral is a decision, not a gap:

  • Pen (P)graduated: specified in vector-edit.md (the mode, the pen state machine, bending, tangent mirroring, escape semantics) and implemented in the reference editor. It is mode-scoped, not a taxonomy member here: the pen lives in the vector content mode's legal tool set (edit-mode), and P outside the mode is the mode's entry (edit the selected vector, or create from scratch on the first placement) — never an armed document tool.

Uniform scale

The scale tool (K) is a transform tool, not an authoring tool: it produces no node and edits no property directly. While armed, a drag applies the parametric-scale operation to the current selection — a single similarity factor about a pivot — in contrast to the cursor tool's per-handle resize, which stretches width and height independently.

The operation itself is owned by its own specification: parameter- space scaling that rewrites each node's geometry-defining values (box, stroke, radii, text size, effects) so the render matches a uniform similarity transform, including its uniformity-across-size-coupled- scalars, baking, and round-trip rules (Scale (K) — parametric scaling). That model is not restated here. This section specifies only the tool rung: how the operation is armed and framed as an interaction.

  • Arming. The scale tool selects the uniform mode of the editor's existing resize gesture; the cursor tool's handle drag runs the same gesture in its ordinary directional mode. The gesture math — how pointer travel maps to a factor, snapping, and the live preview frame — is owned by the surface's resize gesture and is not restated here. The tool contributes only the mode selection and the pivot below.
  • The pivot is the point the transform holds fixed. By default it is the center of the selection bounding box; dragging a specific resize handle pins the opposite corner/edge as the pivot (the cardinal-direction origin), so the grabbed handle follows the pointer while its opposite stays put. The pivot is never the document origin.
  • Lifecycle. Like pencil, the scale tool stays active after a completed scale rather than reverting to cursor — reverting after each drag would defeat repeated tuning (TOOL-10).
  • History. A completed scale drag is one endpoint-shaped entry whose undo restores every affected node's pre-scale state in one step; Escape mid-drag aborts to the pre-gesture document with no entry (the same gesture-frame contract as every other drag here — see history HISB-2/HISB-4).

Contracts

  • TOOL-1 The tool set is closed and the cursor tool is the default: a fresh editor instance is in cursor mode, and activating any tool is observable via the instance's tool query without any document effect.
  • TOOL-2 Tool state is instance UI state: activating or using a tool is never recorded in history, never alters the document by itself, and does not travel over sync.
  • TOOL-3 Insert tools produce exactly one history entry per insertion, for both click-insert (default size, centered on the point) and drag-insert (anchor-to-pointer rect); a single undo removes the inserted node entirely.
  • TOOL-4 Escape during an authoring drag aborts it: the document is mutation-for-mutation identical to the pre-gesture state and no history entry exists.
  • TOOL-5 Completing a container/tray drag-insert adopts exactly the siblings whose bounds are fully contained in the drawn rect: they become children in their prior relative order, their world positions are unchanged, and the insert + adoption is one history entry (one undo restores everything).
  • TOOL-6 Text-tool authoring (insert, then the edit session, then exit-commit) yields one history entry containing the node with its final text; exiting a fresh text node with no content yields no node and no entry.
  • TOOL-7 Draw tools are drag-only: a click (below the drag threshold) with line, arrow, or pencil inserts nothing and records nothing.
  • TOOL-8 After a completed insertion the active tool reverts to cursor — except pencil, which remains active across strokes.
  • TOOL-9 The arrow tool's output differs from the line tool's output only in marker properties: same node kind, same geometry contract.
  • TOOL-10 The scale tool is a transform tool: it inserts no node and, like pencil, stays active after a completed scale rather than reverting to cursor. A completed scale drag is one history entry whose single undo restores the affected nodes' pre-scale state; Escape mid-drag aborts to the pre-gesture document with no entry.
  • TOOL-11 A scale-tool drag arms the parametric-scale operation (Scale (K)) in uniform mode about the pivot (default: selection-box center; a grabbed handle pins the opposite corner/edge). The operation's parameter-space semantics — which values scale and how — are owned there; the tool contributes only mode selection and the pivot. A factor of exactly 1 is a no-op — no gesture, no entry.