From 695e2417cc67bf909e57f0ed03933cdfd4116387 Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Tue, 3 Nov 2026 13:47:39 -0500 Subject: [PATCH] gtauri viewer: mid-commit touch/key takes over the swipe (chained swipes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A second swipe while the new image was still sliding in got dropped (pointerdown bailed while the commit tween was in flight). Now a touch — or arrow key — during a commit takes it over: the commit finishes instantly (roles swap at the settle positions, same dimension-adoption path as natural completion) and the input starts a fresh gesture on the landed image, so rapid successive swipes chain through multiple pictures. A plain tap during a commit simply settles it early. Verified on the emulator: two 100 ms flicks 120 ms apart (the second lands mid-commit) end on the exact same image — pixel-identical — as two deliberate slow swipes. --- harnesses/gtauri/frontend/dist/index.html | 71 ++++++++++++++--------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/harnesses/gtauri/frontend/dist/index.html b/harnesses/gtauri/frontend/dist/index.html index 7826f03..b2d75fb 100644 --- a/harnesses/gtauri/frontend/dist/index.html +++ b/harnesses/gtauri/frontend/dist/index.html @@ -547,7 +547,8 @@ function tweenTo(tx, ty, ms, onDone) { /// the incoming one slides home while the outgoing one slides off /// screen; only then do the roles swap (returns false at the edges). function navigate(delta, vel) { - if (tween && tween.commit) return false; // commit already in flight + // Key nav during a commit takes it over the same way a touch does. + if (tween && tween.commit) takeover(); const i = ids.indexOf(viewerId); const ni = i + delta; if (i < 0 || ni < 0 || ni >= ids.length) return false; @@ -585,38 +586,55 @@ function commitTo(ni, delta, vel) { applyTransform(); navLayout(); if (t < 1) { - tween = { raf: requestAnimationFrame(step), commit: true }; + tween = { raf: requestAnimationFrame(step), commit: true, target: ni }; } else { - const id = ids[ni]; tween = null; - if (nav && nav.id === id && nav.ready) { - // vimg2 (incoming) is exactly at 0: it becomes primary. Adopt - // its dimensions NOW — otherwise the new pixels render in the - // OLD image's width/height box (wrong aspect) until full-res - // lands and fitViewer() fixes it (visible stretch → snap). - const nw = nav.w, nh = nav.h, ns = nav.scale; - vimg.src = vimg2.src; // cached + decoded → paints immediately - base.w = nw; base.h = nh; - vScale = ns; - vimg.style.width = nw + "px"; - vimg.style.height = nh + "px"; - vTx = 0; vTy = 0; - applyTransform(); - openViewer(id, true); - // Hide the preview only once the primary has the bytes. - vimg.onload = () => { if (viewerId === id) navHide(); }; - } else { - // Preview never made it (keyboard nav / slow thumb): plain - // open — the thumb round-trip sets the right dimensions. - openViewer(id, false); - } + finishCommit(ni); } }; - tween = { raf: requestAnimationFrame(step), commit: true }; + tween = { raf: requestAnimationFrame(step), commit: true, target: ni }; +} + +/// Swap the elements' roles at the settled positions: vimg2 (the +/// incoming image) becomes primary at 0 with its own dimensions (see +/// the aspect note there), vimg shows it next paint, the old image is +/// gone. Called when the commit tween completes AND on mid-flight +/// takeover — a new touch/key during a commit finishes it instantly +/// so the new gesture acts on the landed image (swipes chain). +function finishCommit(ni) { + const id = ids[ni]; + if (nav && nav.id === id && nav.ready) { + const nw = nav.w, nh = nav.h, ns = nav.scale; + vimg.src = vimg2.src; // cached + decoded → paints immediately + base.w = nw; base.h = nh; + vScale = ns; + vimg.style.width = nw + "px"; + vimg.style.height = nh + "px"; + vTx = 0; vTy = 0; + applyTransform(); + openViewer(id, true); + // Hide the preview only once the primary has the bytes. + vimg.onload = () => { if (viewerId === id) navHide(); }; + } else { + // Preview never made it (keyboard nav / slow thumb): plain open — + // the thumb round-trip sets the right dimensions. + openViewer(id, false); + } +} + +/// Instantly settle an in-flight commit (touch/key takeover). +function takeover() { + const target = tween.target; + cancelAnimationFrame(tween.raf); + tween = null; + finishCommit(target); } viewer.addEventListener("pointerdown", (e) => { - if (tween && tween.commit) return; // a swipe commit is in flight + // A touch during a swipe commit takes it over: finish the commit + // now and start this gesture on the landed image — a second swipe + // while the new image is sliding in acts as another swipe. + if (tween && tween.commit) takeover(); viewer.setPointerCapture(e.pointerId); if (tween) { cancelAnimationFrame(tween.raf); tween = null; } pts.set(e.pointerId, { x: e.clientX, y: e.clientY }); @@ -716,7 +734,6 @@ const endPt = (e) => { viewer.addEventListener("pointerup", endPt); viewer.addEventListener("pointercancel", endPt); viewer.addEventListener("dblclick", () => { - if (tween && tween.commit) return; vScale = fitScale(); vTx = 0; vTy = 0; vApply(); });