Skip to main content

ferroui_xaml_interactions_custom/control/
delayed_load_behavior.rs

1//! Port of `Control/DelayedLoadBehavior.cs`.
2
3use crate::{AttachedToVisualTreeBehavior, AttachedToVisualTreeBehaviorImpl};
4use ferroui_base::animation::TimeSpan;
5use ferroui_base::reactive::IDisposable;
6use ferroui_base::threading::DispatcherTimer;
7use ferroui_base::{
8    ferro_class, ferro_class_info, ferro_properties, instantiate, FerroObjectImpl, FerroProperty, Ref, StaticType,
9    StyledElementImpl, StyledProperty, Visual,
10};
11use ferroui_controls::Control;
12use ferroui_xaml_interactivity::{DisposableAction, DisposingBehaviorImpl, StyledElementBehaviorImpl};
13use std::cell::RefCell;
14use std::rc::Rc;
15
16/// Hides the associated control when it is attached to the visual tree and shows it again after
17/// a delay.
18#[repr(C)]
19pub struct DelayedLoadBehavior {
20    base: AttachedToVisualTreeBehavior,
21    // The timer and the subscription of its tick (the reference removes its delegate).
22    timer: RefCell<Option<(Rc<DispatcherTimer>, Rc<dyn IDisposable>)>>,
23}
24
25ferro_class!(DelayedLoadBehavior: AttachedToVisualTreeBehavior);
26ferro_class_info!(DelayedLoadBehavior { new: DelayedLoadBehavior::new });
27ferroui_base::ferro_impl_classes!(
28    DelayedLoadBehavior: FerroObjectImpl,
29    StyledElementImpl,
30    StyledElementBehaviorImpl,
31    DisposingBehaviorImpl,
32);
33
34ferro_properties! {
35    impl DelayedLoadBehavior {
36        /// Identifies the `Delay` property.
37        pub fn delay_property() -> StyledProperty<TimeSpan> {
38            FerroProperty::register::<DelayedLoadBehavior, _>("Delay", TimeSpan::from_milliseconds(500.0))
39        }
40    }
41}
42
43ferroui_base::ferro_overrides! {
44    impl AttachedToVisualTreeBehaviorImpl for DelayedLoadBehavior {
45        fn on_attached_to_visual_tree_override(this: &Self) -> Rc<dyn IDisposable> {
46            let Some(associated_object) = this.associated_object_as::<Control>() else {
47                return DisposableAction::empty();
48            };
49
50            associated_object.set_is_visible(false);
51
52            let timer = DispatcherTimer::new();
53            timer.set_interval(this.delay().to_duration().unwrap_or_default());
54            let behavior = this.to_ref().downgrade();
55            let tick = timer.tick(move |_timer: &DispatcherTimer| {
56                if let Some(behavior) = behavior.upgrade() {
57                    behavior.on_tick();
58                }
59            });
60            timer.start();
61
62            // The reference replaces the field without stopping a running timer.
63            *this.timer.borrow_mut() = Some((timer, tick));
64
65            let behavior = this.to_ref().downgrade();
66            DisposableAction::create(move || {
67                if let Some(behavior) = behavior.upgrade() {
68                    behavior.dispose_timer();
69                }
70            })
71        }
72    }
73}
74
75impl DelayedLoadBehavior {
76    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
77    pub fn construct() -> Self {
78        Self { base: AttachedToVisualTreeBehavior::construct(<Control as StaticType>::TYPE), timer: RefCell::new(None) }
79    }
80
81    /// Creates a behavior.
82    pub fn new() -> Ref<Self> {
83        instantiate(Self::construct())
84    }
85
86    /// Gets or sets the delay before the associated control is shown. This is a property of the
87    /// object model.
88    pub fn delay(&self) -> TimeSpan {
89        self.get_value(Self::delay_property())
90    }
91
92    pub fn set_delay(&self, value: TimeSpan) {
93        self.set_value(Self::delay_property(), value)
94    }
95
96    fn on_tick(&self) {
97        if let Some(associated_object) = self.associated_object_as::<Control>() {
98            associated_object.set_current_value(Visual::is_visible_property(), true);
99        }
100        self.dispose_timer();
101    }
102
103    fn dispose_timer(&self) {
104        let timer = self.timer.borrow_mut().take();
105        if let Some((timer, tick)) = timer {
106            tick.dispose();
107            timer.stop();
108        }
109    }
110}