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}