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}