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 {
237 pub(crate) windows_configs: Vec<WindowConfig>,
238 #[cfg(feature = "tray")]
239 pub(crate) tray: (Option<TrayIconGetter>, Option<TrayHandler>),
240 pub(crate) plugins: PluginsManager,
241 pub(crate) embedded_fonts: EmbeddedFonts,
242 pub(crate) fallback_fonts: Vec<Cow<'static, str>>,
243 pub(crate) tasks: Vec<TaskHandler>,
244 pub(crate) exit_on_close: bool,
245 pub(crate) event_loop: Option<winit::event_loop::EventLoop<crate::renderer::NativeEvent>>,
246}
247
248impl Default for LaunchConfig {
249 fn default() -> Self {
250 LaunchConfig {
251 windows_configs: Vec::default(),
252 #[cfg(feature = "tray")]
253 tray: (None, None),
254 plugins: PluginsManager::default(),
255 embedded_fonts: Default::default(),
256 fallback_fonts: default_fonts(),
257 tasks: Vec::new(),
258 exit_on_close: true,
259 event_loop: None,
260 }
261 }
262}
263
264impl LaunchConfig {
265 pub fn new() -> LaunchConfig {
266 LaunchConfig::default()
267 }
268
269 pub fn window_icon(icon: &[u8]) -> Icon {
271 let reader = ImageReader::new(Cursor::new(icon))
272 .with_guessed_format()
273 .expect("Cursor io never fails");
274 let image = reader
275 .decode()
276 .expect("Failed to open icon path")
277 .into_rgba8();
278 let (width, height) = image.dimensions();
279 let rgba = image.into_raw();
280 Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
281 }
282
283 #[cfg(feature = "tray")]
284 pub fn tray_icon(icon: &[u8]) -> tray_icon::Icon {
285 let reader = ImageReader::new(Cursor::new(icon))
286 .with_guessed_format()
287 .expect("Cursor io never fails");
288 let image = reader
289 .decode()
290 .expect("Failed to open icon path")
291 .into_rgba8();
292 let (width, height) = image.dimensions();
293 let rgba = image.into_raw();
294 tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
295 }
296}
297
298impl LaunchConfig {
299 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_exit_on_close(mut self, exit_on_close: bool) -> Self {
351 self.exit_on_close = exit_on_close;
352 self
353 }
354
355 pub fn with_future<F, Fut>(mut self, task: F) -> Self
360 where
361 F: FnOnce(LaunchProxy) -> Fut + 'static,
362 Fut: Future<Output = ()> + 'static,
363 {
364 self.tasks
365 .push(Box::new(move |proxy| Box::pin(task(proxy))));
366 self
367 }
368
369 pub fn with_event_loop(
372 mut self,
373 event_loop: winit::event_loop::EventLoop<crate::renderer::NativeEvent>,
374 ) -> Self {
375 self.event_loop = Some(event_loop);
376 self
377 }
378}