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}