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 clicking_scrollbar = use_state::<Option<(Axis, f64)>>(|| None);
164 let mut size = use_state(SizedEventData::default);
165 let mut scroll_controller = self
166 .scroll_controller
167 .unwrap_or_else(|| use_scroll_controller(ScrollConfig::default));
168 let (scrolled_x, scrolled_y) = scroll_controller.into();
169 let layout = &self.layout.layout;
170 let direction = layout.direction;
171
172 scroll_controller.use_apply(
173 size.read().inner_sizes.width,
174 size.read().inner_sizes.height,
175 );
176
177 let corrected_scrolled_x = get_corrected_scroll_position(
178 size.read().inner_sizes.width,
179 size.read().area.width(),
180 scrolled_x as f32,
181 );
182
183 let corrected_scrolled_y = get_corrected_scroll_position(
184 size.read().inner_sizes.height,
185 size.read().area.height(),
186 scrolled_y as f32,
187 );
188 let horizontal_scrollbar_is_visible = !timeout.elapsed()
189 && is_scrollbar_visible(
190 self.show_scrollbar,
191 size.read().inner_sizes.width,
192 size.read().area.width(),
193 );
194 let vertical_scrollbar_is_visible = !timeout.elapsed()
195 && is_scrollbar_visible(
196 self.show_scrollbar,
197 size.read().inner_sizes.height,
198 size.read().area.height(),
199 );
200
201 let (scrollbar_x, scrollbar_width) = get_scrollbar_pos_and_size(
202 size.read().inner_sizes.width,
203 size.read().area.width(),
204 corrected_scrolled_x,
205 );
206 let (scrollbar_y, scrollbar_height) = get_scrollbar_pos_and_size(
207 size.read().inner_sizes.height,
208 size.read().area.height(),
209 corrected_scrolled_y,
210 );
211
212 let (container_width, content_width) = get_container_sizes(layout.width.clone());
213 let (container_height, content_height) = get_container_sizes(layout.height.clone());
214
215 let scroll_with_arrows = self.scroll_with_arrows;
216 let invert_scroll_wheel = self.invert_scroll_wheel;
217
218 let on_capture_global_pointer_press = move |e: Event<PointerEventData>| {
219 if clicking_scrollbar.read().is_some() {
220 e.prevent_default();
221 clicking_scrollbar.set(None);
222 }
223 };
224
225 let on_wheel = move |e: Event<WheelEventData>| {
226 let invert_direction = e.source == WheelSource::Device
228 && (*pressing_shift.read() || invert_scroll_wheel)
229 && (!*pressing_shift.read() || !invert_scroll_wheel);
230
231 let (x_movement, y_movement) = if invert_direction {
232 (e.delta_y as f32, e.delta_x as f32)
233 } else {
234 (e.delta_x as f32, e.delta_y as f32)
235 };
236
237 let scroll_position_y = get_scroll_position_from_wheel(
239 y_movement,
240 size.read().inner_sizes.height,
241 size.read().area.height(),
242 corrected_scrolled_y,
243 );
244 scroll_controller.scroll_to_y(scroll_position_y).then(|| {
245 e.stop_propagation();
246 });
247
248 let scroll_position_x = get_scroll_position_from_wheel(
250 x_movement,
251 size.read().inner_sizes.width,
252 size.read().area.width(),
253 corrected_scrolled_x,
254 );
255 scroll_controller.scroll_to_x(scroll_position_x).then(|| {
256 e.stop_propagation();
257 });
258 timeout.reset();
259 };
260
261 let on_mouse_move = move |_| {
262 timeout.reset();
263 };
264
265 let on_capture_global_pointer_move = move |e: Event<PointerEventData>| {
266 let clicking_scrollbar = clicking_scrollbar.peek();
267
268 if let Some((Axis::Y, y)) = *clicking_scrollbar {
269 let coordinates = e.element_location();
270 let cursor_y = coordinates.y - y - size.read().area.min_y() as f64;
271
272 let scroll_position = get_scroll_position_from_cursor(
273 cursor_y as f32,
274 size.read().inner_sizes.height,
275 size.read().area.height(),
276 );
277
278 scroll_controller.scroll_to_y(scroll_position);
279 } else if let Some((Axis::X, x)) = *clicking_scrollbar {
280 let coordinates = e.element_location();
281 let cursor_x = coordinates.x - x - size.read().area.min_x() as f64;
282
283 let scroll_position = get_scroll_position_from_cursor(
284 cursor_x as f32,
285 size.read().inner_sizes.width,
286 size.read().area.width(),
287 );
288
289 scroll_controller.scroll_to_x(scroll_position);
290 }
291
292 if clicking_scrollbar.is_some() {
293 e.prevent_default();
294 timeout.reset();
295 if !focus.is_focused() {
296 focus.request_focus();
297 }
298 }
299 };
300
301 let on_key_down = move |e: Event<KeyboardEventData>| {
302 if !scroll_with_arrows
303 && (e.key == Key::Named(NamedKey::ArrowUp)
304 || e.key == Key::Named(NamedKey::ArrowRight)
305 || e.key == Key::Named(NamedKey::ArrowDown)
306 || e.key == Key::Named(NamedKey::ArrowLeft))
307 {
308 return;
309 }
310 let x = corrected_scrolled_x;
311 let y = corrected_scrolled_y;
312 let inner_height = size.read().inner_sizes.height;
313 let inner_width = size.read().inner_sizes.width;
314 let viewport_height = size.read().area.height();
315 let viewport_width = size.read().area.width();
316 if let Some((x, y)) = handle_key_event(
317 &e.key,
318 (x, y),
319 inner_height,
320 inner_width,
321 viewport_height,
322 viewport_width,
323 direction,
324 ) {
325 scroll_controller.scroll_to_x(x as i32);
326 scroll_controller.scroll_to_y(y as i32);
327 e.stop_propagation();
328 timeout.reset();
329 }
330 };
331
332 let on_global_key_down = move |e: Event<KeyboardEventData>| {
333 let data = e;
334 if data.key == Key::Named(NamedKey::Shift) {
335 pressing_shift.set(true);
336 }
337 };
338
339 let on_global_key_up = move |e: Event<KeyboardEventData>| {
340 let data = e;
341 if data.key == Key::Named(NamedKey::Shift) {
342 pressing_shift.set(false);
343 }
344 };
345
346 rect()
347 .width(layout.width.clone())
348 .height(layout.height.clone())
349 .max_width(layout.maximum_width.clone())
350 .max_height(layout.maximum_height.clone())
351 .a11y_id(focus.a11y_id())
352 .a11y_focusable(false)
353 .a11y_role(AccessibilityRole::ScrollView)
354 .a11y_builder(move |node| {
355 node.set_scroll_x(corrected_scrolled_x as f64);
356 node.set_scroll_y(corrected_scrolled_y as f64)
357 })
358 .scrollable(true)
359 .on_wheel(on_wheel)
360 .on_capture_global_pointer_press(on_capture_global_pointer_press)
361 .on_mouse_move(on_mouse_move)
362 .on_capture_global_pointer_move(on_capture_global_pointer_move)
363 .on_key_down(on_key_down)
364 .on_global_key_up(on_global_key_up)
365 .on_global_key_down(on_global_key_down)
366 .child(
367 rect()
368 .width(container_width.clone())
369 .height(container_height.clone())
370 .horizontal()
371 .child(
372 rect()
373 .direction(direction)
374 .width(content_width)
375 .height(content_height.clone())
376 .max_width(layout.maximum_width.clone())
377 .max_height(layout.maximum_height.clone())
378 .offset_x(corrected_scrolled_x)
379 .offset_y(corrected_scrolled_y)
380 .spacing(layout.spacing.get())
381 .overflow(Overflow::Clip)
382 .on_sized(move |e: Event<SizedEventData>| {
383 size.set_if_modified(e.clone())
384 })
385 .children(self.children.clone()),
386 )
387 .maybe_child(vertical_scrollbar_is_visible.then_some({
388 rect().child(ScrollBar {
389 theme: None,
390 clicking_scrollbar,
391 axis: Axis::Y,
392 offset: scrollbar_y,
393 size: Size::px(size.read().area.height()),
394 thumb: ScrollThumb {
395 theme: None,
396 clicking_scrollbar,
397 axis: Axis::Y,
398 size: scrollbar_height,
399 },
400 })
401 })),
402 )
403 .maybe_child(horizontal_scrollbar_is_visible.then_some({
404 rect().child(ScrollBar {
405 theme: None,
406 clicking_scrollbar,
407 axis: Axis::X,
408 offset: scrollbar_x,
409 size: Size::px(size.read().area.width()),
410 thumb: ScrollThumb {
411 theme: None,
412 clicking_scrollbar,
413 axis: Axis::X,
414 size: scrollbar_width,
415 },
416 })
417 }))
418 }
419
420 fn render_key(&self) -> DiffKey {
421 self.key.clone().or(self.default_key())
422 }
423}