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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46pub enum CloseDecision {
47 #[default]
49 Close,
50 KeepOpen,
52}
53
54pub type OnCloseHook =
57 Box<dyn FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision + Send>;
58
59pub struct WindowConfig {
61 pub(crate) app: AppComponent,
63 pub(crate) size: (f64, f64),
65 pub(crate) min_size: Option<(f64, f64)>,
67 pub(crate) max_size: Option<(f64, f64)>,
69 pub(crate) decorations: bool,
71 pub(crate) title: &'static str,
73 pub(crate) transparent: bool,
75 pub(crate) background: Color,
77 pub(crate) resizable: bool,
79 pub(crate) icon: Option<Icon>,
81 pub(crate) window_attributes_hook: Option<WindowBuilderHook>,
83 pub(crate) window_handle_hook: Option<WindowHandleHook>,
85 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 pub fn new(app: impl Into<AppComponent>) -> Self {
108 Self::new_with_defaults(app.into())
109 }
110
111 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 pub fn with_size(mut self, width: f64, height: f64) -> Self {
136 self.size = (width, height);
137 self
138 }
139
140 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 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 pub fn with_decorations(mut self, decorations: bool) -> Self {
154 self.decorations = decorations;
155 self
156 }
157
158 pub fn with_title(mut self, title: &'static str) -> Self {
160 self.title = title;
161 self
162 }
163
164 pub fn with_transparency(mut self, transparency: bool) -> Self {
166 self.transparent = transparency;
167 self
168 }
169
170 pub fn with_background(mut self, background: impl Into<Color>) -> Self {
172 self.background = background.into();
173 self
174 }
175
176 pub fn with_resizable(mut self, resizable: bool) -> Self {
178 self.resizable = resizable;
179 self
180 }
181
182 pub fn with_icon(mut self, icon: Icon) -> Self {
193 self.icon = Some(icon);
194 self
195 }
196
197 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 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 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
240pub 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 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 pub fn with_window(mut self, window_config: WindowConfig) -> Self {
304 self.windows_configs.push(window_config);
305 self
306 }
307
308 #[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 pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
322 self.plugins.add_plugin(plugin);
323 self
324 }
325
326 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 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 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 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 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}