Commit graph

25 commits

Author SHA1 Message Date
Danila Poyarkov 7e9cd46e0b Split PropertiesPanel into sections, add Typography panel
- PropertiesPanel.vue: 513 → 67 lines (composition only)
- Extracted: PositionSection, LayoutSection, AppearanceSection,
  FillSection, StrokeSection, TypographySection
- Shared useNodeProps() composable for update/commit logic
- Typography panel: font family picker with search (queryLocalFonts),
  weight select, font size, line height, letter spacing, text alignment
- Font picker shows each family in its own typeface
- Loads selected font into both CanvasKit and browser on pick
2026-02-28 09:00:25 +03:00
Danila Poyarkov 5c0afd4d06 Pass fontWeight to both CanvasKit text style and textarea overlay 2026-02-28 08:49:28 +03:00
Danila Poyarkov 2715f0d8be Text tool: start empty with cursor, blue editing outline
- Create text nodes with empty text instead of 'Text' placeholder
- Immediately enter editing mode on creation (blinking cursor)
- Blue border on textarea during editing (matches Figma)
2026-02-28 08:47:42 +03:00
Danila Poyarkov 98eeb9582c Fix text editing: hide canvas text/selection during edit, load Inter font
- Skip rendering text node on canvas while textarea overlay is active
- Skip selection handles for the node being edited
- Remove border from textarea (was causing double outline)
- Add @font-face for Inter-Regular.ttf so textarea matches CanvasKit
2026-02-28 08:45:37 +03:00
Danila Poyarkov bcd8bf6bca Add IconsResolver for auto-import, explicit imports only for dynamic maps 2026-02-28 08:29:39 +03:00
Danila Poyarkov 3ddc671bc5 Replace emoji icons with Lucide via Iconify (unplugin-icons)
- unplugin-icons + @iconify-json/lucide for compile-time icon bundling
- Toolbar: proper cursor, frame, square, pen, type, hand icons
- Layers panel: per-type node icons + chevron-right for expand/collapse
- Remove icon field from ToolDef (icons mapped in components)
2026-02-28 08:24:42 +03:00
Danila Poyarkov f0d817c747 Refactor: vendor kiwi-schema, extract types/constants, culori colors
- Fork kiwi-schema from source TypeScript (evanw/kiwi), drop npm dep + patch
- Deduplicate GUID/Color types into src/types.ts
- Extract UI colors and default fills into src/constants.ts
- Replace hand-rolled hex parsing with culori library
- Extract demo shapes from App.vue into src/demo.ts
- useUrlSearchParams from VueUse for test mode detection
- Move fig-file/fig-import into kiwi/ (format layer)
- Delete PoC files (poc-yoga.ts, poc-test.ts)
2026-02-28 08:08:04 +03:00
Danila Poyarkov 179acd0edb Wire undo/redo into all editor operations
Undoable operations:
- Create shape (undo deletes it)
- Delete (undo restores with original z-order)
- Move (was already wired, cleaned up)
- Resize (commitResize on mouseUp)
- Rotate (commitRotation on mouseUp)
- Duplicate (undo deletes copies, restores selection)
- Paste (undo deletes pasted nodes)
- Text editing (undo restores previous text)
- Wrap in auto layout (undo unwraps and restores positions)
- Set layout mode (undo restores all layout properties)
- Fill/stroke add/remove/toggle (via updateNodeWithUndo)
- Alignment and padding changes
- Properties panel scrub inputs: emit commit event on
  pointerup/blur with previous value for undo push
- UndoManager.push() added for entries where forward was
  already applied
2026-02-28 06:28:17 +03:00
Danila Poyarkov 423647dd67 Replace text arrow with SVG chevron icon in layers panel 2026-02-28 01:02:22 +03:00
Danila Poyarkov 9afabfe009 Layers panel: Reka UI Tree with expand/collapse
- TreeRoot/TreeItem from reka-ui for proper tree structure
- Click arrow to expand/collapse frames with children
- Leaf nodes (no children) show no arrow
- Drag reorder preserved with drop indicator
- Bigger arrow icons (text-xs) with hover highlight
2026-02-28 01:01:30 +03:00
Danila Poyarkov 2abd50733f Layer panel drag reordering
Drag layers to reorder within parent or reparent into frames.
Blue indicator line shows insertion point, ring highlight on
drop-into containers. 4px dead zone before drag starts.
Prevents dropping into self or descendants.
2026-02-28 00:57:19 +03:00
Danila Poyarkov 509dd4fab4 Migrate to Tailwind CSS 4.2
- @tailwindcss/vite plugin for zero-config build
- @theme tokens: panel, canvas, border, hover, accent, surface, muted, input
- All scoped CSS replaced with Tailwind utility classes
- Only remaining scoped CSS: ColorPicker range slider thumbs
- Removed conflicting * { padding: 0 } reset (Tailwind preflight handles it)
2026-02-28 00:54:38 +03:00
Danila Poyarkov a78a43e717 Fix scrub label hit area and clean up cursor handling
Label now stretches to full input height (24px vs 11px).
Removed global scrubbing class hack, use body.style.cursor for drag.
2026-02-28 00:32:06 +03:00
Danila Poyarkov 98104cee58 Add ScrubInput component with drag-to-scrub values
Figma-style scrubby inputs: hover icon shows ew-resize cursor,
drag left/right to change value. Click the number to type.
Replaces all raw number inputs in PropertiesPanel.
Uses VueUse useEventListener for automatic cleanup.
2026-02-28 00:27:38 +03:00
Danila Poyarkov b6ae7d40d0 Replace opacity slider with number input
Slider was overlapping the label text. Figma uses a number input
with % suffix for opacity, not a slider.
2026-02-28 00:22:18 +03:00
Danila Poyarkov bc68d660b9 Replace native range input with Reka UI Slider
Accessible, unstyled slider with custom dark theme styling:
thin 3px track, blue range fill, 10px white circular thumb with
hover/focus ring.
2026-02-28 00:17:25 +03:00
Danila Poyarkov 2e91959cc5 Style opacity range slider to match dark theme 2026-02-28 00:15:16 +03:00
Danila Poyarkov 759940194e Figma-style auto-layout properties panel
- 9-dot alignment grid for simultaneous primary + counter axis
- Direction buttons with SVG icons (vertical/horizontal/wrap)
- W/H sizing mode dropdowns (Fixed/Hug/Fill) with click-outside
- Uniform padding input with expand toggle for per-side padding
- Gap input with visual icon
- + / − to add/remove auto layout instead of triple toggle
- Sizing mode awareness: shows Fill option when inside auto-layout
2026-02-28 00:11:01 +03:00
Danila Poyarkov 4de83542d3 Auto-layout with Yoga WASM integration
Layout engine (src/engine/layout.ts):
- Yoga WASM adapter mapping SceneNode layout props to Yoga nodes
- Supports vertical/horizontal direction, padding (per-side), gap,
  primary/counter axis alignment, HUG/FIXED/FILL sizing, wrap,
  absolute positioning, flex grow, stretch
- Bottom-up computation: nested auto-layout frames resolve correctly
- Layout runs automatically on any property change to affected frames

Scene graph:
- New layout properties on SceneNode: layoutMode, itemSpacing,
  padding (4 sides), primaryAxisAlign, counterAxisAlign,
  primaryAxisSizing, counterAxisSizing, layoutWrap, layoutGrow,
  layoutPositioning, layoutAlignSelf, counterAxisSpacing

Properties panel:
- Auto-layout section for frames: direction toggles (none/vertical/
  horizontal), gap, padding (4 inputs), primary and counter axis
  alignment dropdowns

Import/export:
- .fig import maps stackMode/stackSpacing/stackPadding* fields
- Clipboard paste maps all Figma stack* properties
- Clipboard copy exports layout as Kiwi stackMode/stackSpacing/etc.
2026-02-27 23:51:21 +03:00
Danila Poyarkov 7139546cb5 Add text editing with Paragraph API and inline overlay
Font loading:
- Inter Regular font bundled (342KB TTF)
- TypefaceFontProvider for Paragraph API
- MakeFreeTypeFaceFromData for Font object
- Fonts load async, fallback to drawText before ready

Text rendering:
- ParagraphBuilder with font family, size, letter spacing,
  line height, text alignment
- Multi-line text wraps to node width
- Text nodes render at correct position in local space

Inline editing:
- Double-click text node → HTML textarea overlay
- Positioned/sized to match node on canvas (zoom-aware)
- Live updates to scene graph on input
- Escape or blur commits edit
- Keyboard events stop propagation during editing
- Text cursor for TEXT tool
2026-02-27 22:57:39 +03:00
Danila Poyarkov 7d483f340b Add nested frames with reparenting and relative coordinates
Scene graph:
- Nodes use relative coordinates (x,y relative to parent)
- getAbsolutePosition() walks up the parent chain
- reparentNode() preserves visual position when moving between frames
- Deep hit testing traverses into container children
- hitTestFrame() finds deepest frame under cursor (for drop targeting)
- flattenTree() returns indented layer list for the panel
- isDescendant() prevents circular reparenting

Renderer:
- canvas.translate(node.x, node.y) per node — children render relative
- All shapes draw at (0,0) in their local space
- Drop target highlight (blue outline on frame during drag-over)
- Selection UI uses getAbsolutePosition() for screen-space handles

Input:
- Drag onto frame → blue highlight → drop reparents nodes
- Handle hit testing uses absolute positions
- Snap guides work with absolute coordinates

Layers panel:
- Tree view with depth-based indentation (16px per level)
- Expand arrow for containers with children
- Shift+click for additive selection
2026-02-27 22:34:15 +03:00
Danila Poyarkov 94c4635907 Add rotation, multi-select bbox, snap guides, text tool, color picker
Rotation:
- Rotation handle above selection (line + circle)
- Drag to rotate, Shift snaps to 15° increments
- Live preview during drag, applied on release
- Rotated nodes render correctly with canvas.rotate()

Multi-select bounding box:
- Individual outlines per node + unified group bbox
- Group resize handles on the unified bounds
- Handles rotated AABB for rotated nodes

Snap guides:
- Magenta guide lines when moving nodes near edges/centers
- Snaps to left/right/top/bottom/center of other nodes
- 5px threshold, guides clear on mouse up

Text tool:
- T shortcut or toolbar to activate
- Click to place text node (200×24, default 'Text')
- CanvasKit drawText with default font (Inter to come)

Color picker (Reka UI Popover):
- HSV saturation/brightness area with pointer drag
- Hue slider (rainbow gradient)
- Alpha slider
- Hex input + alpha percentage
- Live color updates to fill/stroke

Properties panel:
- Add/remove fills and strokes with +/× buttons
- Toggle fill visibility
- Stroke weight editing
- Multi-select shows layer count + shared opacity
- ColorPicker integrated for fill and stroke colors

Frame clipping:
- Frame children now clip to parent bounds
2026-02-27 22:15:35 +03:00
Danila Poyarkov a3ad587652 Move all event listeners to useEventListener 2026-02-27 22:01:12 +03:00
Danila Poyarkov 1009d6dc12 Add resize handles, marquee selection, duplicate, constrained drawing
- Resize by dragging selection handles (8 positions: corners + edges)
- Shift+resize maintains aspect ratio
- Marquee selection: drag on empty canvas to select nodes in rectangle
- Alt+drag to duplicate and move selected nodes
- Cmd+D to duplicate in place (+20px offset)
- Cmd+A to select all
- Shift+draw constrains to square/circle
- Cursor changes to resize arrows on handle hover
- Marquee drawn as blue transparent rect with selection stroke
2026-02-27 21:59:36 +03:00
Danila Poyarkov 323a1f90c4 Migrate React → Vue 3 + VueUse + Reka UI
- Vue 3 SFC components with <script setup>
- Reactive store (Vue reactivity) replacing React refs/state
- Composables: useCanvas, useCanvasInput, useKeyboard
- Components: EditorCanvas, LayersPanel, PropertiesPanel, Toolbar
- Floating toolbar (Figma UI3 style) with pill shape + shadow
- Properties panel with Design/Prototype tabs + zoom display
- Sections: Position, Rotation, Layout, Appearance, Fill, Stroke, Effects, Export
- CSS custom properties for theming (--panel-bg, --border, --accent, etc.)
- Add Figma UI3 CDP inspection findings (toolbar, panels, sections)
2026-02-27 21:25:31 +03:00