Skip to main content

floem/window/
mod.rs

1pub(crate) mod handle;
2pub(crate) mod id;
3pub mod mock;
4pub(crate) mod state;
5pub(crate) mod tracking;
6
7pub use id::{Urgency, WindowIdExt};
8pub use mock::MockWindow;
9pub use state::WindowState;
10
11use peniko::Color;
12use peniko::kurbo::{Point, Size};
13pub use winit::icon::{Icon, RgbaIcon};
14pub use winit::monitor::Fullscreen;
15pub use winit::window::ResizeDirection;
16pub use winit::window::Theme;
17pub use winit::window::WindowButtons;
18pub use winit::window::WindowId;
19pub use winit::window::WindowLevel;
20
21use crate::AnyView;
22use crate::app::{AppUpdateEvent, add_app_update_event};
23use crate::view::IntoView;
24
25pub struct WindowCreation {
26    pub(crate) view_fn: Box<dyn FnOnce(WindowId) -> AnyView>,
27    pub(crate) config: Option<WindowConfig>,
28}
29
30/// Configures various attributes (e.g. size, position, transparency, etc.) of a window.
31pub struct WindowConfig {
32    pub(crate) size: Option<Size>,
33    pub(crate) min_size: Option<Size>,
34    pub(crate) max_size: Option<Size>,
35    pub(crate) position: Option<Point>,
36    pub(crate) show_titlebar: bool,
37    pub(crate) transparent: bool,
38    pub(crate) fullscreen: Option<Fullscreen>,
39    pub(crate) window_icon: Option<Icon>,
40    pub(crate) title: String,
41    pub(crate) enabled_buttons: WindowButtons,
42    pub(crate) resizable: bool,
43    pub(crate) undecorated: bool,
44    pub(crate) undecorated_shadow: bool,
45    pub(crate) window_level: WindowLevel,
46    /// Applies chosen theme or os theme, when `None` is provided.
47    pub(crate) theme_override: Option<Theme>,
48    pub(crate) apply_default_theme: bool,
49    pub(crate) font_embolden: f32,
50    #[allow(dead_code)]
51    pub(crate) mac_os_config: Option<MacOSWindowConfig>,
52    pub(crate) win_os_config: Option<WinOSWindowConfig>,
53    pub(crate) web_config: Option<WebWindowConfig>,
54}
55
56impl Default for WindowConfig {
57    fn default() -> Self {
58        Self {
59            size: None,
60            min_size: None,
61            max_size: None,
62            position: None,
63            show_titlebar: true,
64            transparent: false,
65            fullscreen: None,
66            window_icon: None,
67            title: std::env::current_exe()
68                .ok()
69                .and_then(|p| p.file_name().map(|f| f.to_string_lossy().into_owned()))
70                .unwrap_or("Floem Window".to_string()),
71            enabled_buttons: WindowButtons::all(),
72            resizable: true,
73            undecorated: false,
74            undecorated_shadow: false,
75            window_level: WindowLevel::Normal,
76            theme_override: None,
77            apply_default_theme: true,
78            font_embolden: if cfg!(target_os = "macos") { 0.1 } else { 0. },
79            mac_os_config: None,
80            win_os_config: None,
81            web_config: None,
82        }
83    }
84}
85
86impl WindowConfig {
87    /// Requests the window to be of specific dimensions.
88    ///
89    /// If this is not set, some platform-specific dimensions will be used.
90    #[inline]
91    pub fn size(mut self, size: impl Into<Size>) -> Self {
92        self.size = Some(size.into());
93        self
94    }
95
96    /// Requests the window to be of specific min dimensions.
97    #[inline]
98    pub fn min_size(mut self, size: impl Into<Size>) -> Self {
99        self.min_size = Some(size.into());
100        self
101    }
102
103    /// Requests the window to be of specific max dimensions.
104    #[inline]
105    pub fn max_size(mut self, size: impl Into<Size>) -> Self {
106        self.max_size = Some(size.into());
107        self
108    }
109
110    /// Sets a desired initial position for the window.
111    ///
112    /// If this is not set, some platform-specific position will be chosen.
113    #[inline]
114    pub fn position(mut self, position: Point) -> Self {
115        self.position = Some(position);
116        self
117    }
118
119    /// Sets whether the window should have a title bar.
120    ///
121    /// The default is `true`.
122    #[inline]
123    pub fn show_titlebar(mut self, show_titlebar: bool) -> Self {
124        self.show_titlebar = show_titlebar;
125        self
126    }
127
128    /// Sets whether the window should have a border, a title bar, etc.
129    ///
130    /// The default is `false`.
131    #[inline]
132    pub fn undecorated(mut self, undecorated: bool) -> Self {
133        self.undecorated = undecorated;
134        self
135    }
136
137    /// Sets whether the window should have background drop shadow when undecorated.
138    ///
139    /// The default is `false`.
140    #[inline]
141    pub fn undecorated_shadow(mut self, undecorated_shadow: bool) -> Self {
142        self.undecorated_shadow = undecorated_shadow;
143        self
144    }
145
146    /// Sets whether the background of the window should be transparent.
147    ///
148    /// The default is `false`.
149    #[inline]
150    pub fn with_transparent(mut self, transparent: bool) -> Self {
151        self.transparent = transparent;
152        self
153    }
154
155    /// Sets whether the window should be put into fullscreen upon creation.
156    ///
157    /// The default is `None`.
158    #[inline]
159    pub fn fullscreen(mut self, fullscreen: Fullscreen) -> Self {
160        self.fullscreen = Some(fullscreen);
161        self
162    }
163
164    /// Sets the window icon.
165    ///
166    /// The default is `None`.
167    #[inline]
168    pub fn window_icon(mut self, window_icon: Icon) -> Self {
169        self.window_icon = Some(window_icon);
170        self
171    }
172
173    /// Sets the initial title of the window in the title bar.
174    ///
175    /// The default is `"Floem window"`.
176    #[inline]
177    pub fn title(mut self, title: impl Into<String>) -> Self {
178        self.title = title.into();
179        self
180    }
181
182    /// Sets the enabled window buttons.
183    ///
184    /// The default is `WindowButtons::all()`.
185    #[inline]
186    pub fn enabled_buttons(mut self, enabled_buttons: WindowButtons) -> Self {
187        self.enabled_buttons = enabled_buttons;
188        self
189    }
190
191    /// Sets whether the window is resizable or not.
192    ///
193    /// The default is `true`.
194    #[inline]
195    pub fn resizable(mut self, resizable: bool) -> Self {
196        self.resizable = resizable;
197        self
198    }
199
200    /// Sets the window level.
201    ///
202    /// This is just a hint to the OS, and the system could ignore it.
203    ///
204    /// The default is `WindowLevel::Normal`.
205    #[inline]
206    pub fn window_level(mut self, window_level: WindowLevel) -> Self {
207        self.window_level = window_level;
208        self
209    }
210
211    /// Set a theme override for the window.
212    ///
213    /// If not provided, the window will follow OS theme.
214    #[inline]
215    pub fn theme_override(mut self, theme_override: Theme) -> Self {
216        self.theme_override = Some(theme_override);
217        self
218    }
219
220    /// .
221    #[inline]
222    pub fn apply_default_theme(mut self, apply: bool) -> Self {
223        self.apply_default_theme = apply;
224        self
225    }
226
227    /// Sets the amount by which fonts are emboldened.
228    ///
229    /// The default is 0.0 except for on macOS where the default is 0.2
230    #[inline]
231    pub fn font_embolden(mut self, font_embolden: f32) -> Self {
232        self.font_embolden = font_embolden;
233        self
234    }
235
236    /// Set up Mac-OS specific configuration.  The passed closure will only be
237    /// called on macOS.
238    #[allow(unused_variables, unused_mut)] // build will complain on non-macOS's otherwise
239    pub fn with_mac_os_config(
240        mut self,
241        mut f: impl FnMut(MacOSWindowConfig) -> MacOSWindowConfig,
242    ) -> Self {
243        #[cfg(target_os = "macos")]
244        if let Some(existing_config) = self.mac_os_config {
245            self.mac_os_config = Some(f(existing_config))
246        } else {
247            let new_config = f(MacOSWindowConfig::default());
248            self.mac_os_config = Some(new_config);
249        }
250        self
251    }
252
253    /// Set up Windows specific configuration. The passed closure will only be
254    /// called on Windows.
255    #[allow(unused_variables, unused_mut)] // build will complain on non-Windows platforms otherwise
256    pub fn with_win_os_config(
257        mut self,
258        mut f: impl FnMut(WinOSWindowConfig) -> WinOSWindowConfig,
259    ) -> Self {
260        #[cfg(target_os = "windows")]
261        if let Some(existing_config) = self.win_os_config {
262            self.win_os_config = Some(f(existing_config))
263        } else {
264            let new_config = f(WinOSWindowConfig::default());
265            self.win_os_config = Some(new_config);
266        }
267        self
268    }
269
270    /// Set up web specific configuration.
271    /// The passed closure will only be called on the web.
272    #[allow(unused_variables, unused_mut)] // build will complain on non-web platforms otherwise
273    pub fn with_web_config(mut self, f: impl FnOnce(WebWindowConfig) -> WebWindowConfig) -> Self {
274        #[cfg(target_arch = "wasm32")]
275        if let Some(existing_config) = self.web_config {
276            self.web_config = Some(f(existing_config))
277        } else {
278            let new_config = f(WebWindowConfig {
279                canvas_id: String::new(),
280            });
281            self.web_config = Some(new_config);
282        }
283        self
284    }
285}
286
287/// Mac-OS specific window configuration properties, accessible via `WindowConfig::with_mac_os_config( FnMut( MacOsWindowConfig ) )`
288///
289/// See [the winit docs](https://docs.rs/winit/latest/winit/platform/macos/trait.WindowExtMacOS.html) for further
290/// information.
291#[derive(Default, Debug, Clone)]
292pub struct MacOSWindowConfig {
293    pub(crate) movable_by_window_background: Option<bool>,
294    pub(crate) titlebar_transparent: Option<bool>,
295    pub(crate) titlebar_hidden: Option<bool>,
296    pub(crate) title_hidden: Option<bool>,
297    pub(crate) titlebar_buttons_hidden: Option<bool>,
298    pub(crate) full_size_content_view: Option<bool>,
299    pub(crate) unified_titlebar: Option<bool>,
300    pub(crate) movable: Option<bool>,
301    pub(crate) traffic_lights_offset: Option<(f64, f64)>,
302    pub(crate) accepts_first_mouse: Option<bool>,
303    pub(crate) tabbing_identifier: Option<String>,
304    pub(crate) option_as_alt: Option<MacOsOptionAsAlt>,
305    pub(crate) has_shadow: Option<bool>,
306    pub(crate) disallow_high_dpi: Option<bool>,
307    pub(crate) panel: Option<bool>,
308}
309
310impl MacOSWindowConfig {
311    /// Allow the window to be
312    /// [moved by dragging its background](https://developer.apple.com/documentation/appkit/nswindow/1419072-movablebywindowbackground).
313    pub fn movable_by_window_background(mut self, val: bool) -> Self {
314        self.movable_by_window_background = Some(val);
315        self
316    }
317
318    /// Make the titlebar's transparency (does nothing on some versions of macOS).
319    pub fn transparent_title_bar(mut self, val: bool) -> Self {
320        self.titlebar_transparent = Some(val);
321        self
322    }
323
324    /// Hides the title bar.
325    pub fn hide_titlebar(mut self, val: bool) -> Self {
326        self.titlebar_hidden = Some(val);
327        self
328    }
329
330    /// Hides the title.
331    pub fn hide_title(mut self, val: bool) -> Self {
332        self.title_hidden = Some(val);
333        self
334    }
335
336    /// Hides the title bar buttons.
337    pub fn hide_titlebar_buttons(mut self, val: bool) -> Self {
338        self.titlebar_buttons_hidden = Some(val);
339        self
340    }
341
342    /// Make the window content [use the full size of the window, including the title bar area](https://developer.apple.com/documentation/appkit/nswindow/stylemask/1644646-fullsizecontentview).
343    pub fn full_size_content_view(mut self, val: bool) -> Self {
344        self.full_size_content_view = Some(val);
345        self
346    }
347
348    /// unify the titlebar
349    pub fn unified_titlebar(mut self, val: bool) -> Self {
350        self.unified_titlebar = Some(val);
351        self
352    }
353
354    /// Allow the window to be moved or not.
355    pub fn movable(mut self, val: bool) -> Self {
356        self.movable = Some(val);
357        self
358    }
359
360    /// Specify the position of the close / minimize / full screen buttons
361    /// on macOS
362    pub fn traffic_lights_offset(mut self, x_y_offset: (f64, f64)) -> Self {
363        self.traffic_lights_offset = Some(x_y_offset);
364        self
365    }
366
367    /// Specify that this window should be sent an event for the initial
368    /// click in it when it was previously inactive, rather than treating
369    /// that click is only activating the window and not forwarding it to
370    /// application code.
371    pub fn accept_first_mouse(mut self, val: bool) -> Self {
372        self.accepts_first_mouse = Some(val);
373        self
374    }
375
376    /// Give this window an identifier when tabbing between windows.
377    pub fn tabbing_identifier(mut self, val: impl Into<String>) -> Self {
378        self.tabbing_identifier = Some(val.into());
379        self
380    }
381
382    /// Specify how the window will treat `Option` keys on the Mac keyboard -
383    /// as a compose key for additional characters, or as a modifier key.
384    pub fn interpret_option_as_alt(mut self, val: MacOsOptionAsAlt) -> Self {
385        self.option_as_alt = Some(val);
386        self
387    }
388
389    /// Set whether the window should have a shadow.
390    pub fn enable_shadow(mut self, val: bool) -> Self {
391        self.has_shadow = Some(val);
392        self
393    }
394
395    /// Set whether the window's coordinate space and painting should
396    /// be scaled for the display or pixel-accurate.
397    pub fn disallow_high_dpi(mut self, val: bool) -> Self {
398        self.disallow_high_dpi = Some(val);
399        self
400    }
401
402    /// Set whether the window is a panel
403    pub fn panel(mut self, val: bool) -> Self {
404        self.panel = Some(val);
405        self
406    }
407}
408
409/// macOS specific configuration for how the Option key is treated
410///
411/// macOS allows altering the way Option and Alt keys so Alt is treated
412/// as a modifier key rather than in character compose key.  This is a proxy
413/// for winit's [OptionAsAlt](https://docs.rs/winit/latest/winit/platform/macos/enum.OptionAsAlt.html).
414#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
415pub enum MacOsOptionAsAlt {
416    OnlyLeft,
417    OnlyRight,
418    Both,
419    #[default]
420    None,
421}
422
423#[cfg(target_os = "macos")]
424impl From<MacOsOptionAsAlt> for winit::platform::macos::OptionAsAlt {
425    fn from(opts: MacOsOptionAsAlt) -> winit::platform::macos::OptionAsAlt {
426        match opts {
427            MacOsOptionAsAlt::OnlyLeft => winit::platform::macos::OptionAsAlt::OnlyLeft,
428            MacOsOptionAsAlt::OnlyRight => winit::platform::macos::OptionAsAlt::OnlyRight,
429            MacOsOptionAsAlt::Both => winit::platform::macos::OptionAsAlt::Both,
430            MacOsOptionAsAlt::None => winit::platform::macos::OptionAsAlt::None,
431        }
432    }
433}
434
435// Windows specific window configuration properties.
436#[derive(Debug, Clone)]
437pub struct WinOSWindowConfig {
438    // pub(crate) top_resize_border: bool,
439    pub(crate) corner_preference: WinOsCornerPreference,
440    pub(crate) set_enable: bool,
441    pub(crate) set_skip_taskbar: bool,
442    // /// Shows or hides the background drop shadow for undecorated windows.
443    // ///
444    // /// Enabling the shadow causes a thin 1px line to appear on the top of the window.
445    // pub(crate) set_undecorated_shadow: bool,
446    pub(crate) set_system_backdrop: WinOsBackdropType,
447    pub(crate) set_border_color: Option<Color>,
448    pub(crate) set_title_background_color: Option<Color>,
449    pub(crate) set_title_text_color: Option<Color>,
450}
451
452impl Default for WinOSWindowConfig {
453    fn default() -> Self {
454        Self {
455            // top_resize_border: true,
456            corner_preference: WinOsCornerPreference::Default,
457            set_enable: true,
458            set_skip_taskbar: false,
459            set_system_backdrop: WinOsBackdropType::Auto,
460            set_border_color: None,
461            set_title_background_color: None,
462            set_title_text_color: None,
463        }
464    }
465}
466
467impl WinOSWindowConfig {
468    // TODO: need to patch winit first
469    // /// Turn window top resize border on or off (for windows without a title bar).
470    // /// By default this is enabled.
471    // pub fn top_resize_border(mut self, top_resize_border: bool) -> Self {
472    //     self.top_resize_border = top_resize_border;
473    //     self
474    // }
475
476    /// Sets the preferred style of the window corners.
477    ///
478    /// Supported starting with Windows 11 Build 22000.
479    pub fn corner_preference(mut self, corner_preference: WinOsCornerPreference) -> Self {
480        self.corner_preference = corner_preference;
481        self
482    }
483
484    /// Enables or disables mouse and keyboard input to the specified window.
485    ///
486    /// A window must be enabled before it can be activated.
487    /// If an application has create a modal dialog box by disabling its owner window
488    /// (as described in [`WindowAttributesExtWindows::with_owner_window`]), the application must
489    /// enable the owner window before destroying the dialog box.
490    /// Otherwise, another window will receive the keyboard focus and be activated.
491    ///
492    /// If a child window is disabled, it is ignored when the system tries to determine which
493    /// window should receive mouse messages.
494    ///
495    /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
496    /// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>.
497    pub fn set_enable(mut self, set_enable: bool) -> Self {
498        self.set_enable = set_enable;
499        self
500    }
501
502    /// Whether to show or hide the window icon in the taskbar.
503    pub fn set_skip_taskbar(mut self, set_skip_taskbar: bool) -> Self {
504        self.set_skip_taskbar = set_skip_taskbar;
505        self
506    }
507
508    /// Sets system-drawn backdrop type.
509    ///
510    /// Requires Windows 11 build 22523+.
511    pub fn set_system_backdrop(mut self, set_system_backdrop: WinOsBackdropType) -> Self {
512        self.set_system_backdrop = set_system_backdrop;
513        self
514    }
515
516    /// Sets the color of the window border.
517    ///
518    /// Supported starting with Windows 11 Build 22000.
519    pub fn set_border_color(mut self, set_border_color: Color) -> Self {
520        self.set_border_color = Some(set_border_color);
521        self
522    }
523
524    /// Sets the background color of the title bar.
525    ///
526    /// Supported starting with Windows 11 Build 22000.
527    pub fn set_title_background_color(mut self, set_title_background_color: Color) -> Self {
528        self.set_title_background_color = Some(set_title_background_color);
529        self
530    }
531
532    /// Sets the color of the window title.
533    ///
534    /// Supported starting with Windows 11 Build 22000.
535    pub fn set_title_text_color(mut self, set_title_text_color: Color) -> Self {
536        self.set_title_text_color = Some(set_title_text_color);
537        self
538    }
539}
540
541/// Describes how the corners of a window should look like.
542///
543/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`].
544///
545/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
546#[derive(Debug, Clone)]
547pub enum WinOsCornerPreference {
548    /// Corresponds to `DWMWCP_DEFAULT`.
549    ///
550    /// Let the system decide when to round window corners.
551    Default,
552
553    /// Corresponds to `DWMWCP_DONOTROUND`.
554    ///
555    /// Never round window corners.
556    DoNotRound,
557
558    /// Corresponds to `DWMWCP_ROUND`.
559    ///
560    /// Round the corners, if appropriate.
561    Round,
562
563    /// Corresponds to `DWMWCP_ROUNDSMALL`.
564    ///
565    /// Round the corners if appropriate, with a small radius.
566    RoundSmall,
567}
568
569#[cfg(target_os = "windows")]
570impl From<WinOsCornerPreference> for winit::platform::windows::CornerPreference {
571    fn from(value: WinOsCornerPreference) -> Self {
572        use winit::platform::windows::CornerPreference;
573        match value {
574            WinOsCornerPreference::Default => CornerPreference::Default,
575            WinOsCornerPreference::DoNotRound => CornerPreference::DoNotRound,
576            WinOsCornerPreference::Round => CornerPreference::Round,
577            WinOsCornerPreference::RoundSmall => CornerPreference::RoundSmall,
578        }
579    }
580}
581
582/// Describes a system-drawn backdrop material of a window.
583///
584/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
585///
586/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
587#[derive(Debug, Clone)]
588pub enum WinOsBackdropType {
589    /// Corresponds to `DWMSBT_AUTO`.
590    ///
591    /// Usually draws a default backdrop effect on the title bar.
592    Auto,
593
594    /// Corresponds to `DWMSBT_NONE`.
595    None,
596
597    /// Corresponds to `DWMSBT_MAINWINDOW`.
598    ///
599    /// Draws the Mica backdrop material.
600    MainWindow,
601
602    /// Corresponds to `DWMSBT_TRANSIENTWINDOW`.
603    ///
604    /// Draws the Background Acrylic backdrop material.
605    TransientWindow,
606
607    /// Corresponds to `DWMSBT_TABBEDWINDOW`.
608    ///
609    /// Draws the Alt Mica backdrop material.
610    TabbedWindow,
611}
612
613#[cfg(target_os = "windows")]
614impl From<WinOsBackdropType> for winit::platform::windows::BackdropType {
615    fn from(value: WinOsBackdropType) -> Self {
616        use winit::platform::windows::BackdropType;
617        match value {
618            WinOsBackdropType::Auto => BackdropType::Auto,
619            WinOsBackdropType::None => BackdropType::None,
620            WinOsBackdropType::MainWindow => BackdropType::MainWindow,
621            WinOsBackdropType::TransientWindow => BackdropType::TransientWindow,
622            WinOsBackdropType::TabbedWindow => BackdropType::TabbedWindow,
623        }
624    }
625}
626
627#[cfg(target_os = "windows")]
628pub(super) fn convert_to_win(c: Option<peniko::Color>) -> Option<winit::platform::windows::Color> {
629    c.map(|c| {
630        let c = c.to_rgba8();
631        winit::platform::windows::Color::from_rgb(c.r, c.g, c.b)
632    })
633}
634
635/// Web specific window (canvas) configuration properties, accessible via
636/// `WindowConfig::with_web_config( WebWindowConfig )`.
637#[derive(Default, Debug, Clone)]
638pub struct WebWindowConfig {
639    /// The id of the HTML canvas element that floem should render to.
640    pub(crate) canvas_id: String,
641}
642
643impl WebWindowConfig {
644    /// Specify the id of the HTML canvas element that floem should render to.
645    pub fn canvas_id(mut self, val: impl Into<String>) -> Self {
646        self.canvas_id = val.into();
647        self
648    }
649}
650
651/// Create a new window. You'll need to create Application first, otherwise it
652/// will panic.
653pub fn new_window<V: IntoView + 'static>(
654    app_view: impl FnOnce(WindowId) -> V + 'static,
655    config: Option<WindowConfig>,
656) {
657    add_app_update_event(AppUpdateEvent::NewWindow {
658        window_creation: WindowCreation {
659            view_fn: Box::new(|window_id| app_view(window_id).into_any()),
660            config,
661        },
662    });
663}
664
665/// Closes the window unconditionally.
666///
667/// This bypasses any `WindowCloseRequested` handlers: no `CloseRequested`
668/// event is dispatched to the view tree and `cx.prevent_default()` has no
669/// effect. The window is destroyed immediately.
670///
671/// Use [`request_close_window`] instead if you want close-request handlers
672/// to have the opportunity to cancel the close (e.g. to prompt for unsaved
673/// changes).
674pub fn close_window(window_id: WindowId) {
675    add_app_update_event(AppUpdateEvent::CloseWindow { window_id });
676}
677
678/// Requests the window to close, dispatching `CloseRequested` first.
679///
680/// This sends a `WindowCloseRequested` event to the view tree before closing.
681/// If any handler calls `cx.prevent_default()`, the close is cancelled and the
682/// window remains open. If no handler prevents the default, the window is
683/// destroyed.
684///
685/// This is the interceptable counterpart to [`close_window`], which closes
686/// unconditionally.
687pub fn request_close_window(window_id: WindowId) {
688    add_app_update_event(AppUpdateEvent::RequestCloseWindow { window_id });
689}