freya_components/scrollviews/
scrollview.rs1use std::time::Duration;
2
3use freya_core::prelude::*;
4use freya_sdk::timeout::use_timeout;
5use torin::{
6 prelude::Direction,
7 size::Size,
8};
9
10use crate::scrollviews::{
11 ScrollBar,
12 ScrollConfig,
13 ScrollController,
14 ScrollThumb,
15 shared::{
16 Axis,
17 get_container_sizes,
18 get_corrected_scroll_position,
19 get_scroll_position_from_cursor,
20 get_scroll_position_from_wheel,
21 get_scrollbar_pos_and_size,
22 handle_key_event,
23 is_scrollbar_visible,
24 },
25 use_scroll_controller,
26};
27
28#[cfg_attr(feature = "docs",
54 doc = embed_doc_image::embed_image!("scrollview", "images/gallery_scrollview.png")
55)]
56#[derive(Clone, PartialEq)]
57pub struct ScrollView {
58 children: Vec<Element>,
59 width: Size,
60 height: Size,
61 show_scrollbar: bool,
62 direction: Direction,
63 spacing: f32,
64 scroll_with_arrows: bool,
65 scroll_controller: Option<ScrollController>,
66 invert_scroll_wheel: bool,
67 key: DiffKey,
68}
69
70impl ChildrenExt for ScrollView {
71 fn get_children(&mut self) -> &mut Vec<Element> {
72 &mut self.children
73 }
74}
75
76impl KeyExt for ScrollView {
77 fn write_key(&mut self) -> &mut DiffKey {
78 &mut self.key
79 }
80}
81
82impl Default for ScrollView {
83 fn default() -> Self {
84 Self {
85 children: Vec::default(),
86 width: Size::fill(),
87 height: Size::fill(),
88 show_scrollbar: true,
89 direction: Direction::Vertical,
90 spacing: 0.,
91 scroll_with_arrows: true,
92 scroll_controller: None,
93 invert_scroll_wheel: false,
94 key: DiffKey::None,
95 }
96 }
97}
98
99impl ScrollView {
100 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn new_controlled(scroll_controller: ScrollController) -> Self {
105 Self {
106 children: Vec::default(),
107 width: Size::fill(),
108 height: Size::fill(),
109 show_scrollbar: true,
110 direction: Direction::Vertical,
111 spacing: 0.,
112 scroll_with_arrows: true,
113 scroll_controller: Some(scroll_controller),
114 invert_scroll_wheel: false,
115 key: DiffKey::None,
116 }
117 }
118
119 pub fn show_scrollbar(mut self, show_scrollbar: bool) -> Self {
120 self.show_scrollbar = show_scrollbar;
121 self
122 }
123
124 pub fn width(mut self, width: Size) -> Self {
125 self.width = width;
126 self
127 }
128
129 pub fn height(mut self, height: Size) -> Self {
130 self.height = height;
131 self
132 }
133
134 pub fn direction(mut self, direction: Direction) -> Self {
135 self.direction = direction;
136 self
137 }
138
139 pub fn spacing(mut self, spacing: impl Into<f32>) -> Self {
140 self.spacing = spacing.into();
141 self
142 }
143
144 pub fn scroll_with_arrows(mut self, scroll_with_arrows: impl Into<bool>) -> Self {
145 self.scroll_with_arrows = scroll_with_arrows.into();
146 self
147 }
148
149 pub fn invert_scroll_wheel(mut self, invert_scroll_wheel: impl Into<bool>) -> Self {
150 self.invert_scroll_wheel = invert_scroll_wheel.into();
151 self
152 }
153}
154
155impl Render for ScrollView {
156 fn render(self: &ScrollView) -> impl IntoElement {
157 let focus = use_focus();
158 let mut timeout = use_timeout(|| Duration::from_millis(800));
159 let mut pressing_shift = use_state(|| false);
160 let mut pressing_alt = use_state(|| false);
161 let mut clicking_scrollbar = use_state::<Option<(Axis, f64)>>(|| None);
162 let mut size = use_state(SizedEventData::default);
163 let mut scroll_controller = self
164 .scroll_controller
165 .unwrap_or_else(|| use_scroll_controller(ScrollConfig::default));
166 let (scrolled_x, scrolled_y) = scroll_controller.into();
167 let direction = self.direction;
168
169 scroll_controller.use_apply(
170 size.read().inner_sizes.width,
171 size.read().inner_sizes.height,
172 );
173
174 let corrected_scrolled_x = get_corrected_scroll_position(
175 size.read().inner_sizes.width,
176 size.read().area.width(),
177 scrolled_x as f32,
178 );
179
180 let corrected_scrolled_y = get_corrected_scroll_position(
181 size.read().inner_sizes.height,
182 size.read().area.height(),
183 scrolled_y as f32,
184 );
185 let horizontal_scrollbar_is_visible = !timeout.elapsed()
186 && is_scrollbar_visible(
187 self.show_scrollbar,
188 size.read().inner_sizes.width,
189 size.read().area.width(),
190 );
191 let vertical_scrollbar_is_visible = !timeout.elapsed()
192 && is_scrollbar_visible(
193 self.show_scrollbar,
194 size.read().inner_sizes.height,
195 size.read().area.height(),
196 );
197
198 let (scrollbar_x, scrollbar_width) = get_scrollbar_pos_and_size(
199 size.read().inner_sizes.width,
200 size.read().area.width(),
201 corrected_scrolled_x,
202 );
203 let (scrollbar_y, scrollbar_height) = get_scrollbar_pos_and_size(
204 size.read().inner_sizes.height,
205 size.read().area.height(),
206 corrected_scrolled_y,
207 );
208
209 let (container_width, content_width) = get_container_sizes(self.width.clone());
210 let (container_height, content_height) = get_container_sizes(self.height.clone());
211
212 let scroll_with_arrows = self.scroll_with_arrows;
213 let invert_scroll_wheel = self.invert_scroll_wheel;
214
215 let on_global_mouse_up = move |_| {
216 clicking_scrollbar.set_if_modified(None);
217 };
218
219 let on_wheel = move |e: Event<WheelEventData>| {
220 let invert_direction = e.source == WheelSource::Device
222 && (*pressing_shift.read() || invert_scroll_wheel)
223 && (!*pressing_shift.read() || !invert_scroll_wheel);
224
225 let (x_movement, y_movement) = if invert_direction {
226 (e.delta_y as f32, e.delta_x as f32)
227 } else {
228 (e.delta_x as f32, e.delta_y as f32)
229 };
230
231 let scroll_position_y = get_scroll_position_from_wheel(
233 y_movement,
234 size.read().inner_sizes.height,
235 size.read().area.height(),
236 corrected_scrolled_y,
237 );
238 scroll_controller.scroll_to_y(scroll_position_y).then(|| {
239 e.stop_propagation();
240 });
241
242 let scroll_position_x = get_scroll_position_from_wheel(
244 x_movement,
245 size.read().inner_sizes.width,
246 size.read().area.width(),
247 corrected_scrolled_x,
248 );
249 scroll_controller.scroll_to_x(scroll_position_x).then(|| {
250 e.stop_propagation();
251 });
252 timeout.reset();
253 };
254
255 let on_mouse_move = move |_| {
256 timeout.reset();
257 };
258
259 let on_capture_global_mouse_move = move |e: Event<MouseEventData>| {
260 let clicking_scrollbar = clicking_scrollbar.peek();
261
262 if let Some((Axis::Y, y)) = *clicking_scrollbar {
263 let coordinates = e.element_location;
264 let cursor_y = coordinates.y - y - size.read().area.min_y() as f64;
265
266 let scroll_position = get_scroll_position_from_cursor(
267 cursor_y as f32,
268 size.read().inner_sizes.height,
269 size.read().area.height(),
270 );
271
272 scroll_controller.scroll_to_y(scroll_position);
273 } else if let Some((Axis::X, x)) = *clicking_scrollbar {
274 let coordinates = e.element_location;
275 let cursor_x = coordinates.x - x - size.read().area.min_x() as f64;
276
277 let scroll_position = get_scroll_position_from_cursor(
278 cursor_x as f32,
279 size.read().inner_sizes.width,
280 size.read().area.width(),
281 );
282
283 scroll_controller.scroll_to_x(scroll_position);
284 }
285
286 if clicking_scrollbar.is_some() {
287 e.prevent_default();
288 timeout.reset();
289 if !focus.is_focused() {
290 focus.request_focus();
291 }
292 }
293 };
294
295 let on_key_down = move |e: Event<KeyboardEventData>| {
296 if !scroll_with_arrows
297 && (e.key == Key::Named(NamedKey::ArrowUp)
298 || e.key == Key::Named(NamedKey::ArrowRight)
299 || e.key == Key::Named(NamedKey::ArrowDown)
300 || e.key == Key::Named(NamedKey::ArrowLeft))
301 {
302 return;
303 }
304 let x = corrected_scrolled_x;
305 let y = corrected_scrolled_y;
306 let inner_height = size.read().inner_sizes.height;
307 let inner_width = size.read().inner_sizes.width;
308 let viewport_height = size.read().area.height();
309 let viewport_width = size.read().area.width();
310 if let Some((x, y)) = handle_key_event(
311 &e.key,
312 (x, y),
313 inner_height,
314 inner_width,
315 viewport_height,
316 viewport_width,
317 direction,
318 ) {
319 scroll_controller.scroll_to_x(x as i32);
320 scroll_controller.scroll_to_y(y as i32);
321 e.stop_propagation();
322 timeout.reset();
323 }
324 };
325
326 let on_global_key_down = move |e: Event<KeyboardEventData>| {
327 let data = e;
328 if data.key == Key::Named(NamedKey::Shift) {
329 pressing_shift.set(true);
330 } else if data.key == Key::Named(NamedKey::Alt) {
331 pressing_alt.set(true);
332 }
333 };
334
335 let on_global_key_up = move |e: Event<KeyboardEventData>| {
336 let data = e;
337 if data.key == Key::Named(NamedKey::Shift) {
338 pressing_shift.set(false);
339 } else if data.key == Key::Named(NamedKey::Alt) {
340 pressing_alt.set(false);
341 }
342 };
343
344 rect()
345 .width(self.width.clone())
346 .height(self.height.clone())
347 .a11y_id(focus.a11y_id())
348 .a11y_focusable(false)
349 .a11y_role(AccessibilityRole::ScrollView)
350 .a11y_builder(move |node| {
351 node.set_scroll_x(corrected_scrolled_x as f64);
352 node.set_scroll_y(corrected_scrolled_y as f64)
353 })
354 .scrollable(true)
355 .on_wheel(on_wheel)
356 .on_global_mouse_up(on_global_mouse_up)
357 .on_mouse_move(on_mouse_move)
358 .on_capture_global_mouse_move(on_capture_global_mouse_move)
359 .on_key_down(on_key_down)
360 .on_global_key_up(on_global_key_up)
361 .on_global_key_down(on_global_key_down)
362 .child(
363 rect()
364 .width(container_width)
365 .height(container_height)
366 .horizontal()
367 .child(
368 rect()
369 .direction(self.direction)
370 .width(content_width)
371 .height(content_height)
372 .offset_x(corrected_scrolled_x)
373 .offset_y(corrected_scrolled_y)
374 .spacing(self.spacing)
375 .overflow(Overflow::Clip)
376 .on_sized(move |e: Event<SizedEventData>| {
377 size.set_if_modified(e.clone())
378 })
379 .children(self.children.clone()),
380 )
381 .maybe_child(vertical_scrollbar_is_visible.then_some({
382 rect().child(ScrollBar {
383 theme: None,
384 clicking_scrollbar,
385 axis: Axis::Y,
386 offset: scrollbar_y,
387 thumb: ScrollThumb {
388 theme: None,
389 clicking_scrollbar,
390 axis: Axis::Y,
391 size: scrollbar_height,
392 },
393 })
394 })),
395 )
396 .maybe_child(horizontal_scrollbar_is_visible.then_some({
397 rect().child(ScrollBar {
398 theme: None,
399 clicking_scrollbar,
400 axis: Axis::X,
401 offset: scrollbar_x,
402 thumb: ScrollThumb {
403 theme: None,
404 clicking_scrollbar,
405 axis: Axis::X,
406 size: scrollbar_width,
407 },
408 })
409 }))
410 }
411
412 fn render_key(&self) -> DiffKey {
413 self.key.clone().or(self.default_key())
414 }
415}