| author | |
| committer | |
| log | 9aaaf21dee13990267048e726313ee04c7861882 |
| tree | 2f97a91df00fb77a3324bb350e7ca7d19a4f8e62 |
| parent | ab00bdcf0c7a9eddb387eab4ad09df0b862c7b32 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
The drawn thumb always stopped 16 points short of its far end, leaving
room for the other axis's bar even in a menu or list, which has none, so
at the bottom of a long menu it still looked scrollable. The corner is now
left only where both axes scroll, as on the page, and the thumb keeps the
same 4 points from either end; the platform's scrollers on Mac OS X 10.6
run full length the same way.
Assisted-by: claude-opus-5.54 files changed, 64 insertions(+), 15 deletions(-)
crates/snowbound/src/main.rs+1| ... | ... | @@ -1754,6 +1754,7 @@ impl State { |
| 1754 | 1754 | -self.view.viewport.origin[index], |
| 1755 | 1755 | [scroll.min[index], scroll.max[index]], |
| 1756 | 1756 | self.view.viewport.size[index] as f32, |
| 1757 | scroll.max[1 - index] > scroll.min[1 - index], | |
| 1757 | 1758 | [0.18, 0.18, 0.18, 0.45], |
| 1758 | 1759 | ) { |
| 1759 | 1760 | let response = self.view.scroll_to(index, offset)?; |
crates/ui/src/list.rs+1| ... | ... | @@ -246,6 +246,7 @@ pub fn list<R: Rows>( |
| 246 | 246 | scroll as f32, |
| 247 | 247 | [0.0, most as f32], |
| 248 | 248 | view as f32, |
| 249 | false, | |
| 249 | 250 | thumb, |
| 250 | 251 | ) { |
| 251 | 252 | // Rows keep their places relative to the new offset, or next frame's hold on the |
crates/ui/src/tests.rs+51-7| ... | ... | @@ -862,13 +862,20 @@ fn scrollbar_thumbs_track_the_offset_and_drags_reach_both_ends() { |
| 862 | 862 | "view", |
| 863 | 863 | Spec { |
| 864 | 864 | flags: Flags::CUSTOM, |
| 865 | size: [px(200.0), px(220.0)], | |
| 865 | size: [px(200.0), px(218.0)], | |
| 866 | 866 | ..Spec::default() |
| 867 | 867 | }, |
| 868 | 868 | ); |
| 869 | if let Some(chosen) = | |
| 870 | scrollbar(ui, "bar", Axis::Y, *offset, [0.0, 1000.0], 400.0, [0.5; 4]) | |
| 871 | { | |
| 869 | if let Some(chosen) = scrollbar( | |
| 870 | ui, | |
| 871 | "bar", | |
| 872 | Axis::Y, | |
| 873 | *offset, | |
| 874 | [0.0, 1000.0], | |
| 875 | 400.0, | |
| 876 | true, | |
| 877 | [0.5; 4], | |
| 878 | ) { | |
| 872 | 879 | *offset = chosen; |
| 873 | 880 | } |
| 874 | 881 | ui.close(); |
| ... | ... | @@ -902,6 +909,36 @@ fn scrollbar_thumbs_track_the_offset_and_drags_reach_both_ends() { |
| 902 | 909 | assert_eq!(offset, 0.0); |
| 903 | 910 | } |
| 904 | 911 | |
| 912 | #[test] | |
| 913 | fn a_scrollbar_without_a_corner_ends_as_far_from_its_end_as_from_its_start() { | |
| 914 | let mut ui = Ui::new(Theme::dark(), DOUBLE_CLICK); | |
| 915 | for _ in 0..2 { | |
| 916 | frame(&mut ui, |ui| { | |
| 917 | ui.open( | |
| 918 | "view", | |
| 919 | Spec { | |
| 920 | flags: Flags::CUSTOM, | |
| 921 | size: [px(200.0), px(208.0)], | |
| 922 | ..Spec::default() | |
| 923 | }, | |
| 924 | ); | |
| 925 | scrollbar( | |
| 926 | ui, | |
| 927 | "bar", | |
| 928 | Axis::Y, | |
| 929 | 1000.0, | |
| 930 | [0.0, 1000.0], | |
| 931 | 400.0, | |
| 932 | false, | |
| 933 | [0.5; 4], | |
| 934 | ); | |
| 935 | ui.close(); | |
| 936 | }); | |
| 937 | } | |
| 938 | let rect = ui.rect(Id::ROOT.child("view").child("bar")).unwrap(); | |
| 939 | assert_eq!(rect[3], 204.0); | |
| 940 | } | |
| 941 | ||
| 905 | 942 | #[test] |
| 906 | 943 | fn system_scrollers_step_page_and_drag_by_the_parts_the_platform_paints() { |
| 907 | 944 | let mut ui = Ui::new(Theme::dark(), DOUBLE_CLICK); |
| ... | ... | @@ -936,9 +973,16 @@ fn system_scrollers_step_page_and_drag_by_the_parts_the_platform_paints() { |
| 936 | 973 | ..Spec::default() |
| 937 | 974 | }, |
| 938 | 975 | ); |
| 939 | if let Some(chosen) = | |
| 940 | scrollbar(ui, "bar", Axis::Y, *offset, [0.0, 1000.0], 400.0, [0.5; 4]) | |
| 941 | { | |
| 976 | if let Some(chosen) = scrollbar( | |
| 977 | ui, | |
| 978 | "bar", | |
| 979 | Axis::Y, | |
| 980 | *offset, | |
| 981 | [0.0, 1000.0], | |
| 982 | 400.0, | |
| 983 | true, | |
| 984 | [0.5; 4], | |
| 985 | ) { | |
| 942 | 986 | *offset = chosen; |
| 943 | 987 | } |
| 944 | 988 | ui.close(); |
crates/ui/src/widgets.rs+11-8| ... | ... | @@ -130,8 +130,10 @@ const REPEAT: Duration = Duration::from_millis(50); |
| 130 | 130 | |
| 131 | 131 | /// A scrollbar along the far edge of the current box, for content whose scroll offset |
| 132 | 132 | /// ranges over `range` while `view` of it shows, all in one unit: the platform's scroller |
| 133 | /// where `Ui::scrollers` has one, otherwise a thumb in `color` over the content. Returns | |
| 134 | /// the offset the scrollbar chose. | |
| 133 | /// where `Ui::scrollers` has one, otherwise a thumb in `color` over the content. With a | |
| 134 | /// `corner`, its far end leaves room for the other axis's scrollbar. Returns the offset | |
| 135 | /// the scrollbar chose. | |
| 136 | #[allow(clippy::too_many_arguments)] | |
| 135 | 137 | pub fn scrollbar( |
| 136 | 138 | ui: &mut Ui, |
| 137 | 139 | part: impl Hash, |
| ... | ... | @@ -139,18 +141,19 @@ pub fn scrollbar( |
| 139 | 141 | offset: f32, |
| 140 | 142 | range: [f32; 2], |
| 141 | 143 | view: f32, |
| 144 | corner: bool, | |
| 142 | 145 | color: [f32; 4], |
| 143 | 146 | ) -> Option<f32> { |
| 144 | 147 | if ui.scrollers.is_some() { |
| 145 | return system_scrollbar(ui, part, axis, offset, range, view); | |
| 148 | return system_scrollbar(ui, part, axis, offset, range, view, corner); | |
| 146 | 149 | } |
| 147 | 150 | let along = usize::from(axis == Axis::Y); |
| 148 | 151 | let rect = ui.rect(ui.current())?; |
| 149 | 152 | let length = rect[along + 2] - rect[along]; |
| 150 | 153 | let cross = rect[3 - along] - rect[1 - along]; |
| 151 | 154 | let span = range[1] - range[0]; |
| 152 | // The far end leaves room for the other axis's bar. | |
| 153 | let track = length - 20.0; | |
| 155 | // A thumb keeps 4 from each end and the box's far edge, and is 6 across. | |
| 156 | let track = length - 8.0 - if corner { 10.0 } else { 0.0 }; | |
| 154 | 157 | if span <= 0.0 || track <= 0.0 { |
| 155 | 158 | return None; |
| 156 | 159 | } |
| ... | ... | @@ -197,13 +200,13 @@ fn system_scrollbar( |
| 197 | 200 | offset: f32, |
| 198 | 201 | range: [f32; 2], |
| 199 | 202 | view: f32, |
| 203 | corner: bool, | |
| 200 | 204 | ) -> Option<f32> { |
| 201 | 205 | let along = usize::from(axis == Axis::Y); |
| 202 | 206 | let rect = ui.rect(ui.current())?; |
| 203 | 207 | let span = range[1] - range[0]; |
| 204 | 208 | let thickness = ui.scrollers.as_ref()?.thickness; |
| 205 | // The far end leaves the corner beside the other axis's scroller. | |
| 206 | let length = rect[along + 2] - rect[along] - thickness; | |
| 209 | let length = rect[along + 2] - rect[along] - if corner { thickness } else { 0.0 }; | |
| 207 | 210 | if span <= 0.0 || length <= 0.0 { |
| 208 | 211 | return None; |
| 209 | 212 | } |
| ... | ... | @@ -283,7 +286,7 @@ fn system_scrollbar( |
| 283 | 286 | ..Spec::default() |
| 284 | 287 | }, |
| 285 | 288 | ); |
| 286 | if axis == Axis::Y { | |
| 289 | if axis == Axis::Y && corner { | |
| 287 | 290 | // The corner between the two scrollers shows the content's white, as AppKit's does. |
| 288 | 291 | ui.leaf( |
| 289 | 292 | ("corner", &part), |