freya_components/scrollviews/
shared.rs1use freya_core::prelude::*;
2use torin::{
3 prelude::Direction,
4 size::Size,
5};
6
7#[derive(Debug, PartialEq, Eq, Clone, Copy)]
8pub enum Axis {
9 X,
10 Y,
11}
12
13#[doc(hidden)]
14pub fn get_scroll_position_from_wheel(
15 wheel_movement: f32,
16 inner_size: f32,
17 viewport_size: f32,
18 scroll_position: f32,
19) -> i32 {
20 if viewport_size >= inner_size {
21 return 0;
22 }
23
24 let new_position = scroll_position + wheel_movement;
25
26 if new_position >= 0.0 && wheel_movement > 0.0 {
27 return 0;
28 }
29
30 if new_position <= -(inner_size - viewport_size) && wheel_movement < 0.0 {
31 return -(inner_size - viewport_size) as i32;
32 }
33
34 new_position as i32
35}
36
37#[doc(hidden)]
38pub fn get_corrected_scroll_position(
39 inner_size: f32,
40 viewport_size: f32,
41 scroll_position: f32,
42) -> f32 {
43 let overscrolled_start = scroll_position > 0.0;
45 let overscrolled_end = (-scroll_position + viewport_size) > inner_size;
46
47 if overscrolled_start {
48 0f32
49 } else if overscrolled_end {
50 if viewport_size < inner_size {
51 -(inner_size - viewport_size)
52 } else {
53 0f32
54 }
55 } else {
56 scroll_position
57 }
58}
59
60#[doc(hidden)]
61pub fn get_container_sizes(size: Size) -> (Size, Size) {
62 if size == Size::Inner {
63 (size.clone(), size)
64 } else {
65 (Size::percent(100.), Size::fill())
66 }
67}
68
69#[doc(hidden)]
70pub fn is_scrollbar_visible(
71 is_scrollbar_enabled: bool,
72 inner_size: f32,
73 viewport_size: f32,
74) -> bool {
75 is_scrollbar_enabled && viewport_size > MIN_SCROLLBAR_SIZE && viewport_size < inner_size
76}
77
78const MIN_SCROLLBAR_SIZE: f32 = 50.0;
79
80pub const SCROLLBAR_MARGIN: f32 = 3.0;
81
82fn get_thumb_ranges(inner_size: f32, viewport_size: f32) -> (f32, f32, f32) {
84 let track_size = viewport_size - SCROLLBAR_MARGIN * 2.0;
85 let viewable_ratio = viewport_size / inner_size;
86 let minimum_thumb_size = if track_size > MIN_SCROLLBAR_SIZE {
87 MIN_SCROLLBAR_SIZE
88 } else {
89 0.
90 };
91 let scrollbar_size = (track_size * viewable_ratio).max(minimum_thumb_size);
92
93 let available_scroll_range = inner_size - viewport_size;
94 let available_thumb_range = track_size - scrollbar_size;
95
96 (
97 scrollbar_size,
98 available_scroll_range,
99 available_thumb_range,
100 )
101}
102
103#[doc(hidden)]
104pub fn get_scrollbar_pos_and_size(
105 inner_size: f32,
106 viewport_size: f32,
107 scroll_position: f32,
108) -> (f32, f32) {
109 if viewport_size <= MIN_SCROLLBAR_SIZE || viewport_size >= inner_size {
110 return (0.0, 0.0);
111 }
112
113 let (scrollbar_size, available_scroll_range, available_thumb_range) =
114 get_thumb_ranges(inner_size, viewport_size);
115
116 let normalized_scroll = -scroll_position / available_scroll_range;
117 let scrollbar_position = normalized_scroll * available_thumb_range;
118
119 (scrollbar_position, scrollbar_size)
120}
121#[doc(hidden)]
122pub fn get_scroll_position_from_cursor(
123 cursor_position: f32,
124 inner_size: f32,
125 viewport_size: f32,
126) -> i32 {
127 if viewport_size <= MIN_SCROLLBAR_SIZE || viewport_size >= inner_size {
128 return 0;
129 }
130
131 let (_, available_scroll_range, available_thumb_range) =
132 get_thumb_ranges(inner_size, viewport_size);
133
134 let cursor_clamped = (cursor_position - SCROLLBAR_MARGIN).clamp(0.0, available_thumb_range);
136
137 let normalized_scroll = cursor_clamped / available_thumb_range;
138 let new_position = -(normalized_scroll * available_scroll_range);
139
140 new_position as i32
141}
142
143pub fn handle_key_event(
144 key: &Key,
145 (mut x, mut y): (f32, f32),
146 inner_height: f32,
147 inner_width: f32,
148 viewport_height: f32,
149 viewport_width: f32,
150 direction: Direction,
151) -> Option<(f32, f32)> {
152 let y_page_delta = viewport_height;
153 let y_line_delta = y_page_delta / 5.0;
154 let x_page_delta = viewport_width;
155 let x_line_delta = x_page_delta / 5.0;
156
157 match key {
160 Key::Named(NamedKey::ArrowUp) => {
161 y = get_corrected_scroll_position(inner_height, viewport_height, y + y_line_delta)
162 }
163 Key::Named(NamedKey::ArrowDown) => {
164 y = get_corrected_scroll_position(inner_height, viewport_height, y - y_line_delta)
165 }
166 Key::Named(NamedKey::PageUp) => {
167 y = get_corrected_scroll_position(inner_height, viewport_height, y + y_line_delta)
168 }
169 Key::Named(NamedKey::PageDown) => {
170 y = get_corrected_scroll_position(inner_height, viewport_height, y - y_line_delta)
171 }
172 Key::Named(NamedKey::ArrowLeft) => {
173 x = get_corrected_scroll_position(inner_width, viewport_width, x + x_line_delta)
174 }
175 Key::Named(NamedKey::ArrowRight) => {
176 x = get_corrected_scroll_position(inner_width, viewport_width, x - x_line_delta)
177 }
178 Key::Named(NamedKey::Home) => {
179 if direction == Direction::Vertical {
180 y = 0.0;
181 } else {
182 x = 0.0;
183 }
184 }
185 Key::Named(NamedKey::End) => {
186 if direction == Direction::Vertical {
187 y = -inner_height;
188 } else {
189 x = -inner_width;
190 }
191 }
192 _ => return None,
193 };
194 Some((x, y))
195}
196
197#[cfg(test)]
198mod tests {
199 use crate::scrollviews::shared::{
200 SCROLLBAR_MARGIN,
201 get_scroll_position_from_cursor,
202 get_scrollbar_pos_and_size,
203 };
204
205 #[test]
206 fn scrollbar_can_drag_when_the_track_is_smaller_than_the_minimum_thumb() {
207 for viewport_size in 51..=56 {
208 let viewport_size = viewport_size as f32;
209 let inner_size = viewport_size * 2.;
210 let track_size = viewport_size - SCROLLBAR_MARGIN * 2.;
211
212 let (_, thumb_size) = get_scrollbar_pos_and_size(inner_size, viewport_size, 0.);
213
214 assert!(thumb_size < track_size);
215 assert_eq!(
216 get_scroll_position_from_cursor(viewport_size, inner_size, viewport_size),
217 -viewport_size as i32
218 );
219 }
220 }
221}