diff --git a/ui/dr-ui/src/library_ui.rs b/ui/dr-ui/src/library_ui.rs index 8b29283..21e51a9 100644 --- a/ui/dr-ui/src/library_ui.rs +++ b/ui/dr-ui/src/library_ui.rs @@ -3480,6 +3480,18 @@ pub fn wire( // controllers would hold each other alive for the life of the process. *ctl.coll_ctl.borrow_mut() = Some(Rc::downgrade(&coll_ctl)); + // TRACES: FR-UI-3 | FR-UI-4 + // Whether the rating strip waits to be hovered or stands open. + // + // On Android touch is not evidence to be gathered, it is the platform. + // The grid latches it from the first finger it sees as well, which is what + // covers a touchscreen on the desktop — but that latch needs a press to + // reach a cell, and a quick flick never delivers one because the Flickable + // claims the gesture before the delay it would forward after. Seeding it + // here means the stars are on screen before the first touch rather than + // after it, which is the whole point of showing them. + window.set_library_touched(cfg!(target_os = "android")); + // Shared rather than moved: a click and `Return` both open an image, and // they are two callbacks. let on_open_image = Rc::new(on_open_image); diff --git a/ui/dr-ui/ui/app.slint b/ui/dr-ui/ui/app.slint index d2fb338..d332ac3 100644 --- a/ui/dr-ui/ui/app.slint +++ b/ui/dr-ui/ui/app.slint @@ -507,6 +507,10 @@ export component AppWindow inherits Window { in-out property library-columns: 1; in property library-syncing: false; in-out property library-cell-size: 180px; + /// Whether this session is a touch one, which decides if the rating strip + /// waits to be hovered or stands open. Seeded from the platform by Rust + /// and latched by the grid on the first finger it sees. + in-out property library-touched: false; callback library-zoom-cells(int); /// A pinch step on the grid: the ratio since the last update. callback library-pinch-cells(float); @@ -1133,6 +1137,7 @@ in property panel-visible: true; timeline-zoom(d) => { root.library-timeline-zoom(d); } syncing: root.library-syncing; requested-cell-size: root.library-cell-size; + touched <=> root.library-touched; zoom-cells(d) => { root.library-zoom-cells(d); } pinch-cells(r) => { root.library-pinch-cells(r); } pinch-started() => { root.library-pinch-started(); } diff --git a/ui/dr-ui/ui/library.slint b/ui/dr-ui/ui/library.slint index 83ae0e6..5089a45 100644 --- a/ui/dr-ui/ui/library.slint +++ b/ui/dr-ui/ui/library.slint @@ -1186,6 +1186,34 @@ export component LibraryGrid inherits Rectangle { // 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. + // --- has this session been touched? ----------------------------------- + // + // **Hover is not a thing a finger does, but Slint reports it anyway.** + // `has-hover` goes true for any pointer event carrying a position, a touch + // press included, and false again on the `Exit` that follows the release. + // So on a tablet the rating strip did appear — for exactly the length of a + // tap. It flashed on under the finger, vanished as it lifted, and the tap + // went through to the cell and opened the photograph. There was no way to + // rate an unjudged frame from the grid at all. + // + // The strip therefore stops keying off hover as soon as there is evidence + // that this is a touch session, and evidence is what `touch-finger-id` + // is: zero for a mouse, never zero for a finger. Latched rather than + // sampled per event, because the strip has to be on screen *before* the + // finger arrives to be worth aiming at. + // + // One-way on purpose. A tablet with a mouse plugged in keeps the strips + // once it has been touched, which is the harmless direction to be wrong + // in — the alternative is chrome that comes and goes as the user changes + // hands. + // + // Seeded by Rust as well as latched here, because the latch alone needs a + // press to reach a cell and a quick flick never delivers one — the + // Flickable claims the gesture before the delay it forwards after. On + // Android touch is not evidence to be gathered, it is the platform, so it + // starts true there and this is left to catch a touchscreen on the desktop. + in-out property touched: false; + property pinching: false; settle := Timer { interval: 350ms; @@ -2202,6 +2230,10 @@ export component LibraryGrid inherits Rectangle { if (ev.kind == PointerEventKind.down) { self.down-finger = ev.touch-finger-id; self.click-pending = false; + // A finger, not a pointer — see `touched`. + if (ev.touch-finger-id != 0) { + root.touched = true; + } // 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 @@ -2290,11 +2322,12 @@ export component LibraryGrid inherits Rectangle { rating: cell.rating; // Empty stars appear once the pointer is over the // cell, so there is something to aim at without - // filling the grid with chrome. On touch there is no - // hover, so the strip is always present where a - // rating exists — and a long-press is not needed to - // discover it. - show-empty: cell-touch.has-hover; + // filling the grid with chrome. That trade only + // works where there is a pointer to hover with: on + // touch the strip stands open, because a control + // that appears under the finger is a control that + // appears too late to aim at. See `touched`. + show-empty: cell-touch.has-hover || root.touched; can-trash: !root.viewing-trash; rate(n) => { root.cell-rated(i, n); } trash() => { root.cell-trashed(i); }