| 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,7 +866,7 @@ impl Ui { |
| 866 | } | 866 | } |
| 867 | 867 | ||
| 868 | /// Scrolls `scroller`, a scrolling box, until `target` within it starts at its top, both | 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 | pub fn scroll_to(&mut self, scroller: Id, target: Id) { | 870 | pub fn scroll_to(&mut self, scroller: Id, target: Id) { |
| 871 | let (Some(top), Some(start)) = (self.rect(target), self.rect(scroller)) else { | 871 | let (Some(top), Some(start)) = (self.rect(target), self.rect(scroller)) else { |
| 872 | return; | 872 | return; |
| ... | @@ -1036,7 +1036,9 @@ impl Ui { | ... | @@ -1036,7 +1036,9 @@ impl Ui { |
| 1036 | Some(id) => { | 1036 | Some(id) => { |
| 1037 | let state = self.states.entry(id).or_default(); | 1037 | let state = self.states.entry(id).or_default(); |
| 1038 | let most = (state.content - (state.rect[3] - state.rect[1])).max(0.0); | 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 | self.signals.entry(id).or_default().events.push(event); | 1042 | self.signals.entry(id).or_default().events.push(event); |
| 1041 | } | 1043 | } |
| 1042 | None => {} | 1044 | None => {} |
crates/ui/src/list.rs+2-1| ... | @@ -132,7 +132,8 @@ pub fn list<R: Rows>( | ... | @@ -132,7 +132,8 @@ pub fn list<R: Rows>( |
| 132 | 132 | ||
| 133 | for event in ui.signal(id).events { | 133 | for event in ui.signal(id).events { |
| 134 | if let Event::Wheel(delta) = event { | 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 | let mut clicked = None; | 139 | let mut clicked = None; |
crates/ui/src/shell.rs+12-2| ... | @@ -65,12 +65,18 @@ pub fn section_tabs( | ... | @@ -65,12 +65,18 @@ pub fn section_tabs( |
| 65 | let wide = left + lean(height) + crate::SHADOW[0]; | 65 | let wide = left + lean(height) + crate::SHADOW[0]; |
| 66 | let view = ui.rect(row).map_or(wide, |rect| rect[2] - rect[0]); | 66 | let view = ui.rect(row).map_or(wide, |rect| rect[2] - rect[0]); |
| 67 | let most = (wide - view).max(0.0); | 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 | let held_value = |ui: &Ui, id: Id| ui.states.get(&id).and_then(|state| state.tween); | 69 | let held_value = |ui: &Ui, id: Id| ui.states.get(&id).and_then(|state| state.tween); |
| 70 | let mut target = held_value(ui, scroll).map_or(0.0, |[_, target]| target); | 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 | for event in ui.signal(row).events { | 73 | for event in ui.signal(row).events { |
| 72 | if let crate::Event::Wheel([x, y]) = event { | 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 | target -= x + y; | 78 | target -= x + y; |
| 79 | wheeled = true; | ||
| 74 | } | 80 | } |
| 75 | } | 81 | } |
| 76 | if held_value(ui, shown).is_none_or(|[_, was]| was != active as f32) | 82 | if held_value(ui, shown).is_none_or(|[_, was]| was != active as f32) |
| ... | @@ -91,7 +97,11 @@ pub fn section_tabs( | ... | @@ -91,7 +97,11 @@ pub fn section_tabs( |
| 91 | Some(start) | 97 | Some(start) |
| 92 | }); | 98 | }); |
| 93 | let target = ui.hold(scroll, target.clamp(0.0, most)); | 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 | let fade = |cut: bool| if cut { EDGE } else { 0.0 }; | 105 | let fade = |cut: bool| if cut { EDGE } else { 0.0 }; |
| 96 | ui.open_as( | 106 | ui.open_as( |
| 97 | row, | 107 | row, |
crates/ui/src/tests.rs+16-15| ... | @@ -542,10 +542,12 @@ fn wheel_scrolls_within_the_content_and_clips_children() { | ... | @@ -542,10 +542,12 @@ fn wheel_scrolls_within_the_content_and_clips_children() { |
| 542 | build(&mut ui); | 542 | build(&mut ui); |
| 543 | ui.event(Event::PointerMoved([50.0, 50.0])); | 543 | ui.event(Event::PointerMoved([50.0, 50.0])); |
| 544 | ui.event(Event::Wheel([0.0, -1000.0])); | 544 | ui.event(Event::Wheel([0.0, -1000.0])); |
| 545 | for _ in 0..60 { | 545 | build(&mut ui); |
| 546 | build(&mut ui); | 546 | assert_eq!( |
| 547 | } | 547 | ui.states[&list.unwrap()].scroll, |
| 548 | assert_eq!(ui.states[&list.unwrap()].scroll, 200.0); | 548 | 200.0, |
| 549 | "at once, as on the page" | ||
| 550 | ); | ||
| 549 | assert_eq!(ui.rect(last.unwrap()), Some([0.0, 70.0, 100.0, 100.0])); | 551 | assert_eq!(ui.rect(last.unwrap()), Some([0.0, 70.0, 100.0, 100.0])); |
| 550 | let layers = ui.layers(); | 552 | let layers = ui.layers(); |
| 551 | assert!(matches!( | 553 | assert!(matches!( |
| ... | @@ -1906,10 +1908,10 @@ fn lists_hold_the_selection_still_as_items_arrive_and_leave_above() { | ... | @@ -1906,10 +1908,10 @@ fn lists_hold_the_selection_still_as_items_arrive_and_leave_above() { |
| 1906 | list_frame(&mut ui, &rows, &mut selected, &[]); | 1908 | list_frame(&mut ui, &rows, &mut selected, &[]); |
| 1907 | rows = Keyed::new((20_000..20_050).chain(rows.keys.iter().copied())); | 1909 | rows = Keyed::new((20_000..20_050).chain(rows.keys.iter().copied())); |
| 1908 | list_frame(&mut ui, &rows, &mut selected, &[]); | 1910 | list_frame(&mut ui, &rows, &mut selected, &[]); |
| 1909 | let moving = row_top(&ui, 500).unwrap(); | 1911 | assert_eq!( |
| 1910 | assert!( | 1912 | row_top(&ui, 500), |
| 1911 | moving < at && moving > at - 60.0, | 1913 | Some(at - 60.0), |
| 1912 | "the wheel eases on through arrivals" | 1914 | "the wheel moves the rows at once, and they hold through arrivals" |
| 1913 | ); | 1915 | ); |
| 1914 | settle_list(&mut ui, &rows, &mut selected); | 1916 | settle_list(&mut ui, &rows, &mut selected); |
| 1915 | assert_eq!(row_top(&ui, 500), Some(at - 60.0)); | 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,16 +2952,15 @@ fn the_wheel_scrolls_overflowing_tabs_sideways_either_way_it_turns() { |
| 2950 | let first = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; | 2952 | let first = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; |
| 2951 | ui.event(Event::PointerMoved([100.0, 14.0])); | 2953 | ui.event(Event::PointerMoved([100.0, 14.0])); |
| 2952 | ui.event(Event::Wheel([0.0, -40.0])); | 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 | let scrolled = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; | 2957 | let scrolled = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; |
| 2955 | assert!( | 2958 | assert_eq!(first - scrolled, 40.0, "at once, as on the page"); |
| 2956 | (first - scrolled - 40.0).abs() < 0.5, | ||
| 2957 | "{first} to {scrolled}" | ||
| 2958 | ); | ||
| 2959 | ui.event(Event::Wheel([30.0, 0.0])); | 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 | let back = ui.rect(shell::tab_id(row, 0)).unwrap()[0]; | 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 | #[test] | 2966 | #[test] |