zng/
window.rs

1#![cfg(feature = "window")]
2
3//! Window service, widget, events, commands and other types.
4//!
5//! The [`Window!`](struct@Window) widget instantiates a window root, the windows service uses the window root as the
6//! root widget of new window.
7//!
8//! The example below declares a window that toggles if it can close.
9//!
10//! ```
11//! # fn main() {}
12//! use zng::prelude::*;
13//!
14//! fn app() {
15//!     APP.defaults().run_window(async { window() });
16//! }
17//!
18//! fn window() -> window::WindowRoot {
19//!     let allow_close = var(true);
20//!     Window! {
21//!         on_close_requested = hn!(allow_close, |args| {
22//!             if !allow_close.get() {
23//!                 args.propagation().stop();
24//!             }
25//!         });
26//!
27//!         title = "Can I Close?";
28//!         child_align = layout::Align::CENTER;
29//!         child = Toggle! {
30//!             child = Text!(allow_close.map(|a| formatx!("allow close = {a:?}")));
31//!             checked = allow_close;
32//!         };
33//!     }
34//! }
35//! ```
36//!
37//! The [`WINDOWS`] service can be used to open, manage and close windows. The example below
38//! opens a parent and child window.
39//!
40//! ```
41//! use zng::prelude::*;
42//!
43//! fn app() {
44//!     APP.defaults().run(async {
45//!         let r = WINDOWS.open(async { main_window() });
46//!         println!("opened {}", r.wait_rsp().await);
47//!     });
48//! }
49//!
50//! fn main_window() -> window::WindowRoot {
51//!     Window! {
52//!         title = "Main Window";
53//!         child_align = layout::Align::CENTER;
54//!         child = {
55//!             let enabled = var(true);
56//!             Button! {
57//!                 child = Text!("Open/Close Child");
58//!                 on_click = async_hn!(enabled, |_| {
59//!                     enabled.set(false);
60//!
61//!                     if WINDOWS.is_open("child-id") {
62//!                         if let Ok(r) = WINDOWS.close("child-id") {
63//!                             r.wait_done().await;
64//!                         }
65//!                     } else {
66//!                         let parent = WINDOW.id();
67//!                         WINDOWS.open_id("child-id", async move { child_window(parent) }).wait_done().await;
68//!                     }
69//!
70//!                     enabled.set(true);
71//!                 });
72//!                 widget::enabled;
73//!             }
74//!         };
75//!     }
76//! }
77//!
78//! fn child_window(parent: WindowId) -> window::WindowRoot {
79//!     Window! {
80//!         parent;
81//!         title = "Child Window";
82//!         size = (200, 100);
83//!         child = Button! {
84//!             child = Text!("Close");
85//!             on_click = hn!(|_| {
86//!                 let _ = WINDOW.close();
87//!             });
88//!         };
89//!     }
90//! }
91//! # fn main() { }
92//! ```
93//!
94//! # Theming
95//!
96//! Themes are a collection of widget styles that replace the look and feel of the entire application. This can
97//! be achieved by setting contextual properties that change the style and appearance for all widgets in all windows.
98//!
99//! The [`WINDOWS.register_style_fn`] method is a convenient entry point for applying themes. It injects a window
100//! style in all subsequent window instances. A custom theme can define a window style that sets all the contextual properties
101//! that affect the other widgets. Widgets either implement the [`zng::style`] feature or provide their own contextual properties.
102//!
103//! Note that the theme concept is different from the [`accent_color`] and [`color_scheme`] (light/dark). Themes usually provide a more drastic
104//! change in appearance, not just changing the colors. Custom theme implementers should strongly consider incorporating these contextual
105//! color values, these values are usually provided by the operating system, derived from the user preferences.
106//!
107//! ```
108//! use zng::prelude::*;
109//!
110//! #[derive(Debug, Clone, Copy, PartialEq)]
111//! pub enum Theme {
112//!     Default,
113//!     Custom,
114//! }
115//! impl Theme {
116//!     pub fn window_style_fn(&self) -> zng::style::StyleFn {
117//!         match self {
118//!             Theme::Default => style_fn!(|_| zng::window::DefaultStyle!()),
119//!             Theme::Custom => style_fn!(|_| themes::custom()),
120//!         }
121//!     }
122//! }
123//!
124//! mod themes {
125//!     use zng::{prelude::*, style::StyleBuilder};
126//!
127//!     pub fn custom() -> StyleBuilder {
128//!         let base_color = colors::RED.with_alpha(60.pct());
129//!
130//!         zng::window::DefaultStyle! {
131//!             zng::button::style_fn = Style! {
132//!                 color::base_color = base_color;
133//!             };
134//!             zng::toggle::style_fn = Style! {
135//!                 color::base_color = base_color;
136//!             };
137//!             zng::toggle::check_style_fn = Style! {
138//!                 color::accent_color = colors::RED;
139//!             };
140//!         }
141//!     }
142//! }
143//!
144//! # fn example() {
145//! let theme = var(Theme::Default);
146//! WINDOWS.register_style_fn(theme.map(|t| t.window_style_fn()));
147//! # }
148//! ```
149//!
150//! The example above provide a simple theming setup and a very basic custom theme, the theme will be applied for all
151//! subsequent window instances and will dynamically update on variable change. Usually in a full app the
152//! `Theme` enum would be serializable and the variable provided by the [`zng::config::CONFIG`] service.
153//! See [`zng::style`] docs for more details about styles that fully replace the appearance of a widget.
154//!
155//! [`WINDOWS.register_style_fn`]: WINDOWS_Ext::register_style_fn
156//! [`color_scheme`]: fn@zng::color::color_scheme
157//! [`accent_color`]: fn@zng::color::accent_color
158//!
159//! # Full API
160//!
161//! See [`zng_ext_window`], [`zng_app::window`] and [`zng_wgt_window`] for the full window API.
162
163use zng_app::handler::APP_HANDLER;
164pub use zng_app::window::{MonitorId, WINDOW, WindowId, WindowMode};
165
166pub use zng_ext_window::{
167    AppRunWindowExt, AutoSize, CloseWindowResult, FocusIndicator, HeadlessAppWindowExt, HeadlessMonitor, IME_EVENT, ImeArgs, MONITORS,
168    MONITORS_CHANGED_EVENT, MonitorInfo, MonitorQuery, MonitorsChangedArgs, ParallelWin, RenderMode, StartPosition, VideoMode,
169    WINDOW_CHANGED_EVENT, WINDOW_CLOSE_EVENT, WINDOW_CLOSE_REQUESTED_EVENT, WINDOW_Ext, WINDOW_LOAD_EVENT, WINDOW_OPEN_EVENT, WINDOWS,
170    WidgetInfoBuilderImeArea, WidgetInfoImeArea, WindowButton, WindowChangedArgs, WindowCloseArgs, WindowCloseRequestedArgs, WindowIcon,
171    WindowLoadingHandle, WindowOpenArgs, WindowRoot, WindowRootExtenderArgs, WindowState, WindowStateAllowed, WindowVars,
172};
173
174#[cfg(feature = "image")]
175pub use zng_ext_window::{FRAME_IMAGE_READY_EVENT, FrameCaptureMode, FrameImageReadyArgs};
176
177/// Window commands.
178pub mod cmd {
179    pub use zng_ext_window::cmd::*;
180
181    #[cfg(feature = "inspector")]
182    pub use zng_wgt_inspector::INSPECT_CMD;
183}
184
185pub use zng_wgt_window::{BlockWindowLoad, DefaultStyle, WINDOWS_Ext, Window};
186
187pub use zng_wgt_window::events::{
188    on_ime, on_pre_ime, on_pre_window_changed, on_pre_window_close_requested, on_pre_window_exited_fullscreen, on_pre_window_fullscreen,
189    on_pre_window_load, on_pre_window_maximized, on_pre_window_minimized, on_pre_window_moved, on_pre_window_open, on_pre_window_resized,
190    on_pre_window_restored, on_pre_window_state_changed, on_pre_window_unmaximized, on_pre_window_unminimized, on_window_changed,
191    on_window_close_requested, on_window_exited_fullscreen, on_window_fullscreen, on_window_load, on_window_maximized, on_window_minimized,
192    on_window_moved, on_window_open, on_window_resized, on_window_restored, on_window_state_changed, on_window_unmaximized,
193    on_window_unminimized,
194};
195
196#[cfg(feature = "image")]
197pub use zng_wgt_window::events::{on_frame_image_ready, on_pre_frame_image_ready};
198
199/// Debug inspection helpers.
200///
201/// The properties in this module can be set on a window or widget to visualize layout and render internals.
202///
203/// The [`INSPECTOR`] service can be used to configure the inspector window, add custom watchers.
204/// Note that you can use the [`cmd::INSPECT_CMD`] command to open the Inspector.
205///
206/// # Examples
207///
208/// The example below registers two custom live updating watchers.
209///
210/// ```
211/// # use zng::prelude::*;
212/// #
213/// # let _scope = APP.minimal();
214/// window::inspector::INSPECTOR.register_watcher(|wgt, builder| {
215///     // watch custom info metadata
216///     use zng::markdown::WidgetInfoExt as _;
217///     let watcher = wgt.info().map(|i| formatx!("{:?}", i.anchor()));
218///     builder.insert("markdown.anchor", watcher);
219///
220///     // watch value that can change every layout/render without info rebuild
221///     let watcher = wgt.render_watcher(|i| formatx!("{:?}", i.bounds_info().inline().is_some()));
222///     builder.insert("is_inlined", watcher);
223/// });
224/// ```
225///
226/// The closure is called on widget selection change (in the inspector screen), the values are presented in the
227/// `/* INFO */` section of the properties panel.
228///
229/// # Full API
230///
231/// See [`zng_wgt_inspector`] for the full API.
232///
233/// [`cmd::INSPECT_CMD`]: crate::window::cmd::INSPECT_CMD
234/// [`INSPECTOR`]: crate::window::inspector::INSPECTOR
235#[cfg(feature = "inspector")]
236pub mod inspector {
237    pub use zng_wgt_inspector::debug::{InspectMode, show_bounds, show_center_points, show_directional_query, show_hit_test, show_rows};
238
239    pub use zng_wgt_inspector::{INSPECTOR, InspectedInfo, InspectedTree, InspectedWidget, InspectorWatcherBuilder};
240}
241
242/// Default handler registered in mobile platforms.
243///
244/// This is registered on app init for platforms that only support one window, it intercepts headed window open requests after the
245/// first and opens them as a nested modal layer on the main window.
246///
247/// See [`WINDOWS::register_open_nested_handler`] for more details.
248pub fn default_mobile_nested_open_handler(args: &mut zng_ext_window::OpenNestedHandlerArgs) {
249    use crate::prelude::*;
250
251    if !matches!(args.ctx().mode(), WindowMode::Headed) {
252        return;
253    }
254
255    let open: Vec<_> = WINDOWS
256        .widget_trees()
257        .into_iter()
258        .filter(|w| WINDOWS.mode(w.window_id()) == Ok(window::WindowMode::Headed) && WINDOWS.nest_parent(w.window_id()).is_none())
259        .take(2)
260        .collect();
261
262    if open.len() == 1 {
263        let id = args.ctx().id();
264        let vars = args.vars();
265        #[cfg(feature = "image")]
266        let icon = vars.icon();
267        let title = vars.title();
268        let node = task::parking_lot::Mutex::new(Some(args.nest()));
269
270        let host_win_id = open[0].window_id();
271        let host_wgt_id = WidgetId::new_unique();
272        layer::LAYERS_INSERT_CMD.scoped(host_win_id).notify_param((
273            layer::LayerIndex::TOP_MOST,
274            wgt_fn!(|_: ()| {
275                let frame = Container! {
276                    layout::margin = 10;
277                    layout::align = Align::CENTER;
278                    widget::modal = true;
279                    #[cfg(feature = "color_filter")]
280                    color::filter::drop_shadow = {
281                        offset: 4,
282                        blur_radius: 6,
283                        color: colors::BLACK.with_alpha(50.pct()),
284                    };
285                    widget::background_color = light_dark(rgb(0.95, 0.95, 0.95), rgb(0.05, 0.05, 0.05));
286                    widget::corner_radius = 4;
287                    layout::padding = 5;
288                    child_spacing = 5;
289                    child_top = Container! {
290                        child_spacing = 4;
291                        #[cfg(feature = "image")]
292                        child_start = Image! {
293                            layout::size = 24;
294                            source = icon.map(|i| match i {
295                                WindowIcon::Image(s) => s.clone(),
296                                WindowIcon::Default => ImageSource::flood(layout::PxSize::zero(), rgba(0, 0, 0, 0), None),
297                                _ => unreachable!(),
298                            });
299                        };
300                        child = Text! {
301                            txt = title.clone();
302                            txt_align = Align::CENTER;
303                            font_weight = FontWeight::BOLD;
304                        };
305                        #[cfg(feature = "button")]
306                        child_end = Button! {
307                            style_fn = zng::button::LightStyle!();
308                            child = ICONS.get_or("close", || Text!("x"));
309                            on_click = hn!(|args| {
310                                args.propagation().stop();
311                                let _ = WINDOWS.close(id);
312                            });
313                        };
314                    };
315                    child = node.lock().take().into_node().into_widget();
316                };
317                Container! {
318                    id = host_wgt_id;
319                    child = frame;
320                    widget::background_color = colors::BLACK.with_alpha(20.pct());
321                    layout::padding = WINDOWS.vars(host_win_id).unwrap().safe_padding().map_into();
322                }
323            }),
324        ));
325
326        window::WINDOW_CLOSE_EVENT
327            .on_pre_event(hn!(|args| {
328                if args.windows.contains(&id) {
329                    APP_HANDLER.unsubscribe();
330                    layer::LAYERS_REMOVE_CMD.scoped(host_win_id).notify_param(host_wgt_id);
331                }
332            }))
333            .perm();
334    }
335}