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#[derive(Debug, Clone)]
435pub struct HandlerCallGraph {
436 /// Map of handler name to its dependencies
437 dependencies: HashMap<String, Vec<String>>,
438}
439
440impl HandlerCallGraph {
441 /// Create a new empty call graph
442 pub fn new() -> Self {
443 Self {
444 dependencies: HashMap::new(),
445 }
446 }
447
448 /// Add a dependency edge (from depends on to)
449 pub fn add_dependency(&mut self, from: &str, to: &str) {
450 self.dependencies
451 .entry(from.to_string())
452 .or_default()
453 .push(to.to_string());
454 }
455
456 /// Detect if adding edge would create a cycle
457 pub fn would_create_cycle(&self, from: &str, to: &str) -> bool {
458 // Check if 'to' can reach 'from' (which would create a cycle)
459 let mut visited = HashSet::new();
460 self.can_reach(to, from, &mut visited)
461 }
462
463 /// Check if 'from' can reach 'to' via dependencies
464 fn can_reach(&self, from: &str, to: &str, visited: &mut HashSet<String>) -> bool {
465 if from == to {
466 return true;
467 }
468
469 if visited.contains(from) {
470 return false;
471 }
472
473 visited.insert(from.to_string());
474
475 if let Some(deps) = self.dependencies.get(from) {
476 for dep in deps {
477 if self.can_reach(dep, to, visited) {
478 return true;
479 }
480 }
481 }
482
483 false
484 }
485
486 /// Get all handlers that depend on the given handler
487 pub fn dependents_of(&self, handler: &str) -> Vec<String> {
488 self.dependencies
489 .iter()
490 .filter_map(|(k, v)| {
491 if v.contains(&handler.to_string()) {
492 Some(k.clone())
493 } else {
494 None
495 }
496 })
497 .collect()
498 }
499
500 /// Detect cycles in the call graph using DFS
501 pub fn detect_cycles(&self) -> Option<Vec<String>> {
502 let mut visited = HashSet::new();
503 let mut recursion_stack = HashSet::new();
504 let mut path = Vec::new();
505
506 for handler in self.dependencies.keys() {
507 if !visited.contains(handler) {
508 if let Some(cycle) =
509 self.dfs_detect_cycle(handler, &mut visited, &mut recursion_stack, &mut path)
510 {
511 return Some(cycle);
512 }
513 }
514 }
515
516 None
517 }
518
519 fn dfs_detect_cycle(
520 &self,
521 handler: &str,
522 visited: &mut HashSet<String>,
523 recursion_stack: &mut HashSet<String>,
524 path: &mut Vec<String>,
525 ) -> Option<Vec<String>> {
526 visited.insert(handler.to_string());
527 recursion_stack.insert(handler.to_string());
528 path.push(handler.to_string());
529
530 if let Some(deps) = self.dependencies.get(handler) {
531 for dep in deps {
532 if !visited.contains(dep) {
533 if let Some(cycle) = self.dfs_detect_cycle(dep, visited, recursion_stack, path)
534 {
535 return Some(cycle);
536 }
537 } else if recursion_stack.contains(dep) {
538 // Found a cycle - construct the cycle path
539 if let Some(cycle_start) = path.iter().position(|h| h == dep) {
540 let mut cycle = path[cycle_start..].to_vec();
541 cycle.push(dep.to_string());
542 return Some(cycle);
543 }
544 }
545 }
546 }
547
548 path.pop();
549 recursion_stack.remove(handler);
550 None
551 }
552}
553
554impl Default for HandlerCallGraph {
555 fn default() -> Self {
556 Self::new()
557 }
558}