| 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,6 +1754,7 @@ impl State { |
| 1754 | -self.view.viewport.origin[index], | 1754 | -self.view.viewport.origin[index], |
| 1755 | [scroll.min[index], scroll.max[index]], | 1755 | [scroll.min[index], scroll.max[index]], |
| 1756 | self.view.viewport.size[index] as f32, | 1756 | self.view.viewport.size[index] as f32, |
| 1757 | scroll.max[1 - index] > scroll.min[1 - index], | ||
| 1757 | [0.18, 0.18, 0.18, 0.45], | 1758 | [0.18, 0.18, 0.18, 0.45], |
| 1758 | ) { | 1759 | ) { |
| 1759 | let response = self.view.scroll_to(index, offset)?; | 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,6 +246,7 @@ pub fn list<R: Rows>( |
| 246 | scroll as f32, | 246 | scroll as f32, |
| 247 | [0.0, most as f32], | 247 | [0.0, most as f32], |
| 248 | view as f32, | 248 | view as f32, |
| 249 | false, | ||
| 249 | thumb, | 250 | thumb, |
| 250 | ) { | 251 | ) { |
| 251 | // Rows keep their places relative to the new offset, or next frame's hold on the | 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,13 +862,20 @@ fn scrollbar_thumbs_track_the_offset_and_drags_reach_both_ends() { |
| 862 | "view", | 862 | "view", |
| 863 | Spec { | 863 | Spec { |
| 864 | flags: Flags::CUSTOM, | 864 | flags: Flags::CUSTOM, |
| 865 | size: [px(200.0), px(220.0)], | 865 | size: [px(200.0), px(218.0)], |
| 866 | ..Spec::default() | 866 | ..Spec::default() |
| 867 | }, | 867 | }, |
| 868 | ); | 868 | ); |
| 869 | if let Some(chosen) = | 869 | if let Some(chosen) = scrollbar( |
| 870 | scrollbar(ui, "bar", Axis::Y, *offset, [0.0, 1000.0], 400.0, [0.5; 4]) | 870 | ui, |
| 871 | { | 871 | "bar", |
| 872 | Axis::Y, | ||
| 873 | *offset, | ||
| 874 | [0.0, 1000.0], | ||
| 875 | 400.0, | ||
| 876 | true, | ||
| 877 | [0.5; 4], | ||
| 878 | ) { | ||
| 872 | *offset = chosen; | 879 | *offset = chosen; |
| 873 | } | 880 | } |
| 874 | ui.close(); | 881 | ui.close(); |
| ... | @@ -902,6 +909,36 @@ fn scrollbar_thumbs_track_the_offset_and_drags_reach_both_ends() { | ... | @@ -902,6 +909,36 @@ fn scrollbar_thumbs_track_the_offset_and_drags_reach_both_ends() { |
| 902 | assert_eq!(offset, 0.0); | 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 | #[test] | 942 | #[test] |
| 906 | fn system_scrollers_step_page_and_drag_by_the_parts_the_platform_paints() { | 943 | fn system_scrollers_step_page_and_drag_by_the_parts_the_platform_paints() { |
| 907 | let mut ui = Ui::new(Theme::dark(), DOUBLE_CLICK); | 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,9 +973,16 @@ fn system_scrollers_step_page_and_drag_by_the_parts_the_platform_paints() { |
| 936 | ..Spec::default() | 973 | ..Spec::default() |
| 937 | }, | 974 | }, |
| 938 | ); | 975 | ); |
| 939 | if let Some(chosen) = | 976 | if let Some(chosen) = scrollbar( |
| 940 | scrollbar(ui, "bar", Axis::Y, *offset, [0.0, 1000.0], 400.0, [0.5; 4]) | 977 | ui, |
| 941 | { | 978 | "bar", |
| 979 | Axis::Y, | ||
| 980 | *offset, | ||
| 981 | [0.0, 1000.0], | ||
| 982 | 400.0, | ||
| 983 | true, | ||
| 984 | [0.5; 4], | ||
| 985 | ) { | ||
| 942 | *offset = chosen; | 986 | *offset = chosen; |
| 943 | } | 987 | } |
| 944 | ui.close(); | 988 | ui.close(); |
crates/ui/src/widgets.rs+11-8| ... | @@ -130,8 +130,10 @@ const REPEAT: Duration = Duration::from_millis(50); | ... | @@ -130,8 +130,10 @@ const REPEAT: Duration = Duration::from_millis(50); |
| 130 | 130 | ||
| 131 | /// A scrollbar along the far edge of the current box, for content whose scroll offset | 131 | /// A scrollbar along the far edge of the current box, for content whose scroll offset |
| 132 | /// ranges over `range` while `view` of it shows, all in one unit: the platform's scroller | 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 | 133 | /// where `Ui::scrollers` has one, otherwise a thumb in `color` over the content. With a |
| 134 | /// the offset the scrollbar chose. | 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 | pub fn scrollbar( | 137 | pub fn scrollbar( |
| 136 | ui: &mut Ui, | 138 | ui: &mut Ui, |
| 137 | part: impl Hash, | 139 | part: impl Hash, |
| ... | @@ -139,18 +141,19 @@ pub fn scrollbar( | ... | @@ -139,18 +141,19 @@ pub fn scrollbar( |
| 139 | offset: f32, | 141 | offset: f32, |
| 140 | range: [f32; 2], | 142 | range: [f32; 2], |
| 141 | view: f32, | 143 | view: f32, |
| 144 | corner: bool, | ||
| 142 | color: [f32; 4], | 145 | color: [f32; 4], |
| 143 | ) -> Option<f32> { | 146 | ) -> Option<f32> { |
| 144 | if ui.scrollers.is_some() { | 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 | let along = usize::from(axis == Axis::Y); | 150 | let along = usize::from(axis == Axis::Y); |
| 148 | let rect = ui.rect(ui.current())?; | 151 | let rect = ui.rect(ui.current())?; |
| 149 | let length = rect[along + 2] - rect[along]; | 152 | let length = rect[along + 2] - rect[along]; |
| 150 | let cross = rect[3 - along] - rect[1 - along]; | 153 | let cross = rect[3 - along] - rect[1 - along]; |
| 151 | let span = range[1] - range[0]; | 154 | let span = range[1] - range[0]; |
| 152 | // The far end leaves room for the other axis's bar. | 155 | // A thumb keeps 4 from each end and the box's far edge, and is 6 across. |
| 153 | let track = length - 20.0; | 156 | let track = length - 8.0 - if corner { 10.0 } else { 0.0 }; |
| 154 | if span <= 0.0 || track <= 0.0 { | 157 | if span <= 0.0 || track <= 0.0 { |
| 155 | return None; | 158 | return None; |
| 156 | } | 159 | } |
| ... | @@ -197,13 +200,13 @@ fn system_scrollbar( | ... | @@ -197,13 +200,13 @@ fn system_scrollbar( |
| 197 | offset: f32, | 200 | offset: f32, |
| 198 | range: [f32; 2], | 201 | range: [f32; 2], |
| 199 | view: f32, | 202 | view: f32, |
| 203 | corner: bool, | ||
| 200 | ) -> Option<f32> { | 204 | ) -> Option<f32> { |
| 201 | let along = usize::from(axis == Axis::Y); | 205 | let along = usize::from(axis == Axis::Y); |
| 202 | let rect = ui.rect(ui.current())?; | 206 | let rect = ui.rect(ui.current())?; |
| 203 | let span = range[1] - range[0]; | 207 | let span = range[1] - range[0]; |
| 204 | let thickness = ui.scrollers.as_ref()?.thickness; | 208 | let thickness = ui.scrollers.as_ref()?.thickness; |
| 205 | // The far end leaves the corner beside the other axis's scroller. | 209 | let length = rect[along + 2] - rect[along] - if corner { thickness } else { 0.0 }; |
| 206 | let length = rect[along + 2] - rect[along] - thickness; | ||
| 207 | if span <= 0.0 || length <= 0.0 { | 210 | if span <= 0.0 || length <= 0.0 { |
| 208 | return None; | 211 | return None; |
| 209 | } | 212 | } |
| ... | @@ -283,7 +286,7 @@ fn system_scrollbar( | ... | @@ -283,7 +286,7 @@ fn system_scrollbar( |
| 283 | ..Spec::default() | 286 | ..Spec::default() |
| 284 | }, | 287 | }, |
| 285 | ); | 288 | ); |
| 286 | if axis == Axis::Y { | 289 | if axis == Axis::Y && corner { |
| 287 | // The corner between the two scrollers shows the content's white, as AppKit's does. | 290 | // The corner between the two scrollers shows the content's white, as AppKit's does. |
| 288 | ui.leaf( | 291 | ui.leaf( |
| 289 | ("corner", &part), | 292 | ("corner", &part), |