gtauri viewer: two-image swipe commit — outgoing stays until off-screen
Fix: on release the old image vanished instantly and only the new one slid in. Now both images live on the page strip for the whole commit: vimg (outgoing) slides off-screen while vimg2 (incoming, the drag preview) slides home, over a 200 ms smoothstep; only then do the elements swap roles (vimg.src = the already-decoded preview image, vimg2 hidden after the swap paints — no blank frame). Details: - navigate() returns whether it committed; at the library edges it refuses and the drag snaps back (previously a past-the-edge drag could leave the image stuck mid-strip). - commit tweens are locked: pointer/dblclick input is ignored for the ~200 ms so a second gesture can't fight the animation. - openViewer gains a keepVisible (promoted) path that skips the hide+thumb round-trip; vFullSeq reset per open (safety-net counter could previously exhaust after ~11 opens). - removed the now-dead slideFrom slide-in mechanism (its purpose is subsumed by the two-image commit). Verified on the emulator: mid-commit frame shows both images (outgoing sliding off left, incoming from right); snap-back slides both back; edge drag right on the first image snaps home.
This commit is contained in:
parent
16cd868f22
commit
029780cc09
92
harnesses/gtauri/frontend/dist/index.html
vendored
92
harnesses/gtauri/frontend/dist/index.html
vendored
|
|
@ -383,8 +383,6 @@ function vApply() {
|
||||||
|
|
||||||
let viewerId = null, vFullTimer = null, vFullDone = false, vFullSeq = 0;
|
let viewerId = null, vFullTimer = null, vFullDone = false, vFullSeq = 0;
|
||||||
|
|
||||||
let slideFrom = null; // pending slide start offset for a swiped-in image
|
|
||||||
|
|
||||||
/* Live swipe preview: while a horizontal drag is in flight at fit
|
/* Live swipe preview: while a horizontal drag is in flight at fit
|
||||||
scale, the picture being swiped IN renders on the page strip
|
scale, the picture being swiped IN renders on the page strip
|
||||||
(vimg2) and follows the finger — no blank side. Its grid thumb is
|
(vimg2) and follows the finger — no blank side. Its grid thumb is
|
||||||
|
|
@ -428,15 +426,7 @@ function fitViewer(w, h) {
|
||||||
vimg.style.height = h + "px";
|
vimg.style.height = h + "px";
|
||||||
vimg.style.visibility = "visible";
|
vimg.style.visibility = "visible";
|
||||||
vload.textContent = "";
|
vload.textContent = "";
|
||||||
if (slideFrom != null) {
|
if (tween) return; // a settle/commit animation owns the transform
|
||||||
// Incoming swiped image: start where the page strip puts it and
|
|
||||||
// slide home. The tween owns the transform from here on.
|
|
||||||
vTx = slideFrom; vTy = 0;
|
|
||||||
slideFrom = null;
|
|
||||||
tweenTo(0, 0, 150);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (tween) return; // settle/slide owns the transform right now
|
|
||||||
vApply();
|
vApply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -457,16 +447,24 @@ function tryShow(id, url, isFull) {
|
||||||
im.src = url;
|
im.src = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openViewer(id, fromX) {
|
/// 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).
|
||||||
|
function openViewer(id, keepVisible) {
|
||||||
viewerId = id;
|
viewerId = id;
|
||||||
navHide(); // the incoming image takes over from the preview
|
if (!keepVisible) navHide();
|
||||||
slideFrom = (fromX == null) ? null : fromX;
|
|
||||||
vFullDone = false;
|
vFullDone = false;
|
||||||
|
vFullSeq = 0;
|
||||||
vScale = 1; vTx = 0; vTy = 0;
|
vScale = 1; vTx = 0; vTy = 0;
|
||||||
vload.textContent = "loading…";
|
if (keepVisible) {
|
||||||
vimg.style.visibility = "hidden";
|
vimg.style.visibility = "visible";
|
||||||
// 1) INSTANT: the grid thumb (Rust mem cache, ~1 ms).
|
vload.textContent = "";
|
||||||
tryShow(id, "http://gallery.localhost/thumb/" + id, false);
|
} else {
|
||||||
|
vload.textContent = "loading…";
|
||||||
|
vimg.style.visibility = "hidden";
|
||||||
|
// 1) INSTANT: the grid thumb (Rust mem cache, ~1 ms).
|
||||||
|
tryShow(id, "http://gallery.localhost/thumb/" + id, false);
|
||||||
|
}
|
||||||
// 2) Full-res: Rust reads the file OFF the main thread; first request
|
// 2) Full-res: Rust reads the file OFF the main thread; first request
|
||||||
// gets the placeholder, `full-ready` triggers the swap.
|
// gets the placeholder, `full-ready` triggers the swap.
|
||||||
tryShow(id, "http://gallery.localhost/full/" + id, true);
|
tryShow(id, "http://gallery.localhost/full/" + id, true);
|
||||||
|
|
@ -545,17 +543,52 @@ function tweenTo(tx, ty, ms, onDone) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Go to the neighbor `delta` steps down the list (+1 = swipe left).
|
/// Go to the neighbor `delta` steps down the list (+1 = swipe left).
|
||||||
/// The incoming image starts on the page strip where the drag left
|
/// Both images stay on the page strip for the whole animation:
|
||||||
/// off and slides home.
|
/// 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) {
|
function navigate(delta) {
|
||||||
|
if (tween && tween.commit) return false; // commit already in flight
|
||||||
const i = ids.indexOf(viewerId);
|
const i = ids.indexOf(viewerId);
|
||||||
const ni = i + delta;
|
const ni = i + delta;
|
||||||
if (i < 0 || ni < 0 || ni >= ids.length) return;
|
if (i < 0 || ni < 0 || ni >= ids.length) return false;
|
||||||
const from = vTx + delta * innerWidth;
|
commitTo(ni, delta);
|
||||||
openViewer(ids[ni], from);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit a swipe: animate vTx to the outgoing offset (the incoming
|
||||||
|
/// preview tracks at vTx + delta*W and lands exactly on 0), then swap
|
||||||
|
/// the elements' roles. The commit tween is locked: input is ignored
|
||||||
|
/// until it finishes (~200 ms).
|
||||||
|
function commitTo(ni, delta) {
|
||||||
|
navSet(ids[ni], delta); // the incoming (no-op if the drag showed it)
|
||||||
|
const out = -delta * innerWidth;
|
||||||
|
const x0 = vTx, t0 = performance.now(), ms = 200;
|
||||||
|
const step = (now) => {
|
||||||
|
const t = Math.min(1, (now - t0) / ms);
|
||||||
|
const e = t * t * (3 - 2 * t); // smoothstep: pick up, land soft
|
||||||
|
vTx = x0 + (out - x0) * e;
|
||||||
|
vTy = 0;
|
||||||
|
applyTransform();
|
||||||
|
navLayout();
|
||||||
|
if (t < 1) {
|
||||||
|
tween = { raf: requestAnimationFrame(step), commit: true };
|
||||||
|
} else {
|
||||||
|
// vimg2 (incoming) is exactly at 0: it becomes primary.
|
||||||
|
const id = ids[ni];
|
||||||
|
vimg.src = vimg2.src; // cached + decoded → paints immediately
|
||||||
|
vTx = 0;
|
||||||
|
applyTransform();
|
||||||
|
tween = null;
|
||||||
|
openViewer(id, true);
|
||||||
|
// Hide the outgoing preview only once the primary has the bytes.
|
||||||
|
vimg.onload = () => { if (viewerId === id) navHide(); };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tween = { raf: requestAnimationFrame(step), commit: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
viewer.addEventListener("pointerdown", (e) => {
|
viewer.addEventListener("pointerdown", (e) => {
|
||||||
|
if (tween && tween.commit) return; // a swipe commit is in flight
|
||||||
viewer.setPointerCapture(e.pointerId);
|
viewer.setPointerCapture(e.pointerId);
|
||||||
if (tween) { cancelAnimationFrame(tween.raf); tween = null; }
|
if (tween) { cancelAnimationFrame(tween.raf); tween = null; }
|
||||||
pts.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
pts.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
||||||
|
|
@ -640,15 +673,22 @@ const endPt = (e) => {
|
||||||
if (pts.size === 0 && g && g.mode === "swipe") {
|
if (pts.size === 0 && g && g.mode === "swipe") {
|
||||||
const dx = e.clientX - g.x0;
|
const dx = e.clientX - g.x0;
|
||||||
const w = innerWidth;
|
const w = innerWidth;
|
||||||
if (dx < -w * 0.25 || g.vx < -0.35) navigate(+1);
|
// Commit past the threshold; at the library edges navigate
|
||||||
else if (dx > w * 0.25 || g.vx > 0.35) navigate(-1);
|
// refuses (nothing to commit to) and we snap back instead.
|
||||||
else tweenTo(0, 0, 120, navHide); // not far enough: snap back
|
if (dx < -w * 0.25 || g.vx < -0.35) {
|
||||||
|
if (!navigate(+1)) tweenTo(0, 0, 120, navHide);
|
||||||
|
} else if (dx > w * 0.25 || g.vx > 0.35) {
|
||||||
|
if (!navigate(-1)) tweenTo(0, 0, 120, navHide);
|
||||||
|
} else {
|
||||||
|
tweenTo(0, 0, 120, navHide); // not far enough: snap back
|
||||||
|
}
|
||||||
}
|
}
|
||||||
gesture = null;
|
gesture = null;
|
||||||
};
|
};
|
||||||
viewer.addEventListener("pointerup", endPt);
|
viewer.addEventListener("pointerup", endPt);
|
||||||
viewer.addEventListener("pointercancel", endPt);
|
viewer.addEventListener("pointercancel", endPt);
|
||||||
viewer.addEventListener("dblclick", () => {
|
viewer.addEventListener("dblclick", () => {
|
||||||
|
if (tween && tween.commit) return;
|
||||||
vScale = fitScale();
|
vScale = fitScale();
|
||||||
vTx = 0; vTy = 0; vApply();
|
vTx = 0; vTy = 0; vApply();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user