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