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}