things-to-remember checklist
git clone https://git.lucas.co/cce-list.git
feat(scroll): glide and coast the list through cce-ui's ScrollMotion
The wheel used to step the offset by whole rows instantly; it now feeds
the shared ScrollMotion (notches glide, trackpad tracks 1:1, a flick
coasts) and tick() advances it, re-deriving the hovered row as the drawn
offset moves. Pixel deltas are now 1:1 pixels like every other list, not
notch-normalized.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/main.rs | 53 ++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 42 insertions(+), 11 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index e2830db..4173d7e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,8 +12,8 @@ use cce_ui::engine::{Application, EngineState, LogicalPosition, LogicalSize, Win
use cce_ui::scene::layout::Rect;
use cce_ui::scene::paint::{Cap, DisplayList, PaintCtx, PlateSpec};
use cce_ui::widget::{
- Adapted, ElementState, Event, Key, KeyEvent, MouseButton, MouseScrollDelta, NamedKey, TextBox,
- WidgetHost,
+ Adapted, Bounds, ElementState, Event, Key, KeyEvent, MouseButton, MouseScrollDelta, NamedKey,
+ ScrollMotion, TextBox, WidgetHost,
};
use std::path::PathBuf;
use wayland_client::QueueHandle;
@@ -158,8 +158,11 @@ struct ListApp {
needs_rebuild: bool,
widgets_registered: bool,
/// How far the list is scrolled down, in logical px; non-zero only once
- /// the rows overflow the window.
+ /// the rows overflow the window. The DRAWN offset — `scroll_motion`
+ /// glides it (wheel) or coasts it (trackpad flick); direct writes (End
+ /// key, clamp) are adopted by the motion on its next step.
scroll: f32,
+ scroll_motion: ScrollMotion,
pointer: Option<(f32, f32)>,
hovered_row: Option<usize>,
}
@@ -202,6 +205,29 @@ impl ListApp {
self.scroll = self.scroll.clamp(0.0, self.max_scroll(&m));
}
+ /// Re-derive what depends on the drawn offset after it moved.
+ fn after_scroll_moved(&mut self) {
+ if let Some((px, py)) = self.pointer {
+ self.hovered_row = self.row_at(px, py);
+ }
+ }
+
+ /// Advance the wheel glide / flick coast; true while the offset moved.
+ fn tick_scroll(&mut self, dt: f32) -> bool {
+ self.scroll_motion.reconcile(0.0, self.scroll);
+ if !self.scroll_motion.is_animating() {
+ return false;
+ }
+ let m = metrics(self.width as f32);
+ let max = self.max_scroll(&m);
+ let moved = self.scroll_motion.tick(dt, Bounds::max(0.0), Bounds::max(max));
+ self.scroll = self.scroll_motion.y.pos();
+ if moved {
+ self.after_scroll_moved();
+ }
+ moved || self.scroll_motion.is_animating()
+ }
+
fn row_at(&self, x: f32, y: f32) -> Option<usize> {
let m = metrics(self.width as f32);
let vp = self.list_viewport(&m);
@@ -256,6 +282,7 @@ impl Application for ListApp {
needs_rebuild: true,
widgets_registered: false,
scroll: 0.0,
+ scroll_motion: ScrollMotion::new(),
pointer: None,
hovered_row: None,
}
@@ -283,6 +310,10 @@ impl Application for ListApp {
*needs_rebuild = true;
self.needs_rebuild = true;
}
+ if self.tick_scroll(dt) {
+ *needs_rebuild = true;
+ self.needs_rebuild = true;
+ }
}
fn display_list(&mut self, size: LogicalSize, scale: f64) -> Option<DisplayList> {
@@ -512,21 +543,21 @@ impl Application for ListApp {
needs_rebuild: &mut bool,
) {
let m = metrics(self.width as f32);
- if self.max_scroll(&m) <= 0.0 {
+ let max = self.max_scroll(&m);
+ if max <= 0.0 {
return;
}
- let before = self.scroll;
- self.scroll -= delta.notches_y() * ROW_H;
- self.clamp_scroll();
- if self.scroll != before {
- if let Some((px, py)) = self.pointer {
- self.hovered_row = self.row_at(px, py);
- }
+ self.scroll_motion.reconcile(0.0, self.scroll);
+ let moved = self.scroll_motion.apply(delta, (ROW_H, ROW_H), Bounds::max(0.0), Bounds::max(max));
+ self.scroll = self.scroll_motion.y.pos();
+ if moved {
+ self.after_scroll_moved();
self.needs_rebuild = true;
*needs_rebuild = true;
}
}
+
fn handle_key_input(
&mut self,
event: &KeyEvent,