1use super::{
2 RangeBase, ScrollBarVisibility, ScrollEventType, TemplateAppliedEventArgs, TemplatedControlImpl, Thumb, Track,
3};
4use crate::metadata::{PseudoClassesAttribute, TemplatePartAttribute};
5use crate::{Button, ControlImpl, ScrollViewer};
6use ferroui_base::animation::TimeSpan;
7use ferroui_base::data::BindingPriority;
8use ferroui_base::input::{
9 ContextRequestedEventArgs, InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs,
10 PointerEventArgs, PointerPointProperties, PointerPressedEventArgs, PointerType, PointerUpdateKind,
11 PointerWheelEventArgs,
12 RawInputModifiers, VectorEventArgs,
13};
14use ferroui_base::interactivity::{InteractiveImpl, RoutedEventHandlerToken, RoutingStrategies};
15use ferroui_base::layout::{LayoutableImpl, Orientation};
16use ferroui_base::reactive::{Disposable, IDisposable};
17use ferroui_base::threading::{DispatcherPriority, DispatcherTimer};
18use ferroui_base::utilities::HandlerList;
19use ferroui_base::{
20 ferro_class, ferro_impl_classes, ferro_property, instantiate, DirectProperty, FerroObject, FerroObjectExtensions,
21 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Point, Ref, Size, StaticType,
22 StyledElementImpl, StyledProperty, Vector, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
23 WeakRef,
24};
25use std::cell::{Cell, RefCell};
26use std::rc::Rc;
27use std::time::Duration;
28
29const PC_VERTICAL: &str = ":vertical";
30const PC_HORIZONTAL: &str = ":horizontal";
31
32#[derive(Clone, Copy, Debug, PartialEq)]
34pub struct ScrollEventArgs {
35 new_value: f64,
36 scroll_event_type: ScrollEventType,
37}
38
39impl ScrollEventArgs {
40 pub fn new(event_type: ScrollEventType, new_value: f64) -> Self {
41 Self { new_value, scroll_event_type: event_type }
42 }
43
44 pub fn new_value(&self) -> f64 {
46 self.new_value
47 }
48
49 pub fn scroll_event_type(&self) -> ScrollEventType {
51 self.scroll_event_type
52 }
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57enum DelayedAction {
58 Collapse,
59 Expand,
60}
61
62#[repr(C)]
64pub struct ScrollBar {
65 base: RangeBase,
66 line_up_button: RefCell<Option<(Ref<Button>, RoutedEventHandlerToken)>>,
67 line_down_button: RefCell<Option<(Ref<Button>, RoutedEventHandlerToken)>>,
68 page_up_button: RefCell<Option<(Ref<Button>, RoutedEventHandlerToken)>>,
69 page_down_button: RefCell<Option<(Ref<Button>, RoutedEventHandlerToken)>>,
70 timer: RefCell<Option<Rc<DispatcherTimer>>>,
71 timer_action: Cell<Option<DelayedAction>>,
72 is_expanded: Cell<bool>,
73 owner_subscriptions: RefCell<Option<Vec<Rc<dyn IDisposable>>>>,
74 owner: RefCell<Option<WeakRef<ScrollViewer>>>,
75 is_dragging: Cell<bool>,
76 last_right_click_position: Cell<Point>,
77 scroll: HandlerList<dyn Fn(&ScrollEventArgs)>,
78}
79
80ferro_class!(ScrollBar: RangeBase);
81ferroui_base::ferro_class_info!(ScrollBar {
85 new: ScrollBar::new,
86 markup: {
87 methods: [
88 fn ScrollHere() => ScrollBar::scroll_here,
89 fn ScrollToHome() => ScrollBar::scroll_to_home,
90 fn ScrollToEnd() => ScrollBar::scroll_to_end,
91 fn PageUp() => ScrollBar::page_up,
92 fn PageDown() => ScrollBar::page_down,
93 fn PageLeft() => ScrollBar::page_left,
94 fn PageRight() => ScrollBar::page_right,
95 fn LineUp() => ScrollBar::line_up,
96 fn LineDown() => ScrollBar::line_down,
97 fn LineLeft() => ScrollBar::line_left,
98 fn LineRight() => ScrollBar::line_right,
99 ],
100 events: [
101 Scroll(Option<ferroui_base::BoxedValue>, crate::primitives::ScrollEventArgs) => |this: &ferroui_base::Ref<ScrollBar>, handler: ferroui_base::metadata::MarkupDelegate| {
102 let sender = this.downgrade();
103 this.scroll(move |e: &crate::primitives::ScrollEventArgs| {
104 handler.invoke(&[ferroui_base::metadata::into_markup_value(sender.upgrade()), ferroui_base::metadata::into_markup_value(e.clone())]);
105 })
106 },
107 ],
108 attributes: [
109 TemplatePart("PART_LineDownButton", type(ferroui_base::Ref<crate::Button>)),
110 TemplatePart("PART_LineUpButton", type(ferroui_base::Ref<crate::Button>)),
111 TemplatePart("PART_PageDownButton", type(ferroui_base::Ref<crate::Button>)),
112 TemplatePart("PART_PageUpButton", type(ferroui_base::Ref<crate::Button>)),
113 PseudoClasses(":vertical", ":horizontal"),
114 ],
115 },
116});
117ferro_impl_classes!(ScrollBar: StyledElementImpl, LayoutableImpl, InteractiveImpl);
118
119impl ControlImpl for ScrollBar {
120 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
121 crate::automation::peers::ScrollBarAutomationPeer::new(this).upcast()
122 }
123}
124
125impl VisualImpl for ScrollBar {
126 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
127 Self::parent_on_attached_to_visual_tree(this, e);
128 this.attach_to_scroll_viewer();
129 }
130}
131
132impl FerroObjectImpl for ScrollBar {
133 fn constructed(this: &Self) {
134 Self::parent_constructed(this);
135 this.update_pseudo_classes(this.orientation());
136 this.add_handler(InputElement::context_requested_event(), |sender, e| {
137 if let Some(this) = sender.downcast_ref::<ScrollBar>() {
138 this.on_context_requested(e);
139 }
140 });
141 }
142
143 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
144 Self::parent_on_property_changed(this, change);
145
146 let property = change.property();
147
148 if property == Self::orientation_property().as_property() {
149 this.update_pseudo_classes(change.get_new_value::<Orientation>());
150 if this.is_attached_to_visual_tree() {
151 this.attach_to_scroll_viewer();
154 }
155 } else if property == Self::allow_auto_hide_property().as_property() {
156 this.update_is_expanded_state();
157 } else if property == RangeBase::value_property().as_property() {
158 let value = change.get_new_value::<f64>();
159 if let Some(owner) = this.owner() {
160 let offset = owner.offset();
161 owner.set_current_value(
162 ScrollViewer::offset_property(),
163 if this.orientation() == Orientation::Horizontal {
164 offset.with_x(value)
165 } else {
166 offset.with_y(value)
167 },
168 );
169 }
170 } else if property == RangeBase::minimum_property().as_property()
171 || property == RangeBase::maximum_property().as_property()
172 || property == Self::viewport_size_property().as_property()
173 || property == Self::visibility_property().as_property()
174 {
175 this.update_is_visible();
176 }
177 }
178}
179
180impl InputElementImpl for ScrollBar {
181 fn on_key_down(this: &Self, e: &KeyEventArgs) {
182 if this.orientation() == Orientation::Vertical {
183 if e.key == Key::PageUp {
184 this.large_decrement();
185 e.set_handled(true);
186 } else if e.key == Key::PageDown {
187 this.large_increment();
188 e.set_handled(true);
189 } else if e.key == Key::Up {
190 this.small_decrement();
191 e.set_handled(true);
192 } else if e.key == Key::Down {
193 this.small_increment();
194 e.set_handled(true);
195 }
196 } else if this.orientation() == Orientation::Horizontal {
197 if e.key == Key::Left {
198 this.small_decrement();
199 e.set_handled(true);
200 } else if e.key == Key::Right {
201 this.small_increment();
202 e.set_handled(true);
203 }
204 }
205 }
206
207 fn on_pointer_wheel_changed(this: &Self, e: &PointerWheelEventArgs) {
208 Self::parent_on_pointer_wheel_changed(this, e);
209
210 if e.handled() {
214 return;
215 }
216
217 let presenter = this.owner().and_then(|owner| owner.presenter());
218 if let (Some(presenter), Some(root)) = (presenter, this.visual_root()) {
219 e.set_handled(true);
220 let key_modifiers = e.key_modifiers();
221 let e = PointerWheelEventArgs::new(
222 this.to_ref(),
223 e.pointer().clone(),
224 &root,
225 e.get_position(Some(&root)),
226 e.timestamp(),
227 PointerPointProperties::new(
228 RawInputModifiers::from_bits_truncate(key_modifiers.bits()),
229 PointerUpdateKind::Other,
230 ),
231 key_modifiers,
232 e.delta(),
233 );
234 presenter.raise_event(&e);
235 }
236 }
237
238 fn on_pointer_entered(this: &Self, e: &PointerEventArgs) {
239 Self::parent_on_pointer_entered(this, e);
240
241 if this.allow_auto_hide() {
242 this.expand_after_delay();
243 }
244 }
245
246 fn on_pointer_exited(this: &Self, e: &PointerEventArgs) {
247 Self::parent_on_pointer_exited(this, e);
248
249 if this.allow_auto_hide() && !this.is_dragging.get() {
250 this.collapse_after_delay();
251 }
252 }
253
254 fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
255 Self::parent_on_pointer_pressed(this, e);
256
257 if e.get_current_point(Some(this)).properties.is_right_button_pressed {
258 this.last_right_click_position.set(e.get_position(Some(this)));
259 }
260 }
261}
262
263impl TemplatedControlImpl for ScrollBar {
264 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
265 for slot in [&this.line_up_button, &this.line_down_button, &this.page_up_button, &this.page_down_button] {
266 let previous = slot.borrow_mut().take();
267 if let Some((button, token)) = previous {
268 button.remove_handler(Button::click_event(), token);
269 }
270 }
271
272 let attach = |slot: &RefCell<Option<(Ref<Button>, RoutedEventHandlerToken)>>,
273 name: &str,
274 action: fn(&ScrollBar)| {
275 if let Some(button) = e.name_scope().find_as::<Button>(name) {
276 let weak = this.to_ref().downgrade();
277 let token = button.click(move |_, _| {
278 if let Some(this) = weak.upgrade() {
279 action(&this);
280 }
281 });
282 *slot.borrow_mut() = Some((button, token));
283 }
284 };
285
286 attach(&this.line_up_button, "PART_LineUpButton", Self::small_decrement);
287 attach(&this.line_down_button, "PART_LineDownButton", Self::small_increment);
288 attach(&this.page_up_button, "PART_PageUpButton", Self::large_decrement);
289 attach(&this.page_down_button, "PART_PageDownButton", Self::large_increment);
290 }
291}
292
293impl ScrollBar {
294 pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] = &[
296 TemplatePartAttribute::new("PART_LineDownButton", <Button as StaticType>::TYPE),
297 TemplatePartAttribute::new("PART_LineUpButton", <Button as StaticType>::TYPE),
298 TemplatePartAttribute::new("PART_PageDownButton", <Button as StaticType>::TYPE),
299 TemplatePartAttribute::new("PART_PageUpButton", <Button as StaticType>::TYPE),
300 ];
301
302 pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[PC_VERTICAL, PC_HORIZONTAL]);
304}
305
306ferroui_base::ferro_properties! { impl ScrollBar {
307 ferro_property!(
308 pub fn viewport_size_property() -> StyledProperty<f64> {
310 FerroProperty::register::<ScrollBar, _>("ViewportSize", f64::NAN)
311 }
312 );
313
314 ferro_property!(
315 pub fn visibility_property() -> StyledProperty<ScrollBarVisibility> {
317 FerroProperty::register::<ScrollBar, _>("Visibility", ScrollBarVisibility::Visible)
318 }
319 );
320
321 ferro_property!(
322 pub fn orientation_property() -> StyledProperty<Orientation> {
324 FerroProperty::register::<ScrollBar, _>("Orientation", Orientation::Vertical)
325 }
326 );
327
328 ferro_property!(
329 pub fn is_expanded_property() -> DirectProperty<ScrollBar, bool> {
331 FerroProperty::register_direct::<ScrollBar, _>("IsExpanded", |o| o.is_expanded(), None, false)
332 }
333 );
334
335 ferro_property!(
336 pub fn allow_auto_hide_property() -> StyledProperty<bool> {
338 FerroProperty::register::<ScrollBar, _>("AllowAutoHide", true)
339 }
340 );
341
342 ferro_property!(
343 pub fn hide_delay_property() -> StyledProperty<TimeSpan> {
345 FerroProperty::register::<ScrollBar, _>("HideDelay", TimeSpan::from_seconds(2.0))
346 }
347 );
348
349 ferro_property!(
350 pub fn show_delay_property() -> StyledProperty<TimeSpan> {
352 FerroProperty::register::<ScrollBar, _>("ShowDelay", TimeSpan::from_seconds(0.5))
353 }
354 );
355} }
356
357impl ScrollBar {
358 fn static_constructor() {
359 Thumb::drag_delta_event().add_class_handler_with::<ScrollBar>(
360 |x, e| x.on_thumb_drag_delta(e),
361 RoutingStrategies::BUBBLE,
362 false,
363 );
364 Thumb::drag_started_event().add_class_handler_with::<ScrollBar>(
365 |x, e| x.on_thumb_drag_start(e),
366 RoutingStrategies::BUBBLE,
367 false,
368 );
369 Thumb::drag_completed_event().add_class_handler_with::<ScrollBar>(
370 |x, e| x.on_thumb_drag_complete(e),
371 RoutingStrategies::BUBBLE,
372 false,
373 );
374
375 InputElement::focusable_property().override_default_value::<ScrollBar>(false);
376 }
377
378 pub fn construct() -> Self {
380 Self {
381 base: RangeBase::construct(),
382 line_up_button: RefCell::new(None),
383 line_down_button: RefCell::new(None),
384 page_up_button: RefCell::new(None),
385 page_down_button: RefCell::new(None),
386 timer: RefCell::new(None),
387 timer_action: Cell::new(None),
388 is_expanded: Cell::new(false),
389 owner_subscriptions: RefCell::new(None),
390 owner: RefCell::new(None),
391 is_dragging: Cell::new(false),
392 last_right_click_position: Cell::new(Point::default()),
393 scroll: HandlerList::new(),
394 }
395 }
396
397 pub fn new() -> Ref<Self> {
398 instantiate(Self::construct())
399 }
400
401 pub fn viewport_size(&self) -> f64 {
403 self.get_value(Self::viewport_size_property())
404 }
405
406 pub fn set_viewport_size(&self, value: f64) {
407 self.set_value(Self::viewport_size_property(), value)
408 }
409
410 pub fn visibility(&self) -> ScrollBarVisibility {
413 self.get_value(Self::visibility_property())
414 }
415
416 pub fn set_visibility(&self, value: ScrollBarVisibility) {
417 self.set_value(Self::visibility_property(), value)
418 }
419
420 pub fn orientation(&self) -> Orientation {
422 self.get_value(Self::orientation_property())
423 }
424
425 pub fn set_orientation(&self, value: Orientation) {
426 self.set_value(Self::orientation_property(), value)
427 }
428
429 pub fn is_expanded(&self) -> bool {
431 self.is_expanded.get()
432 }
433
434 fn set_is_expanded(&self, value: bool) {
435 self.set_and_raise_cell(Self::is_expanded_property(), &self.is_expanded, value);
436 }
437
438 pub fn allow_auto_hide(&self) -> bool {
441 self.get_value(Self::allow_auto_hide_property())
442 }
443
444 pub fn set_allow_auto_hide(&self, value: bool) {
445 self.set_value(Self::allow_auto_hide_property(), value)
446 }
447
448 pub fn hide_delay(&self) -> TimeSpan {
451 self.get_value(Self::hide_delay_property())
452 }
453
454 pub fn set_hide_delay(&self, value: TimeSpan) {
455 self.set_value(Self::hide_delay_property(), value)
456 }
457
458 pub fn show_delay(&self) -> TimeSpan {
461 self.get_value(Self::show_delay_property())
462 }
463
464 pub fn set_show_delay(&self, value: TimeSpan) {
465 self.set_value(Self::show_delay_property(), value)
466 }
467
468 pub fn scroll(&self, handler: impl Fn(&ScrollEventArgs) + 'static) -> Rc<dyn IDisposable> {
471 let token = self.scroll.add(Rc::new(handler));
472 let weak = self.to_ref().downgrade();
473 Disposable::create(move || {
474 if let Some(this) = weak.upgrade() {
475 this.scroll.remove(token);
476 }
477 })
478 }
479
480 fn update_is_visible(&self) {
482 let is_visible = match self.visibility() {
483 ScrollBarVisibility::Visible => true,
484 ScrollBarVisibility::Disabled => false,
485 ScrollBarVisibility::Hidden => false,
486 ScrollBarVisibility::Auto => self.viewport_size().is_nan() || self.maximum() > 0.0,
487 };
488
489 self.set_current_value(Visual::is_visible_property(), is_visible);
490 }
491
492 fn owner(&self) -> Option<Ref<ScrollViewer>> {
493 self.owner.borrow().as_ref().and_then(WeakRef::upgrade)
494 }
495
496 fn dispose_owner_subscriptions(&self) {
497 let subscriptions = self.owner_subscriptions.borrow_mut().take();
498 if let Some(subscriptions) = subscriptions {
499 for subscription in subscriptions {
500 subscription.dispose();
501 }
502 }
503 }
504
505 pub(crate) fn attach_to_scroll_viewer(&self) {
512 let owner = self.templated_parent().and_then(|parent| parent.cast::<ScrollViewer>());
513
514 let Some(owner) = owner else {
515 *self.owner.borrow_mut() = None;
516 self.dispose_owner_subscriptions();
517 return;
518 };
519
520 if self.owner().as_ref() == Some(&owner) {
521 return;
522 }
523
524 self.dispose_owner_subscriptions();
525
526 let is_horizontal = self.orientation() == Orientation::Horizontal;
527
528 let weak = self.to_ref().downgrade();
529 let is_horizontal_now = move || {
530 weak.upgrade().map_or(is_horizontal, |this| this.orientation() == Orientation::Horizontal)
531 };
532 let extract_vector = {
533 let is_horizontal_now = is_horizontal_now.clone();
534 move |v: Vector| if is_horizontal_now() { v.x } else { v.y }
535 };
536 let extract_size = move |v: Size| if is_horizontal_now() { v.width } else { v.height };
537
538 let visibility_source = if is_horizontal {
539 ScrollViewer::horizontal_scroll_bar_visibility_property()
540 } else {
541 ScrollViewer::vertical_scroll_bar_visibility_property()
542 };
543
544 let priority = BindingPriority::Template;
545 let source: &FerroObject = &owner;
546 let mut subscriptions: Vec<Rc<dyn IDisposable>> = Vec::new();
547
548 if !self.is_set(RangeBase::maximum_property().as_property()) {
549 subscriptions.push(self.bind_typed(
550 RangeBase::maximum_property(),
551 FerroObjectExtensions::get_observable_with(source, ScrollViewer::scroll_bar_maximum_property(), extract_vector.clone()),
552 priority,
553 ));
554 }
555 if !self.is_set(RangeBase::value_property().as_property()) {
556 subscriptions.push(self.bind_typed(
557 RangeBase::value_property(),
558 FerroObjectExtensions::get_observable_with(source, ScrollViewer::offset_property(), extract_vector),
559 priority,
560 ));
561 }
562 if !self.is_set(ScrollViewer::is_deferred_scrolling_enabled_property().as_property()) {
563 subscriptions.push(self.bind_typed(
564 ScrollViewer::is_deferred_scrolling_enabled_property(),
565 FerroObjectExtensions::get_observable(source, ScrollViewer::is_deferred_scrolling_enabled_property()),
566 priority,
567 ));
568 }
569 if !self.is_set(Self::viewport_size_property().as_property()) {
570 subscriptions.push(self.bind_typed(
571 Self::viewport_size_property(),
572 FerroObjectExtensions::get_observable_with(source, ScrollViewer::viewport_property(), extract_size.clone()),
573 priority,
574 ));
575 }
576 if !self.is_set(Self::visibility_property().as_property()) {
577 subscriptions.push(self.bind_typed(
578 Self::visibility_property(),
579 FerroObjectExtensions::get_observable(source, visibility_source),
580 priority,
581 ));
582 }
583 if !self.is_set(Self::allow_auto_hide_property().as_property()) {
584 subscriptions.push(self.bind_typed(
585 Self::allow_auto_hide_property(),
586 FerroObjectExtensions::get_observable(source, ScrollViewer::allow_auto_hide_property()),
587 priority,
588 ));
589 }
590 if !self.is_set(RangeBase::large_change_property().as_property()) {
591 subscriptions.push(self.bind_typed(
592 RangeBase::large_change_property(),
593 FerroObjectExtensions::get_observable_with(source, ScrollViewer::large_change_property(), extract_size.clone()),
594 priority,
595 ));
596 }
597 if !self.is_set(RangeBase::small_change_property().as_property()) {
598 subscriptions.push(self.bind_typed(
599 RangeBase::small_change_property(),
600 FerroObjectExtensions::get_observable_with(source, ScrollViewer::small_change_property(), extract_size),
601 priority,
602 ));
603 }
604
605 *self.owner.borrow_mut() = Some(owner.downgrade());
606 *self.owner_subscriptions.borrow_mut() = Some(subscriptions);
607 }
608
609 fn invoke_after_delay(&self, handler: DelayedAction, delay: TimeSpan) {
610 let existing = self.timer.borrow().clone();
611 let timer = match existing {
612 Some(timer) => {
613 timer.stop();
614 timer
615 }
616 None => {
617 let timer = DispatcherTimer::with_priority(DispatcherPriority::NORMAL);
618 let weak = self.to_ref().downgrade();
619 let _ = timer.tick(move |sender_timer| {
621 if let Some(this) = weak.upgrade() {
622 match this.timer_action.get() {
623 Some(DelayedAction::Collapse) => this.collapse(),
624 Some(DelayedAction::Expand) => this.expand(),
625 None => {}
626 }
627 }
628
629 sender_timer.stop();
630 });
631 *self.timer.borrow_mut() = Some(timer.clone());
632 timer
633 }
634 };
635
636 self.timer_action.set(Some(handler));
637 timer.set_interval(delay.to_duration().unwrap_or(Duration::ZERO));
638
639 timer.start();
640 }
641
642 fn update_is_expanded_state(&self) {
643 if !self.allow_auto_hide() {
644 let timer = self.timer.borrow().clone();
645 if let Some(timer) = timer {
646 timer.stop();
647 }
648
649 self.set_is_expanded(true);
650 } else {
651 self.set_is_expanded(self.is_pointer_over());
652 }
653 }
654
655 fn collapse_after_delay(&self) {
656 self.invoke_after_delay(DelayedAction::Collapse, self.hide_delay());
657 }
658
659 fn expand_after_delay(&self) {
660 self.invoke_after_delay(DelayedAction::Expand, self.show_delay());
661 }
662
663 fn collapse(&self) {
664 self.set_is_expanded(false);
665 }
666
667 fn expand(&self) {
668 self.set_is_expanded(true);
669 }
670
671 fn small_decrement(&self) {
672 self.set_current_value(
673 RangeBase::value_property(),
674 f64::max(self.value() - self.small_change(), self.minimum()),
675 );
676 self.on_scroll(ScrollEventType::SmallDecrement);
677 }
678
679 fn small_increment(&self) {
680 self.set_current_value(
681 RangeBase::value_property(),
682 f64::min(self.value() + self.small_change(), self.maximum()),
683 );
684 self.on_scroll(ScrollEventType::SmallIncrement);
685 }
686
687 fn large_decrement(&self) {
688 self.set_current_value(
689 RangeBase::value_property(),
690 f64::max(self.value() - self.large_change(), self.minimum()),
691 );
692 self.on_scroll(ScrollEventType::LargeDecrement);
693 }
694
695 fn large_increment(&self) {
696 self.set_current_value(
697 RangeBase::value_property(),
698 f64::min(self.value() + self.large_change(), self.maximum()),
699 );
700 self.on_scroll(ScrollEventType::LargeIncrement);
701 }
702
703 fn on_thumb_drag_delta(&self, _e: &VectorEventArgs) {
704 self.on_scroll(ScrollEventType::ThumbTrack);
705 }
706
707 fn on_thumb_drag_start(&self, _e: &VectorEventArgs) {
708 self.is_dragging.set(true);
709 }
710
711 fn on_thumb_drag_complete(&self, _e: &VectorEventArgs) {
712 self.is_dragging.set(false);
713
714 if self.allow_auto_hide() && !self.is_pointer_over() {
715 self.collapse_after_delay();
716 }
717
718 self.on_scroll(ScrollEventType::EndScroll);
719 }
720
721 pub fn on_scroll(&self, scroll_event_type: ScrollEventType) {
723 if self.scroll.is_empty() {
724 return;
725 }
726
727 let e = ScrollEventArgs::new(scroll_event_type, self.value());
728 for (_, handler) in self.scroll.snapshot().iter() {
729 handler(&e);
730 }
731 }
732
733 fn update_pseudo_classes(&self, o: Orientation) {
734 self.pseudo_classes().set(PC_VERTICAL, o == Orientation::Vertical);
735 self.pseudo_classes().set(PC_HORIZONTAL, o == Orientation::Horizontal);
736 }
737
738 fn on_context_requested(&self, e: &ContextRequestedEventArgs) {
739 if matches!(e.pointer_type(), Some(PointerType::Touch | PointerType::Pen)) {
740 e.set_handled(true);
741 return;
742 }
743
744 if let Some(position) = e.try_get_position(Some(self)) {
745 self.last_right_click_position.set(position);
746 }
747 }
748
749 pub fn scroll_here(&self) {
752 let Some(track) = self.track() else {
753 return;
754 };
755
756 let is_vertical = self.orientation() == Orientation::Vertical;
757 let last_right_click_position = self.last_right_click_position.get();
758 let thumb_bounds = track.thumb().map(|thumb| thumb.bounds());
759
760 let track_length = if is_vertical { track.bounds().height } else { track.bounds().width };
761 let thumb_length =
762 thumb_bounds.map_or(0.0, |bounds| if is_vertical { bounds.height } else { bounds.width });
763 let click_position = if is_vertical { last_right_click_position.y } else { last_right_click_position.x };
764
765 if track_length > thumb_length {
766 let ratio = click_position / track_length;
767 let range = self.maximum() - self.minimum();
768 let value = self.minimum() + (ratio * range);
769 self.set_current_value(
770 RangeBase::value_property(),
771 f64::max(self.minimum(), f64::min(self.maximum(), value)),
772 );
773 self.on_scroll(ScrollEventType::ThumbTrack);
774 }
775 }
776
777 pub fn scroll_to_home(&self) {
780 self.set_current_value(RangeBase::value_property(), self.minimum());
781 self.on_scroll(ScrollEventType::LargeDecrement);
782 }
783
784 pub fn scroll_to_end(&self) {
787 self.set_current_value(RangeBase::value_property(), self.maximum());
788 self.on_scroll(ScrollEventType::LargeIncrement);
789 }
790
791 pub fn page_up(&self) {
793 self.large_decrement()
794 }
795
796 pub fn page_down(&self) {
798 self.large_increment()
799 }
800
801 pub fn page_left(&self) {
803 self.large_decrement()
804 }
805
806 pub fn page_right(&self) {
808 self.large_increment()
809 }
810
811 pub fn line_up(&self) {
813 self.small_decrement()
814 }
815
816 pub fn line_down(&self) {
818 self.small_increment()
819 }
820
821 pub fn line_left(&self) {
823 self.small_decrement()
824 }
825
826 pub fn line_right(&self) {
828 self.small_increment()
829 }
830
831 fn track(&self) -> Option<Ref<Track>> {
832 self.get_template_descendants().into_iter().find_map(|visual| visual.cast::<Track>())
833 }
834}