1use zng_app::{
6 event::{Command, CommandHandle, CommandInfoExt, CommandNameExt, CommandScope, EventArgs, command},
7 shortcut::{CommandShortcutExt, shortcut},
8 update::EventUpdate,
9 widget::info::WidgetInfo,
10};
11use zng_var::{BoxedVar, merge_var};
12
13use super::*;
14
15command! {
16 pub static FOCUS_NEXT_CMD = {
18 l10n!: true,
19 name: "Focus Next",
20 info: "Focus next focusable",
21 shortcut: shortcut!(Tab),
22 };
23
24 pub static FOCUS_PREV_CMD = {
26 l10n!: true,
27 name: "Focus Previous",
28 info: "Focus previous focusable",
29 shortcut: shortcut!(SHIFT+Tab),
30 };
31
32 pub static FOCUS_ALT_CMD = {
34 l10n!: true,
35 name: "Focus Alt",
36 info: "Focus alt scope",
37 shortcut: shortcut!(Alt),
38 };
39
40 pub static FOCUS_ENTER_CMD = {
42 l10n!: true,
43 name: "Focus Enter",
44 info: "Focus child focusable",
45 shortcut: [shortcut!(Enter), shortcut!(ALT+Enter)],
46 };
47
48 pub static FOCUS_EXIT_CMD = {
50 l10n!: true,
51 name: "Focus Exit",
52 info: "Focus parent focusable, or return focus",
53 shortcut: [shortcut!(Escape), shortcut!(ALT+Escape)],
54 };
55
56 pub static FOCUS_UP_CMD = {
58 l10n!: true,
59 name: "Focus Up",
60 info: "Focus closest focusable up",
61 shortcut: [shortcut!(ArrowUp), shortcut!(ALT+ArrowUp)],
62 };
63
64 pub static FOCUS_DOWN_CMD = {
66 l10n!: true,
67 name: "Focus Down",
68 info: "Focus closest focusable down",
69 shortcut: [shortcut!(ArrowDown), shortcut!(ALT+ArrowDown)],
70 };
71
72 pub static FOCUS_LEFT_CMD = {
74 l10n!: true,
75 name: "Focus Left",
76 info: "Focus closest focusable left",
77 shortcut: [shortcut!(ArrowLeft), shortcut!(ALT+ArrowLeft)],
78 };
79
80 pub static FOCUS_RIGHT_CMD = {
82 l10n!: true,
83 name: "Focus Right",
84 info: "Focus closest focusable right",
85 shortcut: [shortcut!(ArrowRight), shortcut!(ALT+ArrowRight)],
86 };
87
88 pub static FOCUS_CMD;
92}
93
94pub(super) struct FocusCommands {
95 next_handle: CommandHandle,
96 prev_handle: CommandHandle,
97
98 alt_handle: CommandHandle,
99
100 up_handle: CommandHandle,
101 down_handle: CommandHandle,
102 left_handle: CommandHandle,
103 right_handle: CommandHandle,
104
105 exit_handle: CommandHandle,
106 enter_handle: CommandHandle,
107
108 focus_handle: CommandHandle,
109}
110impl FocusCommands {
111 pub fn new() -> Self {
112 Self {
113 next_handle: FOCUS_NEXT_CMD.subscribe(false),
114 prev_handle: FOCUS_PREV_CMD.subscribe(false),
115
116 alt_handle: FOCUS_ALT_CMD.subscribe(false),
117
118 up_handle: FOCUS_UP_CMD.subscribe(false),
119 down_handle: FOCUS_DOWN_CMD.subscribe(false),
120 left_handle: FOCUS_LEFT_CMD.subscribe(false),
121 right_handle: FOCUS_RIGHT_CMD.subscribe(false),
122
123 exit_handle: FOCUS_EXIT_CMD.subscribe(false),
124 enter_handle: FOCUS_ENTER_CMD.subscribe(false),
125
126 focus_handle: FOCUS_CMD.subscribe(true),
127 }
128 }
129
130 pub fn update_enabled(&mut self, nav: FocusNavAction) {
131 self.next_handle.set_enabled(nav.contains(FocusNavAction::NEXT));
132 self.prev_handle.set_enabled(nav.contains(FocusNavAction::PREV));
133
134 self.alt_handle.set_enabled(nav.contains(FocusNavAction::ALT));
135
136 self.up_handle.set_enabled(nav.contains(FocusNavAction::UP));
137 self.down_handle.set_enabled(nav.contains(FocusNavAction::DOWN));
138 self.left_handle.set_enabled(nav.contains(FocusNavAction::LEFT));
139 self.right_handle.set_enabled(nav.contains(FocusNavAction::RIGHT));
140
141 self.exit_handle.set_enabled(nav.contains(FocusNavAction::EXIT));
142 self.enter_handle.set_enabled(nav.contains(FocusNavAction::ENTER));
143 }
144
145 pub fn event_preview(&mut self, update: &EventUpdate) {
146 macro_rules! handle {
147 ($($CMD:ident($handle:ident) => $method:ident,)+) => {$(
148 if let Some(args) = $CMD.on(update) {
149 args.handle(|args| {
150 if args.enabled && self.$handle.is_enabled() {
151 FOCUS.$method();
152 } else {
153 FOCUS.on_disabled_cmd();
154 }
155 });
156 return;
157 }
158 )+};
159 }
160 handle! {
161 FOCUS_NEXT_CMD(next_handle) => focus_next,
162 FOCUS_PREV_CMD(prev_handle) => focus_prev,
163 FOCUS_ALT_CMD(alt_handle) => focus_alt,
164 FOCUS_UP_CMD(up_handle) => focus_up,
165 FOCUS_DOWN_CMD(down_handle) => focus_down,
166 FOCUS_LEFT_CMD(left_handle) => focus_left,
167 FOCUS_RIGHT_CMD(right_handle) => focus_right,
168 FOCUS_ENTER_CMD(enter_handle) => focus_enter,
169 FOCUS_EXIT_CMD(exit_handle) => focus_exit,
170 }
171
172 if let Some(args) = FOCUS_CMD.on(update) {
173 if let Some(req) = args.param::<FocusRequest>() {
174 args.handle_enabled(&self.focus_handle, |_| {
175 FOCUS.focus(*req);
176 });
177 }
178 }
179 }
180}
181
182pub trait CommandFocusExt {
184 fn focus_scoped(self) -> BoxedVar<Command>;
207
208 fn focus_scoped_with(self, map: impl FnMut(Option<WidgetInfo>) -> CommandScope + Send + 'static) -> BoxedVar<Command>;
219}
220
221impl CommandFocusExt for Command {
222 fn focus_scoped(self) -> BoxedVar<Command> {
223 let cmd = self.scoped(CommandScope::App);
224 merge_var!(FOCUS.alt_return(), FOCUS.focused(), move |alt, f| {
225 match alt.as_ref().or(f.as_ref()) {
226 Some(p) => cmd.scoped(p.widget_id()),
227 None => cmd,
228 }
229 })
230 .boxed()
231 }
232
233 fn focus_scoped_with(self, mut map: impl FnMut(Option<WidgetInfo>) -> CommandScope + Send + 'static) -> BoxedVar<Command> {
234 let cmd = self.scoped(CommandScope::App);
235 merge_var!(FOCUS.alt_return(), FOCUS.focused(), |alt, f| {
236 match alt.as_ref().or(f.as_ref()) {
237 Some(p) => WINDOWS.widget_tree(p.window_id()).ok()?.get(p.widget_id()),
238 None => None,
239 }
240 })
241 .map(move |w| cmd.scoped(map(w.clone())))
242 .boxed()
243 }
244}