1use std::sync::Arc;
4
5use zng_app::{
6 event::{CommandHandle, CommandInfoExt, CommandNameExt, CommandScope, command},
7 hn,
8 shortcut::{CommandShortcutExt, shortcut},
9 view_process::VIEW_PROCESS,
10 window::WindowId,
11};
12
13use zng_layout::unit::{Dip, DipPoint, PxPoint, PxToDip as _};
14use zng_view_api::window::{WindowCapability, WindowState};
15use zng_wgt::{CommandIconExt as _, ICONS, prelude::clmv, wgt_fn};
16
17use crate::{IME_EVENT, ImeArgs, WINDOWS, WINDOWS_SV, WindowInstanceState, WindowVars};
18
19pub use zng_view_api::window::ResizeDirection;
20
21command! {
22 pub static CLOSE_CMD {
24 l10n!: true,
25 name: "Close",
26 info: "Close the window",
27 shortcut: [shortcut!(ALT + F4), shortcut!(CTRL + 'W')],
28 icon: wgt_fn!(|_| ICONS.get(["window-close", "close"])),
29 };
30
31 pub static MINIMIZE_CMD {
33 l10n!: true,
34 name: "Minimize",
35 info: "Minimize the window",
36 icon: wgt_fn!(|_| ICONS.get(["window-minimize"])),
37 };
38
39 pub static MAXIMIZE_CMD {
41 l10n!: true,
42 name: "Maximize",
43 info: "Maximize the window",
44 icon: wgt_fn!(|_| ICONS.get(["window-maximize"])),
45 };
46
47 pub static FULLSCREEN_CMD {
54 l10n!: true,
55 name: "Fullscreen",
56 info: "Toggle fullscreen mode on the window",
57 shortcut: {
58 let a = if cfg!(target_os = "macos") {
59 shortcut!(CTRL | SHIFT + 'F')
60 } else {
61 shortcut!(F11)
62 };
63 [a, shortcut!(ZoomToggle)]
64 },
65 icon: wgt_fn!(|_| ICONS.get(["window-windowed-fullscreen", "window-fullscreen", "fullscreen"])),
66 };
67
68 pub static EXCLUSIVE_FULLSCREEN_CMD {
75 l10n!: true,
76 name: "Exclusive Fullscreen",
77 info: "Toggle exclusive fullscreen mode on the window",
78 icon: wgt_fn!(|_| ICONS.get(["window-exclusive-fullscreen", "window-fullscreen", "fullscreen"])),
79 };
80
81 pub static RESTORE_CMD {
85 l10n!: true,
86 name: "Restore",
87 info: "Restores the window to its previous non-minimized state or normal state",
88 icon: wgt_fn!(|_| ICONS.get(["window-restore"])),
89 };
90
91 pub static CANCEL_IME_CMD;
95
96 pub static DRAG_MOVE_RESIZE_CMD;
105
106 pub static OPEN_TITLE_BAR_CONTEXT_MENU_CMD;
116}
117
118pub(super) struct WindowCommands {
119 maximize_handle: CommandHandle,
120 minimize_handle: CommandHandle,
121 restore_handle: CommandHandle,
122
123 fullscreen_handle: CommandHandle,
124 exclusive_handle: CommandHandle,
125
126 close_handle: CommandHandle,
127}
128impl WindowCommands {
129 pub fn init(id: WindowId, vars: &WindowVars) {
131 let s = vars.0.state.get();
132 let c = WindowCommands {
133 maximize_handle: MAXIMIZE_CMD.scoped(id).on_event(
134 !matches!(s, WindowState::Maximized) && vars.0.can_maximize.get(),
135 true,
136 false,
137 hn!(|args| {
138 args.propagation.stop();
139 WINDOWS.vars(id).unwrap().0.state.set(WindowState::Maximized);
140 }),
141 ),
142 minimize_handle: MINIMIZE_CMD.scoped(id).on_event(
143 !matches!(s, WindowState::Minimized) && vars.0.can_minimize.get(),
144 true,
145 false,
146 hn!(|args| {
147 args.propagation.stop();
148 WINDOWS.vars(id).unwrap().0.state.set(WindowState::Minimized);
149 }),
150 ),
151 restore_handle: RESTORE_CMD.scoped(id).on_event(
152 !matches!(s, WindowState::Normal),
153 true,
154 false,
155 hn!(|args| {
156 args.propagation.stop();
157 let vars = WINDOWS.vars(id).unwrap();
158 vars.0.state.set(vars.0.restore_state.get());
159 }),
160 ),
161 fullscreen_handle: FULLSCREEN_CMD.scoped(id).on_event(
162 matches!(s, WindowState::Fullscreen) || vars.0.can_fullscreen.get(),
163 true,
164 false,
165 hn!(|args| {
166 let vars = WINDOWS.vars(id).unwrap();
167 if let WindowState::Fullscreen = vars.0.state.get() {
168 vars.0.state.set(vars.0.restore_state.get());
169 } else {
170 vars.0.state.set(WindowState::Fullscreen);
171 }
172 }),
173 ),
174 exclusive_handle: EXCLUSIVE_FULLSCREEN_CMD.scoped(id).on_event(
175 matches!(s, WindowState::Exclusive) || vars.0.can_fullscreen.get(),
176 true,
177 false,
178 hn!(|args| {
179 let vars = WINDOWS.vars(id).unwrap();
180 if let WindowState::Exclusive = vars.0.state.get() {
181 vars.0.state.set(vars.0.restore_state.get());
182 } else {
183 vars.0.state.set(WindowState::Exclusive);
184 }
185 }),
186 ),
187 close_handle: CLOSE_CMD.scoped(id).on_event(
188 vars.0.can_close.get(),
189 true,
190 false,
191 hn!(|args| {
192 args.propagation.stop();
193 let _ = WINDOWS.close(id);
194 }),
195 ),
196 };
197
198 vars.0.can_close.bind(c.close_handle.enabled()).perm();
199
200 let c = Arc::new(c);
201 vars.0
202 .state
203 .hook(clmv!(c, |a| {
204 if let Some(vars) = WINDOWS.vars(id) {
205 let s = *a.value();
206 c.maximize_handle
207 .enabled()
208 .set(!matches!(s, WindowState::Maximized) && vars.0.can_maximize.get());
209 c.minimize_handle
210 .enabled()
211 .set(!matches!(s, WindowState::Minimized) && vars.0.can_minimize.get());
212 c.restore_handle.enabled().set(!matches!(s, WindowState::Normal));
213 c.fullscreen_handle
214 .enabled()
215 .set(matches!(s, WindowState::Fullscreen) || vars.0.can_fullscreen.get());
216 c.exclusive_handle
217 .enabled()
218 .set(matches!(s, WindowState::Exclusive) || vars.0.can_fullscreen.get());
219 true
220 } else {
221 false
222 }
223 }))
224 .perm();
225 vars.0
226 .can_maximize
227 .hook(clmv!(c, |a| {
228 if let Some(vars) = WINDOWS.vars(id) {
229 let s = vars.0.state.get();
230 c.maximize_handle.enabled().set(!matches!(s, WindowState::Maximized) && *a.value());
231 true
232 } else {
233 false
234 }
235 }))
236 .perm();
237 vars.0
238 .can_minimize
239 .hook(clmv!(c, |a| {
240 if let Some(vars) = WINDOWS.vars(id) {
241 let s = vars.0.state.get();
242 c.minimize_handle.enabled().set(!matches!(s, WindowState::Minimized) && *a.value());
243 true
244 } else {
245 false
246 }
247 }))
248 .perm();
249 vars.0
250 .can_fullscreen
251 .hook(move |a| {
252 if let Some(vars) = WINDOWS.vars(id) {
253 let s = vars.0.state.get();
254 c.fullscreen_handle
255 .enabled()
256 .set(matches!(s, WindowState::Fullscreen) || *a.value());
257 c.exclusive_handle.enabled().set(matches!(s, WindowState::Exclusive) || *a.value());
258 true
259 } else {
260 false
261 }
262 })
263 .perm();
264
265 fn can_open_ctx_menu(state: WindowInstanceState) -> bool {
266 matches!(state, WindowInstanceState::Loaded { has_view: true })
267 && VIEW_PROCESS.info().window.contains(WindowCapability::OPEN_TITLE_BAR_CONTEXT_MENU)
268 }
269 let handle = OPEN_TITLE_BAR_CONTEXT_MENU_CMD.scoped(id).on_event(
270 can_open_ctx_menu(vars.0.instance_state.get()),
271 true,
272 false,
273 hn!(|args| {
274 if let Some(w) = WINDOWS_SV.read().windows.get(&id)
275 && let Some(vars) = &w.vars
276 && let Some(v) = &w.view_window
277 {
278 args.propagation.stop();
279
280 let pos = if let Some(p) = args.param::<DipPoint>() {
281 *p
282 } else if let Some(p) = args.param::<PxPoint>() {
283 p.to_dip(vars.0.scale_factor.get())
284 } else {
285 DipPoint::splat(Dip::new(24))
286 };
287
288 let _ = v.open_title_bar_context_menu(pos);
289 }
290 }),
291 );
292 vars.0
293 .instance_state
294 .hook(move |a| {
295 handle.enabled().set(can_open_ctx_menu(a.value().clone()));
296 true
297 })
298 .perm();
299
300 let handle = CANCEL_IME_CMD.scoped(id).on_event(
301 false,
302 false,
303 false,
304 hn!(|args| {
305 let s = WINDOWS_SV.read();
306 if let Some(w) = s.windows.get(&id)
307 && let Some(v) = &w.view_window
308 && let Some(f) = s.focused.get()
309 && f.window_id() == id
310 && (matches!(args.scope, CommandScope::Window(w) if w == id)
311 || matches!(args.scope, CommandScope::Widget(w) if w == f.widget_id()))
312 {
313 args.propagation.stop();
314
315 let _ = v.set_ime_area(None);
316
317 IME_EVENT.notify(ImeArgs::now(f, "", None));
318 }
319 }),
320 );
321 vars.0
322 .focused
323 .hook(move |a| {
324 handle.enabled().set(*a.value());
325 true
326 })
327 .perm();
328
329 let handle = DRAG_MOVE_RESIZE_CMD.scoped(id).on_event(
330 vars.0.resizable.get(),
331 true,
332 false,
333 hn!(|args| {
334 if let Some(w) = WINDOWS_SV.read().windows.get(&id)
335 && let Some(v) = &w.view_window
336 {
337 args.propagation.stop();
338 let _ = match args.param::<crate::cmd::ResizeDirection>() {
339 Some(r) => v.drag_resize(*r),
340 None => v.drag_move(),
341 };
342 }
343 }),
344 );
345 vars.0
346 .resizable
347 .hook(move |a| {
348 handle.enabled().set(*a.value());
349 true
350 })
351 .perm();
352 }
353}