git.lucas.co / cce-files
file manager
git clone https://git.lucas.co/cce-files.git

commitf65a573f128041f649383f9d5da4e5d3473ede7b
parentf0507316fb
authorLucas Galante <[email protected]>
date2026-08-24 11:19
chooser: scroll actually scrolls; truly colorless footer buttons

Three fixes from live feedback on the real session:

Scroll: the browse layout pass runs every frame and ended with an
unconditional scroll_into_view(selected) — so with any selection, every
wheel scroll was snapped back to the selected row on the next frame. The
chooser ALWAYS has a selection (row 0 starts selected), so its list simply
never scrolled; the full browser had the same bug whenever a row was
selected. Auto-scroll now fires only when the selection CHANGES
(autoscrolled_to tracks the last target). Found via CCE_SCROLL_DEBUG plus
new app-side tracer lines in handle_mouse_wheel (kept, same gating): the
engine delivered the wheel and the list even returned true — the state
change was being reverted, not refused.

Buttons: "plain" was not plain — the toolkit's Primary face is itself
blue-tinted, which at the real session's scale read as exactly the tint
that was supposed to be gone. button_plain now uses a transparent face
over the theme's border/relief (the closed-dropdown convention) with a
faint neutral hover.

Spacing: the extra backplate inset left Cancel/Save hanging short of the
panel column above them; they now sit flush with the panels' right edge.

 src/main.rs         | 14 +++++++++++---
 src/pages/browse.rs | 18 +++++++++++++++---
 src/pages/mod.rs    |  9 ++++++++-
 3 files changed, 34 insertions(+), 7 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 7d21e0f..ec75e46 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -613,8 +613,9 @@ impl FilesystemApp {
             // toolkit's plain button face, not hand-picked red/green tints.
             let btn_w = 84.0;
             let gap = 10.0;
-            let pad = cce_ui::layout::backplate_padding().max(gap);
-            let confirm_x = browse_x + usable_w - pad - btn_w;
+            // Flush with the panels' right edge — an extra inset here left the
+            // buttons hanging short of the column above them.
+            let confirm_x = browse_x + usable_w - btn_w;
             let cancel_x = confirm_x - gap - btn_w;
             window_pc.button_plain("Cancel", cancel_x, btn_y, btn_w, btn_h, Message::SelectCancel);
             let button_label = if self.save_mode { "Save" } else { "Select" };
@@ -2070,6 +2071,9 @@ impl Application for FilesystemApp {
             // The pane hit-tests its own laid-out rect and consumes any wheel
             // over its content region, scrolled or not.
             if let Some(changed) = self.preview.wheel(delta, pos.x as f32, pos.y as f32) {
+                if cce_ui::scroll_debug() {
+                    eprintln!("[scroll] files: preview consumed at ({:.0},{:.0})", pos.x, pos.y);
+                }
                 if changed {
                     *needs_rebuild = true;
                     self.needs_rebuild = true;
@@ -2079,7 +2083,11 @@ impl Application for FilesystemApp {
         }
 
         if self.current_page == Page::Browse {
-            if self.browse.list.wheel(delta, pos.x, pos.y) {
+            let hit = self.browse.list.wheel(delta, pos.x, pos.y);
+            if cce_ui::scroll_debug() {
+                eprintln!("[scroll] files: browse.list.wheel at ({:.0},{:.0}) -> {hit}", pos.x, pos.y);
+            }
+            if hit {
                 *needs_rebuild = true;
                 self.needs_rebuild = true;
             }
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index f272863..497e64e 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -29,6 +29,12 @@ pub struct BrowseState {
     pub search_visible: bool,
     pub search_box: cce_ui::widget::Adapted<cce_ui::widget::TextBox>,
     pub selected: Option<usize>,
+    /// The selection the list was last auto-scrolled to. The layout pass runs
+    /// every frame, and an unconditional scroll_into_view there UNDID every
+    /// wheel scroll on the next frame whenever a selection existed — which in
+    /// the chooser is always, since row 0 starts selected. Auto-scroll fires
+    /// on selection CHANGE only.
+    pub autoscrolled_to: Option<usize>,
     pub breadcrumb: Adapted<Breadcrumb>,
     pub save_name_box: cce_ui::widget::Adapted<cce_ui::widget::TextBox>,
 }
@@ -50,6 +56,7 @@ impl Default for BrowseState {
                 .with_placeholder("Search...")
                 .with_update_on_type(true),
             selected: None,
+            autoscrolled_to: None,
             breadcrumb,
             save_name_box: cce_ui::widget::TextBox::new(String::new()).with_max_width(None),
         };
@@ -314,9 +321,14 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
     state.list.set_rect(list_x, list_y, list_w, list_h, search_offset);
     state.list.update_bounds_from_rows();
 
-    // Auto-scroll to keep selection in view
-    if let Some(selected_idx) = state.selected {
-        state.list.scroll_into_view(selected_idx);
+    // Auto-scroll to keep a NEWLY selected row in view (keyboard nav, click).
+    // Not every frame: this pass runs per frame, and re-asserting the scroll
+    // for an unchanged selection reverted every wheel scroll immediately.
+    if state.selected != state.autoscrolled_to {
+        if let Some(selected_idx) = state.selected {
+            state.list.scroll_into_view(selected_idx);
+        }
+        state.autoscrolled_to = state.selected;
     }
 
     state.list.push_prims(&mut pc);
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 4f74797..907fc09 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -257,7 +257,14 @@ impl PageContent {
     /// hand-tinted variant above predates the themed Button; chrome buttons
     /// (the chooser's Cancel/Save) should look like every other DE button.
     pub fn button_plain(&mut self, label: &str, x: f32, y: f32, w: f32, h: f32, action: crate::Message) {
-        let btn = cce_ui::widget::Button::new(x, y, w, h).with_label(label);
+        // Colorless chrome: transparent face over the theme's border/relief —
+        // the closed-dropdown convention — with a faint neutral hover. The
+        // toolkit's Primary face is itself blue-tinted, which is exactly what
+        // these buttons are not supposed to be.
+        let btn = cce_ui::widget::Button::new(x, y, w, h)
+            .with_label(label)
+            .with_bg([0.0, 0.0, 0.0, 0.0])
+            .with_hover_bg([1.0, 1.0, 1.0, 0.10]);
         self.buttons.push((btn, action));
     }