1#![deny(missing_docs)]
2use floem_reactive::Effect;
5use peniko::kurbo::{Affine, Axis, Point, Rect, RoundedRect, RoundedRectRadii, Stroke, Vec2};
6use peniko::{Brush, Color};
7use std::time::Duration;
8use std::{cell::RefCell, rc::Rc};
9use taffy::Overflow;
10use ui_events::pointer::{PointerButton, PointerEvent, PointerId};
11
12use crate::easing::Linear;
13use crate::event::{
14 DragEvent, DragSourceEvent, PointerCaptureEvent, PointerScrollEventExt, RouteKind, ScrollTo,
15};
16use crate::prelude::EventListenerTrait;
17use crate::prelude::el::UpdatePhaseLayout;
18use crate::style::ScrollbarWidth;
19use crate::{
20 BoxTree, ElementId, Renderer,
21 context::{EventCx, PaintCx, StyleCx},
22 event::{Event, EventPropagation, Phase},
23 prop, prop_extractor,
24 style::{
25 Background, BorderBottomColor, BorderBottomLeftRadius, BorderBottomRightRadius,
26 BorderLeftColor, BorderRightColor, BorderTopColor, BorderTopLeftRadius,
27 BorderTopRightRadius, CustomStylable, CustomStyle, OverflowX, OverflowY, Style, StyleClass,
28 },
29 style_class,
30 unit::{Length, Pt},
31 view::{IntoView, View},
32};
33use crate::{ViewId, custom_event};
34use understory_box_tree::NodeFlags;
35
36use super::Decorators;
37
38#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct ScrollChanged {
45 pub offset: Vec2,
47}
48custom_event!(ScrollChanged);
49
50#[derive(Debug, Clone, Copy)]
51enum ScrollState {
52 EnsureVisible(Rect),
53 ScrollDelta(Vec2),
54 ScrollTo(Point),
55 ScrollToPercent(f32),
56 ScrollToElement(ElementId),
57}
58
59struct ScrollEventResult {
60 propagation: EventPropagation,
61 new_offset: Option<Vec2>,
62}
63
64trait Vec2Ext {
65 fn max_by_component(self, other: Self) -> Self;
67
68 fn min_by_component(self, other: Self) -> Self;
70}
71
72impl Vec2Ext for Vec2 {
73 fn max_by_component(self, other: Self) -> Self {
74 Vec2::new(self.x.max(other.x), self.y.max(other.y))
75 }
76
77 fn min_by_component(self, other: Self) -> Self {
78 Vec2::new(self.x.min(other.x), self.y.min(other.y))
79 }
80}
81
82#[derive(Debug, Clone)]
83struct ScrollHandle {
84 element_id: ElementId,
85 box_tree: Rc<RefCell<BoxTree>>,
86 axis: Axis,
87 style: ScrollTrackStyle,
89 initial_offset: Vec2,
90}
91
92impl ScrollHandle {
93 fn new(parent_id: ViewId, axis: Axis) -> Self {
94 let box_tree = parent_id.box_tree();
95 let element_id = parent_id.create_child_element_id(2);
96
97 Self {
98 element_id,
99 box_tree,
100 axis,
101 style: Default::default(),
102 initial_offset: Vec2::ZERO,
103 }
104 }
105
106 fn style(&mut self, cx: &mut StyleCx) {
107 let resolved =
108 cx.resolve_nested_maps(Style::new(), &[Handle::class_ref()], self.element_id);
109 if self.style.read_style_for(cx, &resolved, self.element_id) {
110 self.element_id.owning_id().request_paint();
111 }
112 }
113
114 fn event(
115 &mut self,
116 cx: &mut EventCx,
117 parent_id: ViewId,
118 child_id: ViewId,
119 ) -> ScrollEventResult {
120 match &cx.event {
121 Event::Pointer(PointerEvent::Down(e)) => {
122 if let Some(pointer_id) = e.pointer.pointer_id
123 && e.state.buttons.contains(PointerButton::Primary)
124 {
125 cx.window_state
126 .set_pointer_capture(pointer_id, self.element_id);
127 }
128 cx.window_state.request_paint(parent_id);
129 }
130 Event::PointerCapture(PointerCaptureEvent::Gained(drag)) => {
131 self.initial_offset = parent_id.get_child_translation();
132 cx.start_drag(
133 *drag,
134 crate::event::DragConfig::new(0., Duration::ZERO, Linear),
135 false,
136 );
137 }
138 Event::Drag(DragEvent::Source(DragSourceEvent::Move(dme))) => {
139 let pos = dme.current_state.logical_point();
140
141 let viewport_size = parent_id
143 .get_content_rect_local()
144 .size()
145 .get_coord(self.axis);
146 let content_size = child_id.get_layout_rect_local().size().get_coord(self.axis);
147 let scale = content_size / viewport_size;
148
149 let scroll_delta = (pos.get_coord(self.axis)
150 - dme.start_state.logical_point().get_coord(self.axis))
151 * scale;
152
153 let mut new_offset: Vec2 = self.initial_offset;
154 new_offset.set_coord(
155 self.axis,
156 self.initial_offset.get_coord(self.axis) + scroll_delta,
157 );
158
159 let viewport_size_vec = parent_id.get_content_rect_local().size();
161 let content_size_vec = child_id.get_layout_rect_local().size();
162 let max_scroll = (content_size_vec.to_vec2() - viewport_size_vec.to_vec2())
163 .max_by_component(Vec2::ZERO);
164
165 let new_offset = new_offset
166 .max_by_component(Vec2::ZERO)
167 .min_by_component(max_scroll);
168
169 return ScrollEventResult {
170 propagation: EventPropagation::Stop,
171 new_offset: Some(new_offset),
172 };
173 }
174
175 _ => {
176 return ScrollEventResult {
177 propagation: EventPropagation::Continue,
178 new_offset: None,
179 };
180 }
181 }
182 ScrollEventResult {
183 propagation: EventPropagation::Stop,
184 new_offset: None,
185 }
186 }
187
188 fn set_position(
189 &mut self,
190 scroll_offset: Vec2,
191 viewport: Rect,
192 full_rect: Rect,
193 content_size: peniko::kurbo::Size,
194 scrollbar_width: f64,
195 bar_inset: f64,
196 ) {
197 let viewport_size = viewport.size().get_coord(self.axis);
198 let content_size_val = content_size.get_coord(self.axis);
199 let full_rect_size = full_rect.size().get_coord(self.axis);
200
201 if content_size_val <= viewport_size + 1.0 {
203 self.box_tree
205 .borrow_mut()
206 .set_flags(self.element_id.0, NodeFlags::empty());
207 return;
208 }
209
210 let percent_visible = viewport_size / content_size_val;
212 let max_scroll = content_size_val - viewport_size;
213 let scroll_offset_val = scroll_offset.get_coord(self.axis);
214
215 let percent_scrolled = if max_scroll > 0.0 {
216 scroll_offset_val / max_scroll
217 } else {
218 0.0
219 };
220
221 let handle_length = (percent_visible * full_rect_size).ceil().max(15.);
222
223 let track_length = full_rect_size;
224 let available_travel = track_length - handle_length;
225 let handle_offset = (available_travel * percent_scrolled).ceil();
226
227 let rect = match self.axis {
228 Axis::Vertical => {
229 let x0 = full_rect.width() - scrollbar_width - bar_inset;
230 let y0 = handle_offset;
231 let x1 = full_rect.width() - bar_inset;
232 let y1 = handle_offset + handle_length;
233 Rect::new(x0, y0, x1, y1)
234 }
235 Axis::Horizontal => {
236 let x0 = handle_offset;
237 let y0 = full_rect.height() - scrollbar_width - bar_inset;
238 let x1 = handle_offset + handle_length;
239 let y1 = full_rect.height() - bar_inset;
240 Rect::new(x0, y0, x1, y1)
241 }
242 };
243
244 self.box_tree
245 .borrow_mut()
246 .set_local_bounds(self.element_id.0, rect);
247 self.box_tree
248 .borrow_mut()
249 .set_flags(self.element_id.0, NodeFlags::VISIBLE | NodeFlags::PICKABLE);
250 }
251
252 fn paint(&self, cx: &mut PaintCx) {
253 let box_tree = self.box_tree.borrow();
254 let rect = box_tree.local_bounds(self.element_id.0).unwrap_or_default();
255
256 let radius = if self.style.rounded() {
257 match self.axis {
258 Axis::Vertical => RoundedRectRadii::from_single_radius((rect.x1 - rect.x0) / 2.),
259 Axis::Horizontal => RoundedRectRadii::from_single_radius((rect.y1 - rect.y0) / 2.),
260 }
261 } else {
262 let size = rect.size().min_side();
263 let border_radius = self.style.border_radius();
264 RoundedRectRadii {
265 top_left: crate::view::border_radius(
266 border_radius.top_left.unwrap_or(Length::Pt(0.)),
267 size,
268 &cx.font_size_cx,
269 ),
270 top_right: crate::view::border_radius(
271 border_radius.top_right.unwrap_or(Length::Pt(0.)),
272 size,
273 &cx.font_size_cx,
274 ),
275 bottom_left: crate::view::border_radius(
276 border_radius.bottom_left.unwrap_or(Length::Pt(0.)),
277 size,
278 &cx.font_size_cx,
279 ),
280 bottom_right: crate::view::border_radius(
281 border_radius.bottom_right.unwrap_or(Length::Pt(0.)),
282 size,
283 &cx.font_size_cx,
284 ),
285 }
286 };
287
288 let edge_width = self.style.border().0;
289 let rect_with_border = rect.inset(-edge_width / 2.0);
290 let rounded_rect = rect_with_border.to_rounded_rect(radius);
291
292 cx.fill(
293 &rounded_rect,
294 &self.style.background().unwrap_or(HANDLE_COLOR),
295 0.0,
296 );
297
298 if edge_width > 0.0
299 && let Some(color) = self.style.border_color().right
300 {
301 cx.stroke(&rounded_rect, &color, &Stroke::new(edge_width));
302 }
303 }
304}
305
306#[derive(Debug, Clone)]
307struct ScrollTrack {
308 element_id: ElementId,
309 handle_element_id: ElementId,
310 box_tree: Rc<RefCell<BoxTree>>,
311 axis: Axis,
312 style: ScrollTrackStyle,
313}
314
315impl ScrollTrack {
316 fn new(parent_id: ViewId, handle_element_id: ElementId, axis: Axis) -> Self {
317 let box_tree = parent_id.box_tree();
318 let element_id = parent_id.create_child_element_id(1);
319
320 Self {
321 element_id,
322 handle_element_id,
323 box_tree,
324 axis,
325 style: Default::default(),
326 }
327 }
328
329 fn style(&mut self, cx: &mut StyleCx) {
330 let resolved = cx.resolve_nested_maps(Style::new(), &[Track::class_ref()], self.element_id);
331 if self.style.read_style_for(cx, &resolved, self.element_id) {
332 self.element_id.owning_id().request_paint();
333 }
334 }
335
336 fn event(
337 &mut self,
338 cx: &mut EventCx,
339 parent_id: ViewId,
340 child_id: ViewId,
341 ) -> ScrollEventResult {
342 match &cx.event {
343 Event::Pointer(PointerEvent::Down(e)) => {
344 if e.state.buttons.contains(PointerButton::Primary) {
345 cx.window_state
346 .set_pointer_capture(PointerId::PRIMARY, self.handle_element_id);
347 }
348 let pos = e.state.logical_point();
349
350 let viewport = parent_id.get_content_rect_local();
352 let full_rect = parent_id.get_layout_rect_local();
353 let content_size = child_id.get_layout_rect_local().size();
354
355 let pos_val = pos.get_coord(self.axis);
356 let viewport_size = viewport.size().get_coord(self.axis);
357 let content_size_val = content_size.get_coord(self.axis);
358 let full_rect_size = full_rect.size().get_coord(self.axis);
359
360 let percent_visible = viewport_size / content_size_val;
361 let handle_length = (percent_visible * full_rect_size).ceil().max(15.);
362 let max_scroll = content_size_val - viewport_size;
363
364 let track_length = full_rect_size;
365 let available_travel = track_length - handle_length;
366
367 let target_handle_offset = (pos_val - handle_length / 2.0)
368 .max(0.0)
369 .min(available_travel);
370 let target_percent = if available_travel > 0.0 {
371 target_handle_offset / available_travel
372 } else {
373 0.0
374 };
375
376 let new_offset = (target_percent * max_scroll).clamp(0.0, max_scroll);
377
378 cx.window_state.request_paint(parent_id);
379
380 let mut offset = parent_id.get_child_translation();
381 offset.set_coord(self.axis, new_offset);
382 ScrollEventResult {
383 propagation: EventPropagation::Stop,
384 new_offset: Some(offset),
385 }
386 }
387 _ => ScrollEventResult {
388 propagation: EventPropagation::Continue,
389 new_offset: None,
390 },
391 }
392 }
393
394 fn set_position(
395 &mut self,
396 viewport: Rect,
397 full_rect: Rect,
398 content_size: peniko::kurbo::Size,
399 scrollbar_width: f64,
400 bar_inset: f64,
401 ) {
402 let viewport_size = viewport.size().get_coord(self.axis);
403 let content_size_val = content_size.get_coord(self.axis);
404
405 if content_size_val <= viewport_size + 1.0 {
407 self.box_tree
409 .borrow_mut()
410 .set_flags(self.element_id.0, NodeFlags::empty());
411 return;
412 }
413
414 let rect = match self.axis {
415 Axis::Vertical => {
416 let x0 = full_rect.width() - scrollbar_width - bar_inset;
417 let y0 = 0.0;
418 let x1 = full_rect.width() - bar_inset;
419 let y1 = full_rect.height();
420 Rect::new(x0, y0, x1, y1)
421 }
422 Axis::Horizontal => {
423 let x0 = 0.0;
424 let y0 = full_rect.height() - scrollbar_width - bar_inset;
425 let x1 = full_rect.width();
426 let y1 = full_rect.height() - bar_inset;
427 Rect::new(x0, y0, x1, y1)
428 }
429 };
430
431 self.box_tree
432 .borrow_mut()
433 .set_local_bounds(self.element_id.0, rect);
434 self.box_tree
435 .borrow_mut()
436 .set_flags(self.element_id.0, NodeFlags::VISIBLE | NodeFlags::PICKABLE);
437 }
438
439 fn paint(&self, cx: &mut PaintCx) {
440 let box_tree = self.box_tree.borrow();
441 let rect = box_tree.local_bounds(self.element_id.0).unwrap_or_default();
442
443 if let Some(color) = self.style.background() {
444 cx.fill(&rect, &color, 0.0);
445 }
446 }
447}
448
449style_class!(
450 pub Handle
452);
453style_class!(
454 pub Track
456);
457
458prop!(
459 pub Rounded: bool {} = cfg!(target_os = "macos")
461);
462prop!(
463 pub Border: Pt {} = Pt(0.0)
465);
466
467prop_extractor! {
468 ScrollTrackStyle {
469 background: Background,
470 border_top_left_radius: BorderTopLeftRadius,
471 border_top_right_radius: BorderTopRightRadius,
472 border_bottom_left_radius: BorderBottomLeftRadius,
473 border_bottom_right_radius: BorderBottomRightRadius,
474 border_left_color: BorderLeftColor,
475 border_top_color: BorderTopColor,
476 border_right_color: BorderRightColor,
477 border_bottom_color: BorderBottomColor,
478 border: Border,
479 rounded: Rounded,
480 }
481}
482
483impl ScrollTrackStyle {
484 fn border_radius(&self) -> crate::style::BorderRadius {
485 crate::style::BorderRadius {
486 top_left: Some(self.border_top_left_radius()),
487 top_right: Some(self.border_top_right_radius()),
488 bottom_left: Some(self.border_bottom_left_radius()),
489 bottom_right: Some(self.border_bottom_right_radius()),
490 }
491 }
492
493 fn border_color(&self) -> crate::style::BorderColor {
494 crate::style::BorderColor {
495 left: self.border_left_color(),
496 top: self.border_top_color(),
497 right: self.border_right_color(),
498 bottom: self.border_bottom_color(),
499 }
500 }
501}
502
503prop!(
504 pub VerticalInset: Pt {} = Pt(0.0)
506);
507
508prop!(
509 pub HorizontalInset: Pt {} = Pt(0.0)
511);
512
513prop!(
514 pub HideBars: bool {} = false
516);
517
518prop!(
519 pub ShowBarsWhenIdle: bool {} = true
521);
522
523prop!(
524 pub PropagatePointerWheel: bool {} = true
526);
527
528prop!(
529 pub VerticalScrollAsHorizontal: bool {} = false
531);
532
533prop_extractor!(ScrollStyle {
534 vertical_bar_inset: VerticalInset,
535 horizontal_bar_inset: HorizontalInset,
536 hide_bar: HideBars,
537 show_bars_when_idle: ShowBarsWhenIdle,
538 propagate_pointer_wheel: PropagatePointerWheel,
539 vertical_scroll_as_horizontal: VerticalScrollAsHorizontal,
540 overflow_x: OverflowX,
541 overflow_y: OverflowY,
542 scrollbar_width: ScrollbarWidth,
543});
544
545const HANDLE_COLOR: Brush = Brush::Solid(Color::from_rgba8(0, 0, 0, 120));
546
547style_class!(
548 pub ScrollClass
550);
551
552pub struct Scroll {
554 id: ViewId,
555 child: ViewId,
556 scroll_offset: Vec2,
558 v_handle: ScrollHandle,
559 h_handle: ScrollHandle,
560 v_track: ScrollTrack,
561 h_track: ScrollTrack,
562 scroll_style: ScrollStyle,
563}
564
565#[deprecated(since = "0.2.0", note = "Use Scroll::new() instead")]
567pub fn scroll<V: IntoView + 'static>(child: V) -> Scroll {
568 Scroll::new(child)
569}
570
571impl Scroll {
572 pub fn new(child: impl IntoView) -> Self {
582 let id = ViewId::new();
583 id.register_listener(UpdatePhaseLayout::listener_key());
584
585 let child = child.into_any();
586 let child_id = child.id();
587 id.add_child(child);
588 id.set_box_tree_clip(Some(RoundedRect::from_rect(Rect::ZERO, 0.)));
590
591 let v_handle = ScrollHandle::new(id, Axis::Vertical);
592 let h_handle = ScrollHandle::new(id, Axis::Horizontal);
593
594 Scroll {
595 id,
596 child: child_id,
597 scroll_offset: Vec2::ZERO,
598 v_track: ScrollTrack::new(id, v_handle.element_id, Axis::Vertical),
599 h_track: ScrollTrack::new(id, h_handle.element_id, Axis::Horizontal),
600 v_handle,
601 h_handle,
602 scroll_style: Default::default(),
603 }
604 .class(ScrollClass)
605 }
606}
607
608impl Scroll {
609 pub fn ensure_visible(self, to: impl Fn() -> Rect + 'static) -> Self {
617 let id = self.id();
618 Effect::new(move |_| {
619 let rect = to();
620 id.update_state_deferred(ScrollState::EnsureVisible(rect));
621 });
622
623 self
624 }
625
626 pub fn scroll_delta(self, delta: impl Fn() -> Vec2 + 'static) -> Self {
632 let id = self.id();
633 Effect::new(move |_| {
634 let delta = delta();
635 id.update_state(ScrollState::ScrollDelta(delta));
636 });
637
638 self
639 }
640
641 pub fn scroll_to(self, origin: impl Fn() -> Option<Point> + 'static) -> Self {
647 let id = self.id();
648 Effect::new(move |_| {
649 if let Some(origin) = origin() {
650 id.update_state_deferred(ScrollState::ScrollTo(origin));
651 }
652 });
653
654 self
655 }
656
657 pub fn scroll_to_percent(self, percent: impl Fn() -> f32 + 'static) -> Self {
663 let id = self.id();
664 Effect::new(move |_| {
665 let percent = percent() / 100.;
666 id.update_state_deferred(ScrollState::ScrollToPercent(percent));
667 });
668 self
669 }
670
671 pub fn scroll_to_view(self, view: impl Fn() -> Option<ViewId> + 'static) -> Self {
677 let id = self.id();
678 Effect::new(move |_| {
679 if let Some(view) = view() {
680 id.update_state_deferred(ScrollState::ScrollToElement(view.get_element_id()));
681 }
682 });
683
684 self
685 }
686}
687
688impl Scroll {
690 fn apply_scroll_delta(&mut self, delta: Vec2) -> Option<Vec2> {
694 let viewport_size = self.id.get_content_rect_local().size();
695 let content_size = self.child.get_layout_rect_local().size();
696
697 let mut max_scroll =
699 (content_size.to_vec2() - viewport_size.to_vec2()).max_by_component(Vec2::ZERO);
700
701 let can_scroll_x = matches!(self.scroll_style.overflow_x(), taffy::Overflow::Scroll);
703 let can_scroll_y = matches!(self.scroll_style.overflow_y(), taffy::Overflow::Scroll);
704
705 let mut new_scroll_offset = self.scroll_offset + delta;
706 if !can_scroll_x {
707 new_scroll_offset.x = 0.0;
708 max_scroll.x = 0.0;
709 }
710 if !can_scroll_y {
711 new_scroll_offset.y = 0.0;
712 max_scroll.y = 0.0;
713 }
714
715 let old_scroll_offset = self.scroll_offset;
716 self.scroll_offset = new_scroll_offset
717 .max_by_component(Vec2::ZERO)
718 .min_by_component(max_scroll);
719 let change = self.id.set_child_translation(self.scroll_offset);
720 if change {
721 self.id.route_event(
722 Event::new_custom(ScrollChanged {
723 offset: self.scroll_offset,
724 }),
725 RouteKind::Directed {
726 target: self.id.get_element_id(),
727 phases: crate::context::Phases::TARGET,
728 },
729 );
730 }
731
732 if change {
733 self.set_positions();
734 Some(self.scroll_offset - old_scroll_offset)
735 } else {
736 None
737 }
738 }
739
740 fn do_scroll_to(&mut self, offset: Point) {
748 self.apply_scroll_delta(offset.to_vec2() - self.scroll_offset);
749 }
750
751 pub fn do_ensure_visible(&mut self, rect: Rect) {
759 let viewport = self.id.get_content_rect_local();
760 let viewport_size = viewport.size();
761
762 let visible_rect = Rect::from_origin_size(self.scroll_offset.to_point(), viewport_size);
764
765 if visible_rect.contains_rect(rect) {
767 return;
768 }
769
770 let mut new_offset = self.scroll_offset;
771
772 if rect.width() > viewport_size.width {
774 new_offset.x = rect.x0;
776 } else if rect.x0 < visible_rect.x0 {
777 new_offset.x = rect.x0;
779 } else if rect.x1 > visible_rect.x1 {
780 new_offset.x = rect.x1 - viewport_size.width;
782 }
783
784 if rect.height() > viewport_size.height {
786 new_offset.y = rect.y0;
788 } else if rect.y0 < visible_rect.y0 {
789 new_offset.y = rect.y0;
791 } else if rect.y1 > visible_rect.y1 {
792 new_offset.y = rect.y1 - viewport_size.height;
794 }
795
796 self.do_scroll_to(new_offset.to_point());
797 }
798
799 fn do_scroll_to_element(&mut self, scroll_to: ScrollTo) -> EventPropagation {
800 let child_element_id = self.child.get_element_id();
801 let box_tree = self.id.box_tree();
802 let box_tree = box_tree.borrow();
803
804 let Some(target_local_rect) = scroll_to
805 .rect
806 .or_else(|| box_tree.local_bounds(scroll_to.id.0))
807 else {
808 return EventPropagation::Continue;
809 };
810
811 let target_transform = box_tree
812 .world_transform(scroll_to.id.0)
813 .unwrap_or(Affine::IDENTITY);
814 let child_transform = box_tree
815 .world_transform(child_element_id.0)
816 .unwrap_or(Affine::IDENTITY);
817
818 let target_world_rect = target_transform.transform_rect_bbox(target_local_rect);
819 let child_world_origin = child_transform * Point::ZERO;
820
821 let target_rect = Rect::new(
822 target_world_rect.x0 - child_world_origin.x,
823 target_world_rect.y0 - child_world_origin.y,
824 target_world_rect.x1 - child_world_origin.x,
825 target_world_rect.y1 - child_world_origin.y,
826 );
827 drop(box_tree);
828
829 self.do_ensure_visible(target_rect);
830
831 let viewport_size = self.id.get_content_rect_local().size();
832 let visible_rect = Rect::from_origin_size(self.scroll_offset.to_point(), viewport_size);
833
834 if visible_rect.contains_rect(target_rect) {
835 EventPropagation::Stop
836 } else {
837 EventPropagation::Continue
838 }
839 }
840
841 fn set_positions(&mut self) {
842 let viewport = self.id.get_content_rect_local();
843 let full_rect = self.id.get_layout_rect_local();
844 let content_size = self.child.get_layout_rect_local().size();
845 let scrollbar_width = self.scroll_style.scrollbar_width().0;
846 let v_bar_inset = self.scroll_style.vertical_bar_inset().0;
847 let h_bar_inset = self.scroll_style.horizontal_bar_inset().0;
848
849 self.v_track.set_position(
850 viewport,
851 full_rect,
852 content_size,
853 scrollbar_width,
854 v_bar_inset,
855 );
856 self.h_track.set_position(
857 viewport,
858 full_rect,
859 content_size,
860 scrollbar_width,
861 h_bar_inset,
862 );
863
864 self.v_handle.set_position(
865 self.scroll_offset,
866 viewport,
867 full_rect,
868 content_size,
869 scrollbar_width,
870 v_bar_inset,
871 );
872 self.h_handle.set_position(
873 self.scroll_offset,
874 viewport,
875 full_rect,
876 content_size,
877 scrollbar_width,
878 h_bar_inset,
879 );
880 }
881}
882
883impl View for Scroll {
884 fn id(&self) -> ViewId {
885 self.id
886 }
887
888 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
889 "Scroll".into()
890 }
891
892 fn view_style(&self) -> Option<Style> {
893 Some(
894 Style::new()
895 .items_start()
896 .overflow_x(Overflow::Scroll)
897 .overflow_y(Overflow::Scroll),
898 )
899 }
900
901 fn update(&mut self, _cx: &mut crate::context::UpdateCx, state: Box<dyn std::any::Any>) {
902 if let Ok(state) = state.downcast::<ScrollState>() {
903 match *state {
904 ScrollState::EnsureVisible(rect) => {
905 self.do_ensure_visible(rect);
906 }
907 ScrollState::ScrollDelta(delta) => {
908 self.apply_scroll_delta(delta);
909 }
910 ScrollState::ScrollTo(origin) => {
911 self.do_scroll_to(origin);
912 }
913 ScrollState::ScrollToPercent(percent) => {
914 let content_size = self.child.get_layout_rect_local().size();
915 let viewport_size = self.id.get_content_rect_local().size();
916
917 let max_scroll = (content_size.to_vec2() - viewport_size.to_vec2())
919 .max_by_component(Vec2::ZERO);
920
921 let target_offset = max_scroll * (percent as f64);
923
924 self.do_scroll_to(target_offset.to_point());
925 }
926 ScrollState::ScrollToElement(id) => {
927 self.do_scroll_to_element(ScrollTo { id, rect: None });
928 }
929 }
930 self.id.request_box_tree_update_for_view();
931 }
932 }
933
934 fn style_pass(&mut self, cx: &mut crate::context::StyleCx<'_>) {
935 self.scroll_style.read(cx);
936
937 if cx.reason.needs_resolve_nested_maps() {
939 self.v_handle.style(cx);
940 self.h_handle.style(cx);
941 self.v_track.style(cx);
942 self.h_track.style(cx);
943 return;
944 }
945
946 for (element_id, _reason) in cx.targeted_elements.clone() {
947 if element_id == self.v_handle.element_id {
948 self.v_handle.style(cx);
949 } else if element_id == self.h_handle.element_id {
950 self.h_handle.style(cx);
951 } else if element_id == self.v_track.element_id {
952 self.v_track.style(cx);
953 } else if element_id == self.h_track.element_id {
954 self.h_track.style(cx);
955 }
956 }
957 }
958
959 fn event(&mut self, cx: &mut EventCx) -> EventPropagation {
960 if UpdatePhaseLayout::extract(&cx.event).is_some() {
962 self.set_positions();
963 return EventPropagation::Stop;
964 }
965
966 if let Some(scroll_to) = ScrollTo::extract(&cx.event) {
967 return self.do_scroll_to_element(*scroll_to);
968 }
969 if cx.phase == Phase::Target {
971 if cx.target == self.v_handle.element_id {
972 let result = self.v_handle.event(cx, self.id, self.child);
973 if let Some(new_offset) = result.new_offset
974 && self
975 .apply_scroll_delta(new_offset - self.scroll_offset)
976 .is_some()
977 {
978 cx.window_state.request_paint(self.id);
979 }
980 return result.propagation;
981 }
982 if cx.target == self.h_handle.element_id {
983 let result = self.h_handle.event(cx, self.id, self.child);
984 if let Some(new_offset) = result.new_offset
985 && self
986 .apply_scroll_delta(new_offset - self.scroll_offset)
987 .is_some()
988 {
989 cx.window_state.request_paint(self.id);
990 }
991 return result.propagation;
992 }
993 if cx.target == self.v_track.element_id {
994 let result = self.v_track.event(cx, self.id, self.child);
995 if let Some(new_offset) = result.new_offset
996 && self
997 .apply_scroll_delta(new_offset - self.scroll_offset)
998 .is_some()
999 {
1000 cx.window_state.request_paint(self.id);
1001 }
1002 return result.propagation;
1003 }
1004 if cx.target == self.h_track.element_id {
1005 let result = self.h_track.event(cx, self.id, self.child);
1006 if let Some(new_offset) = result.new_offset
1007 && self
1008 .apply_scroll_delta(new_offset - self.scroll_offset)
1009 .is_some()
1010 {
1011 cx.window_state.request_paint(self.id);
1012 }
1013 return result.propagation;
1014 }
1015 }
1016
1017 if let Event::Pointer(PointerEvent::Scroll(pse)) = &cx.event {
1019 let size = self.id.get_layout_rect_local().size();
1020 let delta = pse.resolve_to_points(None, Some(size));
1021 let delta = -if self.scroll_style.vertical_scroll_as_horizontal()
1022 && delta.x == 0.0
1023 && delta.y != 0.0
1024 {
1025 Vec2::new(delta.y, delta.x)
1026 } else {
1027 delta
1028 };
1029
1030 let change = self.apply_scroll_delta(delta);
1031
1032 if change.is_some() {
1033 cx.window_state.request_paint(self.id);
1034 }
1035
1036 return if self.scroll_style.propagate_pointer_wheel() && change.is_none() {
1037 EventPropagation::Continue
1038 } else {
1039 EventPropagation::Stop
1040 };
1041 }
1042
1043 EventPropagation::Continue
1044 }
1045
1046 fn paint(&mut self, cx: &mut crate::context::PaintCx) {
1047 self.apply_scroll_delta(Vec2::ZERO);
1050
1051 if cx.target_id == self.id.get_element_id() {
1054 } else if cx.target_id == self.v_handle.element_id {
1056 if !self.scroll_style.hide_bar() && (self.scroll_style.show_bars_when_idle()) {
1058 self.v_handle.paint(cx);
1059 }
1060 } else if cx.target_id == self.h_handle.element_id {
1061 if !self.scroll_style.hide_bar() && (self.scroll_style.show_bars_when_idle()) {
1063 self.h_handle.paint(cx);
1064 }
1065 } else if cx.target_id == self.v_track.element_id {
1066 if !self.scroll_style.hide_bar() && (self.scroll_style.show_bars_when_idle()) {
1068 self.v_track.paint(cx);
1069 }
1070 } else if cx.target_id == self.h_track.element_id {
1071 if !self.scroll_style.hide_bar() && (self.scroll_style.show_bars_when_idle()) {
1073 self.h_track.paint(cx);
1074 }
1075 }
1076 }
1077}
1078#[derive(Default, Debug, Clone)]
1080pub struct ScrollCustomStyle(Style);
1081impl From<ScrollCustomStyle> for Style {
1082 fn from(value: ScrollCustomStyle) -> Self {
1083 value.0
1084 }
1085}
1086impl From<Style> for ScrollCustomStyle {
1087 fn from(value: Style) -> Self {
1088 Self(value)
1089 }
1090}
1091impl CustomStyle for ScrollCustomStyle {
1092 type StyleClass = ScrollClass;
1093}
1094
1095impl CustomStylable<ScrollCustomStyle> for Scroll {
1096 type DV = Self;
1097}
1098
1099impl ScrollCustomStyle {
1100 pub fn new() -> Self {
1102 Self(Style::new())
1103 }
1104
1105 pub fn shrink_to_fit(mut self) -> Self {
1114 self = Self(
1115 self.0
1116 .min_size(0., 0.)
1117 .size_full()
1118 .flex_grow(1.)
1119 .flex_basis(0.),
1120 );
1121 self
1122 }
1123
1124 pub fn handle_background(mut self, color: impl Into<Brush>) -> Self {
1126 self = Self(self.0.class(Handle, |s| s.background(color.into())));
1127 self
1128 }
1129
1130 pub fn handle_border_radius(mut self, border_radius: impl Into<Length>) -> Self {
1132 self = Self(self.0.class(Handle, |s| s.border_radius(border_radius)));
1133 self
1134 }
1135
1136 pub fn handle_border_color(mut self, border_color: impl Into<Brush>) -> Self {
1138 self = Self(self.0.class(Handle, |s| s.border_color(border_color)));
1139 self
1140 }
1141
1142 pub fn handle_border(mut self, border: impl Into<Pt>) -> Self {
1144 self = Self(self.0.class(Handle, |s| s.set(Border, border)));
1145 self
1146 }
1147
1148 pub fn handle_rounded(mut self, rounded: impl Into<bool>) -> Self {
1150 self = Self(self.0.class(Handle, |s| s.set(Rounded, rounded)));
1151 self
1152 }
1153
1154 pub fn track_background(mut self, color: impl Into<Brush>) -> Self {
1156 self = Self(self.0.class(Track, |s| s.background(color.into())));
1157 self
1158 }
1159
1160 pub fn track_border_radius(mut self, border_radius: impl Into<Length>) -> Self {
1162 self = Self(self.0.class(Track, |s| s.border_radius(border_radius)));
1163 self
1164 }
1165
1166 pub fn track_border_color(mut self, border_color: impl Into<Brush>) -> Self {
1168 self = Self(self.0.class(Track, |s| s.border_color(border_color)));
1169 self
1170 }
1171
1172 pub fn track_border(mut self, border: impl Into<Pt>) -> Self {
1174 self = Self(self.0.class(Track, |s| s.set(Border, border)));
1175 self
1176 }
1177
1178 pub fn track_rounded(mut self, rounded: impl Into<bool>) -> Self {
1180 self = Self(self.0.class(Track, |s| s.set(Rounded, rounded)));
1181 self
1182 }
1183
1184 pub fn vertical_track_inset(mut self, inset: impl Into<Pt>) -> Self {
1186 self = Self(self.0.set(VerticalInset, inset));
1187 self
1188 }
1189
1190 pub fn horizontal_track_inset(mut self, inset: impl Into<Pt>) -> Self {
1192 self = Self(self.0.set(HorizontalInset, inset));
1193 self
1194 }
1195
1196 pub fn hide_bars(mut self, hide: impl Into<bool>) -> Self {
1198 self = Self(self.0.set(HideBars, hide));
1199 self
1200 }
1201
1202 pub fn propagate_pointer_wheel(mut self, propagate: impl Into<bool>) -> Self {
1204 self = Self(self.0.set(PropagatePointerWheel, propagate));
1205 self
1206 }
1207
1208 pub fn vertical_scroll_as_horizontal(mut self, vert_as_horiz: impl Into<bool>) -> Self {
1210 self = Self(self.0.set(VerticalScrollAsHorizontal, vert_as_horiz));
1211 self
1212 }
1213
1214 pub fn show_bars_when_idle(mut self, show: impl Into<bool>) -> Self {
1216 self = Self(self.0.set(ShowBarsWhenIdle, show));
1217 self
1218 }
1219}
1220
1221pub trait ScrollExt {
1223 fn scroll(self) -> Scroll;
1225}
1226
1227impl<T: IntoView + 'static> ScrollExt for T {
1228 fn scroll(self) -> Scroll {
1229 Scroll::new(self)
1230 }
1231}