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