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
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum CloseDecision {
40 #[default]
42 Close,
43 KeepOpen,
45}
46
47pub type OnCloseHook =
50 Box<dyn FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision + Send>;
51
52pub struct WindowConfig {
54 pub(crate) app: AppComponent,
56 pub(crate) size: (f64, f64),
58 pub(crate) min_size: Option<(f64, f64)>,
60 pub(crate) max_size: Option<(f64, f64)>,
62 pub(crate) decorations: bool,
64 pub(crate) title: &'static str,
66 pub(crate) transparent: bool,
68 pub(crate) background: Color,
70 pub(crate) resizable: bool,
72 pub(crate) icon: Option<Icon>,
74 pub(crate) window_attributes_hook: Option<WindowBuilderHook>,
76 pub(crate) window_handle_hook: Option<WindowHandleHook>,
78 pub(crate) on_close: Option<OnCloseHook>,
80}
81
82impl Debug for WindowConfig {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.debug_struct("WindowConfig")
85 .field("size", &self.size)
86 .field("min_size", &self.min_size)
87 .field("max_size", &self.max_size)
88 .field("decorations", &self.decorations)
89 .field("title", &self.title)
90 .field("transparent", &self.transparent)
91 .field("background", &self.background)
92 .field("resizable", &self.resizable)
93 .field("icon", &self.icon)
94 .finish()
95 }
96}
97
98impl WindowConfig {
99 pub fn new(app: impl Into<AppComponent>) -> Self {
101 Self::new_with_defaults(app.into())
102 }
103
104 pub fn new_app(app: impl App + 'static) -> Self {
106 Self::new_with_defaults(AppComponent::new(app))
107 }
108
109 fn new_with_defaults(app: impl Into<AppComponent>) -> Self {
110 Self {
111 app: app.into(),
112 size: (700.0, 500.0),
113 min_size: None,
114 max_size: None,
115 decorations: true,
116 title: "Freya",
117 transparent: false,
118 background: Color::WHITE,
119 resizable: true,
120 icon: None,
121 window_attributes_hook: None,
122 window_handle_hook: None,
123 on_close: None,
124 }
125 }
126
127 pub fn with_size(mut self, width: f64, height: f64) -> Self {
129 self.size = (width, height);
130 self
131 }
132
133 pub fn with_min_size(mut self, min_width: f64, min_height: f64) -> Self {
135 self.min_size = Some((min_width, min_height));
136 self
137 }
138
139 pub fn with_max_size(mut self, max_width: f64, max_height: f64) -> Self {
141 self.max_size = Some((max_width, max_height));
142 self
143 }
144
145 pub fn with_decorations(mut self, decorations: bool) -> Self {
147 self.decorations = decorations;
148 self
149 }
150
151 pub fn with_title(mut self, title: &'static str) -> Self {
153 self.title = title;
154 self
155 }
156
157 pub fn with_transparency(mut self, transparency: bool) -> Self {
159 self.transparent = transparency;
160 self
161 }
162
163 pub fn with_background(mut self, background: impl Into<Color>) -> Self {
165 self.background = background.into();
166 self
167 }
168
169 pub fn with_resizable(mut self, resizable: bool) -> Self {
171 self.resizable = resizable;
172 self
173 }
174
175 pub fn with_icon(mut self, icon: Icon) -> Self {
177 self.icon = Some(icon);
178 self
179 }
180
181 pub fn with_window_attributes(
183 mut self,
184 window_attributes_hook: impl FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes
185 + 'static
186 + Send
187 + Sync,
188 ) -> Self {
189 self.window_attributes_hook = Some(Box::new(window_attributes_hook));
190 self
191 }
192
193 pub fn with_window_handle(
195 mut self,
196 window_handle_hook: impl FnOnce(&mut Window) + 'static + Send + Sync,
197 ) -> Self {
198 self.window_handle_hook = Some(Box::new(window_handle_hook));
199 self
200 }
201
202 pub fn with_on_close(
204 mut self,
205 on_close: impl FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision
206 + 'static
207 + Send,
208 ) -> Self {
209 self.on_close = Some(Box::new(on_close));
210 self
211 }
212}
213
214pub type EmbeddedFonts = Vec<(Cow<'static, str>, Bytes)>;
215#[cfg(feature = "tray")]
216pub type TrayIconGetter = Box<dyn FnOnce() -> tray_icon::TrayIcon + Send>;
217#[cfg(feature = "tray")]
218pub type TrayHandler =
219 Box<dyn FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)>;
220
221pub type TaskHandler =
222 Box<dyn FnOnce(crate::renderer::LaunchProxy) -> Pin<Box<dyn Future<Output = ()>>> + 'static>;
223
224pub struct LaunchConfig {
226 pub(crate) windows_configs: Vec<WindowConfig>,
227 #[cfg(feature = "tray")]
228 pub(crate) tray: (Option<TrayIconGetter>, Option<TrayHandler>),
229 pub(crate) plugins: PluginsManager,
230 pub(crate) embedded_fonts: EmbeddedFonts,
231 pub(crate) fallback_fonts: Vec<Cow<'static, str>>,
232 pub(crate) tasks: Vec<TaskHandler>,
233}
234
235impl Default for LaunchConfig {
236 fn default() -> Self {
237 LaunchConfig {
238 windows_configs: Vec::default(),
239 #[cfg(feature = "tray")]
240 tray: (None, None),
241 plugins: PluginsManager::default(),
242 embedded_fonts: Default::default(),
243 fallback_fonts: default_fonts(),
244 tasks: Vec::new(),
245 }
246 }
247}
248
249impl LaunchConfig {
250 pub fn new() -> LaunchConfig {
251 LaunchConfig::default()
252 }
253
254 pub fn window_icon(icon: &[u8]) -> Icon {
255 let reader = ImageReader::new(Cursor::new(icon))
256 .with_guessed_format()
257 .expect("Cursor io never fails");
258 let image = reader
259 .decode()
260 .expect("Failed to open icon path")
261 .into_rgba8();
262 let (width, height) = image.dimensions();
263 let rgba = image.into_raw();
264 Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
265 }
266
267 #[cfg(feature = "tray")]
268 pub fn tray_icon(icon: &[u8]) -> tray_icon::Icon {
269 let reader = ImageReader::new(Cursor::new(icon))
270 .with_guessed_format()
271 .expect("Cursor io never fails");
272 let image = reader
273 .decode()
274 .expect("Failed to open icon path")
275 .into_rgba8();
276 let (width, height) = image.dimensions();
277 let rgba = image.into_raw();
278 tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
279 }
280}
281
282impl LaunchConfig {
283 pub fn with_window(mut self, window_config: WindowConfig) -> Self {
285 self.windows_configs.push(window_config);
286 self
287 }
288
289 #[cfg(feature = "tray")]
291 pub fn with_tray(
292 mut self,
293 tray_icon: impl FnOnce() -> tray_icon::TrayIcon + 'static + Send,
294 tray_handler: impl FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)
295 + 'static,
296 ) -> Self {
297 self.tray = (Some(Box::new(tray_icon)), Some(Box::new(tray_handler)));
298 self
299 }
300
301 pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
303 self.plugins.add_plugin(plugin);
304 self
305 }
306
307 pub fn with_font(
309 mut self,
310 font_name: impl Into<Cow<'static, str>>,
311 font: impl Into<Bytes>,
312 ) -> Self {
313 self.embedded_fonts.push((font_name.into(), font.into()));
314 self
315 }
316
317 pub fn with_fallback_font(mut self, font_family: impl Into<Cow<'static, str>>) -> Self {
319 self.fallback_fonts.push(font_family.into());
320 self
321 }
322
323 pub fn with_default_font(mut self, font_name: impl Into<Cow<'static, str>>) -> Self {
325 self.fallback_fonts.insert(0, font_name.into());
326 self
327 }
328
329 pub fn with_future<F, Fut>(mut self, task: F) -> Self
334 where
335 F: FnOnce(LaunchProxy) -> Fut + 'static,
336 Fut: Future<Output = ()> + 'static,
337 {
338 self.tasks
339 .push(Box::new(move |proxy| Box::pin(task(proxy))));
340 self
341 }
342}