waterui_core/components/
dynamic.rs

1//! Dynamic views that can be updated at runtime.
2//!
3//! This module provides components for creating views that can change their content
4//! based on reactive state or explicit updates.
5//!
6//! - `Dynamic` - A view that can be updated through a `DynamicHandler`
7//! - `watch` - Helper function to create views that respond to reactive state changes
8//!
9//! # Examples
10//!
11//! ```rust
12//! use waterui_core::{dynamic::{Dynamic,watch},Binding};
13//!
14//! // Create a dynamic view with a handler
15//! let (handler, view) = Dynamic::new();
16//! handler.set("Initial content");
17//!
18//! // Create a view that watches a reactive value
19//! let count = Binding::container(0);
20//! let counter_view = watch(count, |value| format!("Count: {}", value));
21use crate::components::metadata::Retain;
22use crate::{AnyView, Metadata, View};
23use alloc::boxed::Box;
24use alloc::rc::Rc;
25use core::cell::RefCell;
26use nami::watcher::Context;
27use nami::{Computed, Signal, watcher::Metadata as WatcherMetadata};
28
29/// A dynamic view that can be updated.
30///
31/// Represents a view whose content can be changed dynamically at runtime.
32///
33/// You should avoid using this component if possible,
34/// most of components in `WaterUI` already provide a way to update their content reactively.
35pub struct Dynamic(DynamicHandler);
36
37raw_view!(Dynamic);
38
39/// A handler for updating a Dynamic view.
40///
41/// Provides methods to set new content for the associated Dynamic view.
42#[derive(Clone)]
43pub struct DynamicHandler(Rc<RefCell<DynamicHandlerState>>);
44
45enum DynamicHandlerState {
46    /// Connected to a receiver (Swift/native side).
47    Connected(Receiver),
48    /// Not yet connected, stores the initial view if set before connection.
49    Unconnected(Option<AnyView>),
50}
51
52type Receiver = Box<dyn Fn(Context<AnyView>)>;
53
54impl_debug!(Dynamic);
55impl_debug!(DynamicHandler);
56
57impl DynamicHandler {
58    /// Sets the content of the Dynamic view with the provided view and metadata.
59    ///
60    /// # Arguments
61    ///
62    /// * `view` - The new view to display
63    /// * `metadata` - Additional metadata associated with the update
64    pub fn set_with_metadata(&self, view: impl View, metadata: WatcherMetadata) {
65        let mut state = self.0.borrow_mut();
66        let view = AnyView::new(view);
67        match &mut *state {
68            DynamicHandlerState::Connected(receiver) => {
69                receiver(Context::new(view, metadata));
70            }
71            DynamicHandlerState::Unconnected(temp_view) => {
72                *temp_view = Some(view);
73            }
74        }
75    }
76
77    /// Sets the content of the Dynamic view with the provided view.
78    ///
79    /// # Arguments
80    ///
81    /// * `view` - The new view to display
82    pub fn set(&self, view: impl View) {
83        self.set_with_metadata(view, WatcherMetadata::new());
84    }
85}
86
87impl Dynamic {
88    /// Creates a new Dynamic view along with its handler.
89    ///
90    /// Returns a tuple of (handler, view) where the handler can be used to update
91    /// the view's content.
92    ///
93    /// # Returns
94    ///
95    /// A tuple containing the [`DynamicHandler`] and Dynamic view
96    #[must_use]
97    pub fn new() -> (DynamicHandler, Self) {
98        let handler = DynamicHandler(Rc::new(RefCell::new(DynamicHandlerState::Unconnected(
99            None,
100        ))));
101        (handler.clone(), Self(handler))
102    }
103
104    /// Creates a Dynamic view that watches a reactive value.
105    ///
106    /// The provided function is used to convert the value to a view.
107    /// Whenever the watched value changes, the view will update automatically.
108    ///
109    /// # Arguments
110    ///
111    /// * `value` - The reactive value to watch
112    /// * `f` - A function that converts the value to a view
113    ///
114    /// # Returns
115    ///
116    /// A Dynamic view that updates when the value changes
117    pub fn watch<T, S, V: View>(value: S, f: impl 'static + Fn(T) -> V) -> impl View
118    where
119        S: Signal<Output = T>,
120    {
121        let (handle, dynamic) = Self::new();
122        handle.set(f(value.get()));
123
124        let guard = value.watch(move |value| handle.set(f(value.into_value())));
125
126        // Use Metadata<Retain> to keep the guard and value alive
127        Metadata::new(dynamic, Retain::new((guard, value)))
128    }
129
130    /// Connects the Dynamic view to a receiver function.
131    ///
132    /// For internal use only.
133    ///
134    /// The receiver function is called whenever the view content is updated.
135    /// If there's a temporary view stored (set before connecting), it will
136    /// be immediately passed to the receiver.
137    ///
138    /// # Arguments
139    ///
140    /// * `receiver` - A function that receives view updates
141    pub fn connect(self, receiver: impl Fn(Context<AnyView>) + 'static) {
142        let mut state = self.0.0.borrow_mut();
143
144        match &mut *state {
145            DynamicHandlerState::Unconnected(temp_view) => {
146                if let Some(view) = temp_view.take() {
147                    receiver(Context::new(view, WatcherMetadata::new()));
148                }
149                *state = DynamicHandlerState::Connected(Box::new(receiver));
150            }
151            DynamicHandlerState::Connected(_) => unreachable!("Dynamic already connected"),
152        }
153    }
154}
155
156/// Creates a view that watches a reactive value.
157///
158/// A convenience function that calls [`Dynamic::watch`].
159///
160/// # Arguments
161///
162/// * `value` - The reactive value to watch
163/// * `f` - A function that converts the value to a view
164///
165/// # Returns
166///
167/// A view that updates when the value changes
168pub fn watch<T, S, V: View>(value: S, f: impl Fn(T) -> V + 'static) -> impl View
169where
170    S: Signal<Output = T>,
171{
172    Dynamic::watch(value, f)
173}
174
175impl<V: View> View for Computed<V>
176where
177    Self: 'static,
178{
179    fn body(self, _env: &crate::Environment) -> impl View {
180        Dynamic::watch(self, |view| view)
181    }
182}