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>` 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//!
13//! # Examples
14//!
15//! Basic usage with document only:
16//!
17//! ```rust,ignore
18//! use dampen_core::{parse, AppState};
19//!
20//! let xml = r#"<column><text value="Hello!" /></column>"#;
21//! let document = parse(xml).unwrap();
22//! let state = AppState::new(document);
23//! ```
24//!
25//! With a custom model:
26//!
27//! ```rust,ignore
28//! use dampen_core::{parse, AppState};
29//! use dampen_macros::UiModel;
30//!
31//! #[derive(UiModel, Default)]
32//! struct MyModel {
33//! count: i32,
34//! }
35//!
36//! let xml = r#"<column><text value="Hello!" /></column>"#;
37//! let document = parse(xml).unwrap();
38//! let state = AppState::with_model(document, MyModel { count: 0 });
39//! ```
40//!
41//! # See Also
42//!
43//! - [`DampenDocument`](crate::ir::DampenDocument) - The parsed UI document
44//! - [`HandlerRegistry`](crate::handler::HandlerRegistry) - Event handler registry
45//! - [`UiBindable`](crate::binding::UiBindable) - Trait for bindable models
46
47use std::marker::PhantomData;
48
49use crate::{binding::UiBindable, handler::HandlerRegistry, ir::DampenDocument};
50
51/// Application state container for a Dampen UI view.
52///
53/// This struct combines the parsed UI document with application state and event handlers.
54/// It is the central state structure used throughout Dampen applications.
55///
56/// # Type Parameters
57///
58/// * `M` - The model type implementing [`UiBindable`](crate::binding::UiBindable). Defaults to unit type `()`.
59///
60/// # Fields
61///
62/// * `document` - The parsed UI document containing widget tree and themes
63/// * `model` - Application state model for data bindings
64/// * `handler_registry` - Registry of event handlers for UI interactions
65/// * `_marker` - Type marker to capture the generic parameter
66#[derive(Debug, Clone)]
67pub struct AppState<M: UiBindable = ()> {
68 /// The parsed UI document containing widget tree and themes.
69 pub document: DampenDocument,
70
71 /// Application state model for data bindings.
72 /// Generic over `UiBindable` for type-safe field access.
73 pub model: M,
74
75 /// Registry of event handlers for UI interactions.
76 pub handler_registry: HandlerRegistry,
77
78 /// Type marker to capture the generic parameter.
79 _marker: PhantomData<M>,
80}
81
82impl<M: UiBindable> AppState<M> {
83 /// Creates a new AppState with default model and empty handler registry.
84 ///
85 /// # Examples
86 ///
87 /// ```rust,ignore
88 /// use dampen_core::{parse, AppState};
89 ///
90 /// let xml = r#"<column><text value="Hello!" /></column>"#;
91 /// let document = parse(xml).unwrap();
92 /// let state = AppState::new(document);
93 /// ```
94 pub fn new(document: DampenDocument) -> Self
95 where
96 M: Default,
97 {
98 Self {
99 document,
100 model: M::default(),
101 handler_registry: HandlerRegistry::default(),
102 _marker: PhantomData,
103 }
104 }
105
106 /// Creates an AppState with a custom model and default handler registry.
107 ///
108 /// # Examples
109 ///
110 /// ```rust,ignore
111 /// use dampen_core::{parse, AppState};
112 /// use dampen_macros::UiModel;
113 ///
114 /// #[derive(UiModel, Default)]
115 /// struct MyModel {
116 /// count: i32,
117 /// }
118 ///
119 /// let xml = r#"<column><text value="Hello!" /></column>"#;
120 /// let document = parse(xml).unwrap();
121 /// let model = MyModel { count: 42 };
122 /// let state = AppState::with_model(document, model);
123 /// ```
124 pub fn with_model(document: DampenDocument, model: M) -> Self {
125 Self {
126 document,
127 model,
128 handler_registry: HandlerRegistry::default(),
129 _marker: PhantomData,
130 }
131 }
132
133 /// Creates an AppState with a custom handler registry and default model.
134 ///
135 /// # Examples
136 ///
137 /// ```rust,ignore
138 /// use dampen_core::{parse, AppState, HandlerRegistry};
139 ///
140 /// let xml = r#"<column><text value="Hello!" /></column>"#;
141 /// let document = parse(xml).unwrap();
142 /// let mut registry = HandlerRegistry::new();
143 /// registry.register_simple("greet", |_model| {
144 /// println!("Button clicked!");
145 /// });
146 /// let state = AppState::with_handlers(document, registry);
147 /// ```
148 pub fn with_handlers(document: DampenDocument, handler_registry: HandlerRegistry) -> Self
149 where
150 M: Default,
151 {
152 Self {
153 document,
154 model: M::default(),
155 handler_registry,
156 _marker: PhantomData,
157 }
158 }
159
160 /// Creates an AppState with custom model and handler registry.
161 ///
162 /// This is the most flexible constructor, allowing you to specify all components
163 /// of the application state. Useful for hot-reload scenarios where both model
164 /// and handlers need to be specified.
165 ///
166 /// # Examples
167 ///
168 /// ```rust,ignore
169 /// use dampen_core::{parse, AppState, HandlerRegistry};
170 /// use dampen_macros::UiModel;
171 ///
172 /// #[derive(UiModel, Default)]
173 /// struct MyModel {
174 /// count: i32,
175 /// }
176 ///
177 /// let xml = r#"<column><text value="Hello!" /></column>"#;
178 /// let document = parse(xml).unwrap();
179 /// let model = MyModel { count: 42 };
180 /// let mut registry = HandlerRegistry::new();
181 /// registry.register_simple("increment", |model| {
182 /// let model = model.downcast_mut::<MyModel>().unwrap();
183 /// model.count += 1;
184 /// });
185 /// let state = AppState::with_all(document, model, registry);
186 /// ```
187 pub fn with_all(document: DampenDocument, model: M, handler_registry: HandlerRegistry) -> Self {
188 Self {
189 document,
190 model,
191 handler_registry,
192 _marker: PhantomData,
193 }
194 }
195
196 /// Hot-reload: updates the UI document while preserving the model and handlers.
197 ///
198 /// This method is designed for development mode hot-reload scenarios where the UI
199 /// definition (XML) changes but the application state (model) should be preserved.
200 ///
201 /// # Examples
202 ///
203 /// ```rust,ignore
204 /// use dampen_core::{parse, AppState};
205 /// use dampen_macros::UiModel;
206 ///
207 /// #[derive(UiModel, Default)]
208 /// struct MyModel {
209 /// count: i32,
210 /// }
211 ///
212 /// let xml_v1 = r#"<column><text value="Old UI" /></column>"#;
213 /// let document_v1 = parse(xml_v1).unwrap();
214 /// let mut state = AppState::with_model(document_v1, MyModel { count: 42 });
215 ///
216 /// // Later, the UI file changes...
217 /// let xml_v2 = r#"<column><text value="New UI" /></column>"#;
218 /// let document_v2 = parse(xml_v2).unwrap();
219 /// state.hot_reload(document_v2);
220 ///
221 /// // Model state (count: 42) is preserved
222 /// assert_eq!(state.model.count, 42);
223 /// ```
224 pub fn hot_reload(&mut self, new_document: DampenDocument) {
225 self.document = new_document;
226 }
227}