1use zng_app::{
6 event::{Command, CommandHandle, CommandInfoExt, CommandNameExt, CommandScope, command},
7 hn,
8 shortcut::{CommandShortcutExt, shortcut},
9 widget::info::WidgetInfo,
10};
11use zng_ext_window::WINDOWS;
12use zng_var::{Var, merge_var};
13
14use super::*;
15
16command! {
17 pub static FOCUS_NEXT_CMD {
19 l10n!: true,
20 name: "Focus Next",
21 info: "Focus next focusable",
22 shortcut: shortcut!(Tab),
23 };
24
25 pub static FOCUS_PREV_CMD {
27 l10n!: true,
28 name: "Focus Previous",
29 info: "Focus previous focusable",
30 shortcut: shortcut!(SHIFT + Tab),
31 };
32
33 pub static FOCUS_ALT_CMD {
35 l10n!: true,
36 name: "Focus Alt",
37 info: "Focus alt scope",
38 shortcut: shortcut!(Alt),
39 };
40
41 pub static FOCUS_ENTER_CMD {
43 l10n!: true,
44 name: "Focus Enter",
45 info: "Focus child focusable",
46 shortcut: [shortcut!(Enter), shortcut!(ALT + Enter)],
47 };
48
49 pub static FOCUS_EXIT_CMD {
53 l10n!: true,
54 name: "Focus Exit",
55 info: "Focus parent focusable, or return focus",
56 shortcut: [shortcut!(Escape), shortcut!(ALT + Escape)],
57 };
58
59 pub static FOCUS_UP_CMD {
61 l10n!: true,
62 name: "Focus Up",
63 info: "Focus closest focusable up",
64 shortcut: [shortcut!(ArrowUp), shortcut!(ALT + ArrowUp)],
65 };
66
67 pub static FOCUS_DOWN_CMD {
69 l10n!: true,
70 name: "Focus Down",
71 info: "Focus closest focusable down",
72 shortcut: [shortcut!(ArrowDown), shortcut!(ALT + ArrowDown)],
73 };
74
75 pub static FOCUS_LEFT_CMD {
77 l10n!: true,
78 name: "Focus Left",
79 info: "Focus closest focusable left",
80 shortcut: [shortcut!(ArrowLeft), shortcut!(ALT + ArrowLeft)],
81 };
82
83 pub static FOCUS_RIGHT_CMD {
85 l10n!: true,
86 name: "Focus Right",
87 info: "Focus closest focusable right",
88 shortcut: [shortcut!(ArrowRight), shortcut!(ALT + ArrowRight)],
89 };
90
91 pub static FOCUS_CMD;
95}
96
97pub(super) struct FocusCommands {
98 next_handle: CommandHandle,
99 prev_handle: CommandHandle,
100
101 alt_handle: CommandHandle,
102
103 up_handle: CommandHandle,
104 down_handle: CommandHandle,
105 left_handle: CommandHandle,
106 right_handle: CommandHandle,
107
108 exit_handle: CommandHandle,
109 enter_handle: CommandHandle,
110
111 _focus_handle: CommandHandle,
112}
113impl FocusCommands {
114 pub fn new() -> Self {
115 macro_rules! handle {
116 ($($CMD:ident($handle:ident) => $method:ident,)+) => {Self {
117 $($handle: $CMD.on_event_with_enabled(false, true, false, hn!(|a| {
118 let (args, enabled) = a;
119 if args.param.is_some() {
120 return;
121 }
122 args.propagation.stop();
123 if enabled.get() {
124 FOCUS.$method();
125 } else {
126 FOCUS.highlight_within_auto();
127 }
128 })),)+
129 exit_handle: FOCUS_EXIT_CMD.on_event(false, true, false, hn!(|args| {
130 if let Some(recursive_alt) = args.param::<bool>() {
131 args.propagation.stop();
132 FOCUS.focus_exit(*recursive_alt);
133 } else if args.param.is_none() {
134 args.propagation.stop();
135 FOCUS.focus_exit(false);
136 }
137 })),
138 _focus_handle: FOCUS_CMD.on_event(true, true, false, hn!(|args| {
139 if let Some(req) = args.param::<FocusRequest>() {
140 args.propagation.stop();
141 FOCUS.focus(*req);
142 }
143 })),
144 }};
145 }
146
147 #[rustfmt::skip] handle! {
149 FOCUS_NEXT_CMD(next_handle) => focus_next,
150 FOCUS_PREV_CMD(prev_handle) => focus_prev,
151 FOCUS_ALT_CMD(alt_handle) => focus_alt,
152 FOCUS_UP_CMD(up_handle) => focus_up,
153 FOCUS_DOWN_CMD(down_handle) => focus_down,
154 FOCUS_LEFT_CMD(left_handle) => focus_left,
155 FOCUS_RIGHT_CMD(right_handle) => focus_right,
156 FOCUS_ENTER_CMD(enter_handle) => focus_enter,
157 }
158 }
159
160 pub fn update_enabled(&mut self, nav: FocusNavAction) {
161 self.next_handle.enabled().set(nav.contains(FocusNavAction::NEXT));
162 self.prev_handle.enabled().set(nav.contains(FocusNavAction::PREV));
163
164 self.alt_handle.enabled().set(nav.contains(FocusNavAction::ALT));
165
166 self.up_handle.enabled().set(nav.contains(FocusNavAction::UP));
167 self.down_handle.enabled().set(nav.contains(FocusNavAction::DOWN));
168 self.left_handle.enabled().set(nav.contains(FocusNavAction::LEFT));
169 self.right_handle.enabled().set(nav.contains(FocusNavAction::RIGHT));
170
171 self.exit_handle.enabled().set(nav.contains(FocusNavAction::EXIT));
172 self.enter_handle.enabled().set(nav.contains(FocusNavAction::ENTER));
173 }
174}
175
176pub trait CommandFocusExt {
178 fn focus_scoped(self) -> Var<Command>;
201
202 fn focus_scoped_with(self, map: impl FnMut(Option<WidgetInfo>) -> CommandScope + Send + 'static) -> Var<Command>;
213}
214
215impl CommandFocusExt for Command {
216 fn focus_scoped(self) -> Var<Command> {
217 let cmd = self.scoped(CommandScope::App);
218 merge_var!(FOCUS.alt_return(), FOCUS.focused(), move |alt, f| {
219 match alt.as_ref().or(f.as_ref()) {
220 Some(p) => cmd.scoped(p.widget_id()),
221 None => cmd,
222 }
223 })
224 }
225
226 fn focus_scoped_with(self, mut map: impl FnMut(Option<WidgetInfo>) -> CommandScope + Send + 'static) -> Var<Command> {
227 let cmd = self.scoped(CommandScope::App);
228 merge_var!(FOCUS.alt_return(), FOCUS.focused(), |alt, f| {
229 match alt.as_ref().or(f.as_ref()) {
230 Some(p) => WINDOWS.widget_tree(p.window_id())?.get(p.widget_id()),
231 None => None,
232 }
233 })
234 .map(move |w| cmd.scoped(map(w.clone())))
235 }
236}