1use crate::UiHost;
2use fret_core::{
3 AppWindowId, Axis, CursorIcon, InternalDragKind, KeyCode, Modifiers, MouseButton, Point,
4 PointerId, PointerType, Rect, UiServices,
5};
6use fret_runtime::{
7 ActionId, CommandId, DefaultAction, DragHost, DragKindId, DragSession, Effect, Model,
8 ModelStore, PlatformTextInputQuery, PlatformTextInputQueryResult, TickId, TimerToken,
9 Utf16Range, WeakModel,
10};
11use std::any::{Any, TypeId};
12use std::sync::Arc;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct ActionCx {
17 pub window: AppWindowId,
18 pub target: crate::GlobalElementId,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum ActivateReason {
24 Pointer,
25 Keyboard,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum PressablePointerDownResult {
31 Continue,
33 SkipDefault,
35 SkipDefaultAndStopPropagation,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum PressablePointerUpResult {
41 Continue,
43 SkipActivate,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum DismissReason {
50 Escape,
51 OutsidePress {
52 pointer: Option<OutsidePressCx>,
53 },
54 FocusOutside,
56 Scroll,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct DismissRequestCx {
69 pub reason: DismissReason,
70 default_prevented: bool,
71}
72
73impl DismissRequestCx {
74 pub fn new(reason: DismissReason) -> Self {
75 Self {
76 reason,
77 default_prevented: false,
78 }
79 }
80
81 pub fn prevent_default(&mut self) {
82 self.default_prevented = true;
83 }
84
85 pub fn default_prevented(&self) -> bool {
86 self.default_prevented
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub(crate) struct DismissibleLastDismissRequest {
92 pub tick_id: TickId,
93 pub reason: Option<DismissReason>,
94 pub default_prevented: bool,
95}
96
97impl Default for DismissibleLastDismissRequest {
98 fn default() -> Self {
99 Self {
100 tick_id: TickId(0),
101 reason: None,
102 default_prevented: false,
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct AutoFocusRequestCx {
113 default_prevented: bool,
114}
115
116impl AutoFocusRequestCx {
117 pub fn new() -> Self {
118 Self {
119 default_prevented: false,
120 }
121 }
122
123 pub fn prevent_default(&mut self) {
124 self.default_prevented = true;
125 }
126
127 pub fn default_prevented(&self) -> bool {
128 self.default_prevented
129 }
130}
131
132impl Default for AutoFocusRequestCx {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct OutsidePressCx {
140 pub pointer_id: PointerId,
141 pub pointer_type: PointerType,
142 pub button: MouseButton,
143 pub modifiers: Modifiers,
144 pub click_count: u8,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq)]
149pub struct PointerDownCx {
150 pub pointer_id: PointerId,
151 pub position: Point,
153 pub position_local: Point,
157 pub position_window: Option<Point>,
161 pub tick_id: TickId,
162 pub pixels_per_point: f32,
166 pub button: MouseButton,
167 pub modifiers: Modifiers,
168 pub click_count: u8,
170 pub pointer_type: PointerType,
171 pub hit_is_text_input: bool,
177 pub hit_is_pressable: bool,
183 pub hit_pressable_target: Option<crate::GlobalElementId>,
189 pub hit_pressable_target_in_descendant_subtree: bool,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq)]
198pub struct PointerMoveCx {
199 pub pointer_id: PointerId,
200 pub position: Point,
202 pub position_local: Point,
206 pub position_window: Option<Point>,
210 pub tick_id: TickId,
211 pub pixels_per_point: f32,
213 pub velocity_window: Option<Point>,
221 pub buttons: fret_core::MouseButtons,
222 pub modifiers: Modifiers,
223 pub pointer_type: PointerType,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq)]
228pub struct WheelCx {
229 pub pointer_id: PointerId,
230 pub position: Point,
232 pub position_local: Point,
236 pub position_window: Option<Point>,
240 pub tick_id: TickId,
241 pub pixels_per_point: f32,
243 pub delta: Point,
245 pub delta_window: Option<Point>,
247 pub modifiers: Modifiers,
248 pub pointer_type: PointerType,
249}
250
251#[derive(Debug, Clone, Copy, PartialEq)]
253pub struct PinchGestureCx {
254 pub pointer_id: PointerId,
255 pub position: Point,
257 pub position_local: Point,
261 pub position_window: Option<Point>,
265 pub tick_id: TickId,
266 pub pixels_per_point: f32,
268 pub delta: f32,
272 pub modifiers: Modifiers,
273 pub pointer_type: PointerType,
274}
275
276#[derive(Debug, Clone, Copy, PartialEq)]
278pub struct PointerCancelCx {
279 pub pointer_id: PointerId,
280 pub position: Option<Point>,
282 pub position_local: Option<Point>,
287 pub position_window: Option<Point>,
290 pub tick_id: TickId,
291 pub pixels_per_point: f32,
293 pub buttons: fret_core::MouseButtons,
294 pub modifiers: Modifiers,
295 pub pointer_type: PointerType,
296 pub reason: fret_core::PointerCancelReason,
297}
298
299#[derive(Debug, Clone, Copy, PartialEq)]
301pub struct PointerUpCx {
302 pub pointer_id: PointerId,
303 pub position: Point,
305 pub position_local: Point,
309 pub position_window: Option<Point>,
313 pub tick_id: TickId,
314 pub pixels_per_point: f32,
316 pub velocity_window: Option<Point>,
318 pub button: MouseButton,
319 pub modifiers: Modifiers,
320 pub is_click: bool,
325 pub click_count: u8,
327 pub pointer_type: PointerType,
328 pub down_hit_pressable_target: Option<crate::GlobalElementId>,
334 pub down_hit_pressable_target_in_descendant_subtree: bool,
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub struct KeyDownCx {
342 pub key: KeyCode,
343 pub modifiers: Modifiers,
344 pub repeat: bool,
345 pub ime_composing: bool,
350}
351
352pub trait UiActionHost {
357 fn models_mut(&mut self) -> &mut ModelStore;
358 fn push_effect(&mut self, effect: Effect);
359 fn request_redraw(&mut self, window: AppWindowId);
360 fn next_timer_token(&mut self) -> TimerToken;
361 fn next_clipboard_token(&mut self) -> fret_runtime::ClipboardToken;
362 fn next_share_sheet_token(&mut self) -> fret_runtime::ShareSheetToken;
363
364 fn set_router_command_availability(
370 &mut self,
371 _window: AppWindowId,
372 _can_back: bool,
373 _can_forward: bool,
374 ) {
375 }
376
377 fn record_transient_event(&mut self, _cx: ActionCx, _key: u64) {}
388
389 fn notify(&mut self, _cx: ActionCx) {}
397
398 fn record_pending_command_dispatch_source(
405 &mut self,
406 _cx: ActionCx,
407 _command: &CommandId,
408 _reason: ActivateReason,
409 ) {
410 }
411
412 fn record_pending_action_payload(
420 &mut self,
421 _cx: ActionCx,
422 _action: &ActionId,
423 _payload: Box<dyn Any + Send + Sync>,
424 ) {
425 }
426
427 fn consume_pending_action_payload(
435 &mut self,
436 _window: AppWindowId,
437 _action: &ActionId,
438 ) -> Option<Box<dyn Any + Send + Sync>> {
439 None
440 }
441
442 fn dispatch_command(&mut self, window: Option<AppWindowId>, command: CommandId) {
443 self.push_effect(Effect::Command { window, command });
444 }
445}
446
447pub trait UiFocusActionHost: UiActionHost {
452 fn request_focus(&mut self, target: crate::GlobalElementId);
453}
454
455pub trait UiDragActionHost: UiActionHost {
461 fn begin_drag_with_kind(
462 &mut self,
463 pointer_id: PointerId,
464 kind: DragKindId,
465 source_window: AppWindowId,
466 start: Point,
467 );
468
469 fn begin_cross_window_drag_with_kind(
470 &mut self,
471 pointer_id: PointerId,
472 kind: DragKindId,
473 source_window: AppWindowId,
474 start: Point,
475 );
476
477 fn drag(&self, pointer_id: PointerId) -> Option<&DragSession>;
478 fn drag_mut(&mut self, pointer_id: PointerId) -> Option<&mut DragSession>;
479 fn cancel_drag(&mut self, pointer_id: PointerId);
480}
481
482pub trait UiActionHostExt: UiActionHost {
483 fn read_weak_model<T: Any, R>(
484 &mut self,
485 model: &WeakModel<T>,
486 f: impl FnOnce(&T) -> R,
487 ) -> Option<R> {
488 let model = model.upgrade()?;
489 self.models_mut().read(&model, f).ok()
490 }
491
492 fn update_model<T: Any, R>(
493 &mut self,
494 model: &Model<T>,
495 f: impl FnOnce(&mut T) -> R,
496 ) -> Option<R> {
497 self.models_mut().update(model, f).ok()
498 }
499
500 fn update_weak_model<T: Any, R>(
501 &mut self,
502 model: &WeakModel<T>,
503 f: impl FnOnce(&mut T) -> R,
504 ) -> Option<R> {
505 let model = model.upgrade()?;
506 self.update_model(&model, f)
507 }
508}
509
510impl<T> UiActionHostExt for T where T: UiActionHost + ?Sized {}
511
512pub trait UiPointerActionHost: UiFocusActionHost + UiDragActionHost {
517 fn bounds(&self) -> fret_core::Rect;
518 fn capture_pointer(&mut self);
519 fn release_pointer_capture(&mut self);
520 fn set_cursor_icon(&mut self, icon: CursorIcon);
521 fn prevent_default(&mut self, action: DefaultAction);
526
527 fn invalidate(&mut self, _invalidation: crate::widget::Invalidation) {}
532}
533
534pub struct UiActionHostAdapter<'a, H: UiHost> {
535 pub app: &'a mut H,
536}
537
538impl<'a, H: UiHost> UiActionHost for UiActionHostAdapter<'a, H> {
539 fn models_mut(&mut self) -> &mut ModelStore {
540 self.app.models_mut()
541 }
542
543 fn push_effect(&mut self, effect: Effect) {
544 self.app.push_effect(effect);
545 }
546
547 fn request_redraw(&mut self, window: AppWindowId) {
548 self.app.request_redraw(window);
549 }
550
551 fn next_timer_token(&mut self) -> TimerToken {
552 self.app.next_timer_token()
553 }
554
555 fn next_clipboard_token(&mut self) -> fret_runtime::ClipboardToken {
556 self.app.next_clipboard_token()
557 }
558
559 fn next_share_sheet_token(&mut self) -> fret_runtime::ShareSheetToken {
560 self.app.next_share_sheet_token()
561 }
562
563 fn set_router_command_availability(
564 &mut self,
565 window: AppWindowId,
566 can_back: bool,
567 can_forward: bool,
568 ) {
569 self.app.with_global_mut(
570 fret_runtime::WindowCommandAvailabilityService::default,
571 |svc, _app| {
572 svc.set_router_availability(window, can_back, can_forward);
573 },
574 );
575 }
576
577 fn record_transient_event(&mut self, cx: ActionCx, key: u64) {
578 crate::elements::record_transient_event(&mut *self.app, cx.window, cx.target, key);
579 }
580
581 fn record_pending_command_dispatch_source(
582 &mut self,
583 cx: ActionCx,
584 command: &CommandId,
585 reason: ActivateReason,
586 ) {
587 let kind = match reason {
588 ActivateReason::Pointer => fret_runtime::CommandDispatchSourceKindV1::Pointer,
589 ActivateReason::Keyboard => fret_runtime::CommandDispatchSourceKindV1::Keyboard,
590 };
591 let source = fret_runtime::CommandDispatchSourceV1 {
592 kind,
593 element: Some(cx.target.0),
594 test_id: None,
595 };
596 self.app.with_global_mut(
597 fret_runtime::WindowPendingCommandDispatchSourceService::default,
598 |svc, app| {
599 svc.record(cx.window, app.tick_id(), command.clone(), source);
600 },
601 );
602 }
603}
604
605impl<'a, H: UiHost> UiDragActionHost for UiActionHostAdapter<'a, H> {
606 fn begin_drag_with_kind(
607 &mut self,
608 pointer_id: PointerId,
609 kind: DragKindId,
610 source_window: AppWindowId,
611 start: Point,
612 ) {
613 DragHost::begin_drag_with_kind(&mut *self.app, pointer_id, kind, source_window, start, ());
614 }
615
616 fn begin_cross_window_drag_with_kind(
617 &mut self,
618 pointer_id: PointerId,
619 kind: DragKindId,
620 source_window: AppWindowId,
621 start: Point,
622 ) {
623 DragHost::begin_cross_window_drag_with_kind(
624 &mut *self.app,
625 pointer_id,
626 kind,
627 source_window,
628 start,
629 (),
630 );
631 }
632
633 fn drag(&self, pointer_id: PointerId) -> Option<&DragSession> {
634 DragHost::drag(&*self.app, pointer_id)
635 }
636
637 fn drag_mut(&mut self, pointer_id: PointerId) -> Option<&mut DragSession> {
638 DragHost::drag_mut(&mut *self.app, pointer_id)
639 }
640
641 fn cancel_drag(&mut self, pointer_id: PointerId) {
642 DragHost::cancel_drag(&mut *self.app, pointer_id);
643 }
644}
645
646#[derive(Debug, Clone, Copy, PartialEq)]
648pub struct InternalDragCx {
649 pub pointer_id: PointerId,
650 pub position: Point,
651 pub position_window: Option<Point>,
655 pub tick_id: TickId,
656 pub kind: InternalDragKind,
657 pub modifiers: Modifiers,
658}
659
660pub type OnInternalDrag =
661 Arc<dyn Fn(&mut dyn UiDragActionHost, ActionCx, InternalDragCx) -> bool + 'static>;
662
663#[derive(Default)]
664pub(crate) struct InternalDragActionHooks {
665 pub on_internal_drag: Option<OnInternalDrag>,
666}
667
668pub type OnExternalDrag =
669 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, &fret_core::ExternalDragEvent) -> bool + 'static>;
670
671#[derive(Default)]
672pub(crate) struct ExternalDragActionHooks {
673 pub on_external_drag: Option<OnExternalDrag>,
674}
675
676pub type OnActivate = Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, ActivateReason) + 'static>;
677
678#[derive(Debug, Clone, PartialEq, Eq)]
680pub struct SelectableTextSpanActivation {
681 pub tag: Arc<str>,
682 pub range: std::ops::Range<usize>,
683}
684
685pub type OnSelectableTextActivateSpan = Arc<
686 dyn Fn(&mut dyn UiActionHost, ActionCx, ActivateReason, SelectableTextSpanActivation) + 'static,
687>;
688
689#[derive(Default)]
690pub(crate) struct SelectableTextActionHooks {
691 pub on_activate_span: Option<OnSelectableTextActivateSpan>,
692}
693pub type OnPressablePointerDown = Arc<
694 dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PointerDownCx) -> PressablePointerDownResult
695 + 'static,
696>;
697pub type OnPressablePointerMove =
698 Arc<dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PointerMoveCx) -> bool + 'static>;
699pub type OnPressablePointerUp = Arc<
700 dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PointerUpCx) -> PressablePointerUpResult
701 + 'static,
702>;
703pub type OnPressableClipboardWriteCompleted = Arc<
704 dyn Fn(
705 &mut dyn UiActionHost,
706 ActionCx,
707 fret_core::ClipboardToken,
708 &fret_core::ClipboardWriteOutcome,
709 ) -> bool
710 + 'static,
711>;
712
713#[derive(Default)]
714pub(crate) struct PressableActionHooks {
715 pub on_activate: Option<OnActivate>,
716 pub on_pointer_down: Option<OnPressablePointerDown>,
717 pub on_pointer_move: Option<OnPressablePointerMove>,
718 pub on_pointer_up: Option<OnPressablePointerUp>,
719 pub on_clipboard_write_completed: Option<OnPressableClipboardWriteCompleted>,
720}
721
722pub type OnHoverChange = Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, bool) + 'static>;
723
724#[derive(Default)]
725pub(crate) struct PressableHoverActionHooks {
726 pub on_hover_change: Option<OnHoverChange>,
727}
728
729pub type OnDismissRequest =
730 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, &mut DismissRequestCx) + 'static>;
731
732pub type OnOpenAutoFocus =
733 Arc<dyn Fn(&mut dyn UiFocusActionHost, ActionCx, &mut AutoFocusRequestCx) + 'static>;
734
735pub type OnCloseAutoFocus =
736 Arc<dyn Fn(&mut dyn UiFocusActionHost, ActionCx, &mut AutoFocusRequestCx) + 'static>;
737
738pub type OnDismissiblePointerMove =
743 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, PointerMoveCx) -> bool + 'static>;
744
745#[derive(Default)]
746pub(crate) struct DismissibleActionHooks {
747 pub on_dismiss_request: Option<OnDismissRequest>,
748 pub on_pointer_move: Option<OnDismissiblePointerMove>,
749}
750
751pub type OnTextInputRegionTextInput =
752 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, &str) -> bool + 'static>;
753
754pub type OnTextInputRegionIme =
755 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, &fret_core::ImeEvent) -> bool + 'static>;
756
757pub type OnTextInputRegionClipboardReadText =
758 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, fret_core::ClipboardToken, &str) -> bool + 'static>;
759
760pub type OnTextInputRegionClipboardReadFailed = Arc<
761 dyn Fn(
762 &mut dyn UiActionHost,
763 ActionCx,
764 fret_core::ClipboardToken,
765 &fret_core::ClipboardAccessError,
766 ) -> bool
767 + 'static,
768>;
769
770pub type OnTextInputRegionSetSelection =
771 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, u32, u32) -> bool + 'static>;
772
773pub type OnTextInputRegionPlatformTextInputQuery = Arc<
774 dyn Fn(
775 &mut dyn UiActionHost,
776 ActionCx,
777 &mut dyn UiServices,
778 Rect,
779 f32,
780 &crate::element::TextInputRegionProps,
781 &PlatformTextInputQuery,
782 ) -> Option<PlatformTextInputQueryResult>
783 + 'static,
784>;
785
786pub type OnTextInputRegionPlatformTextInputReplaceTextInRangeUtf16 = Arc<
787 dyn Fn(
788 &mut dyn UiActionHost,
789 ActionCx,
790 &mut dyn UiServices,
791 Rect,
792 f32,
793 &crate::element::TextInputRegionProps,
794 Utf16Range,
795 &str,
796 ) -> bool
797 + 'static,
798>;
799
800pub type OnTextInputRegionPlatformTextInputReplaceAndMarkTextInRangeUtf16 = Arc<
801 dyn Fn(
802 &mut dyn UiActionHost,
803 ActionCx,
804 &mut dyn UiServices,
805 Rect,
806 f32,
807 &crate::element::TextInputRegionProps,
808 Utf16Range,
809 &str,
810 Option<Utf16Range>,
811 Option<Utf16Range>,
812 ) -> bool
813 + 'static,
814>;
815
816#[derive(Default)]
817pub(crate) struct TextInputRegionActionHooks {
818 pub on_text_input: Option<OnTextInputRegionTextInput>,
819 pub on_ime: Option<OnTextInputRegionIme>,
820 pub on_clipboard_read_text: Option<OnTextInputRegionClipboardReadText>,
821 pub on_clipboard_read_failed: Option<OnTextInputRegionClipboardReadFailed>,
822 pub on_set_selection: Option<OnTextInputRegionSetSelection>,
823 pub on_platform_text_input_query: Option<OnTextInputRegionPlatformTextInputQuery>,
824 pub on_platform_text_input_replace_text_in_range_utf16:
825 Option<OnTextInputRegionPlatformTextInputReplaceTextInRangeUtf16>,
826 pub on_platform_text_input_replace_and_mark_text_in_range_utf16:
827 Option<OnTextInputRegionPlatformTextInputReplaceAndMarkTextInRangeUtf16>,
828}
829
830pub type OnPointerDown =
831 Arc<dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PointerDownCx) -> bool + 'static>;
832
833pub type OnPointerMove =
834 Arc<dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PointerMoveCx) -> bool + 'static>;
835
836pub type OnWheel = Arc<dyn Fn(&mut dyn UiPointerActionHost, ActionCx, WheelCx) -> bool + 'static>;
837
838pub type OnPinchGesture =
839 Arc<dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PinchGestureCx) -> bool + 'static>;
840
841pub type OnPointerUp =
842 Arc<dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PointerUpCx) -> bool + 'static>;
843
844pub type OnPointerCancel =
845 Arc<dyn Fn(&mut dyn UiPointerActionHost, ActionCx, PointerCancelCx) -> bool + 'static>;
846
847#[derive(Default)]
848pub(crate) struct PointerActionHooks {
849 pub on_pointer_down: Option<OnPointerDown>,
850 pub on_pointer_move: Option<OnPointerMove>,
851 pub on_wheel: Option<OnWheel>,
852 pub on_pinch_gesture: Option<OnPinchGesture>,
853 pub on_pointer_up: Option<OnPointerUp>,
854 pub on_pointer_cancel: Option<OnPointerCancel>,
855}
856
857pub type OnKeyDown = Arc<dyn Fn(&mut dyn UiFocusActionHost, ActionCx, KeyDownCx) -> bool + 'static>;
858
859#[derive(Default)]
860pub(crate) struct KeyActionHooks {
861 pub on_key_down_capture: Option<OnKeyDown>,
862 pub on_key_down: Option<OnKeyDown>,
863 pub on_key_down_focused: Option<OnKeyDown>,
868}
869
870pub type OnCommand = Arc<dyn Fn(&mut dyn UiFocusActionHost, ActionCx, CommandId) -> bool + 'static>;
871
872#[derive(Default)]
873pub(crate) struct CommandActionHooks {
874 pub on_command: Option<OnCommand>,
875}
876
877#[derive(Clone)]
878pub(crate) struct ActionRouteOwnerHooks {
879 pub owner: TypeId,
880 pub on_command: Option<OnCommand>,
881 pub on_command_availability: Option<OnCommandAvailability>,
882}
883
884#[derive(Default, Clone)]
889pub(crate) struct ActionRouteHooks {
890 owners: Vec<ActionRouteOwnerHooks>,
891}
892
893impl ActionRouteHooks {
894 fn owner_mut(&mut self, owner: TypeId) -> &mut ActionRouteOwnerHooks {
895 if let Some(index) = self.owners.iter().position(|hooks| hooks.owner == owner) {
896 return &mut self.owners[index];
897 }
898 self.owners.push(ActionRouteOwnerHooks {
899 owner,
900 on_command: None,
901 on_command_availability: None,
902 });
903 self.owners
904 .last_mut()
905 .expect("action route owner slot must exist after insertion")
906 }
907
908 pub(crate) fn set_on_command(&mut self, owner: TypeId, handler: OnCommand) {
909 self.owner_mut(owner).on_command = Some(handler);
910 }
911
912 pub(crate) fn add_on_command(&mut self, owner: TypeId, handler: OnCommand) {
913 let hooks = self.owner_mut(owner);
914 hooks.on_command = match hooks.on_command.clone() {
915 None => Some(handler),
916 Some(prev) => {
917 let next = handler.clone();
918 Some(Arc::new(move |host, cx, command| {
919 prev(host, cx, command.clone()) || next(host, cx, command)
920 }))
921 }
922 };
923 }
924
925 pub(crate) fn clear_on_command(&mut self, owner: TypeId) {
926 self.owner_mut(owner).on_command = None;
927 }
928
929 pub(crate) fn on_command_handlers(&self) -> Vec<OnCommand> {
930 self.owners
931 .iter()
932 .filter_map(|hooks| hooks.on_command.clone())
933 .collect()
934 }
935
936 pub(crate) fn set_on_command_availability(
937 &mut self,
938 owner: TypeId,
939 handler: OnCommandAvailability,
940 ) {
941 self.owner_mut(owner).on_command_availability = Some(handler);
942 }
943
944 pub(crate) fn add_on_command_availability(
945 &mut self,
946 owner: TypeId,
947 handler: OnCommandAvailability,
948 ) {
949 let hooks = self.owner_mut(owner);
950 hooks.on_command_availability = match hooks.on_command_availability.clone() {
951 None => Some(handler),
952 Some(prev) => {
953 let next = handler.clone();
954 Some(Arc::new(move |host, cx, command| {
955 let availability = prev(host, cx.clone(), command.clone());
956 if availability != crate::widget::CommandAvailability::NotHandled {
957 return availability;
958 }
959 next(host, cx, command)
960 }))
961 }
962 };
963 }
964
965 pub(crate) fn clear_on_command_availability(&mut self, owner: TypeId) {
966 self.owner_mut(owner).on_command_availability = None;
967 }
968
969 pub(crate) fn on_command_availability_handlers(&self) -> Vec<OnCommandAvailability> {
970 self.owners
971 .iter()
972 .filter_map(|hooks| hooks.on_command_availability.clone())
973 .collect()
974 }
975}
976
977pub trait UiCommandAvailabilityActionHost {
978 fn models_mut(&mut self) -> &mut fret_runtime::ModelStore;
979}
980
981#[derive(Debug, Clone)]
982pub struct CommandAvailabilityActionCx {
983 pub window: fret_core::AppWindowId,
984 pub target: crate::GlobalElementId,
985 pub node: fret_core::NodeId,
986 pub focus: Option<fret_core::NodeId>,
987 pub focus_in_subtree: bool,
988 pub input_ctx: fret_runtime::InputContext,
989}
990
991pub type OnCommandAvailability = Arc<
992 dyn Fn(
993 &mut dyn UiCommandAvailabilityActionHost,
994 CommandAvailabilityActionCx,
995 CommandId,
996 ) -> crate::widget::CommandAvailability
997 + 'static,
998>;
999
1000#[derive(Default)]
1001pub(crate) struct CommandAvailabilityActionHooks {
1002 pub on_command_availability: Option<OnCommandAvailability>,
1003}
1004
1005pub type OnTimer = Arc<dyn Fn(&mut dyn UiFocusActionHost, ActionCx, TimerToken) -> bool + 'static>;
1006
1007#[derive(Default)]
1008pub(crate) struct TimerActionHooks {
1009 pub on_timer: Option<OnTimer>,
1010}
1011
1012#[derive(Debug, Clone)]
1013pub struct RovingTypeaheadCx {
1014 pub input: char,
1015 pub current: Option<usize>,
1016 pub len: usize,
1017 pub disabled: Arc<[bool]>,
1018 pub wrap: bool,
1019 pub tick: u64,
1020}
1021
1022pub type OnRovingActiveChange = Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, usize) + 'static>;
1023
1024pub type OnRovingTypeahead =
1025 Arc<dyn Fn(&mut dyn UiActionHost, ActionCx, RovingTypeaheadCx) -> Option<usize> + 'static>;
1026
1027#[derive(Debug, Clone)]
1028pub struct RovingNavigateCx {
1029 pub key: KeyCode,
1030 pub modifiers: Modifiers,
1031 pub repeat: bool,
1032 pub axis: Axis,
1033 pub current: Option<usize>,
1034 pub len: usize,
1035 pub disabled: Arc<[bool]>,
1036 pub wrap: bool,
1037}
1038
1039#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1040pub enum RovingNavigateResult {
1041 NotHandled,
1042 Handled { target: Option<usize> },
1043}
1044
1045pub type OnRovingNavigate = Arc<
1046 dyn Fn(&mut dyn UiActionHost, ActionCx, RovingNavigateCx) -> RovingNavigateResult + 'static,
1047>;
1048
1049#[derive(Default)]
1050pub(crate) struct RovingActionHooks {
1051 pub on_active_change: Option<OnRovingActiveChange>,
1052 pub on_typeahead: Option<OnRovingTypeahead>,
1053 pub on_navigate: Option<OnRovingNavigate>,
1054 pub on_key_down: Vec<OnKeyDown>,
1055}