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 },
22};
23
24use crate::{
25 plugins::{
26 FreyaPlugin,
27 PluginsManager,
28 },
29 renderer::LaunchProxy,
30};
31
32pub type WindowBuilderHook =
33 Box<dyn FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes + Send + Sync>;
34pub type WindowHandleHook = Box<dyn FnOnce(&mut Window) + Send + Sync>;
35
36pub struct WindowConfig {
38 pub(crate) app: AppComponent,
40 pub(crate) size: (f64, f64),
42 pub(crate) min_size: Option<(f64, f64)>,
44 pub(crate) max_size: Option<(f64, f64)>,
46 pub(crate) decorations: bool,
48 pub(crate) title: &'static str,
50 pub(crate) transparent: bool,
52 pub(crate) background: Color,
54 pub(crate) resizable: bool,
56 pub(crate) icon: Option<Icon>,
58 pub(crate) window_attributes_hook: Option<WindowBuilderHook>,
60 pub(crate) window_handle_hook: Option<WindowHandleHook>,
62}
63
64impl Debug for WindowConfig {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.debug_struct("WindowConfig")
67 .field("size", &self.size)
68 .field("min_size", &self.min_size)
69 .field("max_size", &self.max_size)
70 .field("decorations", &self.decorations)
71 .field("title", &self.title)
72 .field("transparent", &self.transparent)
73 .field("background", &self.background)
74 .field("resizable", &self.resizable)
75 .field("icon", &self.icon)
76 .finish()
77 }
78}
79
80impl WindowConfig {
81 pub fn new(app: impl Into<AppComponent>) -> Self {
83 Self::new_with_defaults(app.into())
84 }
85
86 fn new_with_defaults(app: impl Into<AppComponent>) -> Self {
87 Self {
88 app: app.into(),
89 size: (700.0, 500.0),
90 min_size: None,
91 max_size: None,
92 decorations: true,
93 title: "Freya",
94 transparent: false,
95 background: Color::WHITE,
96 resizable: true,
97 icon: None,
98 window_attributes_hook: None,
99 window_handle_hook: None,
100 }
101 }
102
103 pub fn with_size(mut self, width: f64, height: f64) -> Self {
105 self.size = (width, height);
106 self
107 }
108
109 pub fn with_min_size(mut self, min_width: f64, min_height: f64) -> Self {
111 self.min_size = Some((min_width, min_height));
112 self
113 }
114
115 pub fn with_max_size(mut self, max_width: f64, max_height: f64) -> Self {
117 self.max_size = Some((max_width, max_height));
118 self
119 }
120
121 pub fn with_decorations(mut self, decorations: bool) -> Self {
123 self.decorations = decorations;
124 self
125 }
126
127 pub fn with_title(mut self, title: &'static str) -> Self {
129 self.title = title;
130 self
131 }
132
133 pub fn with_transparency(mut self, transparency: bool) -> Self {
135 self.transparent = transparency;
136 self
137 }
138
139 pub fn with_background(mut self, background: impl Into<Color>) -> Self {
141 self.background = background.into();
142 self
143 }
144
145 pub fn with_resizable(mut self, resizable: bool) -> Self {
147 self.resizable = resizable;
148 self
149 }
150
151 pub fn with_icon(mut self, icon: Icon) -> Self {
153 self.icon = Some(icon);
154 self
155 }
156
157 pub fn with_window_attributes(
159 mut self,
160 window_attributes_hook: impl FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes
161 + 'static
162 + Send
163 + Sync,
164 ) -> Self {
165 self.window_attributes_hook = Some(Box::new(window_attributes_hook));
166 self
167 }
168
169 pub fn with_window_handle(
171 mut self,
172 window_handle_hook: impl FnOnce(&mut Window) + 'static + Send + Sync,
173 ) -> Self {
174 self.window_handle_hook = Some(Box::new(window_handle_hook));
175 self
176 }
177}
178
179pub type EmbeddedFonts = Vec<(Cow<'static, str>, Bytes)>;
180#[cfg(feature = "tray")]
181pub type TrayIconGetter = Box<dyn FnOnce() -> tray_icon::TrayIcon + Send>;
182#[cfg(feature = "tray")]
183pub type TrayHandler =
184 Box<dyn FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)>;
185
186pub type TaskHandler =
187 Box<dyn FnOnce(crate::renderer::LaunchProxy) -> Pin<Box<dyn Future<Output = ()>>> + 'static>;
188
189pub struct LaunchConfig {
191 pub(crate) windows_configs: Vec<WindowConfig>,
192 #[cfg(feature = "tray")]
193 pub(crate) tray: (Option<TrayIconGetter>, Option<TrayHandler>),
194 pub(crate) plugins: PluginsManager,
195 pub(crate) embedded_fonts: EmbeddedFonts,
196 pub(crate) fallback_fonts: Vec<Cow<'static, str>>,
197 pub(crate) tasks: Vec<TaskHandler>,
198}
199
200impl Default for LaunchConfig {
201 fn default() -> Self {
202 LaunchConfig {
203 windows_configs: Vec::default(),
204 #[cfg(feature = "tray")]
205 tray: (None, None),
206 plugins: PluginsManager::default(),
207 embedded_fonts: Default::default(),
208 fallback_fonts: default_fonts(),
209 tasks: Vec::new(),
210 }
211 }
212}
213
214impl LaunchConfig {
215 pub fn new() -> LaunchConfig {
216 LaunchConfig::default()
217 }
218
219 pub fn window_icon(icon: &[u8]) -> Icon {
220 let reader = ImageReader::new(Cursor::new(icon))
221 .with_guessed_format()
222 .expect("Cursor io never fails");
223 let image = reader
224 .decode()
225 .expect("Failed to open icon path")
226 .into_rgba8();
227 let (width, height) = image.dimensions();
228 let rgba = image.into_raw();
229 Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
230 }
231
232 #[cfg(feature = "tray")]
233 pub fn tray_icon(icon: &[u8]) -> tray_icon::Icon {
234 let reader = ImageReader::new(Cursor::new(icon))
235 .with_guessed_format()
236 .expect("Cursor io never fails");
237 let image = reader
238 .decode()
239 .expect("Failed to open icon path")
240 .into_rgba8();
241 let (width, height) = image.dimensions();
242 let rgba = image.into_raw();
243 tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
244 }
245}
246
247impl LaunchConfig {
248 pub fn with_window(mut self, window_config: WindowConfig) -> Self {
250 self.windows_configs.push(window_config);
251 self
252 }
253
254 #[cfg(feature = "tray")]
256 pub fn with_tray(
257 mut self,
258 tray_icon: impl FnOnce() -> tray_icon::TrayIcon + 'static + Send,
259 tray_handler: impl FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)
260 + 'static,
261 ) -> Self {
262 self.tray = (Some(Box::new(tray_icon)), Some(Box::new(tray_handler)));
263 self
264 }
265
266 pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
268 self.plugins.add_plugin(plugin);
269 self
270 }
271
272 pub fn with_font(
274 mut self,
275 font_name: impl Into<Cow<'static, str>>,
276 font: impl Into<Bytes>,
277 ) -> Self {
278 self.embedded_fonts.push((font_name.into(), font.into()));
279 self
280 }
281
282 pub fn with_fallback_font(mut self, font_family: impl Into<Cow<'static, str>>) -> Self {
284 self.fallback_fonts.push(font_family.into());
285 self
286 }
287
288 pub fn with_default_font(mut self, font_name: impl Into<Cow<'static, str>>) -> Self {
290 self.fallback_fonts.insert(0, font_name.into());
291 self
292 }
293
294 pub fn with_future<F, Fut>(mut self, task: F) -> Self
299 where
300 F: FnOnce(LaunchProxy) -> Fut + 'static,
301 Fut: Future<Output = ()> + 'static,
302 {
303 self.tasks
304 .push(Box::new(move |proxy| Box::pin(task(proxy))));
305 self
306 }
307}