dampen-core 0.3.2

Core parser, IR, and traits for Dampen UI framework
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//! Application state container for Dampen UI views.
//!
//! This module provides the [`AppState`] struct that combines a parsed UI document
//! with application state and event handlers into a cohesive structure.
//!
//! # Overview
//!
//! `AppState<M, S>` is a generic container where:
//! - `document`: The parsed [`DampenDocument`] (mandatory)
//! - `model`: Application state model implementing [`UiBindable`] (optional, defaults to `()`)
//! - `handler_registry`: Event handler registry (optional, defaults to empty)
//! - `shared_context`: Optional reference to shared state across views (defaults to `None`)
//!
//! # Examples
//!
//! Basic usage with document only:
//!
//! ```rust,ignore
//! use dampen_core::{parse, AppState};
//!
//! let xml = r#"<column><text value="Hello!" /></column>"#;
//! let document = parse(xml).unwrap();
//! let state = AppState::new(document);
//! ```
//!
//! With a custom model:
//!
//! ```rust,ignore
//! use dampen_core::{parse, AppState};
//! use dampen_macros::UiModel;
//!
//! #[derive(UiModel, Default)]
//! struct MyModel {
//!     count: i32,
//! }
//!
//! let xml = r#"<column><text value="Hello!" /></column>"#;
//! let document = parse(xml).unwrap();
//! let state = AppState::with_model(document, MyModel { count: 0 });
//! ```
//!
//! With shared state for inter-window communication:
//!
//! ```rust,ignore
//! use dampen_core::{parse, AppState, SharedContext};
//! use dampen_macros::UiModel;
//!
//! #[derive(UiModel, Default)]
//! struct MyModel { count: i32 }
//!
//! #[derive(UiModel, Default, Clone)]
//! struct SharedState { theme: String }
//!
//! let xml = r#"<column><text value="Hello!" /></column>"#;
//! let document = parse(xml).unwrap();
//! let shared = SharedContext::new(SharedState::default());
//! let state = AppState::with_shared(document, MyModel::default(), HandlerRegistry::new(), shared);
//! ```
//!
//! # See Also
//!
//! - [`DampenDocument`] - The parsed UI document
//! - [`HandlerRegistry`] - Event handler registry
//! - [`UiBindable`] - Trait for bindable models
//! - [`SharedContext`] - Shared state container

mod theme_context;

pub use theme_context::ThemeContext;

use std::marker::PhantomData;
use std::sync::{RwLockReadGuard, RwLockWriteGuard};

use crate::shared::SharedContext;
use crate::{binding::UiBindable, handler::HandlerRegistry, ir::DampenDocument};

/// Application state container for a Dampen UI view.
///
/// This struct combines the parsed UI document with application state and event handlers.
/// It is the central state structure used throughout Dampen applications.
///
/// # Type Parameters
///
/// * `M` - The local model type implementing [`UiBindable`]. Defaults to unit type `()`.
/// * `S` - The shared state type implementing [`UiBindable`] + `Send + Sync`. Defaults to unit type `()`.
///
/// # Backward Compatibility
///
/// For applications not using shared state, `S` defaults to `()` and
/// `shared_context` is `None`. All existing code continues to work unchanged.
///
/// # Fields
///
/// * `document` - The parsed UI document containing widget tree and themes
/// * `model` - Application state model for data bindings
/// * `handler_registry` - Registry of event handlers for UI interactions
/// * `shared_context` - Optional reference to shared state across views
/// * `theme_context` - Optional theme context for theming support
#[derive(Debug, Clone)]
pub struct AppState<M: UiBindable = (), S: UiBindable + Send + Sync + 'static = ()> {
    /// The parsed UI document containing widget tree and themes.
    pub document: DampenDocument,

    /// Application state model for data bindings.
    /// Generic over `UiBindable` for type-safe field access.
    pub model: M,

    /// Registry of event handlers for UI interactions.
    pub handler_registry: HandlerRegistry,

    /// Optional reference to shared context for inter-window communication.
    pub shared_context: Option<SharedContext<S>>,

    /// Optional theme context for theming support.
    /// None when no theme.dampen file is present.
    pub theme_context: Option<ThemeContext>,

    /// Type marker to capture the generic parameters.
    _marker: PhantomData<(M, S)>,
}

// ============================================
// Constructors for backward compatibility (M only, S = ())
// ============================================

impl<M: UiBindable> AppState<M, ()> {
    /// Creates a new AppState with default model and empty handler registry.
    ///
    /// This is the simplest constructor for static UIs that don't use data binding
    /// or shared state.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use dampen_core::{parse, AppState};
    ///
    /// let xml = r#"<column><text value="Hello!" /></column>"#;
    /// let document = parse(xml).unwrap();
    /// let state = AppState::new(document);
    /// ```
    pub fn new(document: DampenDocument) -> Self
    where
        M: Default,
    {
        Self {
            document,
            model: M::default(),
            handler_registry: HandlerRegistry::default(),
            shared_context: None,
            theme_context: None,
            _marker: PhantomData,
        }
    }

    /// Creates an AppState with a custom model and default handler registry.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use dampen_core::{parse, AppState};
    /// use dampen_macros::UiModel;
    ///
    /// #[derive(UiModel, Default)]
    /// struct MyModel {
    ///     count: i32,
    /// }
    ///
    /// let xml = r#"<column><text value="Hello!" /></column>"#;
    /// let document = parse(xml).unwrap();
    /// let model = MyModel { count: 42 };
    /// let state = AppState::with_model(document, model);
    /// ```
    pub fn with_model(document: DampenDocument, model: M) -> Self {
        Self {
            document,
            model,
            handler_registry: HandlerRegistry::default(),
            shared_context: None,
            theme_context: None,
            _marker: PhantomData,
        }
    }

    /// Creates an AppState with a custom handler registry and default model.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use dampen_core::{parse, AppState, HandlerRegistry};
    ///
    /// let xml = r#"<column><text value="Hello!" /></column>"#;
    /// let document = parse(xml).unwrap();
    /// let mut registry = HandlerRegistry::new();
    /// registry.register_simple("greet", |_model| {
    ///     println!("Button clicked!");
    /// });
    /// let state = AppState::with_handlers(document, registry);
    /// ```
    pub fn with_handlers(document: DampenDocument, handler_registry: HandlerRegistry) -> Self
    where
        M: Default,
    {
        Self {
            document,
            model: M::default(),
            handler_registry,
            shared_context: None,
            theme_context: None,
            _marker: PhantomData,
        }
    }

    /// Creates an AppState with custom model and handler registry.
    ///
    /// This is the most flexible constructor for apps without shared state,
    /// allowing you to specify all components of the application state.
    /// Useful for hot-reload scenarios where both model and handlers need to be specified.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use dampen_core::{parse, AppState, HandlerRegistry};
    /// use dampen_macros::UiModel;
    ///
    /// #[derive(UiModel, Default)]
    /// struct MyModel {
    ///     count: i32,
    /// }
    ///
    /// let xml = r#"<column><text value="Hello!" /></column>"#;
    /// let document = parse(xml).unwrap();
    /// let model = MyModel { count: 42 };
    /// let mut registry = HandlerRegistry::new();
    /// registry.register_simple("increment", |model| {
    ///     let model = model.downcast_mut::<MyModel>().unwrap();
    ///     model.count += 1;
    /// });
    /// let state = AppState::with_all(document, model, registry);
    /// ```
    pub fn with_all(document: DampenDocument, model: M, handler_registry: HandlerRegistry) -> Self {
        Self {
            document,
            model,
            handler_registry,
            shared_context: None,
            theme_context: None,
            _marker: PhantomData,
        }
    }
}

// ============================================
// Constructors and methods for shared state (M and S)
// ============================================

impl<M: UiBindable, S: UiBindable + Send + Sync + 'static> AppState<M, S> {
    /// Creates an AppState with shared context for inter-window communication.
    ///
    /// This constructor is used when your application needs to share state
    /// across multiple views.
    ///
    /// # Arguments
    ///
    /// * `document` - Parsed UI document
    /// * `model` - Local model for this view
    /// * `handler_registry` - Event handlers for this view
    /// * `shared_context` - Shared state accessible from all views
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use dampen_core::{parse, AppState, SharedContext, HandlerRegistry};
    /// use dampen_macros::UiModel;
    ///
    /// #[derive(UiModel, Default)]
    /// struct MyModel { count: i32 }
    ///
    /// #[derive(UiModel, Default, Clone)]
    /// struct SharedState { theme: String }
    ///
    /// let xml = r#"<column><text value="{shared.theme}" /></column>"#;
    /// let document = parse(xml).unwrap();
    /// let shared = SharedContext::new(SharedState { theme: "dark".to_string() });
    ///
    /// let state = AppState::with_shared(
    ///     document,
    ///     MyModel::default(),
    ///     HandlerRegistry::new(),
    ///     shared,
    /// );
    /// ```
    pub fn with_shared(
        document: DampenDocument,
        model: M,
        handler_registry: HandlerRegistry,
        shared_context: SharedContext<S>,
    ) -> Self {
        Self {
            document,
            model,
            handler_registry,
            shared_context: Some(shared_context),
            theme_context: None,
            _marker: PhantomData,
        }
    }

    /// Get read access to shared state (if configured).
    ///
    /// Returns `None` if this AppState was created without shared context.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// if let Some(guard) = state.shared() {
    ///     println!("Current theme: {}", guard.theme);
    /// }
    /// ```
    pub fn shared(&self) -> Option<RwLockReadGuard<'_, S>> {
        self.shared_context.as_ref().map(|ctx| ctx.read())
    }

    /// Get write access to shared state (if configured).
    ///
    /// Returns `None` if this AppState was created without shared context.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// if let Some(mut guard) = state.shared_mut() {
    ///     guard.theme = "light".to_string();
    /// }
    /// ```
    pub fn shared_mut(&self) -> Option<RwLockWriteGuard<'_, S>> {
        self.shared_context.as_ref().map(|ctx| ctx.write())
    }

    /// Check if this AppState has shared context configured.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// if state.has_shared() {
    ///     // Use shared state features
    /// }
    /// ```
    pub fn has_shared(&self) -> bool {
        self.shared_context.is_some()
    }

    /// Hot-reload: updates the UI document while preserving the model, handlers, and shared context.
    ///
    /// This method is designed for development mode hot-reload scenarios where the UI
    /// definition (XML) changes but the application state (model and shared state)
    /// should be preserved.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use dampen_core::{parse, AppState};
    /// use dampen_macros::UiModel;
    ///
    /// #[derive(UiModel, Default)]
    /// struct MyModel {
    ///     count: i32,
    /// }
    ///
    /// let xml_v1 = r#"<column><text value="Old UI" /></column>"#;
    /// let document_v1 = parse(xml_v1).unwrap();
    /// let mut state = AppState::with_model(document_v1, MyModel { count: 42 });
    ///
    /// // Later, the UI file changes...
    /// let xml_v2 = r#"<column><text value="New UI" /></column>"#;
    /// let document_v2 = parse(xml_v2).unwrap();
    /// state.hot_reload(document_v2);
    ///
    /// // Model state (count: 42) is preserved
    /// assert_eq!(state.model.count, 42);
    /// ```
    pub fn hot_reload(&mut self, new_document: DampenDocument) {
        self.document = new_document;
    }

    /// Set the theme context for this AppState.
    ///
    /// This is used by the application initialization code to load themes
    /// from theme.dampen files and apply them to the application.
    ///
    /// # Arguments
    ///
    /// * `theme_context` - The theme context to use for this application state
    pub fn set_theme_context(&mut self, theme_context: ThemeContext) {
        self.theme_context = Some(theme_context);
    }

    /// Get a reference to the theme context if available.
    ///
    /// # Returns
    ///
    /// Reference to the theme context, or None if no theme is loaded
    pub fn theme_context(&self) -> Option<&ThemeContext> {
        self.theme_context.as_ref()
    }

    /// Get a mutable reference to the theme context if available.
    ///
    /// # Returns
    ///
    /// Mutable reference to the theme context, or None if no theme is loaded
    pub fn theme_context_mut(&mut self) -> Option<&mut ThemeContext> {
        self.theme_context.as_mut()
    }
}