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