1use fret_runtime::{ActionId, CommandId, Effect, Model, WeakModel};
2use fret_ui::ElementContext;
3use fret_ui::UiHost;
4use fret_ui::action::UiActionHostExt;
5
6use crate::command::ElementCommandGatingExt as _;
7use crate::primitives::roving_focus_group;
8
9use std::any::Any;
10use std::cell::RefCell;
11use std::rc::Rc;
12use std::sync::Arc;
13
14pub trait ActionHooksExt {
22 fn pressable_dispatch_command_if_enabled(&mut self, command: CommandId);
23
24 fn pressable_dispatch_command_if_enabled_opt(&mut self, command: Option<CommandId>);
25
26 fn pressable_dispatch_command_with_payload_if_enabled<T>(
27 &mut self,
28 command: CommandId,
29 payload: T,
30 ) where
31 T: Any + Send + Sync + Clone + 'static;
32
33 fn pressable_dispatch_command_with_payload_if_enabled_opt<T>(
34 &mut self,
35 command: Option<CommandId>,
36 payload: T,
37 ) where
38 T: Any + Send + Sync + Clone + 'static;
39
40 fn pressable_dispatch_command_with_payload_factory_if_enabled(
41 &mut self,
42 command: CommandId,
43 payload: Arc<dyn Fn() -> Box<dyn Any + Send + Sync> + 'static>,
44 );
45
46 fn pressable_dispatch_command_with_payload_factory_if_enabled_opt(
47 &mut self,
48 command: Option<CommandId>,
49 payload: Arc<dyn Fn() -> Box<dyn Any + Send + Sync> + 'static>,
50 );
51
52 fn pressable_dispatch_action_if_enabled(&mut self, action: ActionId);
53
54 fn pressable_dispatch_action_if_enabled_opt(&mut self, action: Option<ActionId>);
55
56 fn pressable_dispatch_action_with_payload_if_enabled<T>(
57 &mut self,
58 action: ActionId,
59 payload: T,
60 ) where
61 T: Any + Send + Sync + Clone + 'static;
62
63 fn pressable_dispatch_action_with_payload_if_enabled_opt<T>(
64 &mut self,
65 action: Option<ActionId>,
66 payload: T,
67 ) where
68 T: Any + Send + Sync + Clone + 'static;
69
70 fn pressable_update_model<T, F>(&mut self, model: &Model<T>, update: F)
71 where
72 T: 'static,
73 F: Fn(&mut T) + 'static;
74
75 fn pressable_update_weak_model<T, F>(&mut self, model: &WeakModel<T>, update: F)
76 where
77 T: 'static,
78 F: Fn(&mut T) + 'static;
79
80 fn pressable_set_model<T>(&mut self, model: &Model<T>, value: T)
81 where
82 T: Clone + 'static;
83
84 fn pressable_set_weak_model<T>(&mut self, model: &WeakModel<T>, value: T)
85 where
86 T: Clone + 'static;
87
88 fn pressable_toggle_bool(&mut self, model: &Model<bool>);
89
90 fn pressable_toggle_bool_weak(&mut self, model: &WeakModel<bool>);
91
92 fn pressable_set_bool(&mut self, model: &Model<bool>, value: bool);
93
94 fn pressable_set_bool_weak(&mut self, model: &WeakModel<bool>, value: bool);
95
96 fn pressable_set_arc_str(&mut self, model: &Model<Arc<str>>, value: Arc<str>);
97
98 fn pressable_set_arc_str_weak(&mut self, model: &WeakModel<Arc<str>>, value: Arc<str>);
99
100 fn pressable_set_option_arc_str(&mut self, model: &Model<Option<Arc<str>>>, value: Arc<str>);
101
102 fn pressable_set_option_arc_str_weak(
103 &mut self,
104 model: &WeakModel<Option<Arc<str>>>,
105 value: Arc<str>,
106 );
107
108 fn pressable_toggle_vec_arc_str(&mut self, model: &Model<Vec<Arc<str>>>, value: Arc<str>);
109
110 fn pressable_toggle_vec_arc_str_weak(
111 &mut self,
112 model: &WeakModel<Vec<Arc<str>>>,
113 value: Arc<str>,
114 );
115
116 fn dismissible_close_bool(&mut self, open: &Model<bool>);
117
118 fn dismissible_close_bool_weak(&mut self, open: &WeakModel<bool>);
119
120 #[track_caller]
121 fn roving_select_option_arc_str(
122 &mut self,
123 model: &Model<Option<Arc<str>>>,
124 values: Arc<[Arc<str>]>,
125 );
126
127 #[track_caller]
128 fn roving_select_option_arc_str_weak(
129 &mut self,
130 model: &WeakModel<Option<Arc<str>>>,
131 values: Arc<[Arc<str>]>,
132 );
133
134 #[track_caller]
135 fn roving_typeahead_first_char_arc_str(&mut self, labels: Arc<[Arc<str>]>);
136
137 #[track_caller]
138 fn roving_typeahead_prefix_arc_str(&mut self, labels: Arc<[Arc<str>]>, timeout_ticks: u64);
139
140 fn roving_nav_apg(&mut self);
145}
146
147impl<H: UiHost> ActionHooksExt for ElementContext<'_, H> {
148 fn pressable_dispatch_command_if_enabled(&mut self, command: CommandId) {
149 if !self.command_is_enabled(&command) {
150 return;
151 }
152 self.pressable_add_on_activate(Arc::new(move |host, acx, reason| {
153 host.record_pending_command_dispatch_source(acx, &command, reason);
154 host.dispatch_command(Some(acx.window), command.clone());
155 }));
156 }
157
158 fn pressable_dispatch_command_if_enabled_opt(&mut self, command: Option<CommandId>) {
159 let Some(command) = command else {
160 return;
161 };
162 self.pressable_dispatch_command_if_enabled(command);
163 }
164
165 fn pressable_dispatch_command_with_payload_if_enabled<T>(
166 &mut self,
167 command: CommandId,
168 payload: T,
169 ) where
170 T: Any + Send + Sync + Clone + 'static,
171 {
172 if !self.command_is_enabled(&command) {
173 return;
174 }
175 self.pressable_add_on_activate(Arc::new(move |host, acx, reason| {
176 host.record_pending_command_dispatch_source(acx, &command, reason);
177 host.record_pending_action_payload(acx, &command, Box::new(payload.clone()));
178 host.dispatch_command(Some(acx.window), command.clone());
179 }));
180 }
181
182 fn pressable_dispatch_command_with_payload_if_enabled_opt<T>(
183 &mut self,
184 command: Option<CommandId>,
185 payload: T,
186 ) where
187 T: Any + Send + Sync + Clone + 'static,
188 {
189 let Some(command) = command else {
190 return;
191 };
192 self.pressable_dispatch_command_with_payload_if_enabled(command, payload);
193 }
194
195 fn pressable_dispatch_command_with_payload_factory_if_enabled(
196 &mut self,
197 command: CommandId,
198 payload: Arc<dyn Fn() -> Box<dyn Any + Send + Sync> + 'static>,
199 ) {
200 if !self.command_is_enabled(&command) {
201 return;
202 }
203 self.pressable_add_on_activate(Arc::new(move |host, acx, reason| {
204 host.record_pending_command_dispatch_source(acx, &command, reason);
205 host.record_pending_action_payload(acx, &command, payload());
206 host.dispatch_command(Some(acx.window), command.clone());
207 }));
208 }
209
210 fn pressable_dispatch_command_with_payload_factory_if_enabled_opt(
211 &mut self,
212 command: Option<CommandId>,
213 payload: Arc<dyn Fn() -> Box<dyn Any + Send + Sync> + 'static>,
214 ) {
215 let Some(command) = command else {
216 return;
217 };
218 self.pressable_dispatch_command_with_payload_factory_if_enabled(command, payload);
219 }
220
221 fn pressable_dispatch_action_if_enabled(&mut self, action: ActionId) {
222 self.pressable_dispatch_command_if_enabled(action);
223 }
224
225 fn pressable_dispatch_action_if_enabled_opt(&mut self, action: Option<ActionId>) {
226 self.pressable_dispatch_command_if_enabled_opt(action);
227 }
228
229 fn pressable_dispatch_action_with_payload_if_enabled<T>(&mut self, action: ActionId, payload: T)
230 where
231 T: Any + Send + Sync + Clone + 'static,
232 {
233 self.pressable_dispatch_command_with_payload_if_enabled(action, payload);
234 }
235
236 fn pressable_dispatch_action_with_payload_if_enabled_opt<T>(
237 &mut self,
238 action: Option<ActionId>,
239 payload: T,
240 ) where
241 T: Any + Send + Sync + Clone + 'static,
242 {
243 self.pressable_dispatch_command_with_payload_if_enabled_opt(action, payload);
244 }
245
246 fn pressable_update_model<T, F>(&mut self, model: &Model<T>, update: F)
247 where
248 T: 'static,
249 F: Fn(&mut T) + 'static,
250 {
251 let model = model.clone();
252 self.pressable_add_on_activate(Arc::new(move |host, acx, _reason| {
253 let _ = host.models_mut().update(&model, |v| update(v));
254 host.request_redraw(acx.window);
255 host.push_effect(Effect::RequestAnimationFrame(acx.window));
256 }));
257 }
258
259 fn pressable_update_weak_model<T, F>(&mut self, model: &WeakModel<T>, update: F)
260 where
261 T: 'static,
262 F: Fn(&mut T) + 'static,
263 {
264 let model = model.clone();
265 self.pressable_add_on_activate(Arc::new(move |host, acx, _reason| {
266 let _ = host.update_weak_model(&model, |v| update(v));
267 host.request_redraw(acx.window);
268 host.push_effect(Effect::RequestAnimationFrame(acx.window));
269 }));
270 }
271
272 fn pressable_set_model<T>(&mut self, model: &Model<T>, value: T)
273 where
274 T: Clone + 'static,
275 {
276 self.pressable_update_model(model, move |v| *v = value.clone());
277 }
278
279 fn pressable_set_weak_model<T>(&mut self, model: &WeakModel<T>, value: T)
280 where
281 T: Clone + 'static,
282 {
283 self.pressable_update_weak_model(model, move |v| *v = value.clone());
284 }
285
286 fn pressable_toggle_bool(&mut self, model: &Model<bool>) {
287 self.pressable_update_model(model, |v| *v = !*v);
288 }
289
290 fn pressable_toggle_bool_weak(&mut self, model: &WeakModel<bool>) {
291 self.pressable_update_weak_model(model, |v| *v = !*v);
292 }
293
294 fn pressable_set_bool(&mut self, model: &Model<bool>, value: bool) {
295 self.pressable_set_model(model, value);
296 }
297
298 fn pressable_set_bool_weak(&mut self, model: &WeakModel<bool>, value: bool) {
299 self.pressable_set_weak_model(model, value);
300 }
301
302 fn pressable_set_arc_str(&mut self, model: &Model<Arc<str>>, value: Arc<str>) {
303 self.pressable_set_model(model, value);
304 }
305
306 fn pressable_set_arc_str_weak(&mut self, model: &WeakModel<Arc<str>>, value: Arc<str>) {
307 self.pressable_set_weak_model(model, value);
308 }
309
310 fn pressable_set_option_arc_str(&mut self, model: &Model<Option<Arc<str>>>, value: Arc<str>) {
311 self.pressable_set_model(model, Some(value));
312 }
313
314 fn pressable_set_option_arc_str_weak(
315 &mut self,
316 model: &WeakModel<Option<Arc<str>>>,
317 value: Arc<str>,
318 ) {
319 self.pressable_set_weak_model(model, Some(value));
320 }
321
322 fn pressable_toggle_vec_arc_str(&mut self, model: &Model<Vec<Arc<str>>>, value: Arc<str>) {
323 let model = model.clone();
324 self.pressable_add_on_activate(Arc::new(move |host, _cx, _reason| {
325 let value = value.clone();
326 let _ = host.models_mut().update(&model, |v| {
327 if let Some(pos) = v.iter().position(|it| it.as_ref() == value.as_ref()) {
328 v.remove(pos);
329 } else {
330 v.push(value.clone());
331 }
332 });
333 }));
334 }
335
336 fn pressable_toggle_vec_arc_str_weak(
337 &mut self,
338 model: &WeakModel<Vec<Arc<str>>>,
339 value: Arc<str>,
340 ) {
341 let model = model.clone();
342 self.pressable_add_on_activate(Arc::new(move |host, _cx, _reason| {
343 let value = value.clone();
344 let _ = host.update_weak_model(&model, |v| {
345 if let Some(pos) = v.iter().position(|it| it.as_ref() == value.as_ref()) {
346 v.remove(pos);
347 } else {
348 v.push(value.clone());
349 }
350 });
351 }));
352 }
353
354 fn dismissible_close_bool(&mut self, open: &Model<bool>) {
355 let open = open.clone();
356 self.dismissible_add_on_dismiss_request(Arc::new(move |host, _cx, _req| {
357 let _ = host.models_mut().update(&open, |v| *v = false);
358 }));
359 }
360
361 fn dismissible_close_bool_weak(&mut self, open: &WeakModel<bool>) {
362 let open = open.clone();
363 self.dismissible_add_on_dismiss_request(Arc::new(move |host, _cx, _req| {
364 let _ = host.update_weak_model(&open, |v| *v = false);
365 }));
366 }
367
368 #[track_caller]
369 fn roving_select_option_arc_str(
370 &mut self,
371 model: &Model<Option<Arc<str>>>,
372 values: Arc<[Arc<str>]>,
373 ) {
374 let model = model.clone();
375 struct RovingSelectOptionArcStrState {
376 values: Rc<RefCell<Arc<[Arc<str>]>>>,
377 handler: fret_ui::action::OnRovingActiveChange,
378 }
379
380 let handler = self.slot_state(
381 || {
382 let values_cell: Rc<RefCell<Arc<[Arc<str>]>>> =
383 Rc::new(RefCell::new(values.clone()));
384 let values_read = values_cell.clone();
385 let handler: fret_ui::action::OnRovingActiveChange = Arc::new(
386 move |host: &mut dyn fret_ui::action::UiActionHost, _cx, idx| {
387 let values = values_read.borrow();
388 let Some(value) = values.get(idx).cloned() else {
389 return;
390 };
391 let next = Some(value);
392 let _ = host.models_mut().update(&model, |v| *v = next);
393 },
394 );
395
396 RovingSelectOptionArcStrState {
397 values: values_cell,
398 handler,
399 }
400 },
401 |state| {
402 *state.values.borrow_mut() = values.clone();
403 state.handler.clone()
404 },
405 );
406
407 self.roving_add_on_active_change(handler);
408 }
409
410 #[track_caller]
411 fn roving_select_option_arc_str_weak(
412 &mut self,
413 model: &WeakModel<Option<Arc<str>>>,
414 values: Arc<[Arc<str>]>,
415 ) {
416 let model = model.clone();
417 struct RovingSelectOptionArcStrState {
418 values: Rc<RefCell<Arc<[Arc<str>]>>>,
419 handler: fret_ui::action::OnRovingActiveChange,
420 }
421
422 let handler = self.slot_state(
423 || {
424 let values_cell: Rc<RefCell<Arc<[Arc<str>]>>> =
425 Rc::new(RefCell::new(values.clone()));
426 let values_read = values_cell.clone();
427 let handler: fret_ui::action::OnRovingActiveChange = Arc::new(
428 move |host: &mut dyn fret_ui::action::UiActionHost, _cx, idx| {
429 let values = values_read.borrow();
430 let Some(value) = values.get(idx).cloned() else {
431 return;
432 };
433 let next = Some(value);
434 let _ = host.update_weak_model(&model, |v| *v = next);
435 },
436 );
437
438 RovingSelectOptionArcStrState {
439 values: values_cell,
440 handler,
441 }
442 },
443 |state| {
444 *state.values.borrow_mut() = values.clone();
445 state.handler.clone()
446 },
447 );
448
449 self.roving_add_on_active_change(handler);
450 }
451
452 #[track_caller]
453 fn roving_typeahead_first_char_arc_str(&mut self, labels: Arc<[Arc<str>]>) {
454 roving_focus_group::typeahead_first_char_arc_str(self, labels);
455 }
456
457 #[track_caller]
458 fn roving_typeahead_prefix_arc_str(&mut self, labels: Arc<[Arc<str>]>, timeout_ticks: u64) {
459 roving_focus_group::typeahead_prefix_arc_str(self, labels, timeout_ticks);
460 }
461
462 fn roving_nav_apg(&mut self) {
463 roving_focus_group::nav_apg(self);
464 }
465}