Commit graph

96 commits

Author SHA1 Message Date
Danila Poyarkov 159ee6b7c9 Fix production build type errors 2026-02-28 11:37:02 +03:00
Danila Poyarkov ef65ff8b83 Default clipsContent to false (matching Figma) 2026-02-28 11:25:26 +03:00
Danila Poyarkov 66e68f7349 Drag outside frame reparents to parent, clip content toggle
- Dragging a child fully outside its parent frame's bounds
  automatically reparents it to the grandparent (like Figma)
- clipsContent property on SceneNode (default true for frames)
- Renderer respects clipsContent — unclipped frames show
  children that overflow
- Clip content checkbox in Layout panel for frames
2026-02-28 11:21:03 +03:00
Danila Poyarkov fd0b02642f Fix layers panel reactivity and accidental reparenting on click
- Force TreeRoot re-mount via :key when tree structure changes,
  fixing Reka UI not reacting to items prop updates
- Only reparent into frames on mouseup when user actually dragged,
  preventing click-to-select from moving nodes into overlapping frames
- Expose store on window for test introspection
- 6 new E2E tests for layers panel
2026-02-28 11:15:19 +03:00
Danila Poyarkov ce46dea54d Sort nodes by position when wrapping in auto-layout, fix null node crash
Nodes are sorted top-to-bottom (then left-to-right) before
reparenting into auto-layout frame. Positions are snapshotted
as plain IDs before mutation to avoid reactive proxy issues.

Property panel sections guard against null node with v-if,
preventing crashes during selection transitions.
2026-02-28 10:36:43 +03:00
Danila Poyarkov e6756207a5 Group/ungroup selection (⌘G / ⇧⌘G)
Group wraps selected nodes in a GROUP node, preserving
z-order and visual position. Ungroup dissolves a group,
reparenting children to the group's parent. Both undoable.
2026-02-28 10:27:04 +03:00
Danila Poyarkov 2f33165f1d Add Developer Tools menu item (⌘⌥I) 2026-02-28 10:21:35 +03:00
Danila Poyarkov c9fd34bd48 Wire menu events to frontend
Rust emits 'menu-event' with item ID to webview.
useMenu() composable listens and dispatches actions.
Extract openFileDialog() to use-menu.ts (shared with use-keyboard).
2026-02-28 10:20:00 +03:00
Danila Poyarkov 914abb28d9 README: add cargo install tauri-cli step 2026-02-28 10:05:25 +03:00
Danila Poyarkov bf45ac0f65 Native macOS menu bar
- OpenPencil: About, Services, Hide, Quit
- File: New, Open, Save, Save As, Export, Close
- Edit: Undo, Redo, Cut, Copy, Paste, Paste in Place,
  Select All, Duplicate, Delete
- View: Zoom In/Out/Fit/100%, Toggle Rulers/Grid/UI, Fullscreen
- Object: Group, Ungroup, Frame Selection, Bring to Front,
  Send to Back, Flip H/V
- Window: Minimize, Maximize, Close
- Help: Keyboard Shortcuts

All with standard macOS accelerators.
2026-02-28 10:00:37 +03:00
Danila Poyarkov b0a35a4d24 Fix rotation icon to rotate-ccw, add rotate 90° button 2026-02-28 09:57:45 +03:00
Danila Poyarkov c66e466fb2 Position section: alignment buttons, rotation icon, flip H/V
- 6 alignment buttons (left/center/right, top/center/bottom) for
  multi-select alignment
- Rotation input with rotate-cw icon and degree suffix
- Flip horizontal and flip vertical buttons
- Matching Figma's Position panel layout
2026-02-28 09:55:02 +03:00
Danila Poyarkov cb8bbcf289 Extract magic numbers to constants
Move ruler config (colors, sizes, tick ratios, badge dimensions),
font sizes, pen handle radius, and outline alpha from renderer
inline values to src/constants.ts.
2026-02-28 09:52:56 +03:00
Danila Poyarkov 68dc0ee888 Align ruler tick labels and badge text to same baseline
Horizontal: all text at R * 0.65 y-position
Vertical: translate to R * 0.65 before rotation
Badge text uses same baseline as tick labels
2026-02-28 09:49:14 +03:00
Danila Poyarkov 175565bb16 Ruler coordinate badges at selection start/end
Blue rounded-rect badges with white text showing exact coordinates
at both edges of the selection highlight on each ruler axis.
2026-02-28 09:44:59 +03:00
Danila Poyarkov 5b10d3b7fd Fix ruler highlight: translucent band, remove coordinate labels
Selection highlight uses 30% opacity blue instead of solid opaque.
Removed white coordinate labels on highlight bands — tick numbers
are sufficient.
2026-02-28 09:42:43 +03:00
Danila Poyarkov 2152a9b2d6 Fix vertical ruler text clipping
Clip both ruler strips to their bounds, reposition vertical text
to stay centered within the 20px ruler width.
2026-02-28 09:39:08 +03:00
Danila Poyarkov 9767ead116 Add ?no-rulers URL param to disable canvas rulers 2026-02-28 09:36:39 +03:00
Danila Poyarkov 9d5c85667f Canvas rulers with selection highlight
Horizontal and vertical rulers rendered directly in CanvasKit:
- Dark background, tick marks at adaptive intervals
- Coordinate labels scale with zoom level
- Blue highlight showing selected node extent on each axis
- Corner square at ruler intersection
2026-02-28 09:35:50 +03:00
Danila Poyarkov d0048114a7 Fix layers panel chevron direction 2026-02-28 09:29:57 +03:00
Danila Poyarkov 3f176d2b59 Fix layers panel: include visible in LayerNode interface
LayerNode was missing the visible field, so item.value.visible was
always undefined — eye-off icon showed on every layer.
2026-02-28 09:28:13 +03:00
Danila Poyarkov de83a716cd Remove prop drilling from property sections
All sections now get selected node from useNodeProps() composable
instead of receiving it as a prop from PropertiesPanel.
2026-02-28 09:27:03 +03:00
Danila Poyarkov 2af2a280c6 Appearance section: opacity + corner radius + visibility toggle
- Opacity with blend icon, corner radius with radius icon
- Eye/eye-off toggle in section header
- Layers panel: hidden layers dimmed (opacity-50) with permanent
  eye-off indicator
- Removed duplicate corner radius from LayoutSection
2026-02-28 09:20:31 +03:00
Danila Poyarkov bfe9726035 ScrubInput: entire field is draggable, click-to-edit, suffix styling
- Value + suffix area is now the drag handle (cursor: ew-resize)
- Click without drag enters edit mode (2px dead zone)
- Suffix rendered as separate muted span (right-aligned)
- No more tiny icon-only drag area
2026-02-28 09:16:02 +03:00
Danila Poyarkov c4a3ee6b4a Replace plain number inputs with ScrubInput for drag-to-adjust
Fill opacity, stroke opacity, and stroke weight now use ScrubInput
for consistent drag-to-adjust behavior matching W/H/position inputs.
2026-02-28 09:11:50 +03:00
Danila Poyarkov 8d35d4dbaf Deduplicate hex formatting: colorToHexRaw() in engine/color.ts
Removes duplicate hexString() wrappers from FillSection, StrokeSection,
and ColorPicker — all now use culori-based colorToHexRaw().
2026-02-28 09:08:40 +03:00
Danila Poyarkov babeecc76e Polish fill/stroke sections: editable hex, opacity %, eye toggle, minus
- Hex input is now editable (was read-only label)
- Per-fill/stroke opacity percentage input
- Eye/eye-off icon for visibility toggle (on hover)
- Minus button for removal (replaces ×)
- Stroke weight shown below stroke list
2026-02-28 09:06:04 +03:00
Danila Poyarkov 7b0f6c10f7 Fix text alignment icons: use direct template tags for auto-import 2026-02-28 09:01:54 +03:00
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 e3c80fc790 System font loading via Local Font Access API
- Font service (src/engine/fonts.ts) loads system fonts on demand
- Same font bytes registered in both CanvasKit and browser (FontFace API)
- Eliminates rendering mismatch between canvas and text editing overlay
- queryLocalFonts() for enumeration and blob() for font data
- Bundled Inter-Regular.ttf as fallback when API unavailable
- Renderer uses font service instead of hardcoded fetch
2026-02-28 08:54:53 +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 e37d2b216a Fix silent rendering crash, no-chrome test mode, error detection
- Fix this.this.selColor() typo in renderer (6 occurrences) — broke
  selection/handles rendering since the constants extraction refactor
- Add preserveDrawingBuffer to WebGL surface for reliable screenshots
- Add ?no-chrome URL param to hide panels/toolbar in tests
- CanvasHelper collects pageerror + console.error, assertNoErrors()
  fails tests on any browser-side exception
2026-02-28 08:39:19 +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 bed4e2fdd7 Remove FIGMA_UI3_CDP_FINDINGS.md (research notes, not source) 2026-02-28 08:15:22 +03:00
Danila Poyarkov 2306457b5f Simplify README, add desktop build instructions 2026-02-28 08:14:28 +03:00
Danila Poyarkov 040f3df22f Update README 2026-02-28 08:12:18 +03:00
Danila Poyarkov 71bcc4e6ad Remove src-tauri symlink, Tauri finds desktop/ by scanning 2026-02-28 08:10:59 +03:00
Danila Poyarkov 23f00189e8 Rename src-tauri → desktop, symlink for CLI compat 2026-02-28 08:10:09 +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 e03a01dbe3 Add bun:test unit tests for SceneGraph (15ms) 2026-02-28 07:45:50 +03:00
Danila Poyarkov c1fbf57df2 Skip stability check: toMatchSnapshot, 2.1s → 1.3s 2026-02-28 07:44:20 +03:00
Danila Poyarkov b5eb7f751f Faster E2E: data-ready attr + rAF wait, 4.6s → 2.1s 2026-02-28 07:43:05 +03:00
Danila Poyarkov e8c01fb93c Speed up E2E tests: reuse page, 14s → 4.6s 2026-02-28 07:35:52 +03:00
Danila Poyarkov 40f7b3a648 Playwright visual regression testing with Figma CDP reference
- Playwright E2E tests for OpenPencil: draw, move, delete, undo shapes
- Visual screenshot baselines with toHaveScreenshot()
- SwiftShader for headless WebGL/CanvasKit rendering
- Figma CDP helper for reference tests (connect to Figma Desktop)
- ?test URL param for clean canvas without demo shapes
- Scripts: test, test:update, test:figma, figma:debug
2026-02-28 07:32:15 +03:00
Danila Poyarkov f741eaa296 Fix open paths rendering as closed
Only close a loop path when the last segment ends at the first
segment's start vertex.
2026-02-28 07:15:07 +03:00
Danila Poyarkov b4c7700f05 Decode/encode Figma vectorNetworkBlob binary format
Reverse-engineered the undocumented binary blob format that Figma uses
to serialize vector networks in both .fig files and clipboard data:

  Header:  [numVertices:u32, numSegments:u32, numRegions:u32]
  Vertex:  [styleOverrideIdx:u32, x:f32, y:f32]
  Segment: [styleOverrideIdx:u32, start:u32, tsX:f32, tsY:f32, end:u32, teX:f32, teY:f32]
  Region:  [windingRule:u32, numLoops:u32, {numSegs:u32, segIdx...}...]

- decodeVectorNetworkBlob() and encodeVectorNetworkBlob() in vector.ts
- Clipboard paste (Figma → OpenPencil) resolves blob references
- Clipboard copy (OpenPencil → Figma) encodes blobs into the message
- .fig file import resolves vectorNetworkBlob references from blobs array
2026-02-28 07:14:17 +03:00
Danila Poyarkov 77d8000b36 Fix pen tool preview line to (0,0) on first click
penCursorX/Y now null until mouse actually moves, preventing
the preview line from drawing to canvas origin on first vertex.
2026-02-28 06:42:14 +03:00