From 404acfaf45c1dfde0635eaf3392240aecbaea675 Mon Sep 17 00:00:00 2001 From: Aji Anaz Date: Sun, 12 Jul 2026 23:44:00 +0700 Subject: [PATCH 1/2] feat: add shape tools and brush size control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shapes (Scribbble/DrawPen-style): - Line, Arrow (auto-sized head), Rectangle, Ellipse - Store only start+end points (crisp at any zoom) - Live preview during drag Brush size control: - − / + buttons with live pixel readout - Range 2-20px, step 2 Toolbar: - Center vertically using measured offsetHeight (was off-center) - Layout: tools | shapes | size | colors | actions --- src/lib/Toolbar.svelte | 94 +++++++++++++++++++++++++++++++++++++++-- src/lib/types.ts | 2 +- src/routes/+page.svelte | 79 ++++++++++++++++++++++++++++++++-- 3 files changed, 168 insertions(+), 7 deletions(-) diff --git a/src/lib/Toolbar.svelte b/src/lib/Toolbar.svelte index fa62a65..738eca4 100644 --- a/src/lib/Toolbar.svelte +++ b/src/lib/Toolbar.svelte @@ -11,11 +11,12 @@ let { onundo, onclear, onexit }: Props = $props(); // --- Draggable toolbar state --- - // Start centered vertically on the right edge of the screen. + // Start centered vertically on the left edge of the screen. let position = $state({ x: 0, y: 0 }); let positioned = $state(false); let dragging = $state(false); let dragOffset: Point = { x: 0, y: 0 }; + let toolbarEl: HTMLElement | null = null; // --- Auto-fade state --- let visible = $state(true); @@ -34,8 +35,8 @@ // Place toolbar at right-center on first layout. function initPosition(): void { if (positioned) return; - const tbW = 52; // approx toolbar width - const tbH = 380; // approx toolbar height + // Measure actual toolbar height after layout; fall back to estimate + const tbH = toolbarEl?.offsetHeight ?? 560; position = { x: 24, y: Math.max(16, Math.round((window.innerHeight - tbH) / 2)) @@ -46,6 +47,14 @@ // Start auto-fade + initial position on mount $effect(() => { initPosition(); + // Re-measure after first paint in case layout wasn't ready + requestAnimationFrame(() => { + if (toolbarEl && positioned) { + const tbH = toolbarEl.offsetHeight; + const centeredY = Math.max(16, Math.round((window.innerHeight - tbH) / 2)); + position = { ...position, y: centeredY }; + } + }); resetFade(); return () => { if (fadeTimer) clearTimeout(fadeTimer); @@ -96,12 +105,25 @@ { id: 'eraser', icon: '⌫', label: 'Eraser' } ]; + const shapes: { id: Tool; icon: string; label: string }[] = [ + { id: 'line', icon: '╱', label: 'Line' }, + { id: 'arrow', icon: '→', label: 'Arrow' }, + { id: 'rectangle', icon: '▭', label: 'Rectangle' }, + { id: 'ellipse', icon: '◯', label: 'Ellipse' } + ]; + + function changeThickness(delta: number): void { + currentThickness.update((t) => Math.min(20, Math.max(2, t + delta))); + resetFade(); + } + const colors = ['#ef4444', '#facc15', '#22c55e', '#ffffff'];
+ + {#each shapes as shape (shape.id)} + + {/each} + + +
+ + +
+ + {$currentThickness} + +
+ + +
+ {#each colors as c (c)} -