1use crate::generators::{ItemContainerGenerator, RecycleKey};
2use crate::items_source::ItemsChangedEventArgs;
3use crate::primitives::{register_logical_scrollable, ILogicalScrollable};
4use crate::{
5 Carousel, Control, ControlImpl, ItemsControl, ItemsSourceView, PanelImpl, VirtualizingPanel,
6 VirtualizingPanelImpl, VirtualizingPanelImplExt,
7};
8use ferroui_base::animation::easings::{Easing, QuadraticEaseOut, SineEaseOut};
9use ferroui_base::animation::{
10 Animation, Cue, FillMode, IPageTransition, KeyFrame, PageTransitionItem, SlideAxis, TimeSpan,
11};
12use ferroui_base::collections::NotifyCollectionChangedAction;
13use ferroui_base::input::gesture_recognizers::SwipeGestureRecognizer;
14use ferroui_base::input::{
15 IScrollable, InputElement, InputElementImpl, NavigationDirection, SwipeGestureEndedEventArgs,
16 SwipeGestureEventArgs,
17};
18use ferroui_base::interactivity::{InteractiveImpl, RoutedEventHandlerToken};
19use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
20use ferroui_base::reactive::{Disposable, IDisposable};
21use ferroui_base::styling::Setter;
22use ferroui_base::threading::{
23 CancellationTokenSource, Dispatcher, DispatcherPriority, DispatcherTask, DispatcherTaskScheduler,
24 FerroSynchronizationContext,
25};
26use ferroui_base::utilities::{HandlerList, MathUtilities};
27use ferroui_base::{
28 ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
29 FerroProperty, FerroPropertyChangedEventArgs, Rect, Ref, Size, StyledElementImpl, StyledProperty, Vector,
30 Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs, WeakRef,
31};
32use std::cell::{Cell, RefCell};
33use std::collections::HashMap;
34use std::future::Future;
35use std::pin::Pin;
36use std::rc::Rc;
37use std::task::{Context, Poll};
38
39const SWIPE_COMMIT_THRESHOLD: f64 = 0.25;
40const VELOCITY_COMMIT_THRESHOLD: f64 = 800.0;
41const MIN_SWIPE_DISTANCE_FOR_VELOCITY_COMMIT: f64 = 0.05;
42const RUBBER_BAND_FACTOR: f64 = 0.3;
43const RUBBER_BAND_RETURN_DURATION: f64 = 0.16;
44const MAX_COMPLETION_DURATION: f64 = 0.35;
45const MIN_COMPLETION_DURATION: f64 = 0.12;
46
47#[derive(Clone)]
49struct ViewportRealizedItem {
50 logical_index: i32,
53 item_index: i32,
54 control: Ref<Control>,
55}
56
57type ViewportSlot = (i32, i32);
60
61#[repr(C)]
63pub struct VirtualizingCarouselPanel {
64 base: VirtualizingPanel,
65 extent: Cell<Size>,
66 offset: Cell<Vector>,
67 viewport: Cell<Size>,
68 recycle_pool: RefCell<HashMap<RecycleKey, Vec<Ref<Control>>>>,
69 viewport_realized: RefCell<Vec<ViewportRealizedItem>>,
72 realized: RefCell<Option<Ref<Control>>>,
73 realized_index: Cell<i32>,
74 transition_from: RefCell<Option<Ref<Control>>>,
75 transition_from_index: Cell<i32>,
76 transition: RefCell<Option<Rc<CancellationTokenSource>>>,
77 transition_task: RefCell<Option<DispatcherTask<()>>>,
78 scroll_invalidated: Rc<HandlerList<dyn Fn()>>,
79 can_horizontally_scroll: Cell<bool>,
80 can_vertically_scroll: Cell<bool>,
81
82 swipe_gesture_recognizer: RefCell<Option<Ref<SwipeGestureRecognizer>>>,
83 swipe_gesture_handler: Cell<Option<RoutedEventHandlerToken>>,
85 swipe_gesture_ended_handler: Cell<Option<RoutedEventHandlerToken>>,
86 swipe_gesture_id: Cell<i32>,
87 is_dragging: Cell<bool>,
88 total_delta: Cell<f64>,
89 is_forward: Cell<bool>,
90 swipe_target: RefCell<Option<Ref<Control>>>,
91 swipe_target_index: Cell<i32>,
92 swipe_axis: Cell<Option<SlideAxis>>,
93 locked_axis: Cell<SlideAxis>,
94
95 completion_cts: RefCell<Option<Rc<CancellationTokenSource>>>,
96 offset_animation_cts: RefCell<Option<Rc<CancellationTokenSource>>>,
97 completion_end_progress: Cell<f64>,
98 is_rubber_banding: Cell<bool>,
99 drag_start_offset: Cell<f64>,
100 progress_start_offset: Cell<f64>,
101 offset_animation_start: Cell<f64>,
102 offset_animation_target: Cell<f64>,
103 active_viewport_target_offset: Cell<f64>,
104 progress_from_index: Cell<i32>,
105 progress_to_index: Cell<i32>,
106}
107
108ferro_class!(VirtualizingCarouselPanel: VirtualizingPanel);
109ferroui_base::ferro_class_info!(VirtualizingCarouselPanel { new: VirtualizingCarouselPanel::new });
110ferro_impl_classes!(VirtualizingCarouselPanel: StyledElementImpl, InteractiveImpl, ControlImpl, PanelImpl);
111
112impl FerroObjectImpl for VirtualizingCarouselPanel {
113 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
114 Self::parent_on_property_changed(this, change);
115
116 if change.property() == Self::offset_animation_progress_property().as_property() {
117 if Self::is_active(&this.offset_animation_cts) {
118 let anim_progress = change.get_new_value::<f64>();
119 let primary_offset = this.offset_animation_start.get()
120 + ((this.offset_animation_target.get() - this.offset_animation_start.get()) * anim_progress);
121 this.set_offset_core(this.with_primary_offset(this.offset.get(), primary_offset));
122
123 if this.uses_viewport_fraction_layout() {
124 let transition = this.get_transition();
125 if let Some(progressive) = transition.as_ref().and_then(|t| t.as_progress_page_transition()) {
126 let transition_progress = this.get_fractional_transition_progress(primary_offset);
127 progressive.update(
128 transition_progress,
129 Self::as_visual(this.find_viewport_control(this.progress_from_index.get())).as_ref(),
130 Self::as_visual(this.find_viewport_control(this.progress_to_index.get())).as_ref(),
131 this.is_forward.get(),
132 this.get_viewport_item_extent(this.bounds().size()),
133 &this.build_fractional_visible_items(primary_offset),
134 );
135 }
136 }
137 }
138 } else if change.property() == Self::completion_progress_property().as_property() {
139 let is_completion_animating = Self::is_active(&this.completion_cts);
140
141 if !this.is_dragging.get() && this.swipe_target().is_none() && !is_completion_animating {
142 return;
143 }
144
145 let progress = change.get_new_value::<f64>();
146 let transition = this.get_transition();
147 if let Some(progressive) = transition.as_ref().and_then(|t| t.as_progress_page_transition()) {
148 let realized = this.realized();
149 let swipe_target = this.swipe_target().filter(|target| realized.as_ref() != Some(target));
150 let size = this.locked_axis_size();
151 progressive.update(
152 progress,
153 Self::as_visual(realized).as_ref(),
154 Self::as_visual(swipe_target).as_ref(),
155 this.is_forward.get(),
156 size,
157 &[],
158 );
159 }
160 }
161 }
162}
163
164impl VisualImpl for VirtualizingCarouselPanel {
165 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
166 Self::parent_on_attached_to_visual_tree(this, e);
167 this.refresh_gesture_recognizer();
168 }
169
170 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
171 Self::parent_on_detached_from_visual_tree(this, e);
172 this.teardown_gesture_recognizer();
173 }
174}
175
176impl LayoutableImpl for VirtualizingCarouselPanel {
177 fn measure_override(this: &Self, available_size: Size) -> Size {
178 if this.uses_viewport_fraction_layout() {
179 return this.measure_viewport_fraction_override(available_size);
180 }
181
182 this.clear_viewport_realized();
183 this.cancel_offset_animation();
184
185 this.measure_single_page_override(available_size)
186 }
187
188 fn arrange_override(this: &Self, final_size: Size) -> Size {
189 if this.uses_viewport_fraction_layout() {
190 return this.arrange_viewport_fraction_override(final_size);
191 }
192
193 let result = Self::parent_arrange_override(this, final_size);
195
196 if this.transition.borrow().is_none() {
197 let transition_from = this.transition_from();
198 let to = this.realized();
199 let transition = this.get_transition();
200
201 if let (Some(transition_from), Some(to), Some(transition)) = (transition_from, to, transition) {
202 let transition_cts = Rc::new(CancellationTokenSource::new());
203 *this.transition.borrow_mut() = Some(transition_cts.clone());
204
205 let forward = this.realized_index.get() > this.transition_from_index.get();
206
207 let task = this.run_transition_async(transition_cts, transition_from, to, forward, transition);
208 *this.transition_task.borrow_mut() = Some(task);
209 }
210 }
211
212 result
213 }
214}
215
216impl InputElementImpl for VirtualizingCarouselPanel {
217 fn as_scrollable(this: &Self) -> Option<&dyn IScrollable> {
218 Some(this)
219 }
220}
221
222impl VirtualizingPanelImpl for VirtualizingCarouselPanel {
223 fn on_items_control_changed(this: &Self, old_value: Option<&Ref<ItemsControl>>) {
224 Self::parent_on_items_control_changed(this, old_value);
225 this.refresh_gesture_recognizer();
229 }
230
231 fn get_control_in_direction(
232 _this: &Self,
233 _direction: NavigationDirection,
234 _from: Option<&Ref<InputElement>>,
235 _wrap: bool,
236 ) -> Option<Ref<InputElement>> {
237 None
238 }
239
240 fn container_from_index(this: &Self, index: i32) -> Option<Ref<Control>> {
241 let items = this.items();
242
243 if index < 0 || index as usize >= items.count() {
244 return None;
245 }
246 if let Some(viewport_realized) = this.find_viewport_control(index) {
247 return Some(viewport_realized);
248 }
249 if index == this.realized_index.get() {
250 return this.realized();
251 }
252 if let Some(c) = items.get_at(index as usize).as_ref().and_then(Control::from_boxed) {
253 if c.get_value(Self::recycle_key_property()) == Some(Self::item_is_its_own_container()) {
254 return Some(c);
255 }
256 }
257 None
258 }
259
260 fn get_realized_containers(this: &Self) -> Option<Vec<Ref<Control>>> {
261 {
262 let viewport_realized = this.viewport_realized.borrow();
263 if !viewport_realized.is_empty() {
264 return Some(viewport_realized.iter().map(|x| x.control.clone()).collect());
265 }
266 }
267
268 this.realized().map(|realized| vec![realized])
269 }
270
271 fn index_from_container(this: &Self, container: &Ref<Control>) -> i32 {
272 {
273 let viewport_realized = this.viewport_realized.borrow();
274 if let Some(entry) = viewport_realized.iter().find(|entry| entry.control == *container) {
275 return entry.item_index;
276 }
277 }
278
279 if this.realized.borrow().as_ref() == Some(container) {
280 this.realized_index.get()
281 } else {
282 -1
283 }
284 }
285
286 fn scroll_into_view(_this: &Self, _index: i32) -> Option<Ref<Control>> {
287 None
288 }
289
290 fn on_items_changed(this: &Self, items: &Rc<ItemsSourceView>, e: &ItemsChangedEventArgs<'_>) {
291 Self::parent_on_items_changed(this, items, e);
292
293 if this.uses_viewport_fraction_layout() || !this.viewport_realized.borrow().is_empty() {
294 this.clear_viewport_realized();
295 this.invalidate_measure();
296 return;
297 }
298
299 let add = |index: i32, count: i32| -> bool {
301 if this.realized.borrow().is_none() {
302 this.invalidate_measure();
303 return true;
304 }
305
306 if index <= this.realized_index.get() {
307 this.realized_index.set(this.realized_index.get() + count);
308 }
309 false
310 };
311
312 let remove = |index: i32, count: i32| {
313 let end = index + (count - 1);
314 let realized = this.realized();
315 let realized_index = this.realized_index.get();
316
317 match realized {
318 Some(realized) if index <= realized_index && end >= realized_index => {
319 this.recycle_element(&realized);
320 *this.realized.borrow_mut() = None;
321 this.realized_index.set(-1);
322 }
323 _ => {
324 if index < realized_index {
325 this.realized_index.set(realized_index - count);
326 }
327 }
328 }
329 };
330
331 let new_count = e.new_items.len() as i32;
332 let old_count = e.old_items.len() as i32;
333
334 let mut action = e.action;
335 if matches!(action, NotifyCollectionChangedAction::Replace | NotifyCollectionChangedAction::Move)
336 && e.old_starting_index < 0
337 {
338 action = NotifyCollectionChangedAction::Reset;
339 }
340
341 match action {
342 NotifyCollectionChangedAction::Add => {
343 if add(e.new_starting_index, new_count) {
344 return;
345 }
346 }
347 NotifyCollectionChangedAction::Remove => {
348 remove(e.old_starting_index, old_count);
349 }
350 NotifyCollectionChangedAction::Replace => {
351 remove(e.old_starting_index, old_count);
352 if add(e.new_starting_index, new_count) {
353 return;
354 }
355 }
356 NotifyCollectionChangedAction::Move => {
357 remove(e.old_starting_index, old_count);
358 let mut insert_index = e.new_starting_index;
359
360 if e.new_starting_index > e.old_starting_index {
361 insert_index -= old_count - 1;
362 }
363
364 if add(insert_index, new_count) {
365 return;
366 }
367 }
368 NotifyCollectionChangedAction::Reset => {
369 let realized = this.realized();
370 if let Some(realized) = realized {
371 this.recycle_element(&realized);
372 *this.realized.borrow_mut() = None;
373 this.realized_index.set(-1);
374 }
375 }
376 }
377
378 this.invalidate_measure();
379 }
380}
381
382impl IScrollable for VirtualizingCarouselPanel {
383 fn extent(&self) -> Size {
384 self.extent.get()
385 }
386
387 fn offset(&self) -> Vector {
388 self.offset.get()
389 }
390
391 fn set_offset(&self, value: Vector) {
392 self.set_offset_core(value)
393 }
394
395 fn viewport(&self) -> Size {
396 self.viewport.get()
397 }
398
399 fn can_horizontally_scroll(&self) -> bool {
400 self.can_horizontally_scroll.get()
401 }
402
403 fn can_vertically_scroll(&self) -> bool {
404 self.can_vertically_scroll.get()
405 }
406}
407
408impl ILogicalScrollable for VirtualizingCarouselPanel {
409 fn set_can_horizontally_scroll(&self, value: bool) {
410 self.can_horizontally_scroll.set(value)
411 }
412
413 fn set_can_vertically_scroll(&self, value: bool) {
414 self.can_vertically_scroll.set(value)
415 }
416
417 fn is_logical_scroll_enabled(&self) -> bool {
418 true
419 }
420
421 fn scroll_size(&self) -> Size {
422 Size::new(1.0, 1.0)
423 }
424
425 fn page_scroll_size(&self) -> Size {
426 Size::new(1.0, 1.0)
427 }
428
429 fn scroll_invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
430 let token = self.scroll_invalidated.add(handler);
431 let handlers = Rc::downgrade(&self.scroll_invalidated);
432 Disposable::create(move || {
433 if let Some(handlers) = handlers.upgrade() {
434 handlers.remove(token);
435 }
436 })
437 }
438
439 fn bring_into_view(&self, _target: &Ref<Control>, _target_rect: Rect) -> bool {
440 false
441 }
442
443 fn get_control_in_direction(
444 &self,
445 _direction: NavigationDirection,
446 _from: Option<&Ref<Control>>,
447 ) -> Option<Ref<Control>> {
448 None
449 }
450
451 fn raise_scroll_invalidated(&self) {
452 self.invoke_scroll_invalidated();
453 }
454}
455
456struct ClearSourceOnExit {
459 panel: WeakRef<VirtualizingCarouselPanel>,
460 field: fn(&VirtualizingCarouselPanel) -> &RefCell<Option<Rc<CancellationTokenSource>>>,
461 source: Rc<CancellationTokenSource>,
462}
463
464impl Drop for ClearSourceOnExit {
465 fn drop(&mut self) {
466 if let Some(panel) = self.panel.upgrade() {
467 let field = (self.field)(&panel);
468 let is_current = field.borrow().as_ref().is_some_and(|current| Rc::ptr_eq(current, &self.source));
469 if is_current {
470 *field.borrow_mut() = None;
471 }
472 }
473 }
474}
475
476struct TransitionEnd {
480 task: DispatcherTask<()>,
481}
482
483impl Future for TransitionEnd {
484 type Output = ();
485
486 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
487 if self.task.is_faulted() {
489 return Poll::Ready(());
490 }
491
492 match Pin::new(&mut self.task).poll(cx) {
493 Poll::Ready(_) => Poll::Ready(()),
494 Poll::Pending => Poll::Pending,
495 }
496 }
497}
498
499ferroui_base::ferro_properties! { impl VirtualizingCarouselPanel {
500 ferro_property!(
501 fn recycle_key_property() -> AttachedProperty<Option<RecycleKey>> {
502 FerroProperty::register_attached::<VirtualizingCarouselPanel, Control, _>("RecycleKey", None)
503 }
504 );
505
506 ferro_property!(
507 fn completion_progress_property() -> StyledProperty<f64> {
508 FerroProperty::register::<VirtualizingCarouselPanel, _>("CompletionProgress", 0.0)
509 }
510 );
511
512 ferro_property!(
513 fn offset_animation_progress_property() -> StyledProperty<f64> {
514 FerroProperty::register::<VirtualizingCarouselPanel, _>("OffsetAnimationProgress", 0.0)
515 }
516 );
517} }
518
519impl VirtualizingCarouselPanel {
520 fn item_is_its_own_container() -> RecycleKey {
522 ferroui_base::ferro_thread_local! {
523 static KEY: RecycleKey = RecycleKey::new_unique();
524 }
525 KEY.with(|key| *key)
526 }
527
528 fn static_constructor() {
529 register_logical_scrollable::<VirtualizingCarouselPanel>();
530 }
531
532 pub fn construct() -> Self {
534 Self {
535 base: VirtualizingPanel::construct(),
536 extent: Cell::new(Size::default()),
537 offset: Cell::new(Vector::default()),
538 viewport: Cell::new(Size::default()),
539 recycle_pool: RefCell::new(HashMap::new()),
540 viewport_realized: RefCell::new(Vec::new()),
541 realized: RefCell::new(None),
542 realized_index: Cell::new(-1),
543 transition_from: RefCell::new(None),
544 transition_from_index: Cell::new(-1),
545 transition: RefCell::new(None),
546 transition_task: RefCell::new(None),
547 scroll_invalidated: Rc::new(HandlerList::new()),
548 can_horizontally_scroll: Cell::new(false),
549 can_vertically_scroll: Cell::new(false),
550 swipe_gesture_recognizer: RefCell::new(None),
551 swipe_gesture_handler: Cell::new(None),
552 swipe_gesture_ended_handler: Cell::new(None),
553 swipe_gesture_id: Cell::new(0),
554 is_dragging: Cell::new(false),
555 total_delta: Cell::new(0.0),
556 is_forward: Cell::new(false),
557 swipe_target: RefCell::new(None),
558 swipe_target_index: Cell::new(-1),
559 swipe_axis: Cell::new(None),
560 locked_axis: Cell::new(SlideAxis::Horizontal),
561 completion_cts: RefCell::new(None),
562 offset_animation_cts: RefCell::new(None),
563 completion_end_progress: Cell::new(0.0),
564 is_rubber_banding: Cell::new(false),
565 drag_start_offset: Cell::new(0.0),
566 progress_start_offset: Cell::new(0.0),
567 offset_animation_start: Cell::new(0.0),
568 offset_animation_target: Cell::new(0.0),
569 active_viewport_target_offset: Cell::new(0.0),
570 progress_from_index: Cell::new(-1),
571 progress_to_index: Cell::new(-1),
572 }
573 }
574
575 pub fn new() -> Ref<Self> {
576 instantiate(Self::construct())
577 }
578
579 pub(crate) fn is_managing_interaction_offset(&self) -> bool {
580 self.uses_viewport_fraction_layout() && (self.is_dragging.get() || Self::is_active(&self.offset_animation_cts))
581 }
582
583 #[inline]
586 fn is_active(source: &RefCell<Option<Rc<CancellationTokenSource>>>) -> bool {
587 source.borrow().as_ref().is_some_and(|source| !source.is_cancellation_requested())
588 }
589
590 #[inline]
592 fn carousel(&self) -> Option<Ref<Carousel>> {
593 self.items_control().and_then(|items_control| items_control.cast::<Carousel>())
594 }
595
596 #[inline]
597 fn realized(&self) -> Option<Ref<Control>> {
598 self.realized.borrow().clone()
599 }
600
601 #[inline]
602 fn transition_from(&self) -> Option<Ref<Control>> {
603 self.transition_from.borrow().clone()
604 }
605
606 #[inline]
607 fn swipe_target(&self) -> Option<Ref<Control>> {
608 self.swipe_target.borrow().clone()
609 }
610
611 #[inline]
612 fn as_visual(control: Option<Ref<Control>>) -> Option<Ref<Visual>> {
613 control.map(Ref::upcast)
614 }
615
616 #[inline]
619 fn generator(&self) -> Rc<ItemContainerGenerator> {
620 match self.item_container_generator() {
621 Some(generator) => generator,
622 None => panic!("The VirtualizingPanel does not belong to an ItemsControl."),
623 }
624 }
625
626 #[inline]
628 fn item_count(&self) -> i32 {
629 self.items().count() as i32
630 }
631
632 fn set_extent(&self, value: Size) {
633 if self.extent.get() != value {
634 self.extent.set(value);
635 self.invoke_scroll_invalidated();
636 }
637 }
638
639 fn set_viewport(&self, value: Size) {
640 if self.viewport.get() != value {
641 self.viewport.set(value);
642 self.invoke_scroll_invalidated();
643 }
644 }
645
646 fn invoke_scroll_invalidated(&self) {
647 if self.scroll_invalidated.is_empty() {
648 return;
649 }
650 for (_, handler) in self.scroll_invalidated.snapshot().iter() {
651 handler();
652 }
653 }
654
655 fn uses_viewport_fraction_layout(&self) -> bool {
656 self.carousel().is_some_and(|carousel| !MathUtilities::are_close(carousel.viewport_fraction(), 1.0))
657 }
658
659 fn get_layout_axis(&self) -> SlideAxis {
660 self.carousel().map_or(SlideAxis::Horizontal, |carousel| carousel.get_layout_axis())
661 }
662
663 fn get_viewport_fraction(&self) -> f64 {
664 self.carousel().map_or(1.0, |carousel| carousel.viewport_fraction())
665 }
666
667 fn get_viewport_units(&self) -> f64 {
668 1.0 / self.get_viewport_fraction()
669 }
670
671 fn get_primary_offset(&self, offset: Vector) -> f64 {
672 if self.get_layout_axis() == SlideAxis::Vertical {
673 offset.y
674 } else {
675 offset.x
676 }
677 }
678
679 fn get_primary_size(&self, size: Size) -> f64 {
680 if self.get_layout_axis() == SlideAxis::Vertical {
681 size.height
682 } else {
683 size.width
684 }
685 }
686
687 fn get_cross_size(&self, size: Size) -> f64 {
688 if self.get_layout_axis() == SlideAxis::Vertical {
689 size.width
690 } else {
691 size.height
692 }
693 }
694
695 fn create_logical_size(&self, primary: f64) -> Size {
696 if self.get_layout_axis() == SlideAxis::Vertical {
697 Size::new(1.0, primary)
698 } else {
699 Size::new(primary, 1.0)
700 }
701 }
702
703 fn create_item_size(&self, primary: f64, cross: f64) -> Size {
704 if self.get_layout_axis() == SlideAxis::Vertical {
705 Size::new(cross, primary)
706 } else {
707 Size::new(primary, cross)
708 }
709 }
710
711 fn create_item_rect(&self, primary_offset: f64, primary_size: f64, cross_size: f64) -> Rect {
712 if self.get_layout_axis() == SlideAxis::Vertical {
713 Rect::new(0.0, primary_offset, cross_size, primary_size)
714 } else {
715 Rect::new(primary_offset, 0.0, primary_size, cross_size)
716 }
717 }
718
719 fn with_primary_offset(&self, offset: Vector, primary: f64) -> Vector {
720 if self.get_layout_axis() == SlideAxis::Vertical {
721 Vector::new(offset.x, primary)
722 } else {
723 Vector::new(primary, offset.y)
724 }
725 }
726
727 fn locked_axis_size(&self) -> f64 {
729 if self.locked_axis.get() == SlideAxis::Horizontal {
730 self.bounds().width
731 } else {
732 self.bounds().height
733 }
734 }
735
736 fn resolve_layout_size(&self, available_size: Size) -> Size {
737 let owner = self.items_control();
738
739 fn resolve_dimension(available: f64, bounds: f64, owner_bounds: f64, owner_explicit: f64) -> f64 {
740 if !available.is_infinite() && available > 0.0 {
741 return available;
742 }
743
744 if bounds > 0.0 {
745 return bounds;
746 }
747
748 if owner_bounds > 0.0 {
749 return owner_bounds;
750 }
751
752 if owner_explicit.is_nan() {
753 0.0
754 } else {
755 owner_explicit
756 }
757 }
758
759 let bounds = self.bounds();
760 let owner_bounds = owner.as_ref().map(|owner| owner.bounds());
761 let width = resolve_dimension(
762 available_size.width,
763 bounds.width,
764 owner_bounds.map_or(0.0, |bounds| bounds.width),
765 owner.as_ref().map_or(f64::NAN, |owner| owner.width()),
766 );
767 let height = resolve_dimension(
768 available_size.height,
769 bounds.height,
770 owner_bounds.map_or(0.0, |bounds| bounds.height),
771 owner.as_ref().map_or(f64::NAN, |owner| owner.height()),
772 );
773 Size::new(width, height)
774 }
775
776 fn get_viewport_item_extent(&self, size: Size) -> f64 {
777 let viewport_units = self.get_viewport_units();
778 if viewport_units <= 0.0 {
779 0.0
780 } else {
781 self.get_primary_size(size) / viewport_units
782 }
783 }
784
785 fn uses_viewport_wrap_layout(&self) -> bool {
786 self.uses_viewport_fraction_layout()
787 && self.carousel().is_some_and(|carousel| carousel.wrap_selection())
788 && self.item_count() > 1
789 }
790
791 fn normalize_index(index: i32, count: i32) -> i32 {
792 ((index % count) + count) % count
793 }
794
795 fn get_nearest_logical_offset(&self, item_index: i32, reference_offset: f64) -> f64 {
796 let count = self.item_count();
797
798 if !self.uses_viewport_wrap_layout() || count == 0 {
799 return item_index.clamp(0, (count - 1).max(0)) as f64;
800 }
801
802 let wrap_span = count as f64;
803 let wrap_multiplier = ((reference_offset - item_index as f64) / wrap_span).round_ties_even();
804 item_index as f64 + (wrap_multiplier * wrap_span)
805 }
806
807 fn is_preferred_viewport_slot(
808 &self,
809 candidate_logical_index: i32,
810 existing_logical_index: i32,
811 primary_offset: f64,
812 ) -> bool {
813 let candidate_distance = (candidate_logical_index as f64 - primary_offset).abs();
814 let existing_distance = (existing_logical_index as f64 - primary_offset).abs();
815
816 if !MathUtilities::are_close(candidate_distance, existing_distance) {
817 return candidate_distance < existing_distance;
818 }
819
820 let count = self.item_count();
821 let candidate_in_range = candidate_logical_index >= 0 && candidate_logical_index < count;
822 let existing_in_range = existing_logical_index >= 0 && existing_logical_index < count;
823
824 if candidate_in_range != existing_in_range {
825 return candidate_in_range;
826 }
827
828 if self.is_dragging.get() {
829 return if self.is_forward.get() {
830 candidate_logical_index > existing_logical_index
831 } else {
832 candidate_logical_index < existing_logical_index
833 };
834 }
835
836 candidate_logical_index < existing_logical_index
837 }
838
839 fn get_required_viewport_slots(&self, primary_offset: f64) -> Vec<ViewportSlot> {
841 let count = self.item_count();
842
843 if count == 0 {
844 return Vec::new();
845 }
846
847 let viewport_units = self.get_viewport_units();
848 let edge_inset = (viewport_units - 1.0) / 2.0;
849 let mut start = (primary_offset - edge_inset).floor() as i32;
850 let mut end = (primary_offset + viewport_units - edge_inset).ceil() as i32 - 1;
851
852 if !self.uses_viewport_wrap_layout() {
853 start = start.max(0);
854 end = end.min(count - 1);
855
856 if start > end {
857 return Vec::new();
858 }
859
860 return (start..=end).map(|index| (index, index)).collect();
861 }
862
863 let mut best_slots: Vec<ViewportSlot> = Vec::new();
865
866 for logical_index in start..=end {
867 let item_index = Self::normalize_index(logical_index, count);
868
869 match best_slots.iter().position(|slot| slot.1 == item_index) {
870 None => best_slots.push((logical_index, item_index)),
871 Some(existing) => {
872 if self.is_preferred_viewport_slot(logical_index, best_slots[existing].0, primary_offset) {
873 best_slots[existing].0 = logical_index;
874 }
875 }
876 }
877 }
878
879 best_slots.sort_by_key(|slot| slot.0);
880 best_slots
881 }
882
883 fn viewport_slots_changed(&self, old_primary_offset: f64, new_primary_offset: f64) -> bool {
884 let old_slots = self.get_required_viewport_slots(old_primary_offset);
885 let new_slots = self.get_required_viewport_slots(new_primary_offset);
886
887 old_slots != new_slots
888 }
889
890 fn set_offset_core(&self, value: Vector) {
892 if self.uses_viewport_fraction_layout() {
893 let old_primary_offset = self.get_primary_offset(self.offset.get());
894 let new_primary_offset = self.get_primary_offset(value);
895
896 if MathUtilities::are_close(old_primary_offset, new_primary_offset) {
897 self.offset.set(value);
898 return;
899 }
900
901 self.offset.set(value);
902
903 let range_changed = self.viewport_slots_changed(old_primary_offset, new_primary_offset);
904
905 if range_changed {
906 self.invalidate_measure();
907 } else {
908 self.invalidate_arrange();
909 }
910
911 self.invoke_scroll_invalidated();
912 return;
913 }
914
915 if (self.offset.get().x as i32) as f64 != value.x {
916 self.invalidate_measure();
917 }
918
919 self.offset.set(value);
920 }
921
922 #[inline]
925 fn viewport_realized_at(&self, position: usize) -> Option<ViewportRealizedItem> {
926 self.viewport_realized.borrow().get(position).cloned()
927 }
928
929 fn clear_viewport_realized(&self) {
930 if self.viewport_realized.borrow().is_empty() {
931 return;
932 }
933
934 let mut position = 0;
935 while let Some(entry) = self.viewport_realized_at(position) {
936 self.recycle_element(&entry.control);
937 position += 1;
938 }
939
940 self.viewport_realized.borrow_mut().clear();
941 }
942
943 fn reset_single_page_state(&self) {
944 let transition = self.transition.borrow().clone();
945 if let Some(transition) = transition {
946 transition.cancel();
947 }
948 *self.transition.borrow_mut() = None;
949 *self.transition_task.borrow_mut() = None;
950
951 if let Some(transition_from) = self.transition_from() {
952 self.recycle_element(&transition_from);
953 }
954
955 if let Some(swipe_target) = self.swipe_target() {
956 self.recycle_element(&swipe_target);
957 }
958
959 if let Some(realized) = self.realized() {
960 self.recycle_element(&realized);
961 }
962
963 *self.transition_from.borrow_mut() = None;
964 self.transition_from_index.set(-1);
965 *self.swipe_target.borrow_mut() = None;
966 self.swipe_target_index.set(-1);
967 *self.realized.borrow_mut() = None;
968 self.realized_index.set(-1);
969 }
970
971 fn cancel_offset_animation(&self) {
972 let offset_animation_cts = self.offset_animation_cts.borrow().clone();
973 if let Some(offset_animation_cts) = offset_animation_cts {
974 offset_animation_cts.cancel();
975 }
976 *self.offset_animation_cts.borrow_mut() = None;
977 }
978
979 fn measure_single_page_override(&self, available_size: Size) -> Size {
980 let items = self.items();
981 let item_count = items.count() as i32;
982 let index = match self.carousel() {
983 Some(carousel) => carousel.selected_index(),
984 None => self.offset.get().x as i32,
985 };
986
987 self.complete_finished_transition_if_needed();
988
989 if index != self.realized_index.get() {
990 if let Some(realized) = self.realized() {
991 let transition = self.transition.borrow().clone();
994 if let Some(transition) = transition {
995 transition.cancel();
996 *self.transition.borrow_mut() = None;
997 *self.transition_task.borrow_mut() = None;
998 if let Some(transition_from) = self.transition_from() {
999 self.recycle_element(&transition_from);
1000 }
1001 *self.transition_from.borrow_mut() = None;
1002 self.transition_from_index.set(-1);
1003 self.reset_transition_state(Some(&realized));
1004 }
1005
1006 if self.get_transition().is_none() {
1007 self.recycle_element(&realized);
1008 } else {
1009 *self.transition_from.borrow_mut() = Some(realized);
1013 self.transition_from_index.set(self.realized_index.get());
1014 }
1015
1016 *self.realized.borrow_mut() = None;
1017 self.realized_index.set(-1);
1018 }
1019
1020 if index >= 0 && index < item_count {
1021 let element = self.get_or_create_element(&items, index);
1022 *self.realized.borrow_mut() = Some(element);
1023 self.realized_index.set(index);
1024 }
1025 }
1026
1027 let Some(realized) = self.realized() else {
1028 self.set_viewport(Size::new(0.0, 0.0));
1029 self.set_extent(Size::new(0.0, 0.0));
1030 *self.transition_from.borrow_mut() = None;
1031 self.transition_from_index.set(-1);
1032 return Size::default();
1033 };
1034
1035 realized.measure(available_size);
1036 self.set_extent(Size::new(item_count as f64, 1.0));
1037 self.set_viewport(Size::new(1.0, 1.0));
1038
1039 realized.desired_size()
1040 }
1041
1042 fn measure_viewport_fraction_override(&self, available_size: Size) -> Size {
1043 self.reset_single_page_state();
1044
1045 let items = self.items();
1046 let item_count = items.count() as i32;
1047
1048 if item_count == 0 {
1049 self.clear_viewport_realized();
1050 self.set_viewport(Size::new(0.0, 0.0));
1051 self.set_extent(Size::new(0.0, 0.0));
1052 return Size::default();
1053 }
1054
1055 let layout_size = self.resolve_layout_size(available_size);
1056 let primary_size = self.get_primary_size(layout_size);
1057 let cross_size = self.get_cross_size(layout_size);
1058 let viewport_units = self.get_viewport_units();
1059
1060 if primary_size <= 0.0 || viewport_units <= 0.0 {
1061 self.clear_viewport_realized();
1062 self.set_viewport(Size::new(0.0, 0.0));
1063 self.set_extent(Size::new(0.0, 0.0));
1064 return Size::default();
1065 }
1066
1067 let item_primary_size = primary_size / viewport_units;
1068 let item_size = self.create_item_size(item_primary_size, cross_size);
1069 let required_slots = self.get_required_viewport_slots(self.get_primary_offset(self.offset.get()));
1070
1071 let mut position = 0;
1073 while let Some(entry) = self.viewport_realized_at(position) {
1074 let required =
1075 required_slots.iter().any(|slot| slot.0 == entry.logical_index && slot.1 == entry.item_index);
1076
1077 if required {
1078 position += 1;
1079 } else {
1080 self.recycle_element(&entry.control);
1081
1082 let mut viewport_realized = self.viewport_realized.borrow_mut();
1083 match viewport_realized.iter().position(|x| x.logical_index == entry.logical_index) {
1084 Some(removed) => {
1085 viewport_realized.remove(removed);
1086 if removed > position {
1087 position += 1;
1088 }
1089 }
1090 None => position += 1,
1091 }
1092 }
1093 }
1094
1095 for &(logical_index, item_index) in &required_slots {
1096 let is_realized = self.viewport_realized.borrow().iter().any(|x| x.logical_index == logical_index);
1097
1098 if !is_realized {
1099 let control = self.get_or_create_element(&items, item_index);
1100 let mut viewport_realized = self.viewport_realized.borrow_mut();
1101
1102 match viewport_realized.binary_search_by_key(&logical_index, |x| x.logical_index) {
1103 Ok(existing) => viewport_realized[existing] = ViewportRealizedItem { logical_index, item_index, control },
1104 Err(insert_at) => {
1105 viewport_realized.insert(insert_at, ViewportRealizedItem { logical_index, item_index, control })
1106 }
1107 }
1108 }
1109 }
1110
1111 let mut max_cross_desired_size: f64 = 0.0;
1112
1113 let mut position = 0;
1114 while let Some(entry) = self.viewport_realized_at(position) {
1115 entry.control.measure(item_size);
1116 max_cross_desired_size = max_cross_desired_size.max(self.get_cross_size(entry.control.desired_size()));
1117 position += 1;
1118 }
1119
1120 self.set_viewport(self.create_logical_size(viewport_units));
1121 self.set_extent(self.create_logical_size((item_count as f64 + viewport_units - 1.0).max(0.0)));
1122
1123 let desired_primary = if primary_size.is_infinite() { item_primary_size * viewport_units } else { primary_size };
1124 let desired_cross = if cross_size.is_infinite() { max_cross_desired_size } else { cross_size };
1125 self.create_item_size(desired_primary, desired_cross)
1126 }
1127
1128 fn arrange_viewport_fraction_override(&self, final_size: Size) -> Size {
1129 let primary_size = self.get_primary_size(final_size);
1130 let cross_size = self.get_cross_size(final_size);
1131 let viewport_units = self.get_viewport_units();
1132
1133 if primary_size <= 0.0 || viewport_units <= 0.0 {
1134 return final_size;
1135 }
1136
1137 if self.viewport_realized.borrow().is_empty() && self.item_count() > 0 {
1138 self.invalidate_measure();
1139 return final_size;
1140 }
1141
1142 let item_primary_size = primary_size / viewport_units;
1143 let edge_inset = (viewport_units - 1.0) / 2.0;
1144 let primary_offset = self.get_primary_offset(self.offset.get());
1145
1146 let mut position = 0;
1147 while let Some(entry) = self.viewport_realized_at(position) {
1148 let item_offset = (edge_inset + entry.logical_index as f64 - primary_offset) * item_primary_size;
1149 let rect = self.create_item_rect(item_offset, item_primary_size, cross_size);
1150 entry.control.set_is_visible(true);
1151 entry.control.arrange(rect);
1152 position += 1;
1153 }
1154
1155 final_size
1156 }
1157
1158 fn get_or_create_element(&self, items: &ItemsSourceView, index: i32) -> Ref<Control> {
1159 if let Some(element) = self.get_realized_element(index) {
1160 return element;
1161 }
1162
1163 let item = items.get_at(index as usize);
1164 let generator = self.generator();
1165 let (needs_container, recycle_key) = generator.needs_container(&item, index);
1166
1167 if needs_container {
1168 match self.get_recycled_element(&generator, &item, index, recycle_key) {
1169 Some(recycled) => recycled,
1170 None => self.create_element(&generator, &item, index, recycle_key),
1171 }
1172 } else {
1173 self.get_item_as_own_container(&generator, &item, index)
1174 }
1175 }
1176
1177 fn get_realized_element(&self, index: i32) -> Option<Ref<Control>> {
1178 if let Some(viewport_realized) = self.find_viewport_control(index) {
1179 return Some(viewport_realized);
1180 }
1181
1182 if self.realized_index.get() == index {
1183 self.realized()
1184 } else {
1185 None
1186 }
1187 }
1188
1189 fn get_item_as_own_container(
1190 &self,
1191 generator: &ItemContainerGenerator,
1192 item: &Option<BoxedValue>,
1193 index: i32,
1194 ) -> Ref<Control> {
1195 let control_item = match item.as_ref().and_then(Control::from_boxed) {
1196 Some(control_item) => control_item,
1197 None => panic!("An item that is its own container must be a control."),
1198 };
1199
1200 if !control_item.is_set(Self::recycle_key_property().as_property()) {
1201 generator.prepare_item_container(&control_item, item, index);
1202 self.add_internal_child(&control_item);
1203 control_item.set_value(Self::recycle_key_property(), Some(Self::item_is_its_own_container()));
1204 generator.item_container_prepared(&control_item, item, index);
1205 }
1206
1207 control_item.set_is_visible(true);
1208 control_item
1209 }
1210
1211 fn get_recycled_element(
1212 &self,
1213 generator: &ItemContainerGenerator,
1214 item: &Option<BoxedValue>,
1215 index: i32,
1216 recycle_key: Option<RecycleKey>,
1217 ) -> Option<Ref<Control>> {
1218 let recycle_key = recycle_key?;
1219
1220 let recycled = self.recycle_pool.borrow_mut().get_mut(&recycle_key).and_then(Vec::pop)?;
1221
1222 recycled.set_is_visible(true);
1223 generator.prepare_item_container(&recycled, item, index);
1224 generator.item_container_prepared(&recycled, item, index);
1225 Some(recycled)
1226 }
1227
1228 fn create_element(
1229 &self,
1230 generator: &ItemContainerGenerator,
1231 item: &Option<BoxedValue>,
1232 index: i32,
1233 recycle_key: Option<RecycleKey>,
1234 ) -> Ref<Control> {
1235 let container = generator.create_container(item, index, recycle_key);
1236
1237 container.set_value(Self::recycle_key_property(), recycle_key);
1238 generator.prepare_item_container(&container, item, index);
1239 self.add_internal_child(&container);
1240 generator.item_container_prepared(&container, item, index);
1241
1242 container
1243 }
1244
1245 fn recycle_element(&self, element: &Ref<Control>) {
1246 let recycle_key = element.get_value(Self::recycle_key_property());
1247 debug_assert!(recycle_key.is_some());
1248
1249 element.set_is_visible(false);
1252 self.reset_transition_state(Some(element));
1253
1254 if recycle_key == Some(Self::item_is_its_own_container()) {
1255 return;
1256 }
1257
1258 self.generator().clear_item_container(element);
1259
1260 let Some(recycle_key) = recycle_key else {
1261 panic!("The recycle key of a container of the carousel panel cannot be null.");
1262 };
1263
1264 self.recycle_pool.borrow_mut().entry(recycle_key).or_default().push(element.clone());
1265 }
1266
1267 fn get_transition(&self) -> Option<Rc<dyn IPageTransition>> {
1268 self.carousel().and_then(|carousel| carousel.page_transition())
1269 }
1270
1271 fn complete_finished_transition_if_needed(&self) {
1272 let is_task_completed = self.transition_task.borrow().as_ref().is_some_and(|task| task.is_completed());
1273
1274 if self.transition.borrow().is_some() && is_task_completed {
1275 if let Some(transition_from) = self.transition_from() {
1276 self.recycle_element(&transition_from);
1277 }
1278
1279 *self.transition.borrow_mut() = None;
1280 *self.transition_task.borrow_mut() = None;
1281 *self.transition_from.borrow_mut() = None;
1282 self.transition_from_index.set(-1);
1283 }
1284 }
1285
1286 fn task_scheduler() -> DispatcherTaskScheduler {
1289 let context = FerroSynchronizationContext::current().unwrap_or_else(|| {
1290 FerroSynchronizationContext::with_dispatcher(&Dispatcher::current_dispatcher(), DispatcherPriority::NORMAL)
1291 });
1292 context.to_task_scheduler()
1293 }
1294
1295 fn run_transition_async(
1296 &self,
1297 transition_cts: Rc<CancellationTokenSource>,
1298 transition_from: Ref<Control>,
1299 transition_to: Ref<Control>,
1300 forward: bool,
1301 transition: Rc<dyn IPageTransition>,
1302 ) -> DispatcherTask<()> {
1303 let this = self.to_ref().downgrade();
1304
1305 Self::task_scheduler().start_local(async move {
1306 let from: Ref<Visual> = transition_from.upcast();
1307 let to: Ref<Visual> = transition_to.upcast();
1308 let task = transition.start(Some(&from), Some(&to), forward, transition_cts.token());
1309
1310 TransitionEnd { task }.await;
1313
1314 let Some(this) = this.upgrade() else {
1315 return;
1316 };
1317
1318 let is_current =
1319 this.transition.borrow().as_ref().is_some_and(|current| Rc::ptr_eq(current, &transition_cts));
1320 if transition_cts.is_cancellation_requested() || !is_current {
1321 return;
1322 }
1323
1324 if let Some(transition_from) = this.transition_from() {
1325 this.recycle_element(&transition_from);
1326 }
1327 *this.transition.borrow_mut() = None;
1328 *this.transition_task.borrow_mut() = None;
1329 *this.transition_from.borrow_mut() = None;
1330 this.transition_from_index.set(-1);
1331 })
1332 }
1333
1334 pub(crate) fn sync_selection_offset(&self, selected_index: i32) {
1335 if !self.uses_viewport_fraction_layout() {
1336 self.set_offset_core(self.with_primary_offset(self.offset.get(), selected_index as f64));
1337 return;
1338 }
1339
1340 let current_offset = self.get_primary_offset(self.offset.get());
1341 let target_offset = self.get_nearest_logical_offset(selected_index, current_offset);
1342
1343 if MathUtilities::are_close(current_offset, target_offset) {
1344 self.set_offset_core(self.with_primary_offset(self.offset.get(), target_offset));
1345 return;
1346 }
1347
1348 if self.is_dragging.get() {
1349 return;
1350 }
1351
1352 let transition = self.get_transition();
1353 let can_animate = transition.is_some() && (target_offset - current_offset).abs() <= 1.001;
1354
1355 if !can_animate {
1356 self.reset_viewport_transition_state();
1357 self.clear_fractional_progress_context();
1358 self.set_offset_core(self.with_primary_offset(self.offset.get(), target_offset));
1359 return;
1360 }
1361
1362 let item_count = self.item_count();
1363 let from_index = if item_count > 0 {
1364 Self::normalize_index(current_offset.round_ties_even() as i32, item_count)
1365 } else {
1366 -1
1367 };
1368 let forward = target_offset > current_offset;
1369
1370 self.reset_viewport_transition_state();
1371 self.set_fractional_progress_context(from_index, selected_index, forward, current_offset, target_offset);
1372 self.animate_viewport_offset_async(
1373 current_offset,
1374 target_offset,
1375 TimeSpan::from_seconds(MAX_COMPLETION_DURATION),
1376 QuadraticEaseOut::new().into(),
1377 |this| {
1378 this.reset_viewport_transition_state();
1379 this.clear_fractional_progress_context();
1380 if let Some(carousel) = this.carousel() {
1385 this.sync_selection_offset(carousel.selected_index());
1386 }
1387 },
1388 );
1389 }
1390
1391 pub(crate) fn refresh_gesture_recognizer(&self) {
1394 self.teardown_gesture_recognizer();
1395
1396 let Some(carousel) = self.carousel() else {
1397 return;
1398 };
1399 if !carousel.is_swipe_enabled() {
1400 return;
1401 }
1402
1403 let swipe_axis = if self.uses_viewport_fraction_layout() {
1404 Some(carousel.get_layout_axis())
1405 } else {
1406 carousel.get_transition_axis()
1407 };
1408 self.swipe_axis.set(swipe_axis);
1409
1410 let swipe_gesture_recognizer = SwipeGestureRecognizer::new();
1411 swipe_gesture_recognizer.set_can_horizontally_swipe(swipe_axis != Some(SlideAxis::Vertical));
1412 swipe_gesture_recognizer.set_can_vertically_swipe(swipe_axis != Some(SlideAxis::Horizontal));
1413 *self.swipe_gesture_recognizer.borrow_mut() = Some(swipe_gesture_recognizer.clone());
1414
1415 self.gesture_recognizers().add(swipe_gesture_recognizer);
1416
1417 let this = self.to_ref().downgrade();
1418 let handler = self.add_handler(InputElement::swipe_gesture_event(), move |_, e| {
1419 if let Some(this) = this.upgrade() {
1420 this.on_swipe_gesture(e);
1421 }
1422 });
1423 self.swipe_gesture_handler.set(Some(handler));
1424
1425 let this = self.to_ref().downgrade();
1426 let handler = self.add_handler(InputElement::swipe_gesture_ended_event(), move |_, e| {
1427 if let Some(this) = this.upgrade() {
1428 this.on_swipe_gesture_ended(e);
1429 }
1430 });
1431 self.swipe_gesture_ended_handler.set(Some(handler));
1432 }
1433
1434 fn teardown_gesture_recognizer(&self) {
1435 let completion_cts = self.completion_cts.borrow().clone();
1436 if let Some(completion_cts) = completion_cts {
1437 completion_cts.cancel();
1438 }
1439 *self.completion_cts.borrow_mut() = None;
1440 self.cancel_offset_animation();
1441
1442 let swipe_gesture_recognizer = self.swipe_gesture_recognizer.take();
1443 if let Some(swipe_gesture_recognizer) = swipe_gesture_recognizer {
1444 self.gesture_recognizers().remove(&swipe_gesture_recognizer);
1445 }
1446
1447 if let Some(handler) = self.swipe_gesture_handler.take() {
1448 self.remove_handler(InputElement::swipe_gesture_event(), handler);
1449 }
1450 if let Some(handler) = self.swipe_gesture_ended_handler.take() {
1451 self.remove_handler(InputElement::swipe_gesture_ended_event(), handler);
1452 }
1453 self.reset_swipe_state();
1454 }
1455
1456 fn find_viewport_control(&self, item_index: i32) -> Option<Ref<Control>> {
1457 let viewport_realized = self.viewport_realized.borrow();
1458 viewport_realized.iter().find(|x| x.item_index == item_index).map(|x| x.control.clone())
1459 }
1460
1461 fn set_fractional_progress_context(
1462 &self,
1463 from_index: i32,
1464 to_index: i32,
1465 forward: bool,
1466 start_offset: f64,
1467 target_offset: f64,
1468 ) {
1469 self.progress_from_index.set(from_index);
1470 self.progress_to_index.set(to_index);
1471 self.is_forward.set(forward);
1472 self.progress_start_offset.set(start_offset);
1473 self.active_viewport_target_offset.set(target_offset);
1474 }
1475
1476 fn clear_fractional_progress_context(&self) {
1477 self.progress_from_index.set(-1);
1478 self.progress_to_index.set(-1);
1479 self.progress_start_offset.set(0.0);
1480 self.active_viewport_target_offset.set(0.0);
1481 }
1482
1483 fn get_fractional_transition_progress(&self, current_offset: f64) -> f64 {
1484 let total_distance = (self.active_viewport_target_offset.get() - self.progress_start_offset.get()).abs();
1485 if total_distance <= 0.0 {
1486 return 0.0;
1487 }
1488
1489 ((current_offset - self.progress_start_offset.get()).abs() / total_distance).clamp(0.0, 1.0)
1490 }
1491
1492 fn reset_viewport_transition_state(&self) {
1493 let mut position = 0;
1494 while let Some(entry) = self.viewport_realized_at(position) {
1495 self.reset_transition_state(Some(&entry.control));
1496 position += 1;
1497 }
1498 }
1499
1500 fn on_swipe_gesture(&self, e: &SwipeGestureEventArgs) {
1501 let Some(carousel) = self.carousel() else {
1502 return;
1503 };
1504 if !carousel.is_swipe_enabled() {
1505 return;
1506 }
1507
1508 if self.uses_viewport_fraction_layout() {
1509 self.on_viewport_fraction_swipe_gesture(&carousel, e);
1510 return;
1511 }
1512
1513 let item_count = self.item_count();
1514
1515 if self.realized_index.get() < 0 || item_count == 0 {
1516 return;
1517 }
1518
1519 if Self::is_active(&self.completion_cts) {
1520 let completion_cts = self.completion_cts.take();
1521 if let Some(completion_cts) = completion_cts {
1522 completion_cts.cancel();
1523 }
1524
1525 let was_commit = self.completion_end_progress.get() > 0.5;
1526 let swipe_target = self.swipe_target();
1527 match swipe_target {
1528 Some(swipe_target) if was_commit => {
1529 if let Some(realized) = self.realized() {
1530 self.recycle_element(&realized);
1531 }
1532
1533 *self.realized.borrow_mut() = Some(swipe_target);
1534 self.realized_index.set(self.swipe_target_index.get());
1535 carousel.set_selected_index(self.swipe_target_index.get());
1536 }
1537 _ => self.reset_swipe_state(),
1538 }
1539
1540 *self.swipe_target.borrow_mut() = None;
1541 self.swipe_target_index.set(-1);
1542 self.total_delta.set(0.0);
1543 }
1544
1545 if self.is_dragging.get() && e.id() != self.swipe_gesture_id.get() {
1546 return;
1547 }
1548
1549 if !self.is_dragging.get() {
1550 self.locked_axis.set(self.swipe_axis.get().unwrap_or(if e.delta().x.abs() >= e.delta().y.abs() {
1552 SlideAxis::Horizontal
1553 } else {
1554 SlideAxis::Vertical
1555 }));
1556 }
1557
1558 let delta = if self.locked_axis.get() == SlideAxis::Horizontal { e.delta().x } else { e.delta().y };
1559
1560 if !self.is_dragging.get() {
1561 self.is_forward.set(delta > 0.0);
1562 self.is_rubber_banding.set(false);
1563 let current_index = self.realized_index.get();
1564 let mut target_index = if self.is_forward.get() { current_index + 1 } else { current_index - 1 };
1565
1566 if target_index >= item_count {
1567 if carousel.wrap_selection() {
1568 target_index = 0;
1569 } else {
1570 self.is_rubber_banding.set(true);
1571 }
1572 } else if target_index < 0 {
1573 if carousel.wrap_selection() {
1574 target_index = item_count - 1;
1575 } else {
1576 self.is_rubber_banding.set(true);
1577 }
1578 }
1579
1580 if !self.is_rubber_banding.get()
1581 && (target_index == current_index || target_index < 0 || target_index >= item_count)
1582 {
1583 return;
1584 }
1585
1586 self.is_dragging.set(true);
1587 self.swipe_gesture_id.set(e.id());
1588 self.total_delta.set(0.0);
1589 self.swipe_target_index.set(if self.is_rubber_banding.get() { -1 } else { target_index });
1590 carousel.set_is_swiping(true);
1591
1592 let transition = self.transition.borrow().clone();
1593 if let Some(transition) = transition {
1594 transition.cancel();
1595 *self.transition.borrow_mut() = None;
1596 if let Some(transition_from) = self.transition_from() {
1597 self.recycle_element(&transition_from);
1598 }
1599 *self.transition_from.borrow_mut() = None;
1600 self.transition_from_index.set(-1);
1601 }
1602
1603 if !self.is_rubber_banding.get() {
1604 let swipe_target = self.get_or_create_element(&self.items(), self.swipe_target_index.get());
1605 *self.swipe_target.borrow_mut() = Some(swipe_target.clone());
1606 let size = self.bounds().size();
1607 swipe_target.measure(size);
1608 swipe_target.arrange(Rect::from_size(size));
1609 swipe_target.set_is_visible(true);
1610 }
1611 }
1612
1613 self.accumulate_delta(delta);
1614
1615 let size = self.locked_axis_size();
1616 if size <= 0.0 {
1617 return;
1618 }
1619
1620 let raw_progress = (self.total_delta.get().abs() / size).clamp(0.0, 1.0);
1621 let progress =
1622 if self.is_rubber_banding.get() { RUBBER_BAND_FACTOR * raw_progress.sqrt() } else { raw_progress };
1623
1624 let transition = self.get_transition();
1625 if let Some(progressive) = transition.as_ref().and_then(|t| t.as_progress_page_transition()) {
1626 let swipe_target = if self.is_rubber_banding.get() { None } else { self.swipe_target() };
1627 progressive.update(
1628 progress,
1629 Self::as_visual(self.realized()).as_ref(),
1630 Self::as_visual(swipe_target).as_ref(),
1631 self.is_forward.get(),
1632 size,
1633 &[],
1634 );
1635 }
1636
1637 e.set_handled(true);
1638 }
1639
1640 fn accumulate_delta(&self, delta: f64) {
1643 let total_delta = self.total_delta.get() + delta;
1644
1645 self.total_delta.set(if self.is_forward.get() { total_delta.max(0.0) } else { total_delta.min(0.0) });
1646 }
1647
1648 fn on_viewport_fraction_swipe_gesture(&self, carousel: &Ref<Carousel>, e: &SwipeGestureEventArgs) {
1649 if Self::is_active(&self.offset_animation_cts) {
1650 self.cancel_offset_animation();
1651 self.set_offset_core(self.with_primary_offset(
1652 self.offset.get(),
1653 self.get_nearest_logical_offset(carousel.selected_index(), self.get_primary_offset(self.offset.get())),
1654 ));
1655 }
1656
1657 if self.is_dragging.get() && e.id() != self.swipe_gesture_id.get() {
1658 return;
1659 }
1660
1661 let delta = if self.locked_axis.get() == SlideAxis::Horizontal { e.delta().x } else { e.delta().y };
1662 let item_count = self.item_count();
1663
1664 if !self.is_dragging.get() {
1665 self.locked_axis.set(carousel.get_layout_axis());
1666 self.swipe_gesture_id.set(e.id());
1667 self.drag_start_offset.set(
1668 self.get_nearest_logical_offset(carousel.selected_index(), self.get_primary_offset(self.offset.get())),
1669 );
1670 self.total_delta.set(0.0);
1671 self.is_dragging.set(true);
1672 self.is_rubber_banding.set(false);
1673 carousel.set_is_swiping(true);
1674 self.is_forward.set(delta > 0.0);
1675 let mut target_index =
1676 if self.is_forward.get() { carousel.selected_index() + 1 } else { carousel.selected_index() - 1 };
1677
1678 if target_index >= item_count || target_index < 0 {
1679 if carousel.wrap_selection() && item_count > 1 {
1680 target_index = Self::normalize_index(target_index, item_count);
1681 } else {
1682 self.is_rubber_banding.set(true);
1683 }
1684 }
1685
1686 let drag_start_offset = self.drag_start_offset.get();
1687 let target_offset = if self.is_forward.get() { drag_start_offset + 1.0 } else { drag_start_offset - 1.0 };
1688 self.set_fractional_progress_context(
1689 carousel.selected_index(),
1690 if self.is_rubber_banding.get() { -1 } else { target_index },
1691 self.is_forward.get(),
1692 drag_start_offset,
1693 target_offset,
1694 );
1695 self.reset_viewport_transition_state();
1696 }
1697
1698 self.accumulate_delta(delta);
1699
1700 let item_extent = self.get_viewport_item_extent(self.bounds().size());
1701 if item_extent <= 0.0 {
1702 return;
1703 }
1704
1705 let drag_start_offset = self.drag_start_offset.get();
1706 let logical_delta = (self.total_delta.get().abs() / item_extent).clamp(0.0, 1.0);
1707 let mut proposed_offset = drag_start_offset + if self.is_forward.get() { logical_delta } else { -logical_delta };
1708
1709 if !self.is_rubber_banding.get() {
1710 let active_viewport_target_offset = self.active_viewport_target_offset.get();
1711 proposed_offset = proposed_offset.clamp(
1712 drag_start_offset.min(active_viewport_target_offset),
1713 drag_start_offset.max(active_viewport_target_offset),
1714 );
1715 } else if proposed_offset < 0.0 {
1716 proposed_offset = -(RUBBER_BAND_FACTOR * (-proposed_offset).sqrt());
1717 } else {
1718 let max_offset = (item_count - 1).max(0) as f64;
1719 proposed_offset = max_offset + (RUBBER_BAND_FACTOR * (proposed_offset - max_offset).sqrt());
1720 }
1721
1722 self.set_offset_core(self.with_primary_offset(self.offset.get(), proposed_offset));
1723
1724 let transition = self.get_transition();
1725 if let Some(progressive) = transition.as_ref().and_then(|t| t.as_progress_page_transition()) {
1726 let current_offset = self.get_primary_offset(self.offset.get());
1727 let progress = (current_offset - drag_start_offset).abs().clamp(0.0, 1.0);
1728 progressive.update(
1729 progress,
1730 Self::as_visual(self.find_viewport_control(self.progress_from_index.get())).as_ref(),
1731 Self::as_visual(self.find_viewport_control(self.progress_to_index.get())).as_ref(),
1732 self.is_forward.get(),
1733 self.get_viewport_item_extent(self.bounds().size()),
1734 &self.build_fractional_visible_items(current_offset),
1735 );
1736 }
1737
1738 e.set_handled(true);
1739 }
1740
1741 fn on_viewport_fraction_swipe_gesture_ended(&self, carousel: Ref<Carousel>, e: &SwipeGestureEndedEventArgs) {
1742 let item_extent = self.get_viewport_item_extent(self.bounds().size());
1743 let current_offset = self.get_primary_offset(self.offset.get());
1744 let current_progress = (current_offset - self.drag_start_offset.get()).abs();
1745 let velocity =
1746 if self.locked_axis.get() == SlideAxis::Horizontal { e.velocity().x.abs() } else { e.velocity().y.abs() };
1747 let target_index = self.progress_to_index.get();
1748 let is_rubber_banding = self.is_rubber_banding.get();
1749 let can_commit = !is_rubber_banding && target_index >= 0;
1750 let commit = can_commit
1751 && (current_progress >= SWIPE_COMMIT_THRESHOLD
1752 || (velocity > VELOCITY_COMMIT_THRESHOLD && current_progress >= MIN_SWIPE_DISTANCE_FOR_VELOCITY_COMMIT));
1753 let end_offset = if commit {
1754 self.active_viewport_target_offset.get()
1755 } else {
1756 self.get_nearest_logical_offset(carousel.selected_index(), current_offset)
1757 };
1758 let remaining_distance = (end_offset - current_offset).abs();
1759 let duration_seconds = if is_rubber_banding {
1760 RUBBER_BAND_RETURN_DURATION
1761 } else if velocity > 0.0 && item_extent > 0.0 {
1762 (remaining_distance * item_extent / velocity).clamp(MIN_COMPLETION_DURATION, MAX_COMPLETION_DURATION)
1763 } else {
1764 MAX_COMPLETION_DURATION
1765 };
1766 let easing: Easing = if is_rubber_banding { SineEaseOut::new().into() } else { QuadraticEaseOut::new().into() };
1767
1768 self.is_dragging.set(false);
1769 self.animate_viewport_offset_async(
1770 current_offset,
1771 end_offset,
1772 TimeSpan::from_seconds(duration_seconds),
1773 easing,
1774 move |this| {
1775 this.total_delta.set(0.0);
1776 this.is_rubber_banding.set(false);
1777 carousel.set_is_swiping(false);
1778
1779 if commit {
1780 this.set_offset_core(this.with_primary_offset(
1781 this.offset.get(),
1782 this.get_nearest_logical_offset(target_index, end_offset),
1783 ));
1784 carousel.set_selected_index(target_index);
1785 } else {
1786 this.set_offset_core(this.with_primary_offset(
1787 this.offset.get(),
1788 this.get_nearest_logical_offset(carousel.selected_index(), end_offset),
1789 ));
1790 }
1791
1792 this.reset_viewport_transition_state();
1793 this.clear_fractional_progress_context();
1794 },
1795 );
1796 }
1797
1798 fn create_progress_animation(
1800 property: &'static StyledProperty<f64>,
1801 from: f64,
1802 to: f64,
1803 duration: TimeSpan,
1804 easing: Easing,
1805 ) -> Ref<Animation> {
1806 let animation = Animation::new();
1807 animation.set_fill_mode(FillMode::Forward);
1808 animation.set_duration(duration);
1809 animation.set_easing(easing);
1810 animation.children().add(KeyFrame::with_cue(Cue::new(0.0), [Setter::new(property, from) as _]));
1811 animation.children().add(KeyFrame::with_cue(Cue::new(1.0), [Setter::new(property, to) as _]));
1812 animation
1813 }
1814
1815 fn animate_viewport_offset_async(
1816 &self,
1817 from_offset: f64,
1818 to_offset: f64,
1819 duration: TimeSpan,
1820 easing: Easing,
1821 on_completed: impl FnOnce(&Self) + 'static,
1822 ) {
1823 self.cancel_offset_animation();
1824 let offset_animation_cts = Rc::new(CancellationTokenSource::new());
1825 *self.offset_animation_cts.borrow_mut() = Some(offset_animation_cts.clone());
1826 let cancellation_token = offset_animation_cts.token();
1827
1828 let animation =
1829 Self::create_progress_animation(Self::offset_animation_progress_property(), 0.0, 1.0, duration, easing);
1830
1831 self.offset_animation_start.set(from_offset);
1832 self.offset_animation_target.set(to_offset);
1833 self.set_value(Self::offset_animation_progress_property(), 0.0);
1834
1835 let this = self.to_ref().downgrade();
1836 let _finally = ClearSourceOnExit {
1837 panel: this.clone(),
1838 field: |panel| &panel.offset_animation_cts,
1839 source: offset_animation_cts,
1840 };
1841 let run = animation.run_async(self, cancellation_token.clone());
1842
1843 let _ = Self::task_scheduler().start_local(async move {
1844 let _finally = _finally;
1845
1846 if run.await.is_err() {
1847 return;
1848 }
1849
1850 if cancellation_token.is_cancellation_requested() {
1851 return;
1852 }
1853
1854 let Some(this) = this.upgrade() else {
1855 return;
1856 };
1857
1858 this.set_offset_core(this.with_primary_offset(this.offset.get(), to_offset));
1859
1860 if this.uses_viewport_fraction_layout() {
1861 let transition = this.get_transition();
1862 if let Some(progressive) = transition.as_ref().and_then(|t| t.as_progress_page_transition()) {
1863 let transition_progress = this.get_fractional_transition_progress(to_offset);
1864 progressive.update(
1865 transition_progress,
1866 Self::as_visual(this.find_viewport_control(this.progress_from_index.get())).as_ref(),
1867 Self::as_visual(this.find_viewport_control(this.progress_to_index.get())).as_ref(),
1868 this.is_forward.get(),
1869 this.get_viewport_item_extent(this.bounds().size()),
1870 &this.build_fractional_visible_items(to_offset),
1871 );
1872 }
1873 }
1874
1875 on_completed(&this);
1876 });
1877 }
1878
1879 fn on_swipe_gesture_ended(&self, e: &SwipeGestureEndedEventArgs) {
1880 if !self.is_dragging.get() || e.id() != self.swipe_gesture_id.get() {
1881 return;
1882 }
1883 let Some(carousel) = self.carousel() else {
1884 return;
1885 };
1886
1887 if self.uses_viewport_fraction_layout() {
1888 self.on_viewport_fraction_swipe_gesture_ended(carousel, e);
1889 return;
1890 }
1891
1892 let is_rubber_banding = self.is_rubber_banding.get();
1893 let size = self.locked_axis_size();
1894 let raw_progress = if size > 0.0 { self.total_delta.get().abs() / size } else { 0.0 };
1895 let current_progress = if is_rubber_banding { RUBBER_BAND_FACTOR * raw_progress.sqrt() } else { raw_progress };
1896 let velocity =
1897 if self.locked_axis.get() == SlideAxis::Horizontal { e.velocity().x.abs() } else { e.velocity().y.abs() };
1898 let commit = !is_rubber_banding
1899 && (current_progress >= SWIPE_COMMIT_THRESHOLD
1900 || (velocity > VELOCITY_COMMIT_THRESHOLD && current_progress >= MIN_SWIPE_DISTANCE_FOR_VELOCITY_COMMIT))
1901 && self.swipe_target.borrow().is_some();
1902
1903 self.completion_end_progress.set(if commit { 1.0 } else { 0.0 });
1904 let completion_end_progress = self.completion_end_progress.get();
1905 let remaining_distance = (completion_end_progress - current_progress).abs();
1906 let duration_seconds = if is_rubber_banding {
1907 RUBBER_BAND_RETURN_DURATION
1908 } else if velocity > 0.0 {
1909 (remaining_distance * size / velocity).clamp(MIN_COMPLETION_DURATION, MAX_COMPLETION_DURATION)
1910 } else {
1911 MAX_COMPLETION_DURATION
1912 };
1913 let easing: Easing = if is_rubber_banding { SineEaseOut::new().into() } else { QuadraticEaseOut::new().into() };
1914
1915 let previous_completion_cts = self.completion_cts.borrow().clone();
1916 if let Some(previous_completion_cts) = previous_completion_cts {
1917 previous_completion_cts.cancel();
1918 }
1919 let completion_cts = Rc::new(CancellationTokenSource::new());
1920 *self.completion_cts.borrow_mut() = Some(completion_cts.clone());
1921
1922 self.set_value(Self::completion_progress_property(), current_progress);
1923
1924 let animation = Self::create_progress_animation(
1925 Self::completion_progress_property(),
1926 current_progress,
1927 completion_end_progress,
1928 TimeSpan::from_seconds(duration_seconds),
1929 easing,
1930 );
1931
1932 self.is_dragging.set(false);
1933 self.run_completion_animation(animation, carousel, completion_cts);
1934 }
1935
1936 fn run_completion_animation(
1937 &self,
1938 animation: Ref<Animation>,
1939 carousel: Ref<Carousel>,
1940 completion_cts: Rc<CancellationTokenSource>,
1941 ) {
1942 let cancellation_token = completion_cts.token();
1943
1944 let this = self.to_ref().downgrade();
1945 let _finally =
1946 ClearSourceOnExit { panel: this.clone(), field: |panel| &panel.completion_cts, source: completion_cts };
1947 let run = animation.run_async(self, cancellation_token.clone());
1948
1949 let _ = Self::task_scheduler().start_local(async move {
1950 let _finally = _finally;
1951
1952 if run.await.is_err() {
1953 return;
1954 }
1955
1956 if cancellation_token.is_cancellation_requested() {
1957 return;
1958 }
1959
1960 let Some(this) = this.upgrade() else {
1961 return;
1962 };
1963
1964 let transition = this.get_transition();
1965 if let Some(progressive) = transition.as_ref().and_then(|t| t.as_progress_page_transition()) {
1966 let realized = this.realized();
1967 let swipe_target = this.swipe_target().filter(|target| realized.as_ref() != Some(target));
1968 let size = this.locked_axis_size();
1969 progressive.update(
1970 this.completion_end_progress.get(),
1971 Self::as_visual(realized).as_ref(),
1972 Self::as_visual(swipe_target).as_ref(),
1973 this.is_forward.get(),
1974 size,
1975 &[],
1976 );
1977 }
1978
1979 let commit = this.completion_end_progress.get() > 0.5;
1980
1981 match this.swipe_target() {
1982 Some(target_element) if commit => {
1983 let target_index = this.swipe_target_index.get();
1984
1985 *this.swipe_target.borrow_mut() = None;
1989 this.swipe_target_index.set(-1);
1990
1991 if let Some(realized) = this.realized() {
1992 this.recycle_element(&realized);
1993 }
1994
1995 *this.realized.borrow_mut() = Some(target_element);
1996 this.realized_index.set(target_index);
1997
1998 carousel.set_selected_index(target_index);
1999 }
2000 _ => this.reset_swipe_state(),
2001 }
2002
2003 this.total_delta.set(0.0);
2004 *this.swipe_target.borrow_mut() = None;
2005 this.swipe_target_index.set(-1);
2006 this.is_rubber_banding.set(false);
2007 carousel.set_is_swiping(false);
2008 });
2009 }
2010
2011 fn build_fractional_visible_items(&self, current_offset: f64) -> Vec<PageTransitionItem> {
2012 let viewport_realized = self.viewport_realized.borrow();
2013 viewport_realized
2014 .iter()
2015 .map(|entry| {
2016 PageTransitionItem::new(
2017 entry.item_index,
2018 entry.control.clone().upcast(),
2019 entry.logical_index as f64 - current_offset,
2020 )
2021 })
2022 .collect()
2023 }
2024
2025 fn reset_swipe_state(&self) {
2026 if let Some(carousel) = self.carousel() {
2027 carousel.set_is_swiping(false);
2028 }
2029
2030 self.cancel_offset_animation();
2031
2032 self.reset_viewport_transition_state();
2033 self.reset_transition_state(self.realized().as_ref());
2034
2035 if let Some(swipe_target) = self.swipe_target() {
2036 self.recycle_element(&swipe_target);
2037 }
2038
2039 self.is_dragging.set(false);
2040 self.total_delta.set(0.0);
2041 *self.swipe_target.borrow_mut() = None;
2042 self.swipe_target_index.set(-1);
2043 self.is_rubber_banding.set(false);
2044 self.clear_fractional_progress_context();
2045
2046 if self.uses_viewport_fraction_layout() {
2047 if let Some(viewport_carousel) = self.carousel() {
2048 self.set_offset_core(self.with_primary_offset(
2049 self.offset.get(),
2050 self.get_nearest_logical_offset(
2051 viewport_carousel.selected_index(),
2052 self.get_primary_offset(self.offset.get()),
2053 ),
2054 ));
2055 }
2056 }
2057 }
2058
2059 fn reset_transition_state(&self, control: Option<&Ref<Control>>) {
2060 let Some(control) = control else {
2061 return;
2062 };
2063
2064 let transition = self.get_transition();
2065 match transition.as_ref().and_then(|t| t.as_progress_page_transition()) {
2066 Some(progressive) => progressive.reset(&control.clone().upcast()),
2067 None => Self::reset_visual_state(Some(control)),
2068 }
2069 }
2070
2071 fn reset_visual_state(control: Option<&Ref<Control>>) {
2072 let Some(control) = control else {
2073 return;
2074 };
2075 control.set_render_transform(None);
2076 control.set_opacity(1.0);
2077 control.set_z_index(0);
2078 control.set_value(Visual::clip_property(), None);
2079 }
2080}