Tools & Interactions
A reference to the v2 toolset, interaction overlays, and how tools plug into the editor's command and signal system.
Sidebar Drawers
| Drawer | Description |
|---|---|
| Layers | Z-order, visibility, opacity, blend modes, masks, merge, fragment marking, Merge Back |
| Craft | Brush, Eraser, Marker, Mosaic panels — Brush and Eraser bind an AA switch two-way to hardness; Marker carries a plain AA toggle; Mosaic exposes S/M/L/XL brush+block size presets |
| Adjustment | Color grading: basic sliders, curves, levels, channel mixer, color balance |
| Image Info | Dimensions, DPI, ICC profile, bit depth, EXIF, export configuration |
| Animated Images | Multi-frame GIF playback and export (Canvas 2D overlay beside the WebGPU stage) |
| AI Tools | Client-side background removal, upscale, segmentation |
Tool-Level Anti-Aliasing
AA is a per-tool property, not a global canvas switch. Eraser, Marker, and Brush each hold an independent toggle; on the selection side only tools whose strategy declares supportsAntiAlias (Ellipse (Path), Lasso, SAM) expose it — Rect and Wand are fixed hard-edge:
| Tool | AA ON | AA OFF |
|---|---|---|
| Eraser / Restore | Forced hardness = 100 |
Dropping hardness below 100 forces AA off |
| Brush | Soft tips require AA (hardness < 100 forces AA on) | Forces hardness = 100 |
| Marker | Panel toggle, default on | SDF zero-crossing hard cutoff |
| Selections | 1px sub-pixel coverage ramp | Pixel-center binary test (same staircase line) |
Text Tool
Text uses a box layout model with three modes:
| Mode | Created by | Behavior |
|---|---|---|
auto_width |
Single click (point text) | Width hugs content; dynamic anchor compensation keeps the left/center/right edge visually fixed |
auto_height |
Horizontal-only drag (total travel ≥ 5px, ΔY < 5px) | Fixed width, natural word wrap |
fixed |
Two-axis drag (ΔY ≥ 5px) | Fixed box; after commit, wrapped lines falling below the box height are dropped at layout time (computeTextLayout with clipToBox) — no overflow badge. During live editing overflow stays visible (Figma-style) |
Interaction highlights:
- Double-click any text layer with the Move tool to edit in place — no tool switch needed
- Escape commits and exits (Figma semantics: save, never destructively discard);
Cmd/Ctrl+Entercommits explicitly; clicking outside commits via two-stage click arbitration - IME composition (
isComposing || keyCode === 229) is fully guarded against commit/exit side effects Cmd/Ctrl + dragduring editing moves the layer as an independent immediately-committed undo transaction- Handles are hidden while editing; outside editing, the LayerOverlay draws screen-constant handles (10px handles, 24px rotation offset) regardless of zoom
Transform / Layer Overlay
Selection geometry is owned by a generic LayerOverlay with a usage-contributor registry: plugins declare intent via registerLayerOverlayUsage({ id, resolve }), and LayerOverlay aggregates gizmo types and hover suppression. Overlay hiding is driven by a camera-motion test (draft camera vs committed camera), not an interacting flag — so layer move/resize/rotate keeps handles visible while pan/zoom hides them.
| Input | Action |
|---|---|
| Drag corner / edge | Scale (Shift = proportional) |
| Drag rotation handle | Free rotate (Shift = snap 15°) |
| Arrow keys | Nudge 1px (Shift = 10px) |
| Border band drag (text) | 12% edge band pans the layer; interior click enters editing |
Selection (Clip Mode)
Selections compile to polygon ring sets. The marching-ants display derives from the persisted polygon rings via a pure scanline staircase transform (polygonToStairedPathD) — AA ON and AA OFF share the exact same staircase; only the GPU coverage evaluation differs.
| Shortcut | Action |
|---|---|
| Tab / Shift+Tab | Cycle tool (Rect → Ellipse (Path) → Lasso → Wand → SAM) |
| A×2 | Toggle anti-alias |
| Enter / Escape | Enter bakes the peel (exchange layer merged into host) and exits clip mode; Escape exits clip mode too — any dirty peel is merged first, never discarded |
| Cmd+J / Cmd+Shift+J | Layer via copy / Layer via cut (fragment with hole mask) |
| Cmd+Shift+A | Select from alpha |
Wand output (flood → close → marching squares → simplify) is marked antiAliased: false and merges into the generic selection pipeline with no special-casing.
Smart Guides
v2 layers a "soft snapping" engine on top of world-space AABB alignment:
- Velocity escape: an EMA-smoothed cursor speed (τ = 45ms) maps to a threshold factor; fast flicks bypass snapping entirely and clear guides
- Sticky hysteresis: locked guides get a 0.55 diff discount — a 6px bite / ~11px release band prevents flickering between near-equivalent lines
- Settings expose three sensitivity presets (Gentle 4px / Standard 6px / Strong 10px);
Alt(orShift) temporarily bypasses snapping per-event
Viewport Navigation
| Input | Action |
|---|---|
| Drag on canvas (pan mode) / middle drag | Pan |
| Mouse scroll / Alt+scroll / trackpad pinch | Zoom (cursor-centered) |
| Trackpad two-finger scroll / Shift+scroll / Ctrl+scroll (mouse) | Pan |
| Space | Toggle Pan/Clip mode |
| Ctrl+1 / Ctrl+2 | Fit to viewport / 100% |
| Shift+H / Shift+V | Flip viewport horizontally / vertically |
The ViewPass sampler adapts automatically: nearest-neighbor at scale ≥ 1 (crisp pixel editing), bilinear below 1. Text and vector content additionally quantize zoom into density bands {1, 1.5, 2, 3, 4} so staying inside a band never triggers recomposite.
Command and Signal Flow
Tools do not write pixels directly. Interactions dispatch commands through the editor store (each undoable operation is a transaction, e.g. applyBitmapMaskBatch wraps erase + hole-plugging in a single undo unit), and fast per-frame feedback flows through volatile transients (e.g. fast.setTransient('smartguides', …) at 60Hz) that bypass the reducer entirely. Errors surface only through the EditorHUD toast channel.
Last updated: 2026-10-12