Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/shared_scrollbar.rs
StratoSDK / crates / strato-ui-core / src / elements / shared_scrollbar.rs
1use pathfinder_geometry::{
2 rect::RectF,
3 vector::{vec2f, Vector2F},
4};
5 
6use crate::units::{IntoPixels, Pixels};
7 
8use super::{Axis, F32Ext, ScrollData, ScrollbarWidth, Vector2FExt};
9 
10pub const DEFAULT_SCROLLBAR_PADDING_BETWEEN_CHILD_AND_TRACK: f32 = 2.0;
11pub const DEFAULT_SCROLLBAR_PADDING_AFTER_TRACK: f32 = 2.0;
12pub const DEFAULT_SCROLL_WHEEL_PIXELS_PER_LINE: f32 = 40.0;
13pub const MIN_SCROLLBAR_THUMB_LENGTH: f32 = 20.0;
14 
15#[derive(Clone, Copy, Debug)]
16pub struct ScrollbarAppearance {
17 pub scrollbar_width: ScrollbarWidth,
18 pub overlaid_scrollbar: bool,
19 pub padding_between_child_and_scrollbar: f32,
20 pub padding_after_scrollbar: f32,
21}
22 
23impl ScrollbarAppearance {
24 pub fn new(scrollbar_width: ScrollbarWidth, overlaid_scrollbar: bool) -> Self {
25 Self {
26 scrollbar_width,
27 overlaid_scrollbar,
28 padding_between_child_and_scrollbar: DEFAULT_SCROLLBAR_PADDING_BETWEEN_CHILD_AND_TRACK,
29 padding_after_scrollbar: DEFAULT_SCROLLBAR_PADDING_AFTER_TRACK,
30 }
31 }
32 
33 fn cross_axis_spacing(&self, include_overlaid_scrollbar: bool) -> f32 {
34 if !include_overlaid_scrollbar && self.overlaid_scrollbar {
35 0.0
36 } else {
37 self.padding_between_child_and_scrollbar + self.padding_after_scrollbar
38 }
39 }
40 
41 fn scrollbar_track_length(&self, include_overlaid_scrollbar: bool) -> f32 {
42 if !include_overlaid_scrollbar && self.overlaid_scrollbar {
43 0.0
44 } else {
45 self.scrollbar_width.as_f32()
46 }
47 }
48}
49 
50#[derive(Clone, Copy, Debug)]
51pub struct ScrollbarGeometry {
52 pub track_bounds: RectF,
53 pub thumb_bounds: RectF,
54 pub scrollbar_size_percentage: f32,
55 pub scrollbar_position_percentage: f32,
56}
57 
58impl ScrollbarGeometry {
59 pub fn has_thumb(&self) -> bool {
60 self.scrollbar_size_percentage < 1.0 && !self.thumb_bounds.is_empty()
61 }
62 
63 pub fn thumb_center_along(&self, axis: Axis) -> Pixels {
64 self.thumb_bounds.center().along(axis).into_pixels()
65 }
66}
67 
68pub fn project_scroll_delta_by_sensitivity(delta: Vector2F, sensitivity: f32) -> Vector2F {
69 if delta.x().abs() * sensitivity > delta.y().abs() {
70 delta.project_onto(Axis::Horizontal)
71 } else if delta.y().abs() * sensitivity > delta.x().abs() {
72 delta.project_onto(Axis::Vertical)
73 } else {
74 delta
75 }
76}
77 
78pub fn compute_scrollbar_geometry(
79 axis: Axis,
80 origin: Vector2F,
81 scrollable_size: Vector2F,
82 scroll_data: ScrollData,
83 appearance: ScrollbarAppearance,
84) -> ScrollbarGeometry {
85 let scrollable_size_with_padding = match axis {
86 Axis::Horizontal => vec2f(
87 0.0,
88 appearance.scrollbar_track_length(true) + appearance.cross_axis_spacing(true),
89 ),
90 Axis::Vertical => vec2f(
91 appearance.scrollbar_track_length(true) + appearance.cross_axis_spacing(true),
92 0.0,
93 ),
94 };
95 let viewport_size = (scrollable_size - scrollable_size_with_padding).max(Vector2F::zero());
96 let scrollbar_track_length = scrollable_size_with_padding.along(axis.invert());
97 let scrollbar_track_origin = origin + scrollable_size.project_onto(axis.invert())
98 - scrollbar_track_length.along(axis.invert());
99 let scrollbar_track_size = scrollbar_size(axis, viewport_size, scrollbar_track_length);
100 let track_bounds = RectF::new(scrollbar_track_origin, scrollbar_track_size);
101 
102 let (scrollbar_size_percentage, scrollbar_position_percentage) =
103 scrollbar_percentages(scroll_data, viewport_size.along(axis));
104 
105 if scrollbar_size_percentage >= 1.0 {
106 return ScrollbarGeometry {
107 track_bounds,
108 thumb_bounds: RectF::new(vec2f(0.0, 0.0), vec2f(0.0, 0.0)),
109 scrollbar_size_percentage,
110 scrollbar_position_percentage,
111 };
112 }
113 
114 let thumb_size = scrollbar_size(
115 axis,
116 viewport_size * scrollbar_size_percentage,
117 appearance.scrollbar_width.as_f32(),
118 );
119 let thumb_origin = scrollbar_track_origin
120 + scrollbar_size(
121 axis,
122 (viewport_size - thumb_size).max(Vector2F::zero()) * scrollbar_position_percentage,
123 appearance.padding_between_child_and_scrollbar,
124 );
125 
126 ScrollbarGeometry {
127 track_bounds,
128 thumb_bounds: RectF::new(thumb_origin, thumb_size),
129 scrollbar_size_percentage,
130 scrollbar_position_percentage,
131 }
132}
133 
134pub fn scroll_delta_for_pointer_movement(
135 previous_position_along_axis: Pixels,
136 new_position_along_axis: Pixels,
137 scroll_data: ScrollData,
138) -> Pixels {
139 if scroll_data.total_size <= Pixels::zero()
140 || scroll_data.visible_px <= Pixels::zero()
141 || scroll_data.visible_px >= scroll_data.total_size
142 {
143 return Pixels::zero();
144 }
145 
146 let scroll_size_percentage = scroll_data.visible_px / scroll_data.total_size;
147 if scroll_size_percentage <= Pixels::zero() {
148 return Pixels::zero();
149 }
150 
151 (previous_position_along_axis - new_position_along_axis) / scroll_size_percentage
152}
153 
154fn scrollbar_percentages(scroll_data: ScrollData, scrollable_pixels: f32) -> (f32, f32) {
155 if scroll_data.total_size <= Pixels::zero() {
156 return (1.0, 0.0);
157 }
158 
159 let minimum_size_percentage = (MIN_SCROLLBAR_THUMB_LENGTH / scrollable_pixels).min(1.0);
160 let size_percentage = (scroll_data.visible_px / scroll_data.total_size)
161 .max(Pixels::new(minimum_size_percentage))
162 .as_f32();
163 let scroll_remaining =
164 scroll_data.total_size - scroll_data.scroll_start - scroll_data.visible_px;
165 let position_percentage = if scroll_data.scroll_start + scroll_remaining <= Pixels::zero() {
166 0.0
167 } else {
168 (scroll_data.scroll_start / (scroll_data.scroll_start + scroll_remaining)).as_f32()
169 };
170 
171 (size_percentage, position_percentage)
172}
173 
174pub(crate) fn scrollbar_size(
175 axis: Axis,
176 scrollable_size: Vector2F,
177 scrollbar_track_length: f32,
178) -> Vector2F {
179 match axis {
180 Axis::Horizontal => vec2f(scrollable_size.x(), scrollbar_track_length),
181 Axis::Vertical => vec2f(scrollbar_track_length, scrollable_size.y()),
182 }
183}
184