Skip to main content

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