diff --git a/ui/dr-ui/ui/library.slint b/ui/dr-ui/ui/library.slint index 0f970f3..7b4f7f2 100644 --- a/ui/dr-ui/ui/library.slint +++ b/ui/dr-ui/ui/library.slint @@ -1620,9 +1620,17 @@ export component LibraryGrid inherits Rectangle { // Two-finger pinch, for tablet: the same gesture the timeline // uses, applied to cell size rather than to time. + // + // Sized to the **viewport**, like `zoom-catcher` above and for + // the same reason. `100%` inside a Flickable is the Flickable's + // own height, and the pinch is delivered to whatever lies under + // the midpoint of the two fingers — so a handler one screenful + // tall sat at the top of a viewport thousands of rows long and + // was under the fingers only while the grid had not been + // scrolled. Anywhere else the gesture found nothing to land on. grid-pinch := ScaleRotateGestureHandler { width: 100%; - height: 100%; + height: parent.viewport-height; property last-scale: 1.0; @@ -1876,32 +1884,65 @@ export component LibraryGrid inherits Rectangle { // giving touch its own policy would be a second copy of // the rules in `collections_ui::apply_press` to keep in // step with the first. + // --- which finger, and whether it finished -------- + // + // A pinch begins as an ordinary press. When the second + // finger lands Slint closes the first one's gesture by + // synthesising a `Released` at its position — that is + // how a Flickable is made to let go of a scroll it had + // already claimed. A TouchArea cannot tell that release + // from a real one and fires `clicked`, so every attempt + // to pinch-zoom the grid opened whichever photograph + // the first finger happened to be resting on. + // + // The finger id is what separates them: the synthetic + // release carries the id of the finger that *arrived*, + // never the one that pressed. A mouse reports 0 for + // both, so the desktop path is unchanged. + // + // `clicked` fires before the `up` that follows it, so + // it can only raise a flag — the decision to open has + // to wait for the event that names the finger. + property down-finger: -1; + property click-pending: false; + pointer-event(ev) => { 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, ); } - // Both endings, and `cancel` is the important one: - // the Flickable takes the pointer as soon as the - // finger travels, so without this a scroll that - // began on a cell would come to rest as a long - // press and select it. - if (ev.kind == PointerEventKind.up - || ev.kind == PointerEventKind.cancel) { + if (ev.kind == PointerEventKind.up) { + // A *plain* click opens the image; a modified + // one is purely a selection gesture and must + // not navigate away from the grid the user is + // building a selection in. The modifier state + // is not carried here, so the press above + // records it and Rust decides — `cell-clicked` + // is only honoured when the press was + // unmodified. + if (self.click-pending + && ev.touch-finger-id == self.down-finger) { + root.cell-clicked(i); + } + self.click-pending = false; + root.cell-press-ended(); + } + // `cancel` is the important ending: the Flickable + // takes the pointer as soon as the finger travels, + // so without this a scroll that began on a cell + // would come to rest as a long press and select it. + if (ev.kind == PointerEventKind.cancel) { + self.click-pending = false; root.cell-press-ended(); } } - // A *plain* click opens the image; a modified one is purely - // a selection gesture and must not navigate away from the - // grid the user is building a selection in. The modifier - // state is not carried on `clicked`, so the press above - // records it and Rust decides — `cell-clicked` is only - // honoured when the press was unmodified. - clicked => { root.cell-clicked(i); } + clicked => { self.click-pending = true; } // The far end of a range, in selection mode. Slint // delivers `clicked` for the first tap as well, which // is why the toggling is idempotent-by-union in Rust