1use crate::intent::{Intent, IntentResponse};
18use crate::signal::Prop;
19use crate::widget::EventContext;
20
21pub type ActionHandler = Box<dyn FnMut(&Intent, &mut EventContext) -> IntentResponse + 'static>;
23
24pub struct Action {
26 pub intent: &'static str,
29 pub handler: ActionHandler,
31 pub enabled_when: Option<Prop<bool>>,
40}
41
42impl std::fmt::Debug for Action {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("Action")
45 .field("intent", &self.intent)
46 .field("handler", &"<closure>")
47 .field("enabled_when", &self.enabled_when.is_some())
48 .finish()
49 }
50}
51
52impl Action {
53 #[allow(clippy::new_ret_no_self)]
55 pub fn new(intent: &'static str) -> ActionBuilder {
56 ActionBuilder {
57 intent,
58 handler: None,
59 enabled_when: None,
60 }
61 }
62
63 pub fn is_enabled(&self) -> bool {
66 self.enabled_when.as_ref().map(|s| s.get()).unwrap_or(true)
67 }
68}
69
70pub struct ActionBuilder {
75 intent: &'static str,
76 handler: Option<ActionHandler>,
77 enabled_when: Option<Prop<bool>>,
78}
79
80impl ActionBuilder {
81 pub fn enabled_when(mut self, signal: impl Into<Prop<bool>>) -> Self {
84 self.enabled_when = Some(signal.into());
85 self
86 }
87
88 pub fn on_invoke(mut self, mut f: impl FnMut(&Intent, &mut EventContext) + 'static) -> Action {
92 self.handler = Some(Box::new(move |intent, ctx| {
93 f(intent, ctx);
94 IntentResponse::Handled
95 }));
96 self.finish()
97 }
98
99 pub fn on_invoke_with_response(
104 mut self,
105 f: impl FnMut(&Intent, &mut EventContext) -> IntentResponse + 'static,
106 ) -> Action {
107 self.handler = Some(Box::new(f));
108 self.finish()
109 }
110
111 fn finish(self) -> Action {
112 Action {
113 intent: self.intent,
114 handler: self
115 .handler
116 .expect("ActionBuilder requires on_invoke or on_invoke_with_response"),
117 enabled_when: self.enabled_when,
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use std::cell::Cell;
126 use std::rc::Rc;
127
128 #[test]
129 fn action_builder_on_invoke_defaults_to_handled() {
130 let fired = Rc::new(Cell::new(false));
131 let fired_flag = fired.clone();
132 let mut action = Action::new("app.save").on_invoke(move |_intent, _ctx| {
133 fired_flag.set(true);
134 });
135
136 let mut ctx = EventContext::new();
137 let intent = Intent::new("app.save");
138 let response = (action.handler)(&intent, &mut ctx);
139 assert_eq!(response, IntentResponse::Handled);
140 assert!(fired.get());
141 assert_eq!(action.intent, "app.save");
142 }
143
144 #[test]
145 fn action_handler_receives_typed_payload() {
146 let seen = Rc::new(Cell::new(0_i64));
147 let seen_flag = seen.clone();
148 let mut action = Action::new("tab.switch").on_invoke(move |intent, _ctx| {
149 if let Some(&n) = intent.payload::<i64>() {
150 seen_flag.set(n);
151 }
152 });
153
154 let mut ctx = EventContext::new();
155 let intent = Intent::with_payload("tab.switch", 5_i64);
156 (action.handler)(&intent, &mut ctx);
157 assert_eq!(seen.get(), 5);
158 }
159
160 #[test]
161 fn action_with_response_can_propagate() {
162 let mut action = Action::new("log.observe")
163 .on_invoke_with_response(|_intent, _ctx| IntentResponse::Propagated);
164 let mut ctx = EventContext::new();
165 let intent = Intent::new("log.observe");
166 let response = (action.handler)(&intent, &mut ctx);
167 assert_eq!(response, IntentResponse::Propagated);
168 }
169
170 #[test]
171 fn enabled_when_defaults_true() {
172 let action = Action::new("edit.delete").on_invoke(|_, _| {});
173 assert!(action.is_enabled());
174 }
175
176 #[test]
177 fn enabled_when_follows_signal() {
178 let enabled = crate::signal::Signal::new(false);
179 let action = Action::new("edit.delete")
180 .enabled_when(enabled.clone())
181 .on_invoke(|_, _| {});
182 assert!(!action.is_enabled());
183 enabled.set(true);
184 assert!(action.is_enabled());
185 }
186}