diff --git a/docs/canvas-ui.js b/docs/canvas-ui.js index 3ae4cd3..f69f820 100644 --- a/docs/canvas-ui.js +++ b/docs/canvas-ui.js @@ -1,4 +1,4 @@ -/* canvas-ui.js - Shared Canvas Logic (Mobile/Touch Ready) */ +/* canvas-ui.js - Shared Canvas Logic (Mobile/Touch Ready + Resize Hook) */ class PanZoomCanvas { constructor(id, onDraw, onClick, onDragPt) { @@ -17,7 +17,10 @@ class PanZoomCanvas { this.onClick = onClick; this.onDragPt = onDragPt; this.onMouseMove = null; - this.onPointerDown = null; // New Hook + this.onPointerDown = null; + + // New: Resize Callback hook + this.onResize = null; this.evCache = []; this.prevDiff = -1; @@ -30,6 +33,8 @@ class PanZoomCanvas { this.canvas.width = this.container.clientWidth; this.canvas.height = this.container.clientHeight; this.draw(); + // Trigger Hook + if (this.onResize) this.onResize(this.canvas.width, this.canvas.height); } }).observe(this.container); this.initEvents(); @@ -75,7 +80,6 @@ class PanZoomCanvas { this.evCache.push(e); this.totalDragDist = 0; - // Trigger Hook if defined (Before internal logic) if (this.onPointerDown) this.onPointerDown(e); const coords = this.getImgCoords(e.clientX, e.clientY); @@ -100,7 +104,6 @@ class PanZoomCanvas { if (index > -1) this.evCache[index] = e; if (this.evCache.length === 2) { - // Pinch Zoom const dx = this.evCache[0].clientX - this.evCache[1].clientX; const dy = this.evCache[0].clientY - this.evCache[1].clientY; const curDiff = Math.hypot(dx, dy); @@ -112,14 +115,13 @@ class PanZoomCanvas { this.zoomAt(cx, cy, factor); } this.prevDiff = curDiff; - this.totalDragDist += 20; // Ensure pinch doesn't trigger click + this.totalDragDist += 20; return; } if (this.isDragging && this.evCache.length === 1) { const dx = e.clientX - this.lm.x; const dy = e.clientY - this.lm.y; - this.totalDragDist += Math.hypot(dx, dy); if (this.activePtIdx !== -1) { @@ -146,7 +148,6 @@ class PanZoomCanvas { if (this.evCache.length < 2) this.prevDiff = -1; if (this.evCache.length === 0) { - // Threshold increased to 20px for mobile tap tolerance if (this.totalDragDist < 20) { if (this.activePtIdx === -1 && this.onClick) { const coords = this.getImgCoords(e.clientX, e.clientY); diff --git a/docs/inspector.js b/docs/inspector.js index 5dad75b..4fcbd2a 100644 --- a/docs/inspector.js +++ b/docs/inspector.js @@ -71,12 +71,18 @@ class Inspector { } async renderGrid() { - // --- 1. Snapshot State (Zoom/Pan) --- + // 1. Snapshot State + // We save state ONLY if the viewer exists. + // We also capture 'interacted' status to know if we should restore specific zoom or just auto-fit. const savedStates = {}; if (this.viewers) { Object.entries(this.viewers).forEach(([id, v]) => { - // We clone the object to ensure safety, though not strictly required - if (v.t) savedStates[id] = { ...v.t }; + if (v.t) { + savedStates[id] = { + t: { ...v.t }, + interacted: v.userInteracted || false // Capture the flag from the instance + }; + } }); } @@ -131,13 +137,12 @@ class Inspector { this.grid.appendChild(cell); let wasActiveBeforeDown = false; + let stateRestored = false; const viewer = new PanZoomCanvas(cvs.id, (ctx, k) => this.drawOverlay(id, ctx, k), - async (x, y, e) => { if (e.button !== 0) return; - if (wasActiveBeforeDown) { const hit = await this.handleNodeClick(id, x, y); if (!hit) { @@ -150,10 +155,16 @@ class Inspector { } ); + // Initialize interaction flag on the instance + viewer.userInteracted = false; + + // --- EVENT HANDLERS --- + viewer.onPointerDown = (e) => { + viewer.userInteracted = true; // Mark as dirty/manual + if (e.isPrimary || e.button === 0) { wasActiveBeforeDown = (this.masterId === id); - if (this.masterId !== id) { this.masterId = id; } @@ -162,10 +173,33 @@ class Inspector { } }; + // Detect wheel usage to stop auto-fit + cvs.addEventListener('wheel', () => { viewer.userInteracted = true; }); + viewer.onMouseMove = (x, y) => { if(this.masterId === id) this.syncCursors(id, x, y); }; + // --- SMART RESIZE/FIT LOGIC --- + const updateView = () => { + if (!viewer.bmp || cvs.width < 20 || cvs.height < 20) return; + + // 1. Restore State (Only if user had manually interacted before) + if (savedStates[id] && savedStates[id].interacted && !stateRestored) { + viewer.t = savedStates[id].t; + stateRestored = true; + viewer.userInteracted = true; // Keep it marked as manual + viewer.draw(); + } + // 2. Auto-Fit (Default behavior, continues on resize until user interacts) + else if (!viewer.userInteracted) { + viewer.fit(); + } + }; + + // Attempt fit on Resize + viewer.onResize = (w, h) => updateView(); + cvs.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); this.masterId = null; @@ -180,18 +214,9 @@ class Inspector { try { const bmp = await createImageBitmap(imgRec.blob); viewer.setImage(bmp); - if (cell.clientWidth && cell.clientHeight) { - viewer.canvas.width = cell.clientWidth; - viewer.canvas.height = cell.clientHeight; - } - // --- 2. Restore State or Fit --- - if (savedStates[id]) { - viewer.t = savedStates[id]; - viewer.draw(); // Force redraw with restored transform - } else { - viewer.fit(); - } + // Attempt fit on Load + updateView(); if(imgRec.name.toLowerCase().includes('bot') && !imgRec.name.toLowerCase().includes('top')) { viewer.setMirror(true);