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) window_attributes_hook: Option<WindowBuilderHook>,
75 pub(crate) window_handle_hook: Option<WindowHandleHook>,
77 pub(crate) on_close: Option<OnCloseHook>,
79}
80
81impl Debug for WindowConfig {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("WindowConfig")
84 .field("size", &self.size)
85 .field("min_size", &self.min_size)
86 .field("max_size", &self.max_size)
87 .field("decorations", &self.decorations)
88 .field("title", &self.title)
89 .field("transparent", &self.transparent)
90 .field("background", &self.background)
91 .field("resizable", &self.resizable)
92 .field("icon", &self.icon)
93 .finish()
94 }
95}
96
97impl WindowConfig {
98 pub fn new(app: impl Into<AppComponent>) -> Self {
100 Self::new_with_defaults(app.into())
101 }
102
103 pub fn new_app(app: impl App + 'static) -> Self {
105 Self::new_with_defaults(AppComponent::new(app))
106 }
107
108 fn new_with_defaults(app: impl Into<AppComponent>) -> Self {
109 Self {
110 app: app.into(),
111 size: (700.0, 500.0),
112 min_size: None,
113 max_size: None,
114 decorations: true,
115 title: "Freya",
116 transparent: false,
117 background: Color::WHITE,
118 resizable: true,
119 icon: None,
120 window_attributes_hook: None,
121 window_handle_hook: None,
122 on_close: None,
123 }
124 }
125
126 pub fn with_size(mut self, width: f64, height: f64) -> Self {
128 self.size = (width, height);
129 self
130 }
131
132 pub fn with_min_size(mut self, min_width: f64, min_height: f64) -> Self {
134 self.min_size = Some((min_width, min_height));
135 self
136 }
137
138 pub fn with_max_size(mut self, max_width: f64, max_height: f64) -> Self {
140 self.max_size = Some((max_width, max_height));
141 self
142 }
143
144 pub fn with_decorations(mut self, decorations: bool) -> Self {
146 self.decorations = decorations;
147 self
148 }
149
150 pub fn with_title(mut self, title: &'static str) -> Self {
152 self.title = title;
153 self
154 }
155
156 pub fn with_transparency(mut self, transparency: bool) -> Self {
158 self.transparent = transparency;
159 self
160 }
161
162 pub fn with_background(mut self, background: impl Into<Color>) -> Self {
164 self.background = background.into();
165 self
166 }
167
168 pub fn with_resizable(mut self, resizable: bool) -> Self {
170 self.resizable = resizable;
171 self
172 }
173
174 pub fn with_icon(mut self, icon: Icon) -> Self {
185 self.icon = Some(icon);
186 self
187 }
188
189 pub fn with_window_attributes(
191 mut self,
192 window_attributes_hook: impl FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes
193 + 'static
194 + Send
195 + Sync,
196 ) -> Self {
197 self.window_attributes_hook = Some(Box::new(window_attributes_hook));
198 self
199 }
200
201 pub fn with_window_handle(
203 mut self,
204 window_handle_hook: impl FnOnce(&mut Window) + 'static + Send + Sync,
205 ) -> Self {
206 self.window_handle_hook = Some(Box::new(window_handle_hook));
207 self
208 }
209
210 pub fn with_on_close(
212 mut self,
213 on_close: impl FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision
214 + 'static
215 + Send,
216 ) -> Self {
217 self.on_close = Some(Box::new(on_close));
218 self
219 }
220}
221
222pub type EmbeddedFonts = Vec<(Cow<'static, str>, Bytes)>;
223#[cfg(feature = "tray")]
224pub type TrayIconGetter = Box<dyn FnOnce() -> tray_icon::TrayIcon + Send>;
225#[cfg(feature = "tray")]
226pub type TrayHandler =
227 Box<dyn FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)>;
228
229pub type TaskHandler =
230 Box<dyn FnOnce(crate::renderer::LaunchProxy) -> Pin<Box<dyn Future<Output = ()>>> + 'static>;
231
232pub struct LaunchConfig {
234 pub(crate) windows_configs: Vec<WindowConfig>,
235 #[cfg(feature = "tray")]
236 pub(crate) tray: (Option<TrayIconGetter>, Option<TrayHandler>),
237 pub(crate) plugins: PluginsManager,
238 pub(crate) embedded_fonts: EmbeddedFonts,
239 pub(crate) fallback_fonts: Vec<Cow<'static, str>>,
240 pub(crate) tasks: Vec<TaskHandler>,
241 pub(crate) exit_on_close: bool,
242 pub(crate) event_loop: Option<winit::event_loop::EventLoop<crate::renderer::NativeEvent>>,
243}
244
245impl Default for LaunchConfig {
246 fn default() -> Self {
247 LaunchConfig {
248 windows_configs: Vec::default(),
249 #[cfg(feature = "tray")]
250 tray: (None, None),
251 plugins: PluginsManager::default(),
252 embedded_fonts: Default::default(),
253 fallback_fonts: default_fonts(),
254 tasks: Vec::new(),
255 exit_on_close: true,
256 event_loop: None,
257 }
258 }
259}
260
261impl LaunchConfig {
262 pub fn new() -> LaunchConfig {
263 LaunchConfig::default()
264 }
265
266 pub fn window_icon(icon: &[u8]) -> Icon {
268 let reader = ImageReader::new(Cursor::new(icon))
269 .with_guessed_format()
270 .expect("Cursor io never fails");
271 let image = reader
272 .decode()
273 .expect("Failed to open icon path")
274 .into_rgba8();
275 let (width, height) = image.dimensions();
276 let rgba = image.into_raw();
277 Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
278 }
279
280 #[cfg(feature = "tray")]
281 pub fn tray_icon(icon: &[u8]) -> tray_icon::Icon {
282 let reader = ImageReader::new(Cursor::new(icon))
283 .with_guessed_format()
284 .expect("Cursor io never fails");
285 let image = reader
286 .decode()
287 .expect("Failed to open icon path")
288 .into_rgba8();
289 let (width, height) = image.dimensions();
290 let rgba = image.into_raw();
291 tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
292 }
293}
294
295impl LaunchConfig {
296 pub fn with_window(mut self, window_config: WindowConfig) -> Self {
298 self.windows_configs.push(window_config);
299 self
300 }
301
302 #[cfg(feature = "tray")]
304 pub fn with_tray(
305 mut self,
306 tray_icon: impl FnOnce() -> tray_icon::TrayIcon + 'static + Send,
307 tray_handler: impl FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)
308 + 'static,
309 ) -> Self {
310 self.tray = (Some(Box::new(tray_icon)), Some(Box::new(tray_handler)));
311 self
312 }
313
314 pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
316 self.plugins.add_plugin(plugin);
317 self
318 }
319
320 pub fn with_font(
322 mut self,
323 font_name: impl Into<Cow<'static, str>>,
324 font: impl Into<Bytes>,
325 ) -> Self {
326 self.embedded_fonts.push((font_name.into(), font.into()));
327 self
328 }
329
330 pub fn with_fallback_font(mut self, font_family: impl Into<Cow<'static, str>>) -> Self {
332 self.fallback_fonts.push(font_family.into());
333 self
334 }
335
336 pub fn with_default_font(mut self, font_name: impl Into<Cow<'static, str>>) -> Self {
338 self.fallback_fonts.insert(0, font_name.into());
339 self
340 }
341
342 pub fn with_exit_on_close(mut self, exit_on_close: bool) -> Self {
345 self.exit_on_close = exit_on_close;
346 self
347 }
348
349 pub fn with_future<F, Fut>(mut self, task: F) -> Self
354 where
355 F: FnOnce(LaunchProxy) -> Fut + 'static,
356 Fut: Future<Output = ()> + 'static,
357 {
358 self.tasks
359 .push(Box::new(move |proxy| Box::pin(task(proxy))));
360 self
361 }
362
363 pub fn with_event_loop(
366 mut self,
367 event_loop: winit::event_loop::EventLoop<crate::renderer::NativeEvent>,
368 ) -> Self {
369 self.event_loop = Some(event_loop);
370 self
371 }
372}