floem/
window.rs

1use peniko::kurbo::{Point, Size};
2#[cfg(windows)]
3pub use winit::platform::windows::IconExtWindows;
4pub use winit::window::Fullscreen;
5pub use winit::window::Icon;
6pub use winit::window::ResizeDirection;
7pub use winit::window::Theme;
8pub use winit::window::WindowButtons;
9pub use winit::window::WindowId;
10pub use winit::window::WindowLevel;
11
12use crate::app::{add_app_update_event, AppUpdateEvent};
13use crate::view::IntoView;
14use crate::AnyView;
15
16pub struct WindowCreation {
17    pub(crate) view_fn: Box<dyn FnOnce(WindowId) -> AnyView>,
18    pub(crate) config: Option<WindowConfig>,
19}
20
21/// Configures various attributes (e.g. size, position, transparency, etc.) of a window.
22#[derive(Debug)]
23pub struct WindowConfig {
24    pub(crate) size: Option<Size>,
25    pub(crate) min_size: Option<Size>,
26    pub(crate) max_size: Option<Size>,
27    pub(crate) position: Option<Point>,
28    pub(crate) show_titlebar: bool,
29    pub(crate) transparent: bool,
30    pub(crate) fullscreen: Option<Fullscreen>,
31    pub(crate) window_icon: Option<Icon>,
32    pub(crate) title: String,
33    pub(crate) enabled_buttons: WindowButtons,
34    pub(crate) resizable: bool,
35    pub(crate) undecorated: bool,
36    pub(crate) undecorated_shadow: bool,
37    pub(crate) window_level: WindowLevel,
38    pub(crate) apply_default_theme: bool,
39    pub(crate) font_embolden: f32,
40    #[allow(dead_code)]
41    pub(crate) mac_os_config: Option<MacOSWindowConfig>,
42    pub(crate) web_config: Option<WebWindowConfig>,
43}
44
45impl Default for WindowConfig {
46    fn default() -> Self {
47        Self {
48            size: None,
49            min_size: None,
50            max_size: None,
51            position: None,
52            show_titlebar: true,
53            transparent: false,
54            fullscreen: None,
55            window_icon: None,
56            title: std::env::current_exe()
57                .ok()
58                .and_then(|p| p.file_name().map(|f| f.to_string_lossy().into_owned()))
59                .unwrap_or("Floem Window".to_string()),
60            enabled_buttons: WindowButtons::all(),
61            resizable: true,
62            undecorated: false,
63            undecorated_shadow: false,
64            window_level: WindowLevel::Normal,
65            apply_default_theme: true,
66            font_embolden: if cfg!(target_os = "macos") { 0.2 } else { 0. },
67            mac_os_config: None,
68            web_config: None,
69        }
70    }
71}
72
73impl WindowConfig {
74    /// Requests the window to be of specific dimensions.
75    ///
76    /// If this is not set, some platform-specific dimensions will be used.
77    #[inline]
78    pub fn size(mut self, size: impl Into<Size>) -> Self {
79        self.size = Some(size.into());
80        self
81    }
82
83    /// Requests the window to be of specific min dimensions.
84    #[inline]
85    pub fn min_size(mut self, size: impl Into<Size>) -> Self {
86        self.min_size = Some(size.into());
87        self
88    }
89
90    /// Requests the window to be of specific max dimensions.
91    #[inline]
92    pub fn max_size(mut self, size: impl Into<Size>) -> Self {
93        self.max_size = Some(size.into());
94        self
95    }
96
97    /// Sets a desired initial position for the window.
98    ///
99    /// If this is not set, some platform-specific position will be chosen.
100    #[inline]
101    pub fn position(mut self, position: Point) -> Self {
102        self.position = Some(position);
103        self
104    }
105
106    /// Sets whether the window should have a title bar.
107    ///
108    /// The default is `true`.
109    #[inline]
110    pub fn show_titlebar(mut self, show_titlebar: bool) -> Self {
111        self.show_titlebar = show_titlebar;
112        self
113    }
114
115    /// Sets whether the window should have a border, a title bar, etc.
116    ///
117    /// The default is `false`.
118    #[inline]
119    pub fn undecorated(mut self, undecorated: bool) -> Self {
120        self.undecorated = undecorated;
121        self
122    }
123
124    /// Sets whether the window should have background drop shadow when undecorated.
125    ///
126    /// The default is `false`.
127    #[inline]
128    pub fn undecorated_shadow(mut self, undecorated_shadow: bool) -> Self {
129        self.undecorated_shadow = undecorated_shadow;
130        self
131    }
132
133    /// Sets whether the background of the window should be transparent.
134    ///
135    /// The default is `false`.
136    #[inline]
137    pub fn with_transparent(mut self, transparent: bool) -> Self {
138        self.transparent = transparent;
139        self
140    }
141
142    /// Sets whether the window should be put into fullscreen upon creation.
143    ///
144    /// The default is `None`.
145    #[inline]
146    pub fn fullscreen(mut self, fullscreen: Fullscreen) -> Self {
147        self.fullscreen = Some(fullscreen);
148        self
149    }
150
151    /// Sets the window icon.
152    ///
153    /// The default is `None`.
154    #[inline]
155    pub fn window_icon(mut self, window_icon: Icon) -> Self {
156        self.window_icon = Some(window_icon);
157        self
158    }
159
160    /// Sets the initial title of the window in the title bar.
161    ///
162    /// The default is `"Floem window"`.
163    #[inline]
164    pub fn title(mut self, title: impl Into<String>) -> Self {
165        self.title = title.into();
166        self
167    }
168
169    /// Sets the enabled window buttons.
170    ///
171    /// The default is `WindowButtons::all()`.
172    #[inline]
173    pub fn enabled_buttons(mut self, enabled_buttons: WindowButtons) -> Self {
174        self.enabled_buttons = enabled_buttons;
175        self
176    }
177
178    /// Sets whether the window is resizable or not.
179    ///
180    /// The default is `true`.
181    #[inline]
182    pub fn resizable(mut self, resizable: bool) -> Self {
183        self.resizable = resizable;
184        self
185    }
186
187    /// Sets the window level.
188    ///
189    /// This is just a hint to the OS, and the system could ignore it.
190    ///
191    /// The default is `WindowLevel::Normal`.
192    #[inline]
193    pub fn window_level(mut self, window_level: WindowLevel) -> Self {
194        self.window_level = window_level;
195        self
196    }
197
198    /// If set to true, the stylesheet for Floem's default theme will be
199    /// injected into your window. You may want to disable this when using a
200    /// completely custom theme.
201    #[inline]
202    pub fn apply_default_theme(mut self, apply_default_theme: bool) -> Self {
203        self.apply_default_theme = apply_default_theme;
204        self
205    }
206
207    /// Sets the amount by which fonts are emboldened.
208    ///
209    /// The default is 0.0 except for on macOS where the default is 0.2
210    #[inline]
211    pub fn font_embolden(mut self, font_embolden: f32) -> Self {
212        self.font_embolden = font_embolden;
213        self
214    }
215
216    /// Set up Mac-OS specific configuration.  The passed closure will only be
217    /// called on macOS.
218    #[allow(unused_variables, unused_mut)] // build will complain on non-macOS's otherwise
219    pub fn with_mac_os_config(
220        mut self,
221        mut f: impl FnMut(MacOSWindowConfig) -> MacOSWindowConfig,
222    ) -> Self {
223        #[cfg(target_os = "macos")]
224        if let Some(existing_config) = self.mac_os_config {
225            self.mac_os_config = Some(f(existing_config))
226        } else {
227            let new_config = f(MacOSWindowConfig::default());
228            self.mac_os_config = Some(new_config);
229        }
230        self
231    }
232
233    /// Set up web specific configuration.
234    /// The passed closure will only be called on the web.
235    #[allow(unused_variables, unused_mut)] // build will complain on non-web platforms otherwise
236    pub fn with_web_config(mut self, f: impl FnOnce(WebWindowConfig) -> WebWindowConfig) -> Self {
237        #[cfg(target_arch = "wasm32")]
238        if let Some(existing_config) = self.web_config {
239            self.web_config = Some(f(existing_config))
240        } else {
241            let new_config = f(WebWindowConfig {
242                canvas_id: String::new(),
243            });
244            self.web_config = Some(new_config);
245        }
246        self
247    }
248}
249
250/// Mac-OS specific window configuration properties, accessible via `WindowConfig::with_mac_os_config( FnMut( MacOsWindowConfig ) )`
251///
252/// See [the winit docs](https://docs.rs/winit/latest/winit/platform/macos/trait.WindowExtMacOS.html) for further
253/// information.
254#[derive(Default, Debug, Clone)]
255pub struct MacOSWindowConfig {
256    pub(crate) movable_by_window_background: Option<bool>,
257    pub(crate) titlebar_transparent: Option<bool>,
258    pub(crate) titlebar_hidden: Option<bool>,
259    pub(crate) title_hidden: Option<bool>,
260    pub(crate) titlebar_buttons_hidden: Option<bool>,
261    pub(crate) full_size_content_view: Option<bool>,
262    pub(crate) unified_titlebar: Option<bool>,
263    pub(crate) movable: Option<bool>,
264    pub(crate) traffic_lights_offset: Option<(f64, f64)>,
265    pub(crate) accepts_first_mouse: Option<bool>,
266    pub(crate) tabbing_identifier: Option<String>,
267    pub(crate) option_as_alt: Option<MacOsOptionAsAlt>,
268    pub(crate) has_shadow: Option<bool>,
269    pub(crate) disallow_high_dpi: Option<bool>,
270    pub(crate) panel: Option<bool>,
271}
272
273impl MacOSWindowConfig {
274    /// Allow the window to be
275    /// [moved by dragging its background](https://developer.apple.com/documentation/appkit/nswindow/1419072-movablebywindowbackground).
276    pub fn movable_by_window_background(mut self, val: bool) -> Self {
277        self.movable_by_window_background = Some(val);
278        self
279    }
280
281    /// Make the titlebar's transparency (does nothing on some versions of macOS).
282    pub fn transparent_title_bar(mut self, val: bool) -> Self {
283        self.titlebar_transparent = Some(val);
284        self
285    }
286
287    /// Hides the title bar.
288    pub fn hide_titlebar(mut self, val: bool) -> Self {
289        self.titlebar_hidden = Some(val);
290        self
291    }
292
293    /// Hides the title.
294    pub fn hide_title(mut self, val: bool) -> Self {
295        self.title_hidden = Some(val);
296        self
297    }
298
299    /// Hides the title bar buttons.
300    pub fn hide_titlebar_buttons(mut self, val: bool) -> Self {
301        self.titlebar_buttons_hidden = Some(val);
302        self
303    }
304
305    /// 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).
306    pub fn full_size_content_view(mut self, val: bool) -> Self {
307        self.full_size_content_view = Some(val);
308        self
309    }
310
311    /// unify the titlebar
312    pub fn unified_titlebar(mut self, val: bool) -> Self {
313        self.unified_titlebar = Some(val);
314        self
315    }
316
317    /// Allow the window to be moved or not.
318    pub fn movable(mut self, val: bool) -> Self {
319        self.movable = Some(val);
320        self
321    }
322
323    /// Specify the position of the close / minimize / full screen buttons
324    /// on macOS
325    pub fn traffic_lights_offset(mut self, x_y_offset: (f64, f64)) -> Self {
326        self.traffic_lights_offset = Some(x_y_offset);
327        self
328    }
329
330    /// Specify that this window should be sent an event for the initial
331    /// click in it when it was previously inactive, rather than treating
332    /// that click is only activating the window and not forwarding it to
333    /// application code.
334    pub fn accept_first_mouse(mut self, val: bool) -> Self {
335        self.accepts_first_mouse = Some(val);
336        self
337    }
338
339    /// Give this window an identifier when tabbing between windows.
340    pub fn tabbing_identifier(mut self, val: impl Into<String>) -> Self {
341        self.tabbing_identifier = Some(val.into());
342        self
343    }
344
345    /// Specify how the window will treat `Option` keys on the Mac keyboard -
346    /// as a compose key for additional characters, or as a modifier key.
347    pub fn interpret_option_as_alt(mut self, val: MacOsOptionAsAlt) -> Self {
348        self.option_as_alt = Some(val);
349        self
350    }
351
352    /// Set whether the window should have a shadow.
353    pub fn enable_shadow(mut self, val: bool) -> Self {
354        self.has_shadow = Some(val);
355        self
356    }
357
358    /// Set whether the window's coordinate space and painting should
359    /// be scaled for the display or pixel-accurate.
360    pub fn disallow_high_dpi(mut self, val: bool) -> Self {
361        self.disallow_high_dpi = Some(val);
362        self
363    }
364
365    /// Set whether the window is a panel
366    pub fn panel(mut self, val: bool) -> Self {
367        self.panel = Some(val);
368        self
369    }
370}
371
372/// macOS specific configuration for how the Option key is treated
373///
374/// macOS allows altering the way Option and Alt keys so Alt is treated
375/// as a modifier key rather than in character compose key.  This is a proxy
376/// for winit's [OptionAsAlt](https://docs.rs/winit/latest/winit/platform/macos/enum.OptionAsAlt.html).
377#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
378pub enum MacOsOptionAsAlt {
379    OnlyLeft,
380    OnlyRight,
381    Both,
382    #[default]
383    None,
384}
385
386#[cfg(target_os = "macos")]
387impl From<MacOsOptionAsAlt> for winit::platform::macos::OptionAsAlt {
388    fn from(opts: MacOsOptionAsAlt) -> winit::platform::macos::OptionAsAlt {
389        match opts {
390            MacOsOptionAsAlt::OnlyLeft => winit::platform::macos::OptionAsAlt::OnlyLeft,
391            MacOsOptionAsAlt::OnlyRight => winit::platform::macos::OptionAsAlt::OnlyRight,
392            MacOsOptionAsAlt::Both => winit::platform::macos::OptionAsAlt::Both,
393            MacOsOptionAsAlt::None => winit::platform::macos::OptionAsAlt::None,
394        }
395    }
396}
397
398/// Web specific window (canvas) configuration properties, accessible via `WindowConfig::with_web_config( WebWindowConfig )`.
399#[derive(Default, Debug, Clone)]
400pub struct WebWindowConfig {
401    /// The id of the HTML canvas element that floem should render to.
402    pub(crate) canvas_id: String,
403}
404
405impl WebWindowConfig {
406    /// Specify the id of the HTML canvas element that floem should render to.
407    pub fn canvas_id(mut self, val: impl Into<String>) -> Self {
408        self.canvas_id = val.into();
409        self
410    }
411}
412
413/// create a new window. You'll need to create Application first, otherwise it
414/// will panic
415pub fn new_window<V: IntoView + 'static>(
416    app_view: impl FnOnce(WindowId) -> V + 'static,
417    config: Option<WindowConfig>,
418) {
419    add_app_update_event(AppUpdateEvent::NewWindow {
420        window_creation: WindowCreation {
421            view_fn: Box::new(|window_id| app_view(window_id).into_any()),
422            config,
423        },
424    });
425}
426
427/// request the window to be closed
428pub fn close_window(window_id: WindowId) {
429    add_app_update_event(AppUpdateEvent::CloseWindow { window_id });
430}