diff --git a/harnesses/gtauri/frontend/dist/index.html b/harnesses/gtauri/frontend/dist/index.html index b2d75fb..48fcd3b 100644 --- a/harnesses/gtauri/frontend/dist/index.html +++ b/harnesses/gtauri/frontend/dist/index.html @@ -368,6 +368,12 @@ function fitScale() { return Math.min(innerWidth / base.w, innerHeight / base.h); } +/// P = the element showing the current image, S = the swipe-preview +/// element. A commit PROMOTES S (it already shows the incoming image +/// at its own size) and demotes P — the elements swap roles, so no +/// src copy / re-decode / re-layout ever happens on a swipe. +let P = vimg, S = vimg2; + function vApply() { // Clamp: never zoom out past fit; pan limited to image edges — no // blank space around the image, letterboxed axes stay centered. @@ -377,44 +383,44 @@ function vApply() { const my = Math.max(0, (base.h * vScale - innerHeight) / 2); if (vTx > mx) vTx = mx; else if (vTx < -mx) vTx = -mx; if (vTy > my) vTy = my; else if (vTy < -my) vTy = -my; - vimg.style.transform = + P.style.transform = "translate(" + (vTx - base.w * vScale / 2) + "px," + (vTy - base.h * vScale / 2) + "px) scale(" + vScale + ")"; } let viewerId = null, vFullTimer = null, vFullDone = false, vFullSeq = 0; /* Live swipe preview: while a horizontal drag is in flight at fit - scale, the picture being swiped IN renders on the page strip - (vimg2) and follows the finger — no blank side. Its grid thumb is - a mem hit (~1 ms) in the common case (the grid already requested - its neighbors' thumbs). */ + scale, the picture being swiped IN renders on the page strip (S) + and follows the finger — no blank side. Its grid thumb is a mem + hit (~1 ms) in the common case (the grid already requested its + neighbors' thumbs). */ let nav = null; // {id, dir(+1 = right/next, -1 = left/prev), w, h, scale, ready} function navSet(id, dir) { if (nav && nav.id === id) return; navHide(); if (id == null) return; nav = { id, dir, w: 0, h: 0, scale: 1, ready: false }; - vimg2.style.visibility = "hidden"; - vimg2.onload = () => { - if (!nav || nav.id !== id || vimg2.naturalWidth <= 2) return; - nav.w = vimg2.naturalWidth; nav.h = vimg2.naturalHeight; + S.style.visibility = "hidden"; + S.onload = () => { + if (!nav || nav.id !== id || S.naturalWidth <= 2) return; + nav.w = S.naturalWidth; nav.h = S.naturalHeight; nav.scale = Math.min(innerWidth / nav.w, innerHeight / nav.h); nav.ready = true; - vimg2.style.width = nav.w + "px"; - vimg2.style.height = nav.h + "px"; - vimg2.style.visibility = "visible"; + S.style.width = nav.w + "px"; + S.style.height = nav.h + "px"; + S.style.visibility = "visible"; navLayout(); }; - vimg2.src = "http://gallery.localhost/thumb/" + id; + S.src = "http://gallery.localhost/thumb/" + id; } function navHide() { nav = null; - vimg2.style.visibility = "hidden"; + S.style.visibility = "hidden"; } function navLayout() { if (!nav || !nav.ready) return; const s = nav.scale; - vimg2.style.transform = + S.style.transform = "translate(" + (vTx + nav.dir * innerWidth - nav.w * s / 2) + "px," + (0 - nav.h * s / 2) + "px) scale(" + s + ")"; } @@ -422,9 +428,9 @@ function navLayout() { function fitViewer(w, h) { base.w = w; base.h = h; vScale = Math.min(innerWidth / w, innerHeight / h); - vimg.style.width = w + "px"; - vimg.style.height = h + "px"; - vimg.style.visibility = "visible"; + P.style.width = w + "px"; + P.style.height = h + "px"; + P.style.visibility = "visible"; vload.textContent = ""; if (tween) return; // a settle/commit animation owns the transform vApply(); @@ -437,7 +443,9 @@ function tryShow(id, url, isFull) { im.addEventListener("load", () => { if (viewerId !== id) return; if (im.naturalWidth <= 2) return; - vimg.src = im.src; + if (isFull && tween) return; // a slide owns base/vScale right now; + // the safety timer re-requests shortly + P.src = im.src; fitViewer(im.naturalWidth, im.naturalHeight); if (isFull) { vFullDone = true; @@ -448,20 +456,21 @@ function tryShow(id, url, isFull) { } /// openViewer(id, keepVisible): keepVisible = the image was promoted -/// by a swipe commit — vimg ALREADY shows its thumbnail, so don't -/// hide it or re-request the thumb (only full-res is wanted). +/// by a swipe commit — P ALREADY shows its thumbnail at the right +/// scale/position (finishCommit set it), so don't reset the view or +/// re-request the thumb (only full-res is wanted). function openViewer(id, keepVisible) { viewerId = id; if (!keepVisible) navHide(); vFullDone = false; vFullSeq = 0; - vScale = 1; vTx = 0; vTy = 0; if (keepVisible) { - vimg.style.visibility = "visible"; + P.style.visibility = "visible"; vload.textContent = ""; } else { + vScale = 1; vTx = 0; vTy = 0; vload.textContent = "loading…"; - vimg.style.visibility = "hidden"; + P.style.visibility = "hidden"; // 1) INSTANT: the grid thumb (Rust mem cache, ~1 ms). tryShow(id, "http://gallery.localhost/thumb/" + id, false); } @@ -477,7 +486,7 @@ function openViewer(id, keepVisible) { } if (++vFullSeq > 10) { if (vFullTimer) { clearInterval(vFullTimer); vFullTimer = null; } - if (viewerId === id && vimg.naturalWidth <= 2) { + if (viewerId === id && P.naturalWidth <= 2) { vload.textContent = "image unavailable"; } return; @@ -493,7 +502,8 @@ function closeViewer() { navHide(); if (vFullTimer) { clearInterval(vFullTimer); vFullTimer = null; } viewer.classList.remove("open"); - vimg.src = ""; + P.src = ""; + S.src = ""; // release any parked full-res bitmap } document.getElementById("vclose").addEventListener("click", closeViewer); addEventListener("keydown", (e) => { @@ -520,7 +530,7 @@ function atFit() { /// Raw transform write (no clamping) — used by the tween and swipes. function applyTransform() { - vimg.style.transform = + P.style.transform = "translate(" + (vTx - base.w * vScale / 2) + "px," + (vTy - base.h * vScale / 2) + "px) scale(" + vScale + ")"; } @@ -595,28 +605,29 @@ function commitTo(ni, delta, vel) { 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). +/// Settle the commit and swap the elements' roles. S (the preview) +/// ALREADY shows the incoming image at its own size and scale — it +/// is promoted to primary in place (moved to 0, brought to front), +/// and the old primary is hidden immediately. No src copy, no async +/// paint, no re-layout: the promoted pixels were on screen the whole +/// drag, so the settled frame is the drag frame — no size jump. +/// Called when the commit tween completes AND on mid-flight takeover +/// (a 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"; + base.w = nav.w; base.h = nav.h; + vScale = nav.scale; vTx = 0; vTy = 0; + P.onload = null; S.onload = null; // stale preview/commit handlers + P.style.visibility = "hidden"; // park the outgoing image + viewer.insertBefore(S, P); // promoted element goes in front + [P, S] = [S, P]; 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 — + // Preview never loaded (keyboard nav / slow thumb): plain open — // the thumb round-trip sets the right dimensions. openViewer(id, false); }