Skip to main content

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