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