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(v) = &w.view_window
217 {
218 args.propagation.stop();
219
220 let pos = if let Some(p) = args.param::<DipPoint>() {
221 *p
222 } else if let Some(p) = args.param::<PxPoint>() {
223 p.to_dip(vars.0.scale_factor.get())
224 } else {
225 DipPoint::splat(Dip::new(24))
226 };
227
228 let _ = v.open_title_bar_context_menu(pos);
229 }
230 }),
231 );
232 vars.0
233 .instance_state
234 .hook(move |a| {
235 handle.enabled().set(can_open_ctx_menu(a.value().clone()));
236 true
237 })
238 .perm();
239
240 let handle = CANCEL_IME_CMD.scoped(id).on_event(
241 false,
242 false,
243 false,
244 hn!(|args| {
245 let s = WINDOWS_SV.read();
246 if let Some(w) = s.windows.get(&id)
247 && let Some(v) = &w.view_window
248 && let Some(f) = s.focused.get()
249 && f.window_id() == id
250 && (matches!(args.scope, CommandScope::Window(w) if w == id)
251 || matches!(args.scope, CommandScope::Widget(w) if w == f.widget_id()))
252 {
253 args.propagation.stop();
254
255 let _ = v.set_ime_area(None);
256
257 IME_EVENT.notify(ImeArgs::now(f, "", None));
258 }
259 }),
260 );
261 vars.0
262 .focused
263 .hook(move |a| {
264 handle.enabled().set(*a.value());
265 true
266 })
267 .perm();
268
269 let handle = DRAG_MOVE_RESIZE_CMD.scoped(id).on_event(
270 vars.0.resizable.get(),
271 true,
272 false,
273 hn!(|args| {
274 if let Some(w) = WINDOWS_SV.read().windows.get(&id)
275 && let Some(v) = &w.view_window
276 {
277 args.propagation.stop();
278 let _ = match args.param::<crate::cmd::ResizeDirection>() {
279 Some(r) => v.drag_resize(*r),
280 None => v.drag_move(),
281 };
282 }
283 }),
284 );
285 vars.0
286 .resizable
287 .hook(move |a| {
288 handle.enabled().set(*a.value());
289 true
290 })
291 .perm();
292 }
293}