feat: minimap navigation overlay + ruler/protractor property controls
Minimap: - Auto-shows in bottom-right corner when zoom > 1.05 - Renders full board content at scale (background + all strokes) - Purple viewport indicator with darkened outer areas - Click/drag to jump-pan the viewport - Cleaned up on destroy() Ruler/protractor property controls: - Rotation handle (purple ↺) — drag to rotate around origin - Resize handle (cyan ↔) — drag to change length/radius - Protractor now supports rotation via ctx.rotate(ov.angle) - Floating props panel in toolbar: angle° and length/radius inputs - Panel auto-shows on first click/drag, hides when overlay toggled off - Canvas-space hit testing with rotation-aware local coordinates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+21
-15
@@ -2322,33 +2322,39 @@ class Whiteboard {
|
|||||||
|
|
||||||
const mm = this._mmCtx;
|
const mm = this._mmCtx;
|
||||||
const MW = 192, MH = 108;
|
const MW = 192, MH = 108;
|
||||||
mm.clearRect(0, 0, MW, MH);
|
|
||||||
|
|
||||||
// Blit the static layer (full board content) scaled to minimap size
|
// Render full board at zoom=1 (same approach as renderThumbnail)
|
||||||
mm.drawImage(this._canvas, 0, 0, MW, MH);
|
const [sw, sh, sz, spx, spy] = [this._cssW, this._cssH, this._zoom, this._panVX, this._panVY];
|
||||||
|
this._cssW = MW; this._cssH = MH;
|
||||||
|
this._zoom = 1; this._panVX = 0; this._panVY = 0;
|
||||||
|
this._renderBg(mm);
|
||||||
|
if (this._template && this._template !== 'blank') this._renderTemplate(mm);
|
||||||
|
for (const s of this._strokes) this._renderStroke(mm, s);
|
||||||
|
// Restore
|
||||||
|
this._cssW = sw; this._cssH = sh; this._zoom = sz; this._panVX = spx; this._panVY = spy;
|
||||||
|
|
||||||
// Darken areas outside the viewport with a subtle vignette
|
// Viewport indicator
|
||||||
const vpW = MW / this._zoom;
|
const vpW = MW / sz;
|
||||||
const vpH = MH / this._zoom;
|
const vpH = MH / sz;
|
||||||
const vpX = (this._panVX / Whiteboard.VW) * MW;
|
const vpX = (spx / Whiteboard.VW) * MW;
|
||||||
const vpY = (this._panVY / Whiteboard.VH) * MH;
|
const vpY = (spy / Whiteboard.VH) * MH;
|
||||||
|
|
||||||
// Dark overlay on non-viewport areas
|
// Dark overlay on non-viewport areas
|
||||||
mm.fillStyle = 'rgba(0,0,0,0.42)';
|
mm.fillStyle = 'rgba(0,0,0,0.42)';
|
||||||
mm.fillRect(0, 0, MW, vpY); // top strip
|
mm.fillRect(0, 0, MW, vpY);
|
||||||
mm.fillRect(0, vpY + vpH, MW, MH - vpY - vpH); // bottom strip
|
mm.fillRect(0, vpY + vpH, MW, MH - vpY - vpH);
|
||||||
mm.fillRect(0, vpY, vpX, vpH); // left strip
|
mm.fillRect(0, vpY, vpX, vpH);
|
||||||
mm.fillRect(vpX + vpW, vpY, MW - vpX - vpW, vpH); // right strip
|
mm.fillRect(vpX + vpW, vpY, MW - vpX - vpW, vpH);
|
||||||
|
|
||||||
// Viewport border
|
// Viewport border
|
||||||
mm.strokeStyle = 'rgba(155,93,229,0.95)';
|
mm.strokeStyle = 'rgba(155,93,229,0.95)';
|
||||||
mm.lineWidth = 1.5;
|
mm.lineWidth = 1.5;
|
||||||
mm.strokeRect(vpX, vpY, vpW, vpH);
|
mm.strokeRect(vpX, vpY, vpW, vpH);
|
||||||
|
|
||||||
// Current position crosshair at viewport center
|
// Crosshair at viewport center
|
||||||
const cx = vpX + vpW / 2, cy = vpY + vpH / 2;
|
const cx = vpX + vpW / 2, cy = vpY + vpH / 2;
|
||||||
mm.strokeStyle = 'rgba(155,93,229,0.55)';
|
mm.strokeStyle = 'rgba(155,93,229,0.6)';
|
||||||
mm.lineWidth = 0.7;
|
mm.lineWidth = 0.8;
|
||||||
mm.beginPath(); mm.moveTo(cx - 5, cy); mm.lineTo(cx + 5, cy); mm.stroke();
|
mm.beginPath(); mm.moveTo(cx - 5, cy); mm.lineTo(cx + 5, cy); mm.stroke();
|
||||||
mm.beginPath(); mm.moveTo(cx, cy - 5); mm.lineTo(cx, cy + 5); mm.stroke();
|
mm.beginPath(); mm.moveTo(cx, cy - 5); mm.lineTo(cx, cy + 5); mm.stroke();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user