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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub enum CloseDecision {
39 #[default]
41 Close,
42 KeepOpen,
44}
45
46pub type OnCloseHook =
49 Box<dyn FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision + Send>;
50
51pub struct WindowConfig {
53 pub(crate) app: AppComponent,
55 pub(crate) size: (f64, f64),
57 pub(crate) min_size: Option<(f64, f64)>,
59 pub(crate) max_size: Option<(f64, f64)>,
61 pub(crate) decorations: bool,
63 pub(crate) title: &'static str,
65 pub(crate) transparent: bool,
67 pub(crate) background: Color,
69 pub(crate) resizable: bool,
71 pub(crate) icon: Option<Icon>,
73 pub(crate) app_id: Option<String>,
75 pub(crate) window_attributes_hook: Option<WindowBuilderHook>,
77 pub(crate) window_handle_hook: Option<WindowHandleHook>,
79 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 pub fn new(app: impl Into<AppComponent>) -> Self {
103 Self::new_with_defaults(app.into())
104 }
105
106 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 pub fn with_size(mut self, width: f64, height: f64) -> Self {
132 self.size = (width, height);
133 self
134 }
135
136 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 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 pub fn with_decorations(mut self, decorations: bool) -> Self {
150 self.decorations = decorations;
151 self
152 }
153
154 pub fn with_title(mut self, title: &'static str) -> Self {
156 self.title = title;
157 self
158 }
159
160 pub fn with_transparency(mut self, transparency: bool) -> Self {
162 self.transparent = transparency;
163 self
164 }
165
166 pub fn with_background(mut self, background: impl Into<Color>) -> Self {
168 self.background = background.into();
169 self
170 }
171
172 pub fn with_resizable(mut self, resizable: bool) -> Self {
174 self.resizable = resizable;
175 self
176 }
177
178 pub fn with_icon(mut self, icon: Icon) -> Self {
189 self.icon = Some(icon);
190 self
191 }
192
193 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 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 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 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
242pub 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 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 pub fn with_window(mut self, window_config: WindowConfig) -> Self {
314 self.windows_configs.push(window_config);
315 self
316 }
317
318 #[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 pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
332 self.plugins.add_plugin(plugin);
333 self
334 }
335
336 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 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 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 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 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 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}