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 pub(crate) gpu_resource_cache_limit: usize,
257}
258
259impl Default for LaunchConfig {
260 fn default() -> Self {
261 LaunchConfig {
262 windows_configs: Vec::default(),
263 #[cfg(feature = "tray")]
264 tray: (None, None),
265 plugins: PluginsManager::default(),
266 embedded_fonts: Default::default(),
267 fallback_fonts: default_fonts(),
268 tasks: Vec::new(),
269 exit_on_close: true,
270 event_loop: None,
271 gpu_resource_cache_limit: 1024 * 1024 * 1024,
272 }
273 }
274}
275
276impl LaunchConfig {
277 pub fn new() -> LaunchConfig {
278 LaunchConfig::default()
279 }
280
281 pub fn window_icon(icon: &[u8]) -> Icon {
283 let reader = ImageReader::new(Cursor::new(icon))
284 .with_guessed_format()
285 .expect("Cursor io never fails");
286 let image = reader
287 .decode()
288 .expect("Failed to open icon path")
289 .into_rgba8();
290 let (width, height) = image.dimensions();
291 let rgba = image.into_raw();
292 Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
293 }
294
295 #[cfg(feature = "tray")]
296 pub fn tray_icon(icon: &[u8]) -> tray_icon::Icon {
297 let reader = ImageReader::new(Cursor::new(icon))
298 .with_guessed_format()
299 .expect("Cursor io never fails");
300 let image = reader
301 .decode()
302 .expect("Failed to open icon path")
303 .into_rgba8();
304 let (width, height) = image.dimensions();
305 let rgba = image.into_raw();
306 tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
307 }
308}
309
310impl LaunchConfig {
311 pub fn with_window(mut self, window_config: WindowConfig) -> Self {
316 self.windows_configs.push(window_config);
317 self
318 }
319
320 #[cfg(feature = "tray")]
322 pub fn with_tray(
323 mut self,
324 tray_icon: impl FnOnce() -> tray_icon::TrayIcon + 'static + Send,
325 tray_handler: impl FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)
326 + 'static,
327 ) -> Self {
328 self.tray = (Some(Box::new(tray_icon)), Some(Box::new(tray_handler)));
329 self
330 }
331
332 pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
334 self.plugins.add_plugin(plugin);
335 self
336 }
337
338 pub fn with_font(
340 mut self,
341 font_name: impl Into<Cow<'static, str>>,
342 font: impl Into<Bytes>,
343 ) -> Self {
344 self.embedded_fonts.push((font_name.into(), font.into()));
345 self
346 }
347
348 pub fn with_fallback_font(mut self, font_family: impl Into<Cow<'static, str>>) -> Self {
350 self.fallback_fonts.push(font_family.into());
351 self
352 }
353
354 pub fn with_default_font(mut self, font_name: impl Into<Cow<'static, str>>) -> Self {
356 self.fallback_fonts.insert(0, font_name.into());
357 self
358 }
359
360 pub fn with_exit_on_close(mut self, exit_on_close: bool) -> Self {
363 self.exit_on_close = exit_on_close;
364 self
365 }
366
367 pub fn with_future<F, Fut>(mut self, task: F) -> Self
372 where
373 F: FnOnce(LaunchProxy) -> Fut + 'static,
374 Fut: Future<Output = ()> + 'static,
375 {
376 self.tasks
377 .push(Box::new(move |proxy| Box::pin(task(proxy))));
378 self
379 }
380
381 pub fn with_event_loop(
384 mut self,
385 event_loop: winit::event_loop::EventLoop<crate::renderer::NativeEvent>,
386 ) -> Self {
387 self.event_loop = Some(event_loop);
388 self
389 }
390
391 pub fn with_gpu_resource_cache_limit(mut self, gpu_resource_cache_limit: usize) -> Self {
393 self.gpu_resource_cache_limit = gpu_resource_cache_limit;
394 self
395 }
396}