Make a pinch zoom the grid and nothing else
Two faults left over from making the gesture reach the grid at all. **It still opened photographs.** Checking the finger id stops the synthetic release Slint emits when the *second* finger lands, but not the other end of the gesture: lifting one finger of two leaves the other one down, and Slint replays that survivor as a fresh `Pressed` on whatever is under it — which is how it hands the pointer back to ordinary handling. Under it is a cell. So the cell was selected, and lifting that last finger was a complete, well-formed click on the same finger that pressed. No part of the event stream distinguishes it from a real tap, so the grid now remembers that a pinch just happened: a latch raised when the gesture starts and lowered a beat after it ends, during which cells take neither presses nor clicks. The press that *opens* a pinch is undone rather than suppressed — it has already happened by the time a second finger makes it a pinch. Undoing it has to be exact, or a cancel that restored the selection but left the anchor moved would make the next shift-click select a run from a cell nobody pointed at, so capture and restore are a tested pair. **And it was not smooth.** Two reasons. The pinch was thresholded into ±1 steps of 25%, so the grid lurched and then sat still; it now takes the ratio since the last update and tracks the fingers, with the drawn cell still landing on whole column counts because the columns divide the width. And `zoom-cells` was the one geometry change still reloading inline — a full catalog re-read, 360-row model rebuild and thumbnail batch per step, on the thread drawing the frame. It goes through the same settle timer as the rest now.
This commit is contained in:
@@ -508,6 +508,10 @@ export component AppWindow inherits Window {
|
||||
in property <bool> library-syncing: false;
|
||||
in-out property <length> library-cell-size: 180px;
|
||||
callback library-zoom-cells(int);
|
||||
/// A pinch step on the grid: the ratio since the last update.
|
||||
callback library-pinch-cells(float);
|
||||
/// A pinch on the grid has begun, so the press that opened it was not one.
|
||||
callback library-pinch-started();
|
||||
in property <int> library-scroll-to: 0;
|
||||
in property <int> library-scroll-token: 0;
|
||||
/// Which row of the loaded window is the photograph currently open in
|
||||
@@ -1130,6 +1134,8 @@ in property <bool> panel-visible: true;
|
||||
syncing: root.library-syncing;
|
||||
requested-cell-size: root.library-cell-size;
|
||||
zoom-cells(d) => { root.library-zoom-cells(d); }
|
||||
pinch-cells(r) => { root.library-pinch-cells(r); }
|
||||
pinch-started() => { root.library-pinch-started(); }
|
||||
scroll-to: root.library-scroll-to;
|
||||
scroll-token: root.library-scroll-token;
|
||||
sync-now() => { root.library-sync-now(); }
|
||||
|
||||
+68
-19
@@ -860,6 +860,11 @@ export component LibraryGrid inherits Rectangle {
|
||||
/// Ctrl+wheel or pinch over the grid: resize the cells. A signed step,
|
||||
/// not a size, so Rust owns the bounds.
|
||||
callback zoom-cells(int);
|
||||
/// A pinch step: the ratio since the last update, above 1 spreading. The
|
||||
/// continuous counterpart of `zoom-cells`, which the wheel steps.
|
||||
callback pinch-cells(float);
|
||||
/// A pinch has begun, so the press that opened it was not a press.
|
||||
callback pinch-started();
|
||||
callback columns-changed(int);
|
||||
callback sync-now();
|
||||
/// The grid scrolled: the first visible image's ordinal in the library.
|
||||
@@ -1167,6 +1172,30 @@ export component LibraryGrid inherits Rectangle {
|
||||
changed columns => { root.columns-changed(root.columns); }
|
||||
property <int> row-count: ceil(root.cells.length / max(1, columns));
|
||||
|
||||
// --- while a pinch is happening, and just after -----------------------
|
||||
//
|
||||
// **A pinch does not end cleanly.** Lifting one finger of two leaves the
|
||||
// other one down, and Slint replays that survivor as a *fresh* `Pressed`
|
||||
// on whatever is under it — that is how it hands the pointer back to
|
||||
// ordinary handling. Under it is a cell. So the cell was selected, and
|
||||
// then lifting that last finger was a complete, well-formed click and the
|
||||
// photograph opened. Checking the finger id, which is what stops the
|
||||
// *second* finger's synthetic release from opening anything, cannot help
|
||||
// here: this press and this release are genuinely the same finger.
|
||||
//
|
||||
// Nothing in the event stream distinguishes that survivor from a real tap,
|
||||
// so the grid has to remember that a pinch just happened. The latch is
|
||||
// raised when the gesture starts and lowered a beat after it ends.
|
||||
property <bool> pinching: false;
|
||||
settle := Timer {
|
||||
interval: 350ms;
|
||||
running: false;
|
||||
triggered => {
|
||||
root.pinching = false;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
// How many cells the viewport holds, plus a screenful either side so
|
||||
// scrolling has loaded rows to move into rather than blank ones.
|
||||
//
|
||||
@@ -1884,21 +1913,34 @@ export component LibraryGrid inherits Rectangle {
|
||||
|
||||
property <float> last-scale: 1.0;
|
||||
|
||||
started => { self.last-scale = 1.0; }
|
||||
updated => {
|
||||
// A quarter-step either way is enough to act on: cell
|
||||
// size is continuous, unlike the timeline's discrete
|
||||
// zoom levels.
|
||||
if (self.scale / max(0.01, self.last-scale) > 1.15) {
|
||||
root.zoom-cells(1);
|
||||
self.last-scale = self.scale;
|
||||
} else if (self.scale / max(0.01, self.last-scale) < 0.87) {
|
||||
root.zoom-cells(-1);
|
||||
self.last-scale = self.scale;
|
||||
}
|
||||
started => {
|
||||
self.last-scale = 1.0;
|
||||
root.pinching = true;
|
||||
settle.running = false;
|
||||
// The finger that opened this gesture landed on a cell
|
||||
// and selected it. It was reaching for the grid, not
|
||||
// for that photograph.
|
||||
root.pinch-started();
|
||||
}
|
||||
// Continuous, not stepped. Thresholding this into ±1 zoom
|
||||
// steps meant the grid lurched 25% at a time and sat still
|
||||
// in between, which is the whole of "pinching is not
|
||||
// smooth". The ratio since the last update is what tracks
|
||||
// the fingers; where the drawn cell lands is still a whole
|
||||
// number of columns, because the columns divide the width.
|
||||
updated => {
|
||||
root.pinch-cells(self.scale / max(0.01, self.last-scale));
|
||||
self.last-scale = self.scale;
|
||||
}
|
||||
// The latch outlives the gesture — see `pinching`.
|
||||
ended => {
|
||||
self.last-scale = 1.0;
|
||||
settle.running = true;
|
||||
}
|
||||
cancelled => {
|
||||
self.last-scale = 1.0;
|
||||
settle.running = true;
|
||||
}
|
||||
ended => { self.last-scale = 1.0; }
|
||||
cancelled => { self.last-scale = 1.0; }
|
||||
}
|
||||
|
||||
// Follow a requested position. Without this a scrub moves the
|
||||
@@ -2160,11 +2202,17 @@ export component LibraryGrid inherits Rectangle {
|
||||
if (ev.kind == PointerEventKind.down) {
|
||||
self.down-finger = ev.touch-finger-id;
|
||||
self.click-pending = false;
|
||||
root.cell-pressed(
|
||||
i,
|
||||
ev.modifiers.control || root.select-mode,
|
||||
ev.modifiers.shift,
|
||||
);
|
||||
// Not while the grid is being pinched, nor in
|
||||
// the moment after: this is the finger left
|
||||
// over from the gesture, handed back as a new
|
||||
// press. See `pinching`.
|
||||
if (!root.pinching) {
|
||||
root.cell-pressed(
|
||||
i,
|
||||
ev.modifiers.control || root.select-mode,
|
||||
ev.modifiers.shift,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (ev.kind == PointerEventKind.up) {
|
||||
// A *plain* click opens the image; a modified
|
||||
@@ -2176,6 +2224,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// is only honoured when the press was
|
||||
// unmodified.
|
||||
if (self.click-pending
|
||||
&& !root.pinching
|
||||
&& ev.touch-finger-id == self.down-finger) {
|
||||
root.cell-clicked(i);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user