Skip to main content

euv_core/reactive/
impl.rs

1use crate::*;
2
3/// Implementation of SignalInner construction.
4impl<T> SignalInner<T>
5where
6    T: Clone,
7{
8    /// Creates a new signal inner with the given initial value and no listeners.
9    pub fn new(value: T) -> Self {
10        let inner: SignalInner<T> = SignalInner {
11            value,
12            listeners: Vec::new(),
13        };
14        inner
15    }
16}
17
18/// Implementation of signal inner state notification.
19impl<T> SignalInner<T>
20where
21    T: Clone,
22{
23    /// Notifies all subscribed listeners that the value has changed.
24    pub fn notify(&mut self) {
25        for listener in self.get_listeners() {
26            let mut borrowed = listener.borrow_mut();
27            borrowed();
28        }
29    }
30}
31
32/// Implementation of reactive signal operations.
33impl<T> Signal<T>
34where
35    T: Clone + PartialEq,
36{
37    /// Creates a new `Signal` from an existing raw pointer.
38    ///
39    /// # Safety
40    ///
41    /// The caller must ensure the pointer was allocated via `Box::leak`
42    /// and remains valid for the entire program lifetime.
43    pub fn from_inner(inner: *mut SignalInner<T>) -> Self {
44        Signal { inner }
45    }
46
47    /// Returns a mutable reference to the inner signal state.
48    ///
49    /// # Safety
50    ///
51    /// The caller must ensure no other references to the inner state exist.
52    /// In single-threaded WASM this is always safe.
53    #[allow(clippy::mut_from_ref)]
54    fn get_inner_mut(&self) -> &mut SignalInner<T> {
55        unsafe { &mut *self.inner }
56    }
57
58    /// Returns the current value of the signal.
59    pub fn get(&self) -> T {
60        self.get_inner_mut().get_value().clone()
61    }
62
63    /// Attempts to return the current value of the signal without panicking.
64    ///
65    /// # Returns
66    /// - `Some(T)`: The current value if the borrow succeeds.
67    /// - `None`: If the inner value is already mutably borrowed.
68    pub fn try_get(&self) -> Option<T> {
69        Some(self.get_inner_mut().get_value().clone())
70    }
71
72    /// Subscribes a callback to be invoked when the signal changes.
73    pub fn subscribe<F>(&self, callback: F)
74    where
75        F: FnMut() + 'static,
76    {
77        self.get_inner_mut()
78            .get_mut_listeners()
79            .push(Rc::new(RefCell::new(callback)));
80    }
81
82    /// Sets the value of the signal and notifies listeners.
83    ///
84    /// If the new value is equal to the current value, no update is performed
85    /// and no listeners are notified. This prevents unnecessary re-renders and
86    /// avoids cascading no-op updates through intermediate signal chains.
87    pub fn set(&self, value: T) {
88        let inner: &mut SignalInner<T> = self.get_inner_mut();
89        if inner.get_value() == &value {
90            return;
91        }
92        let listeners: ListenerList = {
93            inner.set_value(value);
94            inner.get_listeners().iter().map(Rc::clone).collect()
95        };
96        for listener in &listeners {
97            let mut borrowed = listener.borrow_mut();
98            borrowed();
99        }
100        schedule_signal_update();
101    }
102
103    /// Attempts to set the value of the signal and notify listeners without panicking.
104    ///
105    /// If the new value is equal to the current value, no update is performed.
106    ///
107    /// # Arguments
108    /// - `value`: The new value to assign to the signal.
109    ///
110    /// # Returns
111    /// - `true`: If the value was successfully updated and listeners were notified.
112    /// - `false`: If the value is unchanged and no update was performed.
113    pub fn try_set(&self, value: T) -> bool {
114        let inner: &mut SignalInner<T> = self.get_inner_mut();
115        if inner.get_value() == &value {
116            return false;
117        }
118        let listeners: ListenerList = {
119            inner.set_value(value);
120            inner.get_listeners().iter().map(Rc::clone).collect()
121        };
122        for listener in &listeners {
123            listener.borrow_mut()();
124        }
125        schedule_signal_update();
126        true
127    }
128}
129
130/// Prevents direct dereference of a signal to enforce explicit API usage.
131impl<T> Deref for Signal<T>
132where
133    T: Clone + PartialEq,
134{
135    type Target = T;
136
137    fn deref(&self) -> &Self::Target {
138        panic!("Signal does not support direct dereference; use .get() instead");
139    }
140}
141
142/// Prevents direct mutable dereference of a signal to enforce explicit API usage.
143impl<T> DerefMut for Signal<T>
144where
145    T: Clone + PartialEq,
146{
147    fn deref_mut(&mut self) -> &mut Self::Target {
148        panic!("Signal does not support direct dereference; use .set() instead");
149    }
150}
151
152/// Clones the signal, sharing the same inner state.
153impl<T> Clone for Signal<T>
154where
155    T: Clone + PartialEq,
156{
157    fn clone(&self) -> Self {
158        *self
159    }
160}
161
162/// Copies the signal, sharing the same inner state.
163///
164/// A `Signal` is just a raw pointer; copying it is a trivial bitwise copy.
165impl<T> Copy for Signal<T> where T: Clone + PartialEq {}
166
167/// Converts a static `String` into a text attribute value.
168impl IntoReactiveValue for String {
169    fn into_reactive_value(self) -> AttributeValue {
170        AttributeValue::Text(self)
171    }
172}
173
174/// Converts a string slice into a text attribute value.
175impl IntoReactiveValue for &str {
176    fn into_reactive_value(self) -> AttributeValue {
177        AttributeValue::Text(self.to_string())
178    }
179}
180
181/// Converts a string signal into a reactive attribute value.
182impl IntoReactiveValue for Signal<String> {
183    fn into_reactive_value(self) -> AttributeValue {
184        AttributeValue::Signal(self)
185    }
186}
187
188/// Converts a mutable bool signal into a reactive attribute value.
189///
190/// The signal is mapped to a `Signal<String>` that yields `"true"` or `"false"`,
191/// enabling boolean attributes like `checked` to reactively update the DOM.
192impl IntoReactiveValue for Signal<bool> {
193    fn into_reactive_value(self) -> AttributeValue {
194        bool_signal_to_string_attribute_value(self)
195    }
196}
197
198/// Converts a CSS class reference into an attribute value.
199impl IntoReactiveValue for crate::vdom::CssClass {
200    fn into_reactive_value(self) -> AttributeValue {
201        AttributeValue::Css(self)
202    }
203}
204
205/// Converts a reference to a CSS class into an attribute value by cloning.
206impl IntoReactiveValue for &'static crate::vdom::CssClass {
207    fn into_reactive_value(self) -> AttributeValue {
208        AttributeValue::Css(self.clone())
209    }
210}
211
212/// Converts a closure into a callback attribute value.
213impl<F> IntoCallbackAttribute for F
214where
215    F: FnMut(NativeEvent) + 'static,
216{
217    fn into_callback_attribute(self) -> AttributeValue {
218        AttributeValue::Event(NativeEventHandler::new(
219            NativeEventName::Other("callback".to_string()),
220            self,
221        ))
222    }
223}
224
225/// Converts an owned event handler into a callback attribute value.
226impl IntoCallbackAttribute for NativeEventHandler {
227    fn into_callback_attribute(self) -> AttributeValue {
228        AttributeValue::Event(self)
229    }
230}
231
232/// Converts an optional event handler into a callback attribute value.
233impl IntoCallbackAttribute for Option<NativeEventHandler> {
234    fn into_callback_attribute(self) -> AttributeValue {
235        match self {
236            Some(handler) => AttributeValue::Event(handler),
237            None => AttributeValue::Text(String::new()),
238        }
239    }
240}
241
242/// Implementation of hook context lifecycle and hook index management.
243impl HookContext {
244    /// Creates a new `HookContext` from an existing raw pointer.
245    ///
246    /// # Safety
247    ///
248    /// The caller must ensure the pointer was allocated via `Box::leak`
249    /// and remains valid for the entire program lifetime.
250    pub fn from_inner(inner: *mut HookContextInner) -> Self {
251        HookContext { inner }
252    }
253
254    /// Returns a mutable reference to the inner hook context state.
255    ///
256    /// # Safety
257    ///
258    /// The caller must ensure no other references to the inner state exist.
259    /// In single-threaded WASM this is always safe.
260    #[allow(clippy::mut_from_ref)]
261    fn get_inner_mut(&self) -> &mut HookContextInner {
262        unsafe { &mut *self.inner }
263    }
264
265    /// Returns the current hook index.
266    pub fn get_hook_index(&self) -> usize {
267        *self.get_inner_mut().get_hook_index()
268    }
269
270    /// Sets the hook index.
271    pub fn set_hook_index(&mut self, index: usize) {
272        self.get_inner_mut().set_hook_index(index);
273    }
274
275    /// Returns a reference to the hooks storage.
276    pub fn get_hooks(&self) -> &Vec<Box<dyn Any>> {
277        self.get_inner_mut().get_hooks()
278    }
279
280    /// Returns a mutable reference to the hooks storage.
281    pub fn get_mut_hooks(&mut self) -> &mut Vec<Box<dyn Any>> {
282        self.get_inner_mut().get_mut_hooks()
283    }
284
285    /// Resets the hook index for a new render cycle.
286    pub fn reset_hook_index(&mut self) {
287        self.set_hook_index(0_usize);
288    }
289}
290
291/// Clones the hook context, sharing the same inner state.
292impl Clone for HookContext {
293    fn clone(&self) -> Self {
294        *self
295    }
296}
297
298/// Copies the hook context, sharing the same inner state.
299///
300/// A `HookContext` is just a raw pointer; copying it is a trivial bitwise copy.
301impl Copy for HookContext {}
302
303/// Provides a default empty hook context.
304impl Default for HookContext {
305    fn default() -> Self {
306        let boxed: Box<HookContextInner> = Box::default();
307        HookContext::from_inner(Box::leak(boxed) as *mut HookContextInner)
308    }
309}
310
311/// Implementation of HookContextInner construction.
312impl HookContextInner {
313    /// Creates a new empty hook context inner.
314    pub const fn new() -> Self {
315        HookContextInner {
316            hooks: Vec::new(),
317            hook_index: 0_usize,
318        }
319    }
320}
321
322/// Provides a default empty hook context inner.
323impl Default for HookContextInner {
324    fn default() -> Self {
325        Self::new()
326    }
327}
328
329/// SAFETY: `HookContextCell` is only used in single-threaded WASM contexts.
330/// Concurrent access from multiple threads would be undefined behavior.
331unsafe impl Sync for HookContextCell {}