diff --git a/TODO.txt b/TODO.txt index a9831f0..3f8f2da 100644 --- a/TODO.txt +++ b/TODO.txt @@ -256,6 +256,113 @@ Comprehensive UX polish adds BEM CSS, space-drag panning, richer keyboard affordances, improved accessibility and styling, refined snapping, and handles so drawing feels production-ready. +================================================================================ +20. Milestone T: Sketching / Pen Tool (perfect-freehand) *wb-T* +================================================================================ + +Goal: +Add a pen tool that produces smooth freehand strokes using perfect-freehand. +Strokes are shapes, undo/redo-able (L), persisted (M), selectable, and render on +Canvas2D. + +Refs: +- perfect-freehand getStroke returns outline polygon points. +- Options: size/thinning/smoothing/streamline/simulatePressure. + +------------------------------------------------------------------------------ +T1. Data model: Stroke shape +------------------------------------------------------------------------------ + +/packages/core/src/model: +[ ] Add ShapeType: 'stroke' +[ ] StrokeShape props (persisted): + - points: Array<[x,y,p?]> " world coords + optional pressure + - style: { color, opacity } + - brush: { size, thinning, smoothing, streamline, simulatePressure } +[ ] Derived (NOT persisted): + - outline?: Array<[x,y]> " computed polygon + - bounds?: Box2 + +(DoD): stroke serializes to JSON and loads back identically. + +------------------------------------------------------------------------------ +T2. Tool: pen (state machine) +------------------------------------------------------------------------------ + +/packages/core/src/tools.ts (PenTool) +[ ] PointerDown: start draft, push first point +[ ] PointerMove: append point if moved > eps; include pressure if available +[ ] PointerUp: create ONE history command that inserts the stroke; clear draft + +Perf: +[ ] Coalesce draft updates (rAF) so you don’t recompute per event. + +(DoD): one stroke == one undo step; no DB writes until finalize (via M). + +------------------------------------------------------------------------------ +T3. Geometry: compute outline via perfect-freehand +------------------------------------------------------------------------------ + +/packages/core/src/geom.ts: +[ ] computeOutline(points, brush) -> outlinePoints using getStroke() +[ ] boundsFromOutline(outline) -> Box2 + +(DoD): outline non-empty for >= 2 points; bounds contain outline. + +------------------------------------------------------------------------------ +T4. Rendering: fill the outline polygon +------------------------------------------------------------------------------ + +/packages/renderer/src/draw.ts: +[ ] drawStroke(ctx, stroke): + - outline = cached || computeOutline(...) + - ctx.beginPath(); moveTo/lineTo...; closePath(); fill() +[ ] Render draft stroke above shapes, below selection UI. + +(DoD): strokes look stable while drawing; committed strokes match preview. + +------------------------------------------------------------------------------ +T5. Hit testing +------------------------------------------------------------------------------ + +/packages/core/src/geom.ts: +[ ] hitTestStroke(p, stroke): + - bounds check first + - inside-outline polygon test (ray cast) (or tolerance-to-segment fallback) + +(DoD): clicking a stroke selects it reliably. + +------------------------------------------------------------------------------ +T6. Brush settings (thin UI slice) +------------------------------------------------------------------------------ + +/apps/web/src/lib/components/BrushPopover.svelte: +[ ] Sliders: size, thinning, smoothing, streamline +[ ] Toggle: simulatePressure +(All map to perfect-freehand options.) + +(DoD): settings affect newly drawn strokes immediately. + +------------------------------------------------------------------------------ +T7. Tests +------------------------------------------------------------------------------ + +/packages/core/test/pen.test.ts: +[ ] outline computed for a simple polyline +[ ] bounds correctness +[ ] hit test inside/outside sanity + +Integration: +[ ] one history command per stroke; undo/redo persists through refresh (M). + +------------------------------------------------------------------------------ +Definition of Done +------------------------------------------------------------------------------ + +- Pen tool draws smooth strokes using perfect-freehand outlines. +- Strokes are selectable, undo/redo-able in one step, and persisted via Dexie. +- Brush controls change appearance of new strokes. + ================================================================================ References (URLs) *wb-refs* ================================================================================ @@ -273,3 +380,7 @@ SvelteKit + Tauri packaging: Canvas/infinite-canvas performance ideas: - https://antv.vision/infinite-canvas-tutorial/guide/lesson-008 - https://harrisonmilbradt.com/blog/canvas-panning-and-zooming + +Perfect Freehand +- https://github.com/steveruizok/perfect-freehand +- (Options) https://github.com/steveruizok/perfect-freehand/blob/main/packages/perfect-freehand/src/types.ts diff --git a/apps/web/.prettierrc b/apps/web/.prettierrc index dda7f98..0f2b258 100644 --- a/apps/web/.prettierrc +++ b/apps/web/.prettierrc @@ -4,6 +4,7 @@ "trailingComma": "none", "printWidth": 99, "objectWrap": "collapse", + "tabWidth": 2, "bracketSameLine": true, "plugins": [ "prettier-plugin-svelte" ], "overrides": [ { "files": "*.svelte", "options": { "parser": "svelte" } } ] diff --git a/apps/web/src/lib/assets/close.svg b/apps/web/src/lib/assets/close.svg new file mode 100644 index 0000000..4749166 --- /dev/null +++ b/apps/web/src/lib/assets/close.svg @@ -0,0 +1,4 @@ + + + diff --git a/apps/web/src/lib/assets/favicon.svg b/apps/web/src/lib/assets/favicon.svg index a59e894..d72cc97 100644 --- a/apps/web/src/lib/assets/favicon.svg +++ b/apps/web/src/lib/assets/favicon.svg @@ -1,4 +1,4 @@ - + diff --git a/apps/web/src/lib/assets/folder.svg b/apps/web/src/lib/assets/folder.svg new file mode 100644 index 0000000..0665fba --- /dev/null +++ b/apps/web/src/lib/assets/folder.svg @@ -0,0 +1,4 @@ + + + diff --git a/apps/web/src/lib/assets/info-circle.svg b/apps/web/src/lib/assets/info-circle.svg new file mode 100644 index 0000000..e651b94 --- /dev/null +++ b/apps/web/src/lib/assets/info-circle.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/apps/web/src/lib/assets/pencil.svg b/apps/web/src/lib/assets/pencil.svg new file mode 100644 index 0000000..3bccf30 --- /dev/null +++ b/apps/web/src/lib/assets/pencil.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/apps/web/src/lib/assets/trash.svg b/apps/web/src/lib/assets/trash.svg new file mode 100644 index 0000000..1c8430f --- /dev/null +++ b/apps/web/src/lib/assets/trash.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/apps/web/src/lib/components/Icon.svelte b/apps/web/src/lib/components/Icon.svelte new file mode 100644 index 0000000..734c517 --- /dev/null +++ b/apps/web/src/lib/components/Icon.svelte @@ -0,0 +1,43 @@ + + + + + + diff --git a/apps/web/src/lib/components/Sheet.svelte b/apps/web/src/lib/components/Sheet.svelte index 0dc89a3..425a896 100644 --- a/apps/web/src/lib/components/Sheet.svelte +++ b/apps/web/src/lib/components/Sheet.svelte @@ -1,29 +1,16 @@