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