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