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