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