freya_winit/
config.rs

1use std::{
2    borrow::Cow,
3    fmt::Debug,
4    future::Future,
5    io::Cursor,
6    pin::Pin,
7};
8
9use bytes::Bytes;
10use freya_core::{
11    integration::*,
12    prelude::Color,
13};
14use image::ImageReader;
15use winit::{
16    event_loop::ActiveEventLoop,
17    window::{
18        Icon,
19        Window,
20        WindowAttributes,
21        WindowId,
22    },
23};
24
25use crate::{
26    plugins::{
27        FreyaPlugin,
28        PluginsManager,
29    },
30    renderer::LaunchProxy,
31};
32
33pub type WindowBuilderHook =
34    Box<dyn FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes + Send + Sync>;
35pub type WindowHandleHook = Box<dyn FnOnce(&mut Window) + Send + Sync>;
36pub type EventLoopBuilderHook = Box<
37    dyn for<'a> FnOnce(
38            &'a mut winit::event_loop::EventLoopBuilder<crate::renderer::NativeEvent>,
39        )
40            -> &'a mut winit::event_loop::EventLoopBuilder<crate::renderer::NativeEvent>
41        + Send,
42>;
43
44/// Decision returned by the `on_close` hook to determine whether a window should close.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46pub enum CloseDecision {
47    /// Close the window.
48    #[default]
49    Close,
50    /// Keep the window open.
51    KeepOpen,
52}
53
54/// Hook called when a window close is requested.
55/// Returns a [`CloseDecision`] to determine whether the window should actually close.
56pub type OnCloseHook =
57    Box<dyn FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision + Send>;
58
59/// Configuration for a Window.
60pub struct WindowConfig {
61    /// Root component for the window app.
62    pub(crate) app: AppComponent,
63    /// Size of the Window.
64    pub(crate) size: (f64, f64),
65    /// Minimum size of the Window.
66    pub(crate) min_size: Option<(f64, f64)>,
67    /// Maximum size of the Window.
68    pub(crate) max_size: Option<(f64, f64)>,
69    /// Enable Window decorations.
70    pub(crate) decorations: bool,
71    /// Title for the Window.
72    pub(crate) title: &'static str,
73    /// Make the Window transparent or not.
74    pub(crate) transparent: bool,
75    /// Background color of the Window.
76    pub(crate) background: Color,
77    /// Enable Window resizable behaviour.
78    pub(crate) resizable: bool,
79    /// Icon for the Window.
80    pub(crate) icon: Option<Icon>,
81    /// Hook function called with the Window Attributes.
82    pub(crate) window_attributes_hook: Option<WindowBuilderHook>,
83    /// Hook function called with the Window.
84    pub(crate) window_handle_hook: Option<WindowHandleHook>,
85    /// Hook function called when the window is requested to close.
86    pub(crate) on_close: Option<OnCloseHook>,
87}
88
89impl Debug for WindowConfig {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("WindowConfig")
92            .field("size", &self.size)
93            .field("min_size", &self.min_size)
94            .field("max_size", &self.max_size)
95            .field("decorations", &self.decorations)
96            .field("title", &self.title)
97            .field("transparent", &self.transparent)
98            .field("background", &self.background)
99            .field("resizable", &self.resizable)
100            .field("icon", &self.icon)
101            .finish()
102    }
103}
104
105impl WindowConfig {
106    /// Create a window with the given app.
107    pub fn new(app: impl Into<AppComponent>) -> Self {
108        Self::new_with_defaults(app.into())
109    }
110
111    /// Create a window using an `App` directly.
112    pub fn new_app(app: impl App + 'static) -> Self {
113        Self::new_with_defaults(AppComponent::new(app))
114    }
115
116    fn new_with_defaults(app: impl Into<AppComponent>) -> Self {
117        Self {
118            app: app.into(),
119            size: (700.0, 500.0),
120            min_size: None,
121            max_size: None,
122            decorations: true,
123            title: "Freya",
124            transparent: false,
125            background: Color::WHITE,
126            resizable: true,
127            icon: None,
128            window_attributes_hook: None,
129            window_handle_hook: None,
130            on_close: None,
131        }
132    }
133
134    /// Specify a Window size.
135    pub fn with_size(mut self, width: f64, height: f64) -> Self {
136        self.size = (width, height);
137        self
138    }
139
140    /// Specify a minimum Window size.
141    pub fn with_min_size(mut self, min_width: f64, min_height: f64) -> Self {
142        self.min_size = Some((min_width, min_height));
143        self
144    }
145
146    /// Specify a maximum Window size.
147    pub fn with_max_size(mut self, max_width: f64, max_height: f64) -> Self {
148        self.max_size = Some((max_width, max_height));
149        self
150    }
151
152    /// Whether the Window will have decorations or not.
153    pub fn with_decorations(mut self, decorations: bool) -> Self {
154        self.decorations = decorations;
155        self
156    }
157
158    /// Specify the Window title.
159    pub fn with_title(mut self, title: &'static str) -> Self {
160        self.title = title;
161        self
162    }
163
164    /// Make the Window transparent or not.
165    pub fn with_transparency(mut self, transparency: bool) -> Self {
166        self.transparent = transparency;
167        self
168    }
169
170    /// Specify the Window's background color.
171    pub fn with_background(mut self, background: impl Into<Color>) -> Self {
172        self.background = background.into();
173        self
174    }
175
176    /// Is Window resizable.
177    pub fn with_resizable(mut self, resizable: bool) -> Self {
178        self.resizable = resizable;
179        self
180    }
181
182    /// Specify the Window icon. Use [`LaunchConfig::window_icon`] to load the icon from bytes.
183    ///
184    /// # Example
185    /// ```no_run
186    /// # use freya::prelude::*;
187    /// const ICON: &[u8] = include_bytes!("../../../examples/freya_icon.png");
188    ///
189    /// WindowConfig::new(app).with_icon(LaunchConfig::window_icon(ICON));
190    /// # fn app() -> impl IntoElement { "" }
191    /// ```
192    pub fn with_icon(mut self, icon: Icon) -> Self {
193        self.icon = Some(icon);
194        self
195    }
196
197    /// Register a Window Attributes hook.
198    pub fn with_window_attributes(
199        mut self,
200        window_attributes_hook: impl FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes
201        + 'static
202        + Send
203        + Sync,
204    ) -> Self {
205        self.window_attributes_hook = Some(Box::new(window_attributes_hook));
206        self
207    }
208
209    /// Register a Window handle hook.
210    pub fn with_window_handle(
211        mut self,
212        window_handle_hook: impl FnOnce(&mut Window) + 'static + Send + Sync,
213    ) -> Self {
214        self.window_handle_hook = Some(Box::new(window_handle_hook));
215        self
216    }
217
218    /// Register an on-close hook that is called when the window is requested to close by the user.
219    pub fn with_on_close(
220        mut self,
221        on_close: impl FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision
222        + 'static
223        + Send,
224    ) -> Self {
225        self.on_close = Some(Box::new(on_close));
226        self
227    }
228}
229
230pub type EmbeddedFonts = Vec<(Cow<'static, str>, Bytes)>;
231#[cfg(feature = "tray")]
232pub type TrayIconGetter = Box<dyn FnOnce() -> tray_icon::TrayIcon + Send>;
233#[cfg(feature = "tray")]
234pub type TrayHandler =
235    Box<dyn FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)>;
236
237pub type TaskHandler =
238    Box<dyn FnOnce(crate::renderer::LaunchProxy) -> Pin<Box<dyn Future<Output = ()>>> + 'static>;
239
240/// Launch configuration.
241pub struct LaunchConfig {
242    pub(crate) windows_configs: Vec<WindowConfig>,
243    #[cfg(feature = "tray")]
244    pub(crate) tray: (Option<TrayIconGetter>, Option<TrayHandler>),
245    pub(crate) plugins: PluginsManager,
246    pub(crate) embedded_fonts: EmbeddedFonts,
247    pub(crate) fallback_fonts: Vec<Cow<'static, str>>,
248    pub(crate) tasks: Vec<TaskHandler>,
249    pub(crate) event_loop_builder_hook: Option<EventLoopBuilderHook>,
250}
251
252impl Default for LaunchConfig {
253    fn default() -> Self {
254        LaunchConfig {
255            windows_configs: Vec::default(),
256            #[cfg(feature = "tray")]
257            tray: (None, None),
258            plugins: PluginsManager::default(),
259            embedded_fonts: Default::default(),
260            fallback_fonts: default_fonts(),
261            tasks: Vec::new(),
262            event_loop_builder_hook: None,
263        }
264    }
265}
266
267impl LaunchConfig {
268    pub fn new() -> LaunchConfig {
269        LaunchConfig::default()
270    }
271
272    /// Load a window icon from image bytes. Pass the result to [`WindowConfig::with_icon`].
273    pub fn window_icon(icon: &[u8]) -> Icon {
274        let reader = ImageReader::new(Cursor::new(icon))
275            .with_guessed_format()
276            .expect("Cursor io never fails");
277        let image = reader
278            .decode()
279            .expect("Failed to open icon path")
280            .into_rgba8();
281        let (width, height) = image.dimensions();
282        let rgba = image.into_raw();
283        Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
284    }
285
286    #[cfg(feature = "tray")]
287    pub fn tray_icon(icon: &[u8]) -> tray_icon::Icon {
288        let reader = ImageReader::new(Cursor::new(icon))
289            .with_guessed_format()
290            .expect("Cursor io never fails");
291        let image = reader
292            .decode()
293            .expect("Failed to open icon path")
294            .into_rgba8();
295        let (width, height) = image.dimensions();
296        let rgba = image.into_raw();
297        tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
298    }
299}
300
301impl LaunchConfig {
302    /// Register a window configuration. You can call this multiple times.
303    pub fn with_window(mut self, window_config: WindowConfig) -> Self {
304        self.windows_configs.push(window_config);
305        self
306    }
307
308    /// Register a tray icon and its handler.
309    #[cfg(feature = "tray")]
310    pub fn with_tray(
311        mut self,
312        tray_icon: impl FnOnce() -> tray_icon::TrayIcon + 'static + Send,
313        tray_handler: impl FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)
314        + 'static,
315    ) -> Self {
316        self.tray = (Some(Box::new(tray_icon)), Some(Box::new(tray_handler)));
317        self
318    }
319
320    /// Register a plugin.
321    pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
322        self.plugins.add_plugin(plugin);
323        self
324    }
325
326    /// Embed a font.
327    pub fn with_font(
328        mut self,
329        font_name: impl Into<Cow<'static, str>>,
330        font: impl Into<Bytes>,
331    ) -> Self {
332        self.embedded_fonts.push((font_name.into(), font.into()));
333        self
334    }
335
336    /// Register a fallback font. Will be used if the default fonts are not available.
337    pub fn with_fallback_font(mut self, font_family: impl Into<Cow<'static, str>>) -> Self {
338        self.fallback_fonts.push(font_family.into());
339        self
340    }
341
342    /// Register a default font. Will be used if found.
343    pub fn with_default_font(mut self, font_name: impl Into<Cow<'static, str>>) -> Self {
344        self.fallback_fonts.insert(0, font_name.into());
345        self
346    }
347
348    /// Register a single-thread launch task.
349    /// The task receives a [LaunchProxy] that can be used to get access to [RendererContext](crate::renderer::RendererContext).
350    /// The provided callback should return a `'static` future which will be scheduled on the renderer
351    /// thread and polled until completion.
352    pub fn with_future<F, Fut>(mut self, task: F) -> Self
353    where
354        F: FnOnce(LaunchProxy) -> Fut + 'static,
355        Fut: Future<Output = ()> + 'static,
356    {
357        self.tasks
358            .push(Box::new(move |proxy| Box::pin(task(proxy))));
359        self
360    }
361
362    /// Customize the winit [EventLoopBuilder](winit::event_loop::EventLoopBuilder) before the event loop is created.
363    /// This can be used to configure platform-specific options on the event loop.
364    pub fn with_event_loop_builder(
365        mut self,
366        hook: impl for<'a> FnOnce(
367            &'a mut winit::event_loop::EventLoopBuilder<crate::renderer::NativeEvent>,
368        ) -> &'a mut winit::event_loop::EventLoopBuilder<
369            crate::renderer::NativeEvent,
370        > + Send
371        + 'static,
372    ) -> Self {
373        self.event_loop_builder_hook = Some(Box::new(hook));
374        self
375    }
376}