mail client (IMAP/SMTP)
git clone https://git.lucas.co/cce-mail.git
feat: wire list keyboard scrolling + scrollbar above rows
ScrollRegion::keyboard() (ArrowUp/Down, PageUp/Down, Home/End, ctrl
n/p) existed since the Phase 6q port but was never called — the list
had wheel-only scrolling. It now runs in handle_key_input, scoped by
the region's own hover-or-focus gate, after app shortcuts and the
detail-body scroll (which wins while the detail pane is hovered), and
never while the search box is editing.
Also split ScrollRegion::push_quads into background + scrollbar halves
and emit the scrollbar after the rows: drawn before them, the thumb
passed under the opaque row panels and peeked through the 4px inter-row
gaps as dotted segments (visible whenever the list overflows — which
the 50-message fetch makes the common case).
Live-verified via keypress injection: PageDown pages 20-14 -> 12-07,
Home returns to top, thumb renders as one continuous bar over the rows.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/main.rs | 15 +++++++++++++++
src/scroll_region.rs | 12 ++++++++----
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index a78a4b1..c2accf2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2023,6 +2023,13 @@ impl Application for ClearEmailApp {
}
}
}
+ // Scrollbar after the rows so the thumb rides on top of them instead of
+ // peeking through the inter-row gaps.
+ {
+ let mut sb_quads = Vec::new();
+ self.email_list.push_scrollbar_quads(&mut sb_quads);
+ quads.extend(sb_quads);
+ }
// 4. Detail View Area
if self.current_folder == Folder::Accounts {
@@ -2675,6 +2682,14 @@ impl Application for ClearEmailApp {
}
}
+ // Email/accounts list keyboard scrolling. ScrollRegion scopes itself to
+ // hover-or-focus (a row click focuses the region, a press elsewhere
+ // unfocuses); the search box owns the keys while editing, and a
+ // body-scroll above wins when the detail pane is hovered.
+ if !handled && !self.search_box.editing && self.email_list.keyboard(event) {
+ handled = true;
+ }
+
if !handled && self.current_folder != Folder::Accounts && self.search_box.editing {
if ctx.propagate_event(&kev, self.search_box.id()) {
handled = true;
diff --git a/src/scroll_region.rs b/src/scroll_region.rs
index 7ac084e..17c7779 100644
--- a/src/scroll_region.rs
+++ b/src/scroll_region.rs
@@ -214,12 +214,16 @@ impl ScrollRegion {
(self.scroll_y - old).abs() > 0.01
}
- /// The legacy frame, single-drawn: 1px rounded border (focus/hover tinted, from
- /// `List::solid_border`), inset rounded bg, then the scrollbar track and thumb ON TOP.
- /// The legacy `ScrollBox::extra_quads` emission: flat background, then the
- /// scrollbar track + thumb when the content overflows.
+ /// The region's flat background (the legacy `ScrollBox::extra_quads` fill).
+ /// The scrollbar is split into [`push_scrollbar_quads`](Self::push_scrollbar_quads)
+ /// so the host can emit it AFTER the rows — drawn together, the rows paint over
+ /// the thumb and it peeks through the inter-row gaps as dotted segments.
pub fn push_quads(&self, quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>) {
quads.push((self.x, self.y, self.w, self.h, cce_ui::color::list_bg_color()));
+ }
+
+ /// Scrollbar track + thumb when the content overflows; emit after the rows.
+ pub fn push_scrollbar_quads(&self, quads: &mut Vec<(f32, f32, f32, f32, [f32; 4])>) {
if self.content_h > self.viewport_h {
let (sb_x, track_y, sb_w, track_h, thumb_y, thumb_h) = self.scrollbar_geom();
quads.push((sb_x, track_y, sb_w, track_h, cce_ui::color::scrollbar_track_color()));