zng_wgt_window/lib.rs
1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Window widget, properties, properties and nodes.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12// used by fallback_chrome
13zng_wgt::enable_widget_macros!();
14
15use zng_color::colors::BASE_COLOR_VAR;
16use zng_ext_input::focus::{DirectionalNav, FocusScopeOnFocus, TabNav};
17use zng_ext_window::{
18 HeadlessMonitor, RenderMode, StartPosition, WINDOW_Ext as _, WINDOWS, WindowChangedArgs, WindowCloseArgs, WindowCloseRequestedArgs,
19 WindowOpenArgs, WindowRoot,
20};
21use zng_var::contextual_var;
22use zng_wgt::{base_color, is_mobile, prelude::*};
23use zng_wgt_fill::background_color;
24use zng_wgt_input::focus::{
25 FOCUS_HIGHLIGHT_OFFSETS_VAR, FOCUS_HIGHLIGHT_WIDTHS_VAR, directional_nav, focus_highlight, focus_scope, focus_scope_behavior, tab_nav,
26};
27use zng_wgt_text::{FONT_SIZE_VAR, font_color, lang};
28
29#[cfg(feature = "image")]
30use zng_ext_window::FrameImageReadyArgs;
31
32pub mod events;
33mod window_properties;
34
35pub use self::window_properties::*;
36
37mod fallback_chrome;
38pub use fallback_chrome::fallback_chrome;
39
40/// A window container.
41///
42/// The instance type is [`WindowRoot`], it can be given to the [`WINDOWS`] service
43/// to open a system window that is kept in sync with the window properties set in the widget.
44///
45/// See [`run_window`] for more details.
46///
47/// [`WindowRoot`]: zng_ext_window::WindowRoot
48/// [`run_window`]: zng_ext_window::AppRunWindowExt::run_window
49#[widget($crate::Window)]
50pub struct Window(zng_wgt_style::StyleMix<zng_wgt_container::Container>);
51zng_wgt_style::impl_style_fn!(Window, DefaultStyle);
52impl Window {
53 fn widget_intrinsic(&mut self) {
54 self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
55 widget_set! {
56 self;
57
58 // set the root font size
59 font_size = FONT_SIZE_VAR;
60
61 // set layout direction.
62 lang = zng_ext_l10n::LANG_VAR;
63
64 focus_scope = true;
65 tab_nav = TabNav::Cycle;
66 directional_nav = DirectionalNav::Cycle;
67 focus_scope_behavior = FocusScopeOnFocus::LastFocused;
68
69 #[cfg(feature = "config")]
70 config_block_window_load = true;
71 #[cfg(feature = "config")]
72 save_state = SaveState::enabled();
73
74 safe_padding = contextual_var(|| WINDOW.vars().safe_padding().map(|p| SideOffsets::from(*p)));
75
76 when #is_mobile {
77 // users tap the main background to dismiss `TextInput!` soft keyboard
78 focus_scope_behavior = FocusScopeOnFocus::Widget;
79 }
80 }
81
82 self.widget_builder().push_build_action(|wgt| {
83 wgt.push_intrinsic(NestGroup::EVENT, "layers", zng_wgt_layer::layers_node);
84 });
85 }
86
87 /// Build a [`WindowRoot`].
88 ///
89 /// [`WindowRoot`]: zng_ext_window::WindowRoot
90 pub fn widget_build(&mut self) -> WindowRoot {
91 let mut wgt = self.widget_take();
92 WindowRoot::new(
93 wgt.capture_value_or_else(property_id!(Self::id), WidgetId::new_unique),
94 wgt.capture_value_or_default::<StartPosition>(property_id!(Self::start_position)),
95 wgt.capture_value_or_default(property_id!(Self::kiosk)),
96 wgt.capture_value_or_else(property_id!(Self::allow_transparency), || true),
97 wgt.capture_value_or_default::<Option<RenderMode>>(property_id!(Self::render_mode)),
98 wgt.capture_value_or_default::<HeadlessMonitor>(property_id!(Self::headless_monitor)),
99 wgt.capture_value_or_default(property_id!(Self::start_focused)),
100 wgt.build(),
101 )
102 }
103}
104
105/// Default window style.
106///
107/// See also [`register_style_fn`] for how to set a style for all windows in the app.
108///
109/// [`register_style_fn`]: WINDOWS_Ext::register_style_fn
110#[widget($crate::DefaultStyle)]
111pub struct DefaultStyle(zng_wgt_style::Style);
112impl DefaultStyle {
113 fn widget_intrinsic(&mut self) {
114 widget_set! {
115 self;
116
117 replace = true;
118 font_color = light_dark(rgb(0.08, 0.08, 0.08), rgb(0.92, 0.92, 0.92));
119 base_color = light_dark(rgb(0.9, 0.9, 0.9), rgb(0.1, 0.1, 0.1));
120 background_color = BASE_COLOR_VAR.rgba();
121 clear_color = BASE_COLOR_VAR.rgba();
122 focus_highlight = {
123 offsets: FOCUS_HIGHLIGHT_OFFSETS_VAR,
124 widths: FOCUS_HIGHLIGHT_WIDTHS_VAR,
125 sides: light_dark(colors::BLACK, rgb(200, 200, 200)).rgba_map(BorderSides::dashed),
126 };
127
128 when #is_mobile {
129 font_size = FONT_SIZE_VAR.map(|f| f.clone() * 1.5.fct());
130 }
131
132 when #needs_fallback_chrome {
133 custom_chrome_adorner_fn = wgt_fn!(|_| { fallback_chrome() });
134 safe_padding = 0;
135 custom_chrome_padding_fn = contextual_var(|| {
136 let vars = WINDOW.vars();
137 expr_var! {
138 let title_padding = SideOffsets::new(28, 0, 0, 0);
139 let chrome_padding = if matches!(#{vars.state()}, zng_ext_window::WindowState::Maximized) {
140 title_padding
141 } else {
142 title_padding + SideOffsets::new_all(5)
143 };
144 // safe_padding is 0 in GNOME+Wayland, but better be safe :D
145 let safe_padding = SideOffsets::from(*#{vars.safe_padding()});
146 chrome_padding + safe_padding
147 }
148 });
149 }
150 }
151 }
152}
153
154/// Padding required to avoid physical screen obstructions.
155///
156/// By default this is [`WINDOW.vars().safe_padding()`] that is defined by the operating system. You can
157/// unset this property to implement your own *unsafe area* handling.
158///
159/// [`WINDOW.vars().safe_padding()`]: zng_ext_window::WindowVars::safe_padding
160#[property(CHILD_LAYOUT, default(0), widget_impl(Window, DefaultStyle))]
161pub fn safe_padding(child: impl IntoUiNode, padding: impl IntoVar<SideOffsets>) -> UiNode {
162 zng_wgt_container::padding(child, padding)
163}
164
165/// Defines how the window is positioned when it first opens.
166#[property(LAYOUT, widget_impl(Window, DefaultStyle))]
167pub fn start_position(wgt: &mut WidgetBuilding, position: impl IntoValue<StartPosition>) {
168 let _ = position;
169 wgt.expect_property_capture();
170}
171
172/// If the window steals keyboard focus on open.
173///
174/// By default the operating system decides if the window will receive focus after opening, usually it is focused
175/// only if the process that started the window already has focus. Enabling this ensures that focus
176/// is moved to the new window, potentially stealing the focus from other apps and disrupting the user.
177#[property(CONTEXT, widget_impl(Window))]
178pub fn start_focused(wgt: &mut WidgetBuilding, enabled: impl IntoValue<bool>) {
179 let _ = enabled;
180 wgt.expect_property_capture();
181}
182
183/// Lock-in kiosk mode.
184///
185/// In kiosk mode the only window states allowed are fullscreen or fullscreen exclusive, and
186/// all subsequent windows opened are child of the kiosk window.
187///
188/// Note that this does not configure the operating system,
189/// you still need to setup a kiosk environment. This just stops the
190/// app itself from accidentally exiting fullscreen.
191#[property(CONTEXT, widget_impl(Window))]
192pub fn kiosk(wgt: &mut WidgetBuilding, kiosk: impl IntoValue<bool>) {
193 let _ = kiosk;
194 wgt.expect_property_capture();
195}
196
197/// If semi-transparent content is see-through, mixing with the operating system pixels behind the window.
198///
199/// Note that to actually see behind the window you must set the [`clear_color`] and [`background_color`] to a transparent color.
200/// The composition is a simple alpha blend, effects like blur do not apply to the pixels behind the window.
201///
202/// [`clear_color`]: fn@clear_color
203/// [`background_color`]: fn@background_color
204#[property(CONTEXT, widget_impl(Window, DefaultStyle))]
205pub fn allow_transparency(wgt: &mut WidgetBuilding, allow: impl IntoValue<bool>) {
206 let _ = allow;
207 wgt.expect_property_capture();
208}
209
210/// Render performance mode overwrite for this window, if set to `None` the [`WINDOWS.default_render_mode`] is used.
211///
212/// The `view-process` will try to match the mode, if it is not available a fallback mode is selected,
213/// see [`RenderMode`] for more details about each mode and fallbacks.
214///
215/// [`WINDOWS.default_render_mode`]: zng_ext_window::WINDOWS::default_render_mode
216/// [`RenderMode`]: crate::RenderMode
217#[property(CONTEXT, widget_impl(Window))]
218pub fn render_mode(wgt: &mut WidgetBuilding, mode: impl IntoValue<Option<RenderMode>>) {
219 let _ = mode;
220 wgt.expect_property_capture();
221}
222
223/// Event just after the window opens.
224///
225/// This event notifies once per window, after the window content is inited.
226///
227/// This property is the same as [`on_pre_window_open`].
228///
229/// [`on_pre_window_open`]: fn@events::on_pre_window_open
230#[property(EVENT, widget_impl(Window))]
231pub fn on_open(child: impl IntoUiNode, handler: Handler<WindowOpenArgs>) -> UiNode {
232 events::on_pre_window_open(child, handler)
233}
234
235/// Event just after the window loads.
236///
237/// This event notifies once per window, after the first layout and all [`WindowLoadingHandle`]
238/// have expired or dropped.
239///
240/// This property is the same as [`on_pre_window_load`].
241///
242/// [`WindowLoadingHandle`]: zng_ext_window::WindowLoadingHandle
243/// [`on_pre_window_load`]: fn@events::on_pre_window_load
244#[property(EVENT, widget_impl(Window))]
245pub fn on_load(child: impl IntoUiNode, handler: Handler<WindowOpenArgs>) -> UiNode {
246 events::on_pre_window_load(child, handler)
247}
248
249/// On window close requested.
250///
251/// This event notifies every time an attempt to close the window is made. Close can be cancelled by stopping propagation
252/// on the event args, the window only closes after all handlers receive this event and propagation is not stopped.
253///
254/// This property is the same as [`on_window_close_requested`].
255///
256/// [`on_window_close_requested`]: fn@events::on_window_close_requested
257#[property(EVENT, widget_impl(Window))]
258pub fn on_close_requested(child: impl IntoUiNode, handler: Handler<WindowCloseRequestedArgs>) -> UiNode {
259 events::on_window_close_requested(child, handler)
260}
261
262/// On window close.
263///
264/// The window will deinit after this event.
265///
266/// This property is the same as [`on_pre_window_close`].
267///
268/// [`on_pre_window_close`]: fn@events::on_pre_window_close
269#[property(EVENT, widget_impl(Window))]
270pub fn on_close(child: impl IntoUiNode, handler: Handler<WindowCloseArgs>) -> UiNode {
271 events::on_pre_window_close(child, handler)
272}
273
274/// On window position changed.
275///
276/// This event notifies every time the window position changes. You can also track the window
277/// position using the [`actual_position`] variable.
278///
279/// This property is the same as [`on_pre_window_moved`].
280///
281/// [`actual_position`]: zng_ext_window::WindowVars::actual_position
282/// [`on_pre_window_moved`]: fn@events::on_pre_window_moved
283#[property(EVENT, widget_impl(Window))]
284pub fn on_moved(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
285 events::on_pre_window_moved(child, handler)
286}
287
288/// On window size changed.
289///
290/// This event notifies every time the window content area size changes. You can also track
291/// the window size using the [`actual_size`] variable.
292///
293/// This property is the same as [`on_pre_window_resized`].
294///
295/// [`actual_size`]: zng_ext_window::WindowVars::actual_size
296/// [`on_pre_window_resized`]: fn@events::on_pre_window_resized
297#[property(EVENT, widget_impl(Window))]
298pub fn on_resized(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
299 events::on_pre_window_resized(child, handler)
300}
301
302/// On window state changed.
303///
304/// This event notifies every time the window state changes.
305///
306/// Note that you can also track the window
307/// state by setting [`state`] to a read-write variable.
308///
309/// This property is the same as [`on_pre_window_state_changed`].
310///
311/// [`state`]: fn@state
312/// [`on_pre_window_state_changed`]: fn@events::on_pre_window_state_changed
313#[property(EVENT, widget_impl(Window))]
314pub fn on_state_changed(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
315 events::on_pre_window_state_changed(child, handler)
316}
317
318/// On window maximized.
319///
320/// This event notifies every time the window state changes to maximized.
321///
322/// This property is the same as [`on_pre_window_maximized`].
323///
324/// [`on_pre_window_maximized`]: fn@events::on_pre_window_maximized
325#[property(EVENT, widget_impl(Window))]
326pub fn on_maximized(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
327 events::on_pre_window_maximized(child, handler)
328}
329
330/// On window exited the maximized state.
331///
332/// This event notifies every time the window state changes to a different state from maximized.
333///
334/// This property is the same as [`on_pre_window_unmaximized`].
335///
336/// [`on_pre_window_unmaximized`]: fn@events::on_pre_window_unmaximized
337#[property(EVENT, widget_impl(Window))]
338pub fn on_unmaximized(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
339 events::on_pre_window_unmaximized(child, handler)
340}
341
342/// On window minimized.
343///
344/// This event notifies every time the window state changes to minimized.
345///
346/// This property is the same as [`on_pre_window_maximized`].
347///
348/// [`on_pre_window_maximized`]: fn@events::on_pre_window_maximized
349#[property(EVENT, widget_impl(Window))]
350pub fn on_minimized(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
351 events::on_pre_window_minimized(child, handler)
352}
353
354/// On window exited the minimized state.
355///
356/// This event notifies every time the window state changes to a different state from minimized.
357///
358/// This property is the same as [`on_pre_window_unminimized`].
359///
360/// [`on_pre_window_unminimized`]: fn@events::on_pre_window_unminimized
361#[property(EVENT, widget_impl(Window))]
362pub fn on_unminimized(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
363 events::on_pre_window_unminimized(child, handler)
364}
365
366/// On window state changed to [`Normal`].
367///
368/// This event notifies every time the window state changes to [`Normal`].
369///
370/// This property is the same as [`on_pre_window_restored`].
371///
372/// [`Normal`]: zng_ext_window::WindowState::Normal
373/// [`on_pre_window_restored`]: fn@events::on_pre_window_restored
374#[property(EVENT, widget_impl(Window))]
375pub fn on_restored(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
376 events::on_pre_window_restored(child, handler)
377}
378
379/// On window enter one of the fullscreen states.
380///
381/// This event notifies every time the window state changes to [`Fullscreen`] or [`Exclusive`].
382///
383/// This property is the same as [`on_pre_window_fullscreen`].
384///
385/// [`Fullscreen`]: zng_ext_window::WindowState::Fullscreen
386/// [`Exclusive`]: zng_ext_window::WindowState::Exclusive
387/// [`on_pre_window_fullscreen`]: fn@events::on_pre_window_fullscreen
388#[property(EVENT, widget_impl(Window))]
389pub fn on_fullscreen(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
390 events::on_pre_window_fullscreen(child, handler)
391}
392
393/// On window is no longer fullscreen.
394///
395/// This event notifies every time the window state changes to one that is not fullscreen.
396///
397/// This property is the same as [`on_pre_window_exited_fullscreen`].
398///
399/// [`on_pre_window_exited_fullscreen`]: fn@events::on_pre_window_exited_fullscreen
400#[property(EVENT, widget_impl(Window))]
401pub fn on_exited_fullscreen(child: impl IntoUiNode, handler: Handler<WindowChangedArgs>) -> UiNode {
402 events::on_pre_window_exited_fullscreen(child, handler)
403}
404
405/// On window frame rendered.
406///
407/// If [`frame_capture_mode`](fn@frame_capture_mode) is set the image will be available in the event args.
408#[cfg(feature = "image")]
409#[property(EVENT, widget_impl(Window))]
410pub fn on_frame_image_ready(child: impl IntoUiNode, handler: Handler<FrameImageReadyArgs>) -> UiNode {
411 events::on_pre_frame_image_ready(child, handler)
412}
413
414/// Imaginary monitor used by the window when it runs in [headless mode](zng_app::window::WindowMode::is_headless).
415#[property(LAYOUT, widget_impl(Window))]
416pub fn headless_monitor(wgt: &mut WidgetBuilding, monitor: impl IntoValue<HeadlessMonitor>) {
417 let _ = monitor;
418 wgt.expect_property_capture();
419}
420
421/// Extension methods for [`WINDOWS`].
422#[allow(non_camel_case_types)]
423pub trait WINDOWS_Ext {
424 /// Set the `style_fn` in all windows instantiated after this call.
425 ///
426 /// This method is the recommended entry point for themes. It uses [`register_root_extender`]
427 /// to inject the style in every new window instance.
428 ///
429 /// [`register_root_extender`]: WINDOWS::register_root_extender
430 fn register_style_fn(&self, style_fn: impl IntoVar<zng_wgt_style::StyleFn>);
431}
432impl WINDOWS_Ext for WINDOWS {
433 fn register_style_fn(&self, style: impl IntoVar<zng_wgt_style::StyleFn>) {
434 let style = style.into_var();
435 WINDOWS.register_root_extender(move |args| style_fn(args.root, style.clone()));
436 }
437}