dampen_core/state/mod.rs
1//! Application state container for Dampen UI views.
2//!
3//! This module provides the [`AppState`] struct that combines a parsed UI document
4//! with application state and event handlers into a cohesive structure.
5//!
6//! # Overview
7//!
8//! `AppState<M, S>` is a generic container where:
9//! - `document`: The parsed [`DampenDocument`](crate::ir::DampenDocument) (mandatory)
10//! - `model`: Application state model implementing [`UiBindable`](crate::binding::UiBindable) (optional, defaults to `()`)
11//! - `handler_registry`: Event handler registry (optional, defaults to empty)
12//! - `shared_context`: Optional reference to shared state across views (defaults to `None`)
13//!
14//! # Examples
15//!
16//! Basic usage with document only:
17//!
18//! ```rust,ignore
19//! use dampen_core::{parse, AppState};
20//!
21//! let xml = r#"<column><text value="Hello!" /></column>"#;
22//! let document = parse(xml).unwrap();
23//! let state = AppState::new(document);
24//! ```
25//!
26//! With a custom model:
27//!
28//! ```rust,ignore
29//! use dampen_core::{parse, AppState};
30//! use dampen_macros::UiModel;
31//!
32//! #[derive(UiModel, Default)]
33//! struct MyModel {
34//! count: i32,
35//! }
36//!
37//! let xml = r#"<column><text value="Hello!" /></column>"#;
38//! let document = parse(xml).unwrap();
39//! let state = AppState::with_model(document, MyModel { count: 0 });
40//! ```
41//!
42//! With shared state for inter-window communication:
43//!
44//! ```rust,ignore
45//! use dampen_core::{parse, AppState, SharedContext};
46//! use dampen_macros::UiModel;
47//!
48//! #[derive(UiModel, Default)]
49//! struct MyModel { count: i32 }
50//!
51//! #[derive(UiModel, Default, Clone)]
52//! struct SharedState { theme: String }
53//!
54//! let xml = r#"<column><text value="Hello!" /></column>"#;
55//! let document = parse(xml).unwrap();
56//! let shared = SharedContext::new(SharedState::default());
57//! let state = AppState::with_shared(document, MyModel::default(), HandlerRegistry::new(), shared);
58//! ```
59//!
60//! # See Also
61//!
62//! - [`DampenDocument`](crate::ir::DampenDocument) - The parsed UI document
63//! - [`HandlerRegistry`](crate::handler::HandlerRegistry) - Event handler registry
64//! - [`UiBindable`](crate::binding::UiBindable) - Trait for bindable models
65//! - [`SharedContext`](crate::shared::SharedContext) - Shared state container
66
67use std::marker::PhantomData;
68use std::sync::{RwLockReadGuard, RwLockWriteGuard};
69
70use crate::shared::SharedContext;
71use crate::{binding::UiBindable, handler::HandlerRegistry, ir::DampenDocument};
72
73/// Application state container for a Dampen UI view.
74///
75/// This struct combines the parsed UI document with application state and event handlers.
76/// It is the central state structure used throughout Dampen applications.
77///
78/// # Type Parameters
79///
80/// * `M` - The local model type implementing [`UiBindable`](crate::binding::UiBindable). Defaults to unit type `()`.
81/// * `S` - The shared state type implementing [`UiBindable`](crate::binding::UiBindable) + `Send + Sync`. Defaults to unit type `()`.
82///
83/// # Backward Compatibility
84///
85/// For applications not using shared state, `S` defaults to `()` and
86/// `shared_context` is `None`. All existing code continues to work unchanged.
87///
88/// # Fields
89///
90/// * `document` - The parsed UI document containing widget tree and themes
91/// * `model` - Application state model for data bindings
92/// * `handler_registry` - Registry of event handlers for UI interactions
93/// * `shared_context` - Optional reference to shared state across views
94#[derive(Debug, Clone)]
95pub struct AppState<M: UiBindable = (), S: UiBindable + Send + Sync + 'static = ()> {
96 /// The parsed UI document containing widget tree and themes.
97 pub document: DampenDocument,
98
99 /// Application state model for data bindings.
100 /// Generic over `UiBindable` for type-safe field access.
101 pub model: M,
102
103 /// Registry of event handlers for UI interactions.
104 pub handler_registry: HandlerRegistry,
105
106 /// Optional reference to shared context for inter-window communication.
107 pub shared_context: Option<SharedContext<S>>,
108
109 /// Type marker to capture the generic parameters.
110 _marker: PhantomData<(M, S)>,
111}
112
113// ============================================
114// Constructors for backward compatibility (M only, S = ())
115// ============================================
116
117impl<M: UiBindable> AppState<M, ()> {
118 /// Creates a new AppState with default model and empty handler registry.
119 ///
120 /// This is the simplest constructor for static UIs that don't use data binding
121 /// or shared state.
122 ///
123 /// # Examples
124 ///
125 /// ```rust,ignore
126 /// use dampen_core::{parse, AppState};
127 ///
128 /// let xml = r#"<column><text value="Hello!" /></column>"#;
129 /// let document = parse(xml).unwrap();
130 /// let state = AppState::new(document);
131 /// ```
132 pub fn new(document: DampenDocument) -> Self
133 where
134 M: Default,
135 {
136 Self {
137 document,
138 model: M::default(),
139 handler_registry: HandlerRegistry::default(),
140 shared_context: None,
141 _marker: PhantomData,
142 }
143 }
144
145 /// Creates an AppState with a custom model and default handler registry.
146 ///
147 /// # Examples
148 ///
149 /// ```rust,ignore
150 /// use dampen_core::{parse, AppState};
151 /// use dampen_macros::UiModel;
152 ///
153 /// #[derive(UiModel, Default)]
154 /// struct MyModel {
155 /// count: i32,
156 /// }
157 ///
158 /// let xml = r#"<column><text value="Hello!" /></column>"#;
159 /// let document = parse(xml).unwrap();
160 /// let model = MyModel { count: 42 };
161 /// let state = AppState::with_model(document, model);
162 /// ```
163 pub fn with_model(document: DampenDocument, model: M) -> Self {
164 Self {
165 document,
166 model,
167 handler_registry: HandlerRegistry::default(),
168 shared_context: None,
169 _marker: PhantomData,
170 }
171 }
172
173 /// Creates an AppState with a custom handler registry and default model.
174 ///
175 /// # Examples
176 ///
177 /// ```rust,ignore
178 /// use dampen_core::{parse, AppState, HandlerRegistry};
179 ///
180 /// let xml = r#"<column><text value="Hello!" /></column>"#;
181 /// let document = parse(xml).unwrap();
182 /// let mut registry = HandlerRegistry::new();
183 /// registry.register_simple("greet", |_model| {
184 /// println!("Button clicked!");
185 /// });
186 /// let state = AppState::with_handlers(document, registry);
187 /// ```
188 pub fn with_handlers(document: DampenDocument, handler_registry: HandlerRegistry) -> Self
189 where
190 M: Default,
191 {
192 Self {
193 document,
194 model: M::default(),
195 handler_registry,
196 shared_context: None,
197 _marker: PhantomData,
198 }
199 }
200
201 /// Creates an AppState with custom model and handler registry.
202 ///
203 /// This is the most flexible constructor for apps without shared state,
204 /// allowing you to specify all components of the application state.
205 /// Useful for hot-reload scenarios where both model and handlers need to be specified.
206 ///
207 /// # Examples
208 ///
209 /// ```rust,ignore
210 /// use dampen_core::{parse, AppState, HandlerRegistry};
211 /// use dampen_macros::UiModel;
212 ///
213 /// #[derive(UiModel, Default)]
214 /// struct MyModel {
215 /// count: i32,
216 /// }
217 ///
218 /// let xml = r#"<column><text value="Hello!" /></column>"#;
219 /// let document = parse(xml).unwrap();
220 /// let model = MyModel { count: 42 };
221 /// let mut registry = HandlerRegistry::new();
222 /// registry.register_simple("increment", |model| {
223 /// let model = model.downcast_mut::<MyModel>().unwrap();
224 /// model.count += 1;
225 /// });
226 /// let state = AppState::with_all(document, model, registry);
227 /// ```
228 pub fn with_all(document: DampenDocument, model: M, handler_registry: HandlerRegistry) -> Self {
229 Self {
230 document,
231 model,
232 handler_registry,
233 shared_context: None,
234 _marker: PhantomData,
235 }
236 }
237}
238
239// ============================================
240// Constructors and methods for shared state (M and S)
241// ============================================
242
243impl<M: UiBindable, S: UiBindable + Send + Sync + 'static> AppState<M, S> {
244 /// Creates an AppState with shared context for inter-window communication.
245 ///
246 /// This constructor is used when your application needs to share state
247 /// across multiple views.
248 ///
249 /// # Arguments
250 ///
251 /// * `document` - Parsed UI document
252 /// * `model` - Local model for this view
253 /// * `handler_registry` - Event handlers for this view
254 /// * `shared_context` - Shared state accessible from all views
255 ///
256 /// # Examples
257 ///
258 /// ```rust,ignore
259 /// use dampen_core::{parse, AppState, SharedContext, HandlerRegistry};
260 /// use dampen_macros::UiModel;
261 ///
262 /// #[derive(UiModel, Default)]
263 /// struct MyModel { count: i32 }
264 ///
265 /// #[derive(UiModel, Default, Clone)]
266 /// struct SharedState { theme: String }
267 ///
268 /// let xml = r#"<column><text value="{shared.theme}" /></column>"#;
269 /// let document = parse(xml).unwrap();
270 /// let shared = SharedContext::new(SharedState { theme: "dark".to_string() });
271 ///
272 /// let state = AppState::with_shared(
273 /// document,
274 /// MyModel::default(),
275 /// HandlerRegistry::new(),
276 /// shared,
277 /// );
278 /// ```
279 pub fn with_shared(
280 document: DampenDocument,
281 model: M,
282 handler_registry: HandlerRegistry,
283 shared_context: SharedContext<S>,
284 ) -> Self {
285 Self {
286 document,
287 model,
288 handler_registry,
289 shared_context: Some(shared_context),
290 _marker: PhantomData,
291 }
292 }
293
294 /// Get read access to shared state (if configured).
295 ///
296 /// Returns `None` if this AppState was created without shared context.
297 ///
298 /// # Examples
299 ///
300 /// ```rust,ignore
301 /// if let Some(guard) = state.shared() {
302 /// println!("Current theme: {}", guard.theme);
303 /// }
304 /// ```
305 pub fn shared(&self) -> Option<RwLockReadGuard<'_, S>> {
306 self.shared_context.as_ref().map(|ctx| ctx.read())
307 }
308
309 /// Get write access to shared state (if configured).
310 ///
311 /// Returns `None` if this AppState was created without shared context.
312 ///
313 /// # Examples
314 ///
315 /// ```rust,ignore
316 /// if let Some(mut guard) = state.shared_mut() {
317 /// guard.theme = "light".to_string();
318 /// }
319 /// ```
320 pub fn shared_mut(&self) -> Option<RwLockWriteGuard<'_, S>> {
321 self.shared_context.as_ref().map(|ctx| ctx.write())
322 }
323
324 /// Check if this AppState has shared context configured.
325 ///
326 /// # Examples
327 ///
328 /// ```rust,ignore
329 /// if state.has_shared() {
330 /// // Use shared state features
331 /// }
332 /// ```
333 pub fn has_shared(&self) -> bool {
334 self.shared_context.is_some()
335 }
336
337 /// Hot-reload: updates the UI document while preserving the model, handlers, and shared context.
338 ///
339 /// This method is designed for development mode hot-reload scenarios where the UI
340 /// definition (XML) changes but the application state (model and shared state)
341 /// should be preserved.
342 ///
343 /// # Examples
344 ///
345 /// ```rust,ignore
346 /// use dampen_core::{parse, AppState};
347 /// use dampen_macros::UiModel;
348 ///
349 /// #[derive(UiModel, Default)]
350 /// struct MyModel {
351 /// count: i32,
352 /// }
353 ///
354 /// let xml_v1 = r#"<column><text value="Old UI" /></column>"#;
355 /// let document_v1 = parse(xml_v1).unwrap();
356 /// let mut state = AppState::with_model(document_v1, MyModel { count: 42 });
357 ///
358 /// // Later, the UI file changes...
359 /// let xml_v2 = r#"<column><text value="New UI" /></column>"#;
360 /// let document_v2 = parse(xml_v2).unwrap();
361 /// state.hot_reload(document_v2);
362 ///
363 /// // Model state (count: 42) is preserved
364 /// assert_eq!(state.model.count, 42);
365 /// ```
366 pub fn hot_reload(&mut self, new_document: DampenDocument) {
367 self.document = new_document;
368 }
369}