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