git.lucas.co / cce-cloud
cloud storage client
git clone https://git.lucas.co/cce-cloud.git

commit6eedc3c78762f8b29b2fe11d14e1b6152fc7ae5e
parent10634222e4
authorLucas Galante <[email protected]>
date2026-08-14 14:11
fix: wire up the scrollbar so it can actually be dragged, and fix the wheel's coord space

`ScrollRegion::press`/`cursor_moved`/`release` were fully implemented and unit-tested
but never called, so the scrollbar drew and did nothing: no thumb grab, no track jump,
and `hovered`/`focused` were never maintained. They are now called from `pointer_frame`
for the non-Json modes.

The press had to be handled there rather than inside `FuzzelWidget::on_event`, because
the row branch treats ANY handled press as a selection and — in Dmenu/switcher mode —
prints it and closes the popup. A scrollbar press must scroll, not choose, so it takes
the event before the row dispatch is reached.

The same trace also turned up a coordinate-space bug in the existing wheel path.
`scale_pointer_pos` MULTIPLIES by the output scale, but this app's widget geometry is
logical (the window tracks `width / scale`), and both the Json dispatch and
`on_event` already use the raw surface-local `event.position`. So the wheel compared
physical cursor coords against a logical rect. Measured on this scale-2 output: the
list region is (15, 60, 270, 725), and a cursor at logical (250, 176) arrived as
(500, 352) — `hit()` false, nothing scrolled. Wheel scrolling was dead over roughly
the right half and bottom half of the list. `cursor_x`/`cursor_y` (whose only consumer
is the wheel) now hold logical coords.

Live-verified on a 100-item --dmenu list, standalone and through the daemon:
- thumb drag from the top of the track to y=650 scrolls item-1 -> item-50, prints
  nothing and leaves the popup open
- track jump near the bottom scrolls item-46 -> item-72, likewise silent
- the wheel at logical (250, 176), previously inert, now scrolls
- a normal row click still selects the row under the cursor and commits (item-73)

`ScrollRegion::keyboard` is still unwired: Up/Down already drive selection in the
launcher and would conflict. It is now reachable if wanted, since `hovered` is
maintained again.

Co-Authored-By: Claude Opus 5 <[email protected]>

 src/main.rs | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/src/main.rs b/src/main.rs
index 999562d..cf30366 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1710,7 +1710,15 @@ impl PointerHandler for AppState {
             }
             if let Some(st) = &mut self.state {
                 log::debug!("Event: position={:?}, scale={}, kind={:?}", event.position, st.scale, event.kind);
-                let (cx, cy) = cce_ui::wayland::scale_pointer_pos(event.position, st.scale);
+                // Surface-local LOGICAL coords. This app's widget geometry is logical (the
+                // window tracks `width / scale`), and every other consumer — the Json
+                // dispatch and `FuzzelWidget::on_event` below — already uses the raw
+                // `event.position`. `scale_pointer_pos` multiplies by the scale, so feeding
+                // its result to the scroll region compared PHYSICAL cursor coords against a
+                // LOGICAL rect: on a scale-2 output the wheel silently stopped working past
+                // the list's midpoint (cursor at logical x=250 arrived as 500 against a rect
+                // ending at 285, so `hit()` was false and nothing scrolled).
+                let (cx, cy) = (event.position.0 as f32, event.position.1 as f32);
                 match &event.kind {
                     PointerEventKind::Motion { .. } => {
                         st.cursor_x = cx;
@@ -1735,6 +1743,12 @@ impl PointerHandler for AppState {
                                 st.upload_vertices();
                                 self.redraw = true;
                             }
+                        } else if st.fuzzel.scroll_box.cursor_moved(cx, cy) {
+                            // Returns true only while a thumb drag is live; it also keeps
+                            // `hovered` current for the wheel/keyboard scope either way.
+                            st.fuzzel.update_scroll();
+                            st.upload_vertices();
+                            self.redraw = true;
                         }
                     }
                     PointerEventKind::Press { button, .. } => {
@@ -1762,6 +1776,15 @@ impl PointerHandler for AppState {
                                     st.upload_vertices();
                                     self.redraw = true;
                                 }
+                            } else if st.fuzzel.scroll_box.press(cx, cy) {
+                                // Thumb grab or track jump. This must be handled here rather
+                                // than inside `on_event`, because the row branch below treats
+                                // ANY handled press as a selection and — in Dmenu/switcher
+                                // mode — commits it and closes the popup. A scrollbar press
+                                // must scroll, not choose.
+                                st.fuzzel.update_scroll();
+                                st.upload_vertices();
+                                self.redraw = true;
                             } else {
                                 let prev_selected = st.fuzzel.selected;
                                 let changed = {
@@ -1866,6 +1889,11 @@ impl PointerHandler for AppState {
                                     self.selected_item = Some(out_str);
                                     should_close = true;
                                 }
+                            } else if st.fuzzel.scroll_box.release() {
+                                // Ends a thumb drag. Returns true only if one was live, so a
+                                // plain click on a row is unaffected.
+                                st.upload_vertices();
+                                self.redraw = true;
                             }
                         }
                     }