1use zng_app::{
4 event::{CommandHandle, CommandInfoExt, CommandNameExt, CommandScope, command},
5 hn,
6 shortcut::{CommandShortcutExt, shortcut},
7 view_process::VIEW_PROCESS,
8 window::WindowId,
9};
10
11use zng_layout::unit::{Dip, DipPoint, PxPoint, PxToDip as _};
12use zng_view_api::window::{WindowCapability, WindowState};
13use zng_wgt::{CommandIconExt as _, ICONS, wgt_fn};
14
15use crate::{IME_EVENT, ImeArgs, WINDOWS, WINDOWS_SV, WindowInstanceState, WindowVars};
16
17pub use zng_view_api::window::ResizeDirection;
18
19command! {
20 pub static CLOSE_CMD {
22 l10n!: true,
23 name: "Close",
24 info: "Close the window",
25 shortcut: [shortcut!(ALT + F4), shortcut!(CTRL + 'W')],
26 icon: wgt_fn!(|_| ICONS.get(["window-close", "close"])),
27 };
28
29 pub static MINIMIZE_CMD {
31 l10n!: true,
32 name: "Minimize",
33 info: "Minimize the window",
34 icon: wgt_fn!(|_| ICONS.get(["window-minimize"])),
35 };
36
37 pub static MAXIMIZE_CMD {
39 l10n!: true,
40 name: "Maximize",
41 info: "Maximize the window",
42 icon: wgt_fn!(|_| ICONS.get(["window-maximize"])),
43 };
44
45 pub static FULLSCREEN_CMD {
52 l10n!: true,
53 name: "Fullscreen",
54 info: "Toggle fullscreen mode on the window",
55 shortcut: {
56 let a = if cfg!(target_os = "macos") {
57 shortcut!(CTRL | SHIFT + 'F')
58 } else {
59 shortcut!(F11)
60 };
61 [a, shortcut!(ZoomToggle)]
62 },
63 icon: wgt_fn!(|_| ICONS.get(["window-windowed-fullscreen", "window-fullscreen", "fullscreen"])),
64 };
65
66 pub static EXCLUSIVE_FULLSCREEN_CMD {
73 l10n!: true,
74 name: "Exclusive Fullscreen",
75 info: "Toggle exclusive fullscreen mode on the window",
76 icon: wgt_fn!(|_| ICONS.get(["window-exclusive-fullscreen", "window-fullscreen", "fullscreen"])),
77 };
78
79 pub static RESTORE_CMD {
83 l10n!: true,
84 name: "Restore",
85 info: "Restores the window to its previous non-minimized state or normal state",
86 icon: wgt_fn!(|_| ICONS.get(["window-restore"])),
87 };
88
89 pub static CANCEL_IME_CMD;
93
94 pub static DRAG_MOVE_RESIZE_CMD;
103
104 pub static OPEN_TITLE_BAR_CONTEXT_MENU_CMD;
114}
115
116pub(super) struct WindowCommands {
117 maximize_handle: CommandHandle,
118 minimize_handle: CommandHandle,
119 restore_handle: CommandHandle,
120
121 _fullscreen_handle: CommandHandle,
122 _exclusive_handle: CommandHandle,
123
124 _close_handle: CommandHandle,
125}
126impl WindowCommands {
127 pub fn init(id: WindowId, vars: &WindowVars) {
129 let state = vars.state();
130 let restore_state = vars.restore_state();
131 let s = state.get();
132 let c = WindowCommands {
133 maximize_handle: MAXIMIZE_CMD.scoped(id).on_event(
134 !matches!(s, WindowState::Maximized),
135 true,
136 false,
137 hn!(state, |args| {
138 args.propagation.stop();
139 state.set(WindowState::Maximized);
140 }),
141 ),
142 minimize_handle: MINIMIZE_CMD.scoped(id).on_event(
143 !matches!(s, WindowState::Minimized),
144 true,
145 false,
146 hn!(state, |args| {
147 args.propagation.stop();
148 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!(state, restore_state, |args| {
156 args.propagation.stop();
157 state.set(restore_state.get());
158 }),
159 ),
160 _fullscreen_handle: FULLSCREEN_CMD.scoped(id).on_event(
161 true,
162 true,
163 false,
164 hn!(state, restore_state, |args| {
165 if let WindowState::Fullscreen = state.get() {
166 state.set(restore_state.get());
167 } else {
168 state.set(WindowState::Fullscreen);
169 }
170 }),
171 ),
172 _exclusive_handle: EXCLUSIVE_FULLSCREEN_CMD.scoped(id).on_event(
173 true,
174 true,
175 false,
176 hn!(state, |args| {
177 if let WindowState::Exclusive = state.get() {
178 state.set(restore_state.get());
179 } else {
180 state.set(WindowState::Exclusive);
181 }
182 }),
183 ),
184 _close_handle: CLOSE_CMD.scoped(id).on_event(
185 true,
186 true,
187 false,
188 hn!(|args| {
189 args.propagation.stop();
190 let _ = WINDOWS.close(id);
191 }),
192 ),
193 };
194 state
195 .hook(move |a| {
196 let state = *a.value();
197 let c = &c; c.restore_handle.enabled().set(state != WindowState::Normal);
199 c.maximize_handle.enabled().set(state != WindowState::Maximized);
200 c.minimize_handle.enabled().set(state != WindowState::Minimized);
201 true
202 })
203 .perm();
204
205 fn can_open_ctx_menu(state: WindowInstanceState) -> bool {
206 matches!(state, WindowInstanceState::Loaded { has_view: true })
207 && VIEW_PROCESS.info().window.contains(WindowCapability::OPEN_TITLE_BAR_CONTEXT_MENU)
208 }
209 let handle = OPEN_TITLE_BAR_CONTEXT_MENU_CMD.scoped(id).on_event(
210 can_open_ctx_menu(vars.0.instance_state.get()),
211 true,
212 false,
213 hn!(|args| {
214 if let Some(w) = WINDOWS_SV.read().windows.get(&id)
215 && let Some(vars) = &w.vars
216 && let Some(r) = &w.root
217 && let Some(v) = &r.view_window
218 {
219 args.propagation.stop();
220
221 let pos = if let Some(p) = args.param::<DipPoint>() {
222 *p
223 } else if let Some(p) = args.param::<PxPoint>() {
224 p.to_dip(vars.0.scale_factor.get())
225 } else {
226 DipPoint::splat(Dip::new(24))
227 };
228
229 let _ = v.open_title_bar_context_menu(pos);
230 }
231 }),
232 );
233 vars.0
234 .instance_state
235 .hook(move |a| {
236 handle.enabled().set(can_open_ctx_menu(a.value().clone()));
237 true
238 })
239 .perm();
240
241 let handle = CANCEL_IME_CMD.scoped(id).on_event(
242 false,
243 false,
244 false,
245 hn!(|args| {
246 let s = WINDOWS_SV.read();
247 if let Some(w) = s.windows.get(&id)
248 && let Some(r) = &w.root
249 && let Some(v) = &r.view_window
250 && let Some(f) = s.focused.get()
251 && f.window_id() == id
252 && (matches!(args.scope, CommandScope::Window(w) if w == id)
253 || matches!(args.scope, CommandScope::Widget(w) if w == f.widget_id()))
254 {
255 args.propagation.stop();
256
257 let _ = v.set_ime_area(None);
258
259 IME_EVENT.notify(ImeArgs::now(f, "", None));
260 }
261 }),
262 );
263 vars.0
264 .focused
265 .hook(move |a| {
266 handle.enabled().set(*a.value());
267 true
268 })
269 .perm();
270
271 let handle = DRAG_MOVE_RESIZE_CMD.scoped(id).on_event(
272 vars.0.resizable.get(),
273 true,
274 false,
275 hn!(|args| {
276 if let Some(w) = WINDOWS_SV.read().windows.get(&id)
277 && let Some(r) = &w.root
278 && let Some(v) = &r.view_window
279 {
280 args.propagation.stop();
281 let _ = match args.param::<crate::cmd::ResizeDirection>() {
282 Some(r) => v.drag_resize(*r),
283 None => v.drag_move(),
284 };
285 }
286 }),
287 );
288 vars.0
289 .resizable
290 .hook(move |a| {
291 handle.enabled().set(*a.value());
292 true
293 })
294 .perm();
295 }
296}