| author | |
| committer | |
| log | ba16a18feec9b6b0d5b03464b1e8cda9ded4a81e |
| tree | 9ecd9d41c836152bb8342c0b34c03c18fe6a873c |
| parent | 12d02ba99c1881032feaeb079d60181d0f066945 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Lists, menus, the sidebar, Options and the section tabs eased towards
where the wheel sent them, so on GNOME, where the page follows each
delta as it comes, the settings felt smooth-scrolled and out of step.
Every scroller in the interface kit now moves by each wheel or trackpad
delta at once, from the same deltas the page takes; only programmatic
jumps, as scrolling to an index entry or a newly opened tab, still ease.
Assisted-by: claude-opus-5.54 files changed, 34 insertions(+), 20 deletions(-)
crates/ui/src/lib.rs+4-2| ... | ... | @@ -866,7 +866,7 @@ impl Ui { |
| 866 | 866 | } |
| 867 | 867 | |
| 868 | 868 | /// Scrolls `scroller`, a scrolling box, until `target` within it starts at its top, both |
| 869 | /// as last laid out; the wheel's easing carries it there. | |
| 869 | /// as last laid out, easing there. | |
| 870 | 870 | pub fn scroll_to(&mut self, scroller: Id, target: Id) { |
| 871 | 871 | let (Some(top), Some(start)) = (self.rect(target), self.rect(scroller)) else { |
| 872 | 872 | return; |
| ... | ... | @@ -1036,7 +1036,9 @@ impl Ui { |
| 1036 | 1036 | Some(id) => { |
| 1037 | 1037 | let state = self.states.entry(id).or_default(); |
| 1038 | 1038 | let most = (state.content - (state.rect[3] - state.rect[1])).max(0.0); |
| 1039 | state.scroll_target = (state.scroll_target - delta[1]).clamp(0.0, most); | |
| 1039 | // The wheel moves the content at once, as on the page; only jumps ease. | |
| 1040 | state.scroll = (state.scroll - delta[1]).clamp(0.0, most); | |
| 1041 | state.scroll_target = state.scroll; | |
| 1040 | 1042 | self.signals.entry(id).or_default().events.push(event); |
| 1041 | 1043 | } |
| 1042 | 1044 | None => {} |
crates/ui/src/list.rs+2-1| ... | ... | @@ -132,7 +132,8 @@ pub fn list<R: Rows>( |
| 132 | 132 | |
| 133 | 133 | for event in ui.signal(id).events { |
| 134 | 134 | if let Event::Wheel(delta) = event { |
| 135 | state.target = (state.target - f64::from(delta[1])).clamp(0.0, most); | |
| 135 | state.scroll = (state.scroll - f64::from(delta[1])).clamp(0.0, most); | |
| 136 | state.target = state.scroll; | |
| 136 | 137 | } |
| 137 | 138 | } |
| 138 | 139 | let mut clicked = None; |
crates/ui/src/shell.rs+12-2| ... | ... | @@ -65,12 +65,18 @@ pub fn section_tabs( |
| 65 | 65 | let wide = left + lean(height) + crate::SHADOW[0]; |
| 66 | 66 | let view = ui.rect(row).map_or(wide, |rect| rect[2] - rect[0]); |
| 67 | 67 | let most = (wide - view).max(0.0); |
| 68 | let [scroll, shown] = [row.child("scroll"), row.child("shown")]; | |
| 68 | let [scroll, shown, shift] = [row.child("scroll"), row.child("shown"), row.child("offset")]; | |
| 69 | 69 | let held_value = |ui: &Ui, id: Id| ui.states.get(&id).and_then(|state| state.tween); |
| 70 | 70 | let mut target = held_value(ui, scroll).map_or(0.0, |[_, target]| target); |
| 71 | // The wheel moves the tabs at once from where they show, as on the page; only jumps ease. | |
| 72 | let mut wheeled = false; | |
| 71 | 73 | for event in ui.signal(row).events { |
| 72 | 74 | if let crate::Event::Wheel([x, y]) = event { |
| 75 | if !wheeled { | |
| 76 | target = held_value(ui, shift).map_or(target, |[value, _]| value); | |
| 77 | } | |
| 73 | 78 | target -= x + y; |
| 79 | wheeled = true; | |
| 74 | 80 | } |
| 75 | 81 | } |
| 76 | 82 | if held_value(ui, shown).is_none_or(|[_, was]| was != active as f32) |
| ... | ... | @@ -91,7 +97,11 @@ pub fn section_tabs( |
| 91 | 97 | Some(start) |
| 92 | 98 | }); |
| 93 | 99 | let target = ui.hold(scroll, target.clamp(0.0, most)); |
| 94 | let offset = ui.animate(row.child("offset"), target); | |
| 100 | let offset = if wheeled { | |
| 101 | ui.hold(shift, target) | |
| 102 | } else { | |
| 103 | ui.animate(shift, target) | |
| 104 | }; | |
| 95 | 105 | let fade = |cut: bool| if cut { EDGE } else { 0.0 }; |
| 96 | 106 | ui.open_as( |
| 97 | 107 | row, |
crates/ui/src/tests.rs+16-15| ... | ... | @@ -542,10 +542,12 @@ fn wheel_scrolls_within_the_content_and_clips_children() { |
| 542 | 542 | build(&mut ui); |
| 543 | 543 | ui.event(Event::PointerMoved([50.0, 50.0])); |
| 544 | 544 | ui.event(Event::Wheel([0.0, -1000.0])); |
| 545 | for _ in 0..60 { | |
| 546 | build(&mut ui); | |
| 547 | } | |
| 548 | assert_eq!(ui.states[&list.unwrap()].scroll, 200.0); | |
| 545 | build(&mut ui); | |
| 546 | assert_eq!( | |
| 547 | ui.states[&list.unwrap()].scroll, | |
| 548 | 200.0, | |
| 549 | "at once, as on the page" | |
| 550 | ); | |
| 549 | 551 | assert_eq!(ui.rect(last.unwrap()), Some([0.0, 70.0, 100.0, 100.0])); |
| 550 | 552 | let layers = ui.layers(); |
| 551 | 553 | assert!(matches!( |
| ... | ... | @@ -1906,10 +1908,10 @@ fn lists_hold_the_selection_still_as_items_arrive_and_leave_above() { |
| 1906 | 1908 | list_frame(&mut ui, &rows, &mut selected, &[]); |
| 1907 | 1909 | rows = Keyed::new((20_000..20_050).chain(rows.keys.iter().copied())); |
| 1908 | 1910 | list_frame(&mut ui, &rows, &mut selected, &[]); |
| 1909 | let moving = row_top(&ui, 500).unwrap(); | |
| 1910 | assert!( | |
| 1911 | moving < at && moving > at - 60.0, | |
| 1912 | "the wheel eases on through arrivals" | |
| 1911 | assert_eq!( | |
| 1912 | row_top(&ui, 500), | |
| 1913 | Some(at - 60.0), | |
| 1914 | "the wheel moves the rows at once, and they hold through arrivals" | |
| 1913 | 1915 | ); |
| 1914 | 1916 | settle_list(&mut ui, &rows, &mut selected); |
| 1915 | 1917 | assert_eq!(row_top(&ui, 500), Some(at - 60.0)); |
| ... | ... | @@ -2950,16 +2952,15 @@ fn the_wheel_scrolls_overflowing_tabs_sideways_either_way_it_turns() { |
| 2950 | 2952 | let first = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; |
| 2951 | 2953 | ui.event(Event::PointerMoved([100.0, 14.0])); |
| 2952 | 2954 | ui.event(Event::Wheel([0.0, -40.0])); |
| 2953 | settle_tabs(&mut ui, 0); | |
| 2955 | tab_row(&mut ui, 0); | |
| 2956 | tab_row(&mut ui, 0); | |
| 2954 | 2957 | let scrolled = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; |
| 2955 | assert!( | |
| 2956 | (first - scrolled - 40.0).abs() < 0.5, | |
| 2957 | "{first} to {scrolled}" | |
| 2958 | ); | |
| 2958 | assert_eq!(first - scrolled, 40.0, "at once, as on the page"); | |
| 2959 | 2959 | ui.event(Event::Wheel([30.0, 0.0])); |
| 2960 | settle_tabs(&mut ui, 0); | |
| 2960 | tab_row(&mut ui, 0); | |
| 2961 | tab_row(&mut ui, 0); | |
| 2961 | 2962 | let back = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; |
| 2962 | assert!((back - scrolled - 30.0).abs() < 0.5, "{scrolled} to {back}"); | |
| 2963 | assert_eq!(back - scrolled, 30.0); | |
| 2963 | 2964 | } |
| 2964 | 2965 | |
| 2965 | 2966 | #[test] |