Skip to main content

dampen_core/handler/
mod.rs

1//! Handler system for event dispatch
2
3use std::any::Any;
4use std::collections::{HashMap, HashSet};
5use std::sync::{Arc, RwLock};
6
7/// Registry of event handlers
8#[derive(Clone, Debug)]
9pub struct HandlerRegistry {
10    handlers: Arc<RwLock<HashMap<String, HandlerEntry>>>,
11}
12
13/// Entry in the handler registry
14#[derive(Clone)]
15#[allow(clippy::type_complexity)]
16pub enum HandlerEntry {
17    // ============================================
18    // Existing variants (unchanged for compatibility)
19    // ============================================
20    /// Simple handler: `fn(&mut Model)`
21    Simple(Arc<dyn Fn(&mut dyn Any) + Send + Sync>),
22
23    /// Handler with value: `fn(&mut Model, T)`
24    WithValue(Arc<dyn Fn(&mut dyn Any, Box<dyn Any>) + Send + Sync>),
25
26    /// Handler returning command: `fn(&mut Model) -> Command<Message>`
27    WithCommand(Arc<dyn Fn(&mut dyn Any) -> Box<dyn Any> + Send + Sync>),
28
29    // ============================================
30    // New variants for shared state access
31    // ============================================
32    /// Handler with shared context: `fn(&mut Model, &SharedContext<S>)`
33    ///
34    /// Use when the handler needs to read or write shared state.
35    WithShared(Arc<dyn Fn(&mut dyn Any, &dyn Any) + Send + Sync>),
36
37    /// Handler with value and shared: `fn(&mut Model, T, &SharedContext<S>)`
38    ///
39    /// Use when the handler receives input value and needs shared state.
40    WithValueAndShared(Arc<dyn Fn(&mut dyn Any, Box<dyn Any>, &dyn Any) + Send + Sync>),
41
42    /// Handler with command and shared: `fn(&mut Model, &SharedContext<S>) -> Command`
43    ///
44    /// Use when the handler needs shared state and returns a command.
45    WithCommandAndShared(Arc<dyn Fn(&mut dyn Any, &dyn Any) -> Box<dyn Any> + Send + Sync>),
46
47    /// Handler with canvas event: `fn(&mut Model, CanvasEvent)`
48    WithCanvasEvent(Arc<dyn Fn(&mut dyn Any, CanvasEvent) + Send + Sync>),
49}
50
51impl std::fmt::Debug for HandlerEntry {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            HandlerEntry::Simple(_) => f.write_str("Simple(handler)"),
55            HandlerEntry::WithValue(_) => f.write_str("WithValue(handler)"),
56            HandlerEntry::WithCommand(_) => f.write_str("WithCommand(handler)"),
57            HandlerEntry::WithShared(_) => f.write_str("WithShared(handler)"),
58            HandlerEntry::WithValueAndShared(_) => f.write_str("WithValueAndShared(handler)"),
59            HandlerEntry::WithCommandAndShared(_) => f.write_str("WithCommandAndShared(handler)"),
60            HandlerEntry::WithCanvasEvent(_) => f.write_str("WithCanvasEvent(handler)"),
61        }
62    }
63}
64
65impl HandlerRegistry {
66    /// Create a new empty handler registry
67    pub fn new() -> Self {
68        Self {
69            handlers: Arc::new(RwLock::new(HashMap::new())),
70        }
71    }
72
73    /// Register a simple handler
74    pub fn register_simple<F>(&self, name: &str, handler: F)
75    where
76        F: Fn(&mut dyn Any) + Send + Sync + 'static,
77    {
78        if let Ok(mut handlers) = self.handlers.write() {
79            handlers.insert(name.to_string(), HandlerEntry::Simple(Arc::new(handler)));
80        }
81    }
82
83    /// Register a handler with a value parameter
84    pub fn register_with_value<F>(&self, name: &str, handler: F)
85    where
86        F: Fn(&mut dyn Any, Box<dyn Any>) + Send + Sync + 'static,
87    {
88        if let Ok(mut handlers) = self.handlers.write() {
89            handlers.insert(name.to_string(), HandlerEntry::WithValue(Arc::new(handler)));
90        }
91    }
92
93    /// Register a handler that returns a command
94    pub fn register_with_command<F>(&self, name: &str, handler: F)
95    where
96        F: Fn(&mut dyn Any) -> Box<dyn Any> + Send + Sync + 'static,
97    {
98        if let Ok(mut handlers) = self.handlers.write() {
99            handlers.insert(
100                name.to_string(),
101                HandlerEntry::WithCommand(Arc::new(handler)),
102            );
103        }
104    }
105
106    // ============================================
107    // New registration methods for shared state
108    // ============================================
109
110    /// Register a handler that receives shared context.
111    ///
112    /// Use this for handlers that need to read or modify shared state
113    /// that is accessible across multiple views.
114    ///
115    /// # Arguments
116    ///
117    /// * `name` - Handler name (referenced in XML `on_click="name"`)
118    /// * `handler` - Function that receives `(&mut Model, &SharedContext<S>)`
119    ///
120    /// # Example
121    ///
122    /// ```rust,ignore
123    /// use dampen_core::HandlerRegistry;
124    ///
125    /// let registry = HandlerRegistry::new();
126    /// registry.register_with_shared("update_theme", |model, shared| {
127    ///     let model = model.downcast_mut::<Model>().unwrap();
128    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
129    ///     shared.write().theme = model.selected_theme.clone();
130    /// });
131    /// ```
132    pub fn register_with_shared<F>(&self, name: &str, handler: F)
133    where
134        F: Fn(&mut dyn Any, &dyn Any) + Send + Sync + 'static,
135    {
136        if let Ok(mut handlers) = self.handlers.write() {
137            handlers.insert(
138                name.to_string(),
139                HandlerEntry::WithShared(Arc::new(handler)),
140            );
141        }
142    }
143
144    /// Register a handler with both a value parameter and shared context.
145    ///
146    /// Use this for handlers that receive input (like text field values)
147    /// and also need access to shared state.
148    ///
149    /// # Arguments
150    ///
151    /// * `name` - Handler name (referenced in XML `on_change="name"`)
152    /// * `handler` - Function that receives `(&mut Model, Box<dyn Any>, &SharedContext<S>)`
153    ///
154    /// # Example
155    ///
156    /// ```rust,ignore
157    /// use dampen_core::HandlerRegistry;
158    ///
159    /// let registry = HandlerRegistry::new();
160    /// registry.register_with_value_and_shared("set_username", |model, value, shared| {
161    ///     let model = model.downcast_mut::<Model>().unwrap();
162    ///     let name = value.downcast_ref::<String>().unwrap();
163    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
164    ///     shared.write().username = name.clone();
165    /// });
166    /// ```
167    pub fn register_with_value_and_shared<F>(&self, name: &str, handler: F)
168    where
169        F: Fn(&mut dyn Any, Box<dyn Any>, &dyn Any) + Send + Sync + 'static,
170    {
171        if let Ok(mut handlers) = self.handlers.write() {
172            handlers.insert(
173                name.to_string(),
174                HandlerEntry::WithValueAndShared(Arc::new(handler)),
175            );
176        }
177    }
178
179    /// Register a handler that receives shared context and returns a command.
180    ///
181    /// Use this for async handlers that need shared state access.
182    ///
183    /// # Arguments
184    ///
185    /// * `name` - Handler name (referenced in XML `on_click="name"`)
186    /// * `handler` - Function that receives `(&mut Model, &SharedContext<S>) -> Command`
187    ///
188    /// # Example
189    ///
190    /// ```rust,ignore
191    /// use dampen_core::HandlerRegistry;
192    ///
193    /// let registry = HandlerRegistry::new();
194    /// registry.register_with_command_and_shared("sync_settings", |model, shared| {
195    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
196    ///     let settings = shared.read().clone();
197    ///     Box::new(Task::perform(save_settings(settings), Message::SettingsSaved))
198    /// });
199    /// ```
200    pub fn register_with_command_and_shared<F>(&self, name: &str, handler: F)
201    where
202        F: Fn(&mut dyn Any, &dyn Any) -> Box<dyn Any> + Send + Sync + 'static,
203    {
204        if let Ok(mut handlers) = self.handlers.write() {
205            handlers.insert(
206                name.to_string(),
207                HandlerEntry::WithCommandAndShared(Arc::new(handler)),
208            );
209        }
210    }
211
212    /// Register a handler that receives a canvas event
213    pub fn register_canvas_event<F>(&self, name: &str, handler: F)
214    where
215        F: Fn(&mut dyn Any, CanvasEvent) + Send + Sync + 'static,
216    {
217        if let Ok(mut handlers) = self.handlers.write() {
218            handlers.insert(
219                name.to_string(),
220                HandlerEntry::WithCanvasEvent(Arc::new(handler)),
221            );
222        }
223    }
224
225    /// Look up a handler by name
226    pub fn get(&self, name: &str) -> Option<HandlerEntry> {
227        self.handlers.read().ok()?.get(name).cloned()
228    }
229
230    /// Dispatches a handler by name, executing it with the provided model and optional value.
231    ///
232    /// This is a convenience method that combines `get()` and handler invocation.
233    /// For handlers that require shared state, use [`dispatch_with_shared`](Self::dispatch_with_shared)
234    /// instead.
235    ///
236    /// # Arguments
237    ///
238    /// * `handler_name` - Name of the handler to dispatch
239    /// * `model` - Mutable reference to the model (as `&mut dyn Any`)
240    /// * `value` - Optional string value passed to WithValue handlers
241    ///
242    /// # Note
243    ///
244    /// This method does NOT support `WithShared`, `WithValueAndShared`, or `WithCommandAndShared`
245    /// handlers. Those handlers will be silently ignored. Use `dispatch_with_shared` instead.
246    ///
247    /// # Example
248    ///
249    /// ```rust,ignore
250    /// use dampen_core::HandlerRegistry;
251    ///
252    /// let registry = HandlerRegistry::new();
253    /// registry.register_simple("greet", |model| {
254    ///     let model = model.downcast_mut::<MyModel>().unwrap();
255    ///     model.count += 1;
256    /// });
257    ///
258    /// let model = &mut MyModel { count: 0 } as &mut dyn std::any::Any;
259    /// registry.dispatch("greet", model, None);
260    /// ```
261    pub fn dispatch(&self, handler_name: &str, model: &mut dyn Any, value: Option<String>) {
262        if let Some(entry) = self.get(handler_name) {
263            match entry {
264                HandlerEntry::Simple(h) => h(model),
265                HandlerEntry::WithValue(h) => {
266                    let val = value.unwrap_or_default();
267                    h(model, Box::new(val));
268                }
269                HandlerEntry::WithCommand(h) => {
270                    h(model);
271                }
272                // Shared handlers require shared context - silently skip in dispatch()
273                HandlerEntry::WithShared(_)
274                | HandlerEntry::WithValueAndShared(_)
275                | HandlerEntry::WithCommandAndShared(_)
276                | HandlerEntry::WithCanvasEvent(_) => {
277                    // These handlers require shared context or event data. Use specific dispatch methods.
278                }
279            }
280        }
281    }
282
283    /// Dispatches a handler by name and returns any command/task it produces.
284    ///
285    /// This method is similar to `dispatch()` but returns the command/task from
286    /// `WithCommand` handlers instead of discarding it. This is essential for
287    /// integrating with the Elm/MVU pattern where handlers can return tasks.
288    ///
289    /// For handlers that require shared state, use [`dispatch_with_shared`](Self::dispatch_with_shared)
290    /// instead.
291    ///
292    /// # Arguments
293    ///
294    /// * `handler_name` - Name of the handler to dispatch
295    /// * `model` - Mutable reference to the model (as `&mut dyn Any`)
296    /// * `value` - Optional string value passed to WithValue handlers
297    ///
298    /// # Returns
299    ///
300    /// * `Some(Box<dyn Any>)` - The command/task from a `WithCommand` handler
301    /// * `None` - For `Simple` and `WithValue` handlers, or if handler not found
302    ///
303    /// # Note
304    ///
305    /// This method does NOT support shared handlers. Use `dispatch_with_shared` instead.
306    ///
307    /// # Example
308    ///
309    /// ```rust,ignore
310    /// use dampen_core::HandlerRegistry;
311    /// use iced::Task;
312    ///
313    /// let registry = HandlerRegistry::new();
314    /// registry.register_with_command("navigate", |model| {
315    ///     let model = model.downcast_mut::<MyModel>().unwrap();
316    ///     Box::new(Task::done(Message::SwitchView))
317    /// });
318    ///
319    /// let model = &mut MyModel::default() as &mut dyn std::any::Any;
320    /// if let Some(boxed_task) = registry.dispatch_with_command("navigate", model, None) {
321    ///     if let Ok(task) = boxed_task.downcast::<Task<Message>>() {
322    ///         return *task;
323    ///     }
324    /// }
325    /// ```
326    pub fn dispatch_with_command(
327        &self,
328        handler_name: &str,
329        model: &mut dyn Any,
330        value: Option<String>,
331    ) -> Option<Box<dyn Any>> {
332        if let Some(entry) = self.get(handler_name) {
333            match entry {
334                HandlerEntry::Simple(h) => {
335                    h(model);
336                    None
337                }
338                HandlerEntry::WithValue(h) => {
339                    let val = value.unwrap_or_default();
340                    h(model, Box::new(val));
341                    None
342                }
343                HandlerEntry::WithCommand(h) => Some(h(model)),
344                // Shared handlers require shared context - not supported here
345                HandlerEntry::WithShared(_)
346                | HandlerEntry::WithValueAndShared(_)
347                | HandlerEntry::WithCommandAndShared(_)
348                | HandlerEntry::WithCanvasEvent(_) => None,
349            }
350        } else {
351            None
352        }
353    }
354
355    /// Dispatches a handler with shared context and returns any command it produces.
356    ///
357    /// This is the primary dispatch method for applications using shared state.
358    /// It handles all handler variants, passing the shared context to variants
359    /// that expect it.
360    ///
361    /// # Arguments
362    ///
363    /// * `handler_name` - Name of the handler to dispatch
364    /// * `model` - Mutable reference to the local model (as `&mut dyn Any`)
365    /// * `shared` - Reference to the shared context (as `&dyn Any`)
366    /// * `value` - Optional string value passed to WithValue/WithValueAndShared handlers
367    ///
368    /// # Returns
369    ///
370    /// * `Some(Box<dyn Any>)` - The command from `WithCommand` or `WithCommandAndShared` handlers
371    /// * `None` - For simple handlers, value handlers, or if handler not found
372    ///
373    /// # Example
374    ///
375    /// ```rust,ignore
376    /// use dampen_core::HandlerRegistry;
377    ///
378    /// let registry = HandlerRegistry::new();
379    /// registry.register_with_shared("toggle_theme", |model, shared| {
380    ///     let shared = shared.downcast_ref::<SharedContext<SharedState>>().unwrap();
381    ///     let current = shared.read().dark_mode;
382    ///     shared.write().dark_mode = !current;
383    /// });
384    ///
385    /// let model = &mut Model::default() as &mut dyn std::any::Any;
386    /// let shared = &shared_context as &dyn std::any::Any;
387    /// registry.dispatch_with_shared("toggle_theme", model, shared, None);
388    /// ```
389    pub fn dispatch_with_shared(
390        &self,
391        handler_name: &str,
392        model: &mut dyn Any,
393        shared: &dyn Any,
394        value: Option<String>,
395    ) -> Option<Box<dyn Any>> {
396        let entry = self.get(handler_name)?;
397
398        match entry {
399            // Existing variants (backward compatible - ignore shared)
400            HandlerEntry::Simple(h) => {
401                h(model);
402                None
403            }
404            HandlerEntry::WithValue(h) => {
405                h(model, Box::new(value.unwrap_or_default()));
406                None
407            }
408            HandlerEntry::WithCommand(h) => Some(h(model)),
409
410            // New shared variants
411            HandlerEntry::WithShared(h) => {
412                h(model, shared);
413                None
414            }
415            HandlerEntry::WithValueAndShared(h) => {
416                h(model, Box::new(value.unwrap_or_default()), shared);
417                None
418            }
419            HandlerEntry::WithCommandAndShared(h) => Some(h(model, shared)),
420            // Canvas handlers require event data - ignore here
421            HandlerEntry::WithCanvasEvent(_) => None,
422        }
423    }
424
425    /// Dispatch a canvas event handler
426    pub fn dispatch_canvas_event(
427        &self,
428        handler_name: &str,
429        model: &mut dyn Any,
430        event: CanvasEvent,
431    ) {
432        if let Some(HandlerEntry::WithCanvasEvent(h)) = self.get(handler_name) {
433            h(model, event);
434        }
435    }
436
437    /// Check if a handler exists
438    pub fn contains(&self, name: &str) -> bool {
439        if let Ok(handlers) = self.handlers.read() {
440            handlers.contains_key(name)
441        } else {
442            false
443        }
444    }
445}
446
447impl Default for HandlerRegistry {
448    fn default() -> Self {
449        Self::new()
450    }
451}
452
453/// Handler metadata for compile-time validation
454#[derive(Debug, Clone, PartialEq)]
455pub struct HandlerSignature {
456    /// Handler name
457    pub name: String,
458
459    /// Parameter type if applicable
460    pub param_type: Option<String>,
461
462    /// Whether handler returns Command
463    pub returns_command: bool,
464}
465
466/// Build-time analysis structure for circular dependency detection
467///
468/// **Feature T130 - Not Yet Integrated**
469///
470/// This structure is used for static analysis of handler dependencies to detect
471/// circular call chains at compile time. It is not yet integrated into the
472/// handler registration or dispatch system.
473///
474/// # Example
475///
476/// ```rust,ignore
477/// use dampen_core::handler::HandlerCallGraph;
478///
479/// let mut graph = HandlerCallGraph::new();
480/// graph.add_dependency("handler_a", "handler_b");
481/// graph.add_dependency("handler_b", "handler_c");
482///
483/// if let Some(cycle) = graph.detect_cycles() {
484///     println!("Circular dependency detected: {:?}", cycle);
485/// }
486/// ```
487#[allow(dead_code)]
488#[derive(Debug, Clone)]
489pub struct HandlerCallGraph {
490    /// Map of handler name to its dependencies
491    dependencies: HashMap<String, Vec<String>>,
492}
493
494impl HandlerCallGraph {
495    /// Create a new empty call graph
496    pub fn new() -> Self {
497        Self {
498            dependencies: HashMap::new(),
499        }
500    }
501
502    /// Add a dependency edge (from depends on to)
503    pub fn add_dependency(&mut self, from: &str, to: &str) {
504        self.dependencies
505            .entry(from.to_string())
506            .or_default()
507            .push(to.to_string());
508    }
509
510    /// Detect if adding edge would create a cycle
511    pub fn would_create_cycle(&self, from: &str, to: &str) -> bool {
512        // Check if 'to' can reach 'from' (which would create a cycle)
513        let mut visited = HashSet::new();
514        self.can_reach(to, from, &mut visited)
515    }
516
517    /// Check if 'from' can reach 'to' via dependencies
518    fn can_reach(&self, from: &str, to: &str, visited: &mut HashSet<String>) -> bool {
519        if from == to {
520            return true;
521        }
522
523        if visited.contains(from) {
524            return false;
525        }
526
527        visited.insert(from.to_string());
528
529        if let Some(deps) = self.dependencies.get(from) {
530            for dep in deps {
531                if self.can_reach(dep, to, visited) {
532                    return true;
533                }
534            }
535        }
536
537        false
538    }
539
540    /// Get all handlers that depend on the given handler
541    pub fn dependents_of(&self, handler: &str) -> Vec<String> {
542        self.dependencies
543            .iter()
544            .filter_map(|(k, v)| {
545                if v.contains(&handler.to_string()) {
546                    Some(k.clone())
547                } else {
548                    None
549                }
550            })
551            .collect()
552    }
553
554    /// Detect cycles in the call graph using DFS
555    pub fn detect_cycles(&self) -> Option<Vec<String>> {
556        let mut visited = HashSet::new();
557        let mut recursion_stack = HashSet::new();
558        let mut path = Vec::new();
559
560        for handler in self.dependencies.keys() {
561            if !visited.contains(handler)
562                && let Some(cycle) =
563                    self.dfs_detect_cycle(handler, &mut visited, &mut recursion_stack, &mut path)
564            {
565                return Some(cycle);
566            }
567        }
568
569        None
570    }
571
572    fn dfs_detect_cycle(
573        &self,
574        handler: &str,
575        visited: &mut HashSet<String>,
576        recursion_stack: &mut HashSet<String>,
577        path: &mut Vec<String>,
578    ) -> Option<Vec<String>> {
579        visited.insert(handler.to_string());
580        recursion_stack.insert(handler.to_string());
581        path.push(handler.to_string());
582
583        if let Some(deps) = self.dependencies.get(handler) {
584            for dep in deps {
585                if !visited.contains(dep) {
586                    if let Some(cycle) = self.dfs_detect_cycle(dep, visited, recursion_stack, path)
587                    {
588                        return Some(cycle);
589                    }
590                } else if recursion_stack.contains(dep) {
591                    // Found a cycle - construct the cycle path
592                    if let Some(cycle_start) = path.iter().position(|h| h == dep) {
593                        let mut cycle = path[cycle_start..].to_vec();
594                        cycle.push(dep.to_string());
595                        return Some(cycle);
596                    }
597                }
598            }
599        }
600
601        path.pop();
602        recursion_stack.remove(handler);
603        None
604    }
605}
606
607impl Default for HandlerCallGraph {
608    fn default() -> Self {
609        Self::new()
610    }
611}
612
613/// Canvas event data passed to handlers
614#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
615pub struct CanvasEvent {
616    pub kind: CanvasEventKind,
617    pub x: f32,
618    pub y: f32,
619    pub delta_x: Option<f32>,
620    pub delta_y: Option<f32>,
621}
622
623/// Type of canvas interaction
624#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
625pub enum CanvasEventKind {
626    Click,
627    Drag,
628    Move,
629    Release,
630}