1use std::{
2 ops::Range,
3 time::Duration,
4};
5
6use freya_core::prelude::*;
7use freya_sdk::timeout::use_timeout;
8use torin::{
9 geometry::{
10 CursorPoint,
11 Point2D,
12 Size2D,
13 },
14 node::Node,
15 prelude::Direction,
16 size::Size,
17};
18
19use crate::scrollviews::{
20 ScrollBar,
21 ScrollBarContext,
22 ScrollBarThumbEvents,
23 ScrollConfig,
24 ScrollController,
25 shared::{
26 Axis,
27 get_container_sizes,
28 get_corrected_scroll_position,
29 get_scroll_position_from_cursor,
30 get_scroll_position_from_wheel,
31 get_scrollbar_pos_and_size,
32 handle_key_event,
33 is_scrollbar_visible,
34 },
35 use_scroll_controller,
36};
37
38#[derive(Clone, PartialEq)]
50pub enum ItemSize {
51 Fixed(f32),
53 Dynamic(Callback<usize, f32>),
55}
56
57impl ItemSize {
58 fn at(&self, index: usize) -> f32 {
60 match self {
61 Self::Fixed(size) => *size,
62 Self::Dynamic(callback) => callback.call(index),
63 }
64 }
65
66 fn visible_range(
69 &self,
70 viewport_size: f32,
71 scroll_position: f32,
72 length: usize,
73 ) -> (Range<usize>, f32) {
74 let scroll_distance = (-scroll_position).max(0.0);
75 match self {
76 Self::Fixed(size) => {
77 if *size <= 0.0 {
78 return (0..0, 0.0);
79 }
80 let start = scroll_distance / size;
81 let potentially_visible = (viewport_size / size) + 1.0;
82 let start_index = (start as usize).min(length);
83 let end_index = ((start + potentially_visible) as usize).min(length);
84 (
85 start_index..end_index,
86 start.floor() * size - scroll_distance,
87 )
88 }
89 Self::Dynamic(callback) => {
90 let mut start = 0;
91 let mut cumulative = 0.0;
92 while start < length {
93 let item = callback.call(start);
94 if cumulative + item > scroll_distance {
95 break;
96 }
97 cumulative += item;
98 start += 1;
99 }
100 let offset = cumulative - scroll_distance;
101 let mut end = start;
102 while end < length && cumulative < scroll_distance + viewport_size {
103 cumulative += callback.call(end);
104 end += 1;
105 }
106 (start..end, offset)
107 }
108 }
109 }
110
111 fn total_size(&self, viewport_size: f32, scroll_position: f32, length: usize) -> f32 {
116 match self {
117 Self::Fixed(size) => size * length as f32,
118 Self::Dynamic(callback) => {
119 if length == 0 {
120 return 0.0;
121 }
122 let viewport_bottom = (-scroll_position).max(0.0) + viewport_size;
123 let mut measured = callback.call(0);
124 let mut count = 1;
125 while count < length && measured < viewport_bottom {
126 measured += callback.call(count);
127 count += 1;
128 }
129 (measured / count as f32) * length as f32
130 }
131 }
132 }
133}
134
135impl Default for ItemSize {
136 fn default() -> Self {
137 Self::Fixed(0.)
138 }
139}
140
141impl From<f32> for ItemSize {
142 fn from(size: f32) -> Self {
143 Self::Fixed(size)
144 }
145}
146
147impl<F: Fn(usize) -> f32 + 'static> From<F> for ItemSize {
148 fn from(callback: F) -> Self {
149 Self::Dynamic(Callback::new(callback))
150 }
151}
152
153#[derive(Debug, Clone, Copy, PartialEq)]
155pub struct VirtualItem {
156 pub index: usize,
157 pub size: f32,
158}
159
160#[derive(Clone)]
193pub struct VirtualScrollView<D, B: Fn(VirtualItem, &D) -> Element> {
194 builder: B,
195 builder_data: D,
196 item_size: ItemSize,
197 length: usize,
198 layout: LayoutData,
199 show_scrollbar: bool,
200 auto_hide_scrollbars: bool,
201 scroll_with_arrows: bool,
202 scroll_controller: Option<ScrollController>,
203 on_sized: Option<EventHandler<Event<SizedEventData>>>,
204 invert_scroll_wheel: bool,
205 drag_scrolling: bool,
206 scrollbar: Callback<ScrollBarContext, Element>,
207 key: DiffKey,
208}
209
210impl<D: PartialEq, B: Fn(VirtualItem, &D) -> Element> LayoutExt for VirtualScrollView<D, B> {
211 fn layout_mut(&mut self) -> &mut LayoutData {
212 &mut self.layout
213 }
214}
215
216impl<D: PartialEq, B: Fn(VirtualItem, &D) -> Element> ContainerSizeExt for VirtualScrollView<D, B> {}
217
218impl<D: PartialEq, B: Fn(VirtualItem, &D) -> Element> KeyExt for VirtualScrollView<D, B> {
219 fn key_mut(&mut self) -> &mut DiffKey {
220 &mut self.key
221 }
222}
223
224impl<D: PartialEq, B: Fn(VirtualItem, &D) -> Element> PartialEq for VirtualScrollView<D, B> {
225 fn eq(&self, other: &Self) -> bool {
226 self.builder_data == other.builder_data
227 && self.item_size == other.item_size
228 && self.length == other.length
229 && self.layout == other.layout
230 && self.show_scrollbar == other.show_scrollbar
231 && self.auto_hide_scrollbars == other.auto_hide_scrollbars
232 && self.scroll_with_arrows == other.scroll_with_arrows
233 && self.scroll_controller == other.scroll_controller
234 && self.invert_scroll_wheel == other.invert_scroll_wheel
235 && self.scrollbar == other.scrollbar
236 }
237}
238
239impl<B: Fn(VirtualItem, &()) -> Element> VirtualScrollView<(), B> {
240 pub fn new(builder: B) -> Self {
242 Self {
243 builder,
244 builder_data: (),
245 item_size: ItemSize::default(),
246 length: 0,
247 layout: {
248 let mut l = LayoutData::default();
249 l.layout.width = Size::fill();
250 l.layout.height = Size::fill();
251 l
252 },
253 show_scrollbar: true,
254 auto_hide_scrollbars: true,
255 scroll_with_arrows: true,
256 scroll_controller: None,
257 on_sized: None,
258 invert_scroll_wheel: false,
259 drag_scrolling: true,
260 scrollbar: ScrollBar::default_renderer(),
261 key: DiffKey::None,
262 }
263 }
264
265 pub fn new_controlled(builder: B, scroll_controller: ScrollController) -> Self {
267 Self {
268 builder,
269 builder_data: (),
270 item_size: ItemSize::default(),
271 length: 0,
272 layout: {
273 let mut l = LayoutData::default();
274 l.layout.width = Size::fill();
275 l.layout.height = Size::fill();
276 l
277 },
278 show_scrollbar: true,
279 auto_hide_scrollbars: true,
280 scroll_with_arrows: true,
281 scroll_controller: Some(scroll_controller),
282 on_sized: None,
283 invert_scroll_wheel: false,
284 drag_scrolling: true,
285 scrollbar: ScrollBar::default_renderer(),
286 key: DiffKey::None,
287 }
288 }
289}
290
291impl<D, B: Fn(VirtualItem, &D) -> Element> VirtualScrollView<D, B> {
292 pub fn new_with_data(builder_data: D, builder: B) -> Self {
316 Self {
317 builder,
318 builder_data,
319 item_size: ItemSize::default(),
320 length: 0,
321 layout: Node {
322 width: Size::fill(),
323 height: Size::fill(),
324 ..Default::default()
325 }
326 .into(),
327 show_scrollbar: true,
328 auto_hide_scrollbars: true,
329 scroll_with_arrows: true,
330 scroll_controller: None,
331 on_sized: None,
332 invert_scroll_wheel: false,
333 drag_scrolling: true,
334 scrollbar: ScrollBar::default_renderer(),
335 key: DiffKey::None,
336 }
337 }
338
339 pub fn new_with_data_controlled(
341 builder_data: D,
342 builder: B,
343 scroll_controller: ScrollController,
344 ) -> Self {
345 Self {
346 builder,
347 builder_data,
348 item_size: ItemSize::default(),
349 length: 0,
350
351 layout: Node {
352 width: Size::fill(),
353 height: Size::fill(),
354 ..Default::default()
355 }
356 .into(),
357 show_scrollbar: true,
358 auto_hide_scrollbars: true,
359 scroll_with_arrows: true,
360 scroll_controller: Some(scroll_controller),
361 on_sized: None,
362 invert_scroll_wheel: false,
363 drag_scrolling: true,
364 scrollbar: ScrollBar::default_renderer(),
365 key: DiffKey::None,
366 }
367 }
368
369 pub fn show_scrollbar(mut self, show_scrollbar: bool) -> Self {
371 self.show_scrollbar = show_scrollbar;
372 self
373 }
374
375 pub fn auto_hide_scrollbars(mut self, auto_hide_scrollbars: bool) -> Self {
378 self.auto_hide_scrollbars = auto_hide_scrollbars;
379 self
380 }
381
382 pub fn direction(mut self, direction: Direction) -> Self {
384 self.layout.direction = direction;
385 self
386 }
387
388 pub fn scroll_with_arrows(mut self, scroll_with_arrows: impl Into<bool>) -> Self {
390 self.scroll_with_arrows = scroll_with_arrows.into();
391 self
392 }
393
394 pub fn item_size(mut self, item_size: impl Into<ItemSize>) -> Self {
399 self.item_size = item_size.into();
400 self
401 }
402
403 pub fn length(mut self, length: impl Into<usize>) -> Self {
405 self.length = length.into();
406 self
407 }
408
409 pub fn invert_scroll_wheel(mut self, invert_scroll_wheel: impl Into<bool>) -> Self {
411 self.invert_scroll_wheel = invert_scroll_wheel.into();
412 self
413 }
414
415 pub fn drag_scrolling(mut self, drag_scrolling: bool) -> Self {
417 self.drag_scrolling = drag_scrolling;
418 self
419 }
420
421 pub fn scrollbar(mut self, scrollbar: impl Into<Callback<ScrollBarContext, Element>>) -> Self {
423 self.scrollbar = scrollbar.into();
424 self
425 }
426
427 pub fn scroll_controller(
429 mut self,
430 scroll_controller: impl Into<Option<ScrollController>>,
431 ) -> Self {
432 self.scroll_controller = scroll_controller.into();
433 self
434 }
435
436 pub fn on_sized(mut self, on_sized: impl Into<EventHandler<Event<SizedEventData>>>) -> Self {
438 self.on_sized = Some(on_sized.into());
439 self
440 }
441
442 pub fn min_width(mut self, min_width: impl Into<Size>) -> Self {
444 self.layout.minimum_width = min_width.into();
445 self
446 }
447
448 pub fn min_height(mut self, min_height: impl Into<Size>) -> Self {
450 self.layout.minimum_height = min_height.into();
451 self
452 }
453
454 pub fn max_width(mut self, max_width: impl Into<Size>) -> Self {
456 self.layout.maximum_width = max_width.into();
457 self
458 }
459
460 pub fn max_height(mut self, max_height: impl Into<Size>) -> Self {
462 self.layout.maximum_height = max_height.into();
463 self
464 }
465}
466
467impl<D: PartialEq + 'static, B: Fn(VirtualItem, &D) -> Element + 'static> Component
468 for VirtualScrollView<D, B>
469{
470 fn render(self: &VirtualScrollView<D, B>) -> impl IntoElement {
471 let a11y_id = use_a11y();
472 let mut timeout = use_timeout(|| Duration::from_millis(800));
473 let mut pressing_shift = use_state(|| false);
474 let mut clicking_scrollbar = use_state::<Option<(Axis, f64)>>(|| None);
475 let mut size = use_state(SizedEventData::default);
476 let mut scroll_controller = self
477 .scroll_controller
478 .unwrap_or_else(|| use_scroll_controller(ScrollConfig::default));
479 let mut dragging_content = use_state::<Option<CursorPoint>>(|| None);
480 let mut drag_origin = use_state::<Option<CursorPoint>>(|| None);
481 let (scrolled_x, scrolled_y) = scroll_controller.into();
482 let layout = &self.layout.layout;
483 let direction = layout.direction;
484 let drag_scrolling = self.drag_scrolling;
485
486 let viewport_width = size.read().area.width();
487 let viewport_height = size.read().area.height();
488
489 let (inner_width, inner_height) = match direction {
490 Direction::Vertical => (
491 size.read().inner_sizes.width,
492 self.item_size
493 .total_size(viewport_height, scrolled_y as f32, self.length),
494 ),
495 Direction::Horizontal => (
496 self.item_size
497 .total_size(viewport_width, scrolled_x as f32, self.length),
498 size.read().inner_sizes.height,
499 ),
500 };
501
502 let corrected_scrolled_x =
503 get_corrected_scroll_position(inner_width, size.read().area.width(), scrolled_x as f32);
504
505 let corrected_scrolled_y = get_corrected_scroll_position(
506 inner_height,
507 size.read().area.height(),
508 scrolled_y as f32,
509 );
510
511 let smooth_position = scroll_controller
512 .animated_position(Point2D::new(corrected_scrolled_x, corrected_scrolled_y));
513 let rendered_position = Point2D::new(
514 get_corrected_scroll_position(inner_width, size.read().area.width(), smooth_position.x),
515 get_corrected_scroll_position(
516 inner_height,
517 size.read().area.height(),
518 smooth_position.y,
519 ),
520 );
521
522 let horizontal_scrollbar_is_visible =
523 is_scrollbar_visible(self.show_scrollbar, inner_width, size.read().area.width());
524 let vertical_scrollbar_is_visible =
525 is_scrollbar_visible(self.show_scrollbar, inner_height, size.read().area.height());
526
527 let (scrollbar_x, scrollbar_width) =
528 get_scrollbar_pos_and_size(inner_width, size.read().area.width(), rendered_position.x);
529 let (scrollbar_y, scrollbar_height) = get_scrollbar_pos_and_size(
530 inner_height,
531 size.read().area.height(),
532 rendered_position.y,
533 );
534
535 let (container_width, content_width) = get_container_sizes(self.layout.width.clone());
536 let (container_height, content_height) = get_container_sizes(self.layout.height.clone());
537
538 let scroll_with_arrows = self.scroll_with_arrows;
539 let invert_scroll_wheel = self.invert_scroll_wheel;
540
541 let on_capture_global_pointer_up = move |e: Event<PointerEventData>| {
542 if clicking_scrollbar.read().is_some() {
543 e.prevent_default();
544 clicking_scrollbar.set(None);
545 }
546
547 if drag_scrolling && (dragging_content().is_some() || drag_origin().is_some()) {
548 if dragging_content().is_some() {
549 e.prevent_default();
550 let content = Size2D::new(inner_width, inner_height);
551 scroll_controller.release_drag(
552 rendered_position,
553 content,
554 size.read().area.size,
555 );
556 }
557 dragging_content.set(None);
558 drag_origin.set(None);
559 }
560 };
561
562 let on_wheel = move |e: Event<WheelEventData>| {
563 let invert_direction = e.source.is_device()
565 && (*pressing_shift.read() || invert_scroll_wheel)
566 && (!*pressing_shift.read() || !invert_scroll_wheel);
567
568 let (x_movement, y_movement) = if invert_direction {
569 (e.delta_y as f32, e.delta_x as f32)
570 } else {
571 (e.delta_x as f32, e.delta_y as f32)
572 };
573
574 let scroll_gesture =
576 TargetPlatform::get() == TargetPlatform::Linux && e.source == WheelSource::Pixel;
577 if scroll_gesture
578 && scroll_controller.scroll_gesture_phase(e.phase, (x_movement, y_movement).into())
579 {
580 return;
581 }
582
583 let animate = e.source == WheelSource::Line || scroll_gesture;
584 if animate {
585 scroll_controller.animate_from(rendered_position);
586 } else {
587 scroll_controller.stop();
588 }
589 let (base_x, base_y) = if animate {
590 let position = scroll_controller.position();
591 (position.x, position.y)
592 } else {
593 (rendered_position.x, rendered_position.y)
594 };
595
596 let scroll_position_y = get_scroll_position_from_wheel(
598 y_movement,
599 inner_height,
600 size.read().area.height(),
601 base_y,
602 );
603 scroll_controller.scroll_to_y(scroll_position_y).then(|| {
604 e.stop_propagation();
605 });
606
607 let scroll_position_x = get_scroll_position_from_wheel(
609 x_movement,
610 inner_width,
611 size.read().area.width(),
612 base_x,
613 );
614 scroll_controller.scroll_to_x(scroll_position_x).then(|| {
615 e.stop_propagation();
616 });
617 timeout.reset();
618 };
619
620 let on_mouse_move = move |_| {
621 timeout.reset();
622 };
623
624 let on_capture_global_pointer_move = move |e: Event<PointerEventData>| {
625 if drag_scrolling {
626 if let Some(prev) = dragging_content() {
627 let coords = e.global_location();
628 let delta = prev - coords;
629
630 scroll_controller.drag(delta.to_f32());
631 scroll_controller.scroll_to_y((rendered_position.y - delta.y as f32) as i32);
632 scroll_controller.scroll_to_x((rendered_position.x - delta.x as f32) as i32);
633
634 dragging_content.set(Some(coords));
635 e.prevent_default();
636 timeout.reset();
637 a11y_id.request_focus();
638 return;
639 } else if let Some(origin) = drag_origin() {
640 let coords = e.global_location();
641 let distance = (origin - coords).abs();
642
643 const DRAG_THRESHOLD: f64 = 2.0;
646
647 if distance.x > DRAG_THRESHOLD || distance.y > DRAG_THRESHOLD {
648 let delta = origin - coords;
649
650 scroll_controller.drag(delta.to_f32());
651 scroll_controller
652 .scroll_to_y((rendered_position.y - delta.y as f32) as i32);
653 scroll_controller
654 .scroll_to_x((rendered_position.x - delta.x as f32) as i32);
655
656 dragging_content.set(Some(coords));
657 e.prevent_default();
658 timeout.reset();
659 a11y_id.request_focus();
660 }
661 return;
662 }
663 }
664
665 let clicking_scrollbar = clicking_scrollbar.peek();
666
667 if clicking_scrollbar.is_some() {
668 scroll_controller.stop();
669 }
670
671 if let Some((Axis::Y, y)) = *clicking_scrollbar {
672 let coordinates = e.element_location();
673 let cursor_y = coordinates.y - y - size.read().area.min_y() as f64;
674
675 let scroll_position = get_scroll_position_from_cursor(
676 cursor_y as f32,
677 inner_height,
678 size.read().area.height(),
679 );
680
681 scroll_controller.scroll_to_y(scroll_position);
682 } else if let Some((Axis::X, x)) = *clicking_scrollbar {
683 let coordinates = e.element_location();
684 let cursor_x = coordinates.x - x - size.read().area.min_x() as f64;
685
686 let scroll_position = get_scroll_position_from_cursor(
687 cursor_x as f32,
688 inner_width,
689 size.read().area.width(),
690 );
691
692 scroll_controller.scroll_to_x(scroll_position);
693 }
694
695 if clicking_scrollbar.is_some() {
696 e.prevent_default();
697 timeout.reset();
698 a11y_id.request_focus();
699 }
700 };
701
702 let on_key_down = move |e: Event<KeyboardEventData>| {
703 if !scroll_with_arrows
704 && (e.key == Key::Named(NamedKey::ArrowUp)
705 || e.key == Key::Named(NamedKey::ArrowRight)
706 || e.key == Key::Named(NamedKey::ArrowDown)
707 || e.key == Key::Named(NamedKey::ArrowLeft))
708 {
709 return;
710 }
711 let x = corrected_scrolled_x;
712 let y = corrected_scrolled_y;
713 let inner_height = inner_height;
714 let inner_width = inner_width;
715 let viewport_height = size.read().area.height();
716 let viewport_width = size.read().area.width();
717 if let Some((x, y)) = handle_key_event(
718 &e.key,
719 (x, y),
720 inner_height,
721 inner_width,
722 viewport_height,
723 viewport_width,
724 direction,
725 ) {
726 scroll_controller.animate_from(rendered_position);
727 scroll_controller.scroll_to_x(x as i32);
728 scroll_controller.scroll_to_y(y as i32);
729 e.stop_propagation();
730 timeout.reset();
731 }
732 };
733
734 let on_global_key_down = move |e: Event<KeyboardEventData>| {
735 let data = e;
736 if data.key == Key::Named(NamedKey::Shift) {
737 pressing_shift.set(true);
738 }
739 };
740
741 let on_global_key_up = move |e: Event<KeyboardEventData>| {
742 let data = e;
743 if data.key == Key::Named(NamedKey::Shift) {
744 pressing_shift.set(false);
745 }
746 };
747
748 let (viewport_size, scroll_position) = if direction == Direction::vertical() {
749 (viewport_height, rendered_position.y)
750 } else {
751 (viewport_width, rendered_position.x)
752 };
753
754 let (render_range, item_offset) =
755 self.item_size
756 .visible_range(viewport_size, scroll_position, self.length);
757
758 let children = render_range
759 .map(|i| {
760 let item = VirtualItem {
761 index: i,
762 size: self.item_size.at(i),
763 };
764 (self.builder)(item, &self.builder_data)
765 })
766 .collect::<Vec<Element>>();
767
768 let (offset_x, offset_y) = match direction {
769 Direction::Vertical => (rendered_position.x, item_offset),
770 Direction::Horizontal => (item_offset, rendered_position.y),
771 };
772
773 let on_pointer_down = move |e: Event<PointerEventData>| {
774 if drag_scrolling && matches!(e.data(), PointerEventData::Touch(_)) {
775 scroll_controller.begin_drag();
776 drag_origin.set(Some(e.global_location()));
777 }
778 };
779
780 rect()
781 .width(layout.width.clone())
782 .height(layout.height.clone())
783 .min_width(layout.minimum_width.clone())
784 .min_height(layout.minimum_height.clone())
785 .max_width(layout.maximum_width.clone())
786 .max_height(layout.maximum_height.clone())
787 .a11y_id(a11y_id)
788 .a11y_role(AccessibilityRole::ScrollView)
789 .a11y_builder(move |node| {
790 node.set_scroll_x(rendered_position.x as f64);
791 node.set_scroll_y(rendered_position.y as f64)
792 })
793 .scrollable(true)
794 .on_wheel(on_wheel)
795 .on_capture_global_pointer_up(on_capture_global_pointer_up)
796 .on_mouse_move(on_mouse_move)
797 .on_capture_global_pointer_move(on_capture_global_pointer_move)
798 .on_key_down(on_key_down)
799 .on_global_key_up(on_global_key_up)
800 .on_global_key_down(on_global_key_down)
801 .on_pointer_down(on_pointer_down)
802 .child(
803 rect()
804 .width(container_width)
805 .height(container_height)
806 .horizontal()
807 .child(
808 rect()
809 .direction(direction)
810 .width(content_width)
811 .height(content_height)
812 .offset_x(offset_x)
813 .offset_y(offset_y)
814 .overflow(Overflow::Clip)
815 .on_sized({
816 let item_size = self.item_size.clone();
817 let length = self.length;
818 let on_sized = self.on_sized.clone();
819 move |e: Event<SizedEventData>| {
820 size.set_if_modified(e.clone());
821 let content_size = match direction {
822 Direction::Vertical => Size2D::new(
823 e.inner_sizes.width,
824 item_size.total_size(
825 e.area.height(),
826 scrolled_y as f32,
827 length,
828 ),
829 ),
830 Direction::Horizontal => Size2D::new(
831 item_size.total_size(
832 e.area.width(),
833 scrolled_x as f32,
834 length,
835 ),
836 e.inner_sizes.height,
837 ),
838 };
839
840 scroll_controller.apply_layout(content_size, e.area.size);
841 if let Some(on_sized) = &on_sized {
842 on_sized.call(e);
843 }
844 }
845 })
846 .children(children),
847 )
848 .maybe_child(vertical_scrollbar_is_visible.then_some({
849 rect().child(self.scrollbar.call(ScrollBarContext {
850 axis: Axis::Y,
851 scroll_position: rendered_position,
852 viewport_size: size.read().area.size,
853 content_size: Size2D::new(inner_width, inner_height),
854 scroll_controller,
855 timeout,
856 auto_hide_scrollbars: self.auto_hide_scrollbars,
857 clicking_scrollbar,
858 thumb_events: ScrollBarThumbEvents::new(Axis::Y, clicking_scrollbar),
859 thumb_offset: scrollbar_y,
860 track_size: Size::px(size.read().area.height()),
861 thumb_length: scrollbar_height,
862 }))
863 })),
864 )
865 .maybe_child(horizontal_scrollbar_is_visible.then_some({
866 rect().child(self.scrollbar.call(ScrollBarContext {
867 axis: Axis::X,
868 scroll_position: rendered_position,
869 viewport_size: size.read().area.size,
870 content_size: Size2D::new(inner_width, inner_height),
871 scroll_controller,
872 timeout,
873 auto_hide_scrollbars: self.auto_hide_scrollbars,
874 clicking_scrollbar,
875 thumb_events: ScrollBarThumbEvents::new(Axis::X, clicking_scrollbar),
876 thumb_offset: scrollbar_x,
877 track_size: Size::px(size.read().area.width()),
878 thumb_length: scrollbar_width,
879 }))
880 }))
881 }
882
883 fn render_key(&self) -> DiffKey {
884 self.key.clone().or(self.default_key())
885 }
886}