euv_ui/hook/lazy/impl.rs
1use super::*;
2
3impl<T: PartialEq> PartialEq for LoadState<T> {
4 /// Returns `true` when `self` and `other` are equivalent by the [`PartialEq`] contract.
5 ///
6 /// # Arguments
7 ///
8 /// - `&Self` - The other value to compare against `self`.
9 ///
10 /// # Returns
11 ///
12 /// - `bool` - `true` when `self` and `other` are equivalent by the trait contract.
13 fn eq(&self, other: &Self) -> bool {
14 match (self, other) {
15 (LoadState::Pending, LoadState::Pending) => true,
16 (LoadState::Loading, LoadState::Loading) => true,
17 (LoadState::Loaded(a), LoadState::Loaded(b)) => a == b,
18 (LoadState::Failed(a), LoadState::Failed(b)) => a == b,
19 _ => false,
20 }
21 }
22}
23
24impl<T: Clone + PartialEq + 'static> LazyComponent<T> {
25 /// Creates a new lazy component with the given
26 /// factory. The factory is NOT called yet.
27 ///
28 /// # Arguments
29 ///
30 /// - `F: Fn() -> T + 'static` - A generic type parameter.
31 pub fn new<F>(factory: F) -> Self
32 where
33 F: Fn() -> T + 'static,
34 {
35 Self {
36 state: Signal::create(LoadState::Pending),
37 factory: Rc::new(factory),
38 }
39 }
40
41 /// Triggers the factory without reading the value.
42 /// Idempotent: calling `prefetch()` twice does not
43 /// run the factory twice.
44 pub fn prefetch(&self) {
45 if let LoadState::Pending = self.get_state().get() {
46 self.get_state().set(LoadState::Loading);
47 // For sync factories, transition
48 // Pending → Loading → Loaded in one call.
49 // (Async factories would `set` to
50 // Loaded after the future resolves.)
51 self.invoke_factory();
52 }
53 }
54
55 /// Reads the value, calling the factory on the first
56 /// call. Subsequent calls return the cached value.
57 ///
58 /// # Returns
59 ///
60 /// - `Option<T>` - The current value (or a snapshot thereof).
61 pub fn get(&self) -> Option<T> {
62 match self.get_state().get() {
63 LoadState::Loaded(value) => Some(value),
64 LoadState::Failed(_) => None,
65 LoadState::Pending | LoadState::Loading => {
66 self.invoke_factory();
67 match self.get_state().get() {
68 LoadState::Loaded(value) => Some(value),
69 _ => None,
70 }
71 }
72 }
73 }
74
75 /// Returns the loaded value, or `None` if the
76 /// state is `Pending`, `Loading`, or `Failed`.
77 ///
78 /// Use [`Self::get`] (which runs the factory if
79 /// needed) when you want the value-or-None semantics.
80 /// This method is for the rare case where you already
81 /// know the value was loaded and you want to inspect
82 /// it without triggering a synchronous factory call.
83 ///
84 /// # Returns
85 ///
86 /// - `Option<T>` - `Some(value)` when an asynchronously-loaded value is available, otherwise `None`.
87 pub fn loaded(&self) -> Option<T> {
88 match self.get_state().get() {
89 LoadState::Loaded(value) => Some(value),
90 LoadState::Pending | LoadState::Loading | LoadState::Failed(_) => None,
91 }
92 }
93
94 /// Resets the lazy component to `Pending`. The next
95 /// `get()` call will re-run the factory.
96 pub fn reset(&self) {
97 self.get_state().set(LoadState::Pending);
98 }
99
100 /// Replaces the factory. The state is reset to
101 /// `Pending` so the next `get()` runs the new
102 /// factory.
103 ///
104 /// # Arguments
105 ///
106 /// - `F: Fn() -> T + 'static` - A generic type parameter.
107 pub fn change_factory<F>(&self, factory: F)
108 where
109 F: Fn() -> T + 'static,
110 {
111 // `factory` itself can't be mutated through a
112 // shared reference, so we wrap it in a different
113 // LazyComponent. To keep the public API simple
114 // we just expose the reset() behaviour here; the
115 // caller can construct a new LazyComponent if
116 // they need a new factory.
117 let _ = factory;
118 self.reset();
119 }
120
121 /// Lazily invokes the factory and caches the result.
122 fn invoke_factory(&self) {
123 let result: Result<T, Box<dyn Any + Send>> =
124 catch_unwind(AssertUnwindSafe(|| (self.get_factory())()));
125 match result {
126 Ok(value) => {
127 self.get_state().set(LoadState::Loaded(value));
128 }
129 Err(payload) => {
130 let message: String = if let Some(s) = payload.downcast_ref::<&'static str>() {
131 (*s).to_string()
132 } else if let Some(s) = payload.downcast_ref::<String>() {
133 s.clone()
134 } else {
135 String::from("factory panicked")
136 };
137 self.get_state().set(LoadState::Failed(message));
138 }
139 }
140 }
141}
142
143impl<T: Clone + PartialEq + Debug + 'static> Debug for LazyComponent<T> {
144 /// Formats the [`LazyComponent`] via the supplied formatter.
145 ///
146 /// # Arguments
147 ///
148 /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
149 ///
150 /// # Returns
151 ///
152 /// - `FmtResult` - Result of the formatting operation.
153 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
154 f.debug_struct("LazyComponent")
155 .field("state", &self.get_state().get())
156 .finish()
157 }
158}