Skip to main content

euv_ui/hook/debounced_value/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`DebouncedValue`].
4impl<T: Clone + PartialEq + Default + 'static> DebouncedValue<T> {
5    /// Schedules `next` to become the emitted value. The
6    /// commit happens on the next `tick` call at or after
7    /// `now_ms + delay_ms`.
8    ///
9    /// Calling `set` repeatedly within `delay_ms` of each
10    /// other means only the last value wins — that's the
11    /// "debounce" contract.
12    ///
13    /// # Arguments
14    ///
15    /// - `T: Clone + PartialEq + Default + 'static` - A generic type parameter.
16    /// - `u64` - The current time in milliseconds (any monotonic
17    ///   source; on the web use `performance.now()`).
18    pub fn set(&self, next: T, now_ms: u64) {
19        self.get_state().set(DebounceState::Pending(now_ms, next));
20    }
21
22    /// Drives the state machine forward.
23    ///
24    /// - If the state is `Idle`, this is a no-op.
25    /// - If the state is `Pending(set_at, value)`, this
26    ///   emits `value` (writing it to the `value` signal)
27    ///   iff `now_ms - set_at >= delay_ms`. Otherwise the
28    ///   pending value is preserved and the call returns
29    ///   `false`.
30    ///
31    /// Returns `true` when a pending value was emitted,
32    /// `false` otherwise.
33    ///
34    /// # Arguments
35    ///
36    /// - `u64` - The current time in milliseconds.
37    ///
38    /// # Returns
39    ///
40    /// - `bool` - A boolean.
41    pub fn tick(&self, now_ms: u64) -> bool {
42        match self.get_state().get() {
43            DebounceState::Idle => false,
44            DebounceState::Pending(set_at, _) => {
45                if now_ms.saturating_sub(set_at) >= u64::from(self.delay_ms) {
46                    let pending: T = match self.get_state().get() {
47                        DebounceState::Pending(_, value) => value,
48                        DebounceState::Idle => unreachable!(),
49                    };
50                    self.get_value().set(pending);
51                    self.get_state().set(DebounceState::Idle);
52                    true
53                } else {
54                    false
55                }
56            }
57        }
58    }
59
60    /// Cancels any pending value without emitting it.
61    /// The emitted value is left untouched.
62    pub fn cancel(&self) {
63        self.get_state().set(DebounceState::Idle);
64    }
65
66    /// Returns the currently emitted value as a snapshot.
67    ///
68    /// # Returns
69    ///
70    /// - `T` - The current value (or a snapshot thereof).
71    pub fn get(&self) -> T {
72        self.get_value().get()
73    }
74
75    /// Returns `true` when a value is waiting to be
76    /// emitted.
77    ///
78    /// # Returns
79    ///
80    /// - `bool` - `true` when a value is waiting to be emitted.
81    pub fn is_pending(&self) -> bool {
82        matches!(self.get_state().get(), DebounceState::Pending(_, _))
83    }
84}
85
86/// Debug formatting for [`DebouncedValue`].
87impl<T: Clone + PartialEq + Debug + Default + 'static> Display for DebouncedValue<T> {
88    /// Formats the [`DebouncedValue`] via the supplied formatter.
89    ///
90    /// # Arguments
91    ///
92    /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
93    ///
94    /// # Returns
95    ///
96    /// - `FmtResult` - Result of the formatting operation.
97    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
98        let pending: &DebounceState<T> = &self.get_state().get();
99        match pending {
100            DebounceState::Idle => {
101                write!(formatter, "DebouncedValue({:?})", self.get_value().get())
102            }
103            DebounceState::Pending(_, value) => {
104                write!(formatter, "DebouncedValue(pending={value:?})")
105            }
106        }
107    }
108}