use std::{
borrow::Cow,
fmt::Debug,
future::Future,
io::Cursor,
pin::Pin,
};
use bytes::Bytes;
use freya_core::{
integration::*,
prelude::Color,
};
use image::ImageReader;
use winit::{
event_loop::ActiveEventLoop,
window::{
Icon,
Window,
WindowAttributes,
WindowId,
},
};
use crate::{
plugins::{
FreyaPlugin,
PluginsManager,
},
renderer::LaunchProxy,
};
pub type WindowBuilderHook =
Box<dyn FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes + Send + Sync>;
pub type WindowHandleHook = Box<dyn FnOnce(&mut Window) + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CloseDecision {
#[default]
Close,
KeepOpen,
}
pub type OnCloseHook =
Box<dyn FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision + Send>;
pub struct WindowConfig {
pub(crate) app: AppComponent,
pub(crate) size: (f64, f64),
pub(crate) min_size: Option<(f64, f64)>,
pub(crate) max_size: Option<(f64, f64)>,
pub(crate) decorations: bool,
pub(crate) title: &'static str,
pub(crate) transparent: bool,
pub(crate) background: Color,
pub(crate) resizable: bool,
pub(crate) icon: Option<Icon>,
pub(crate) app_id: Option<String>,
pub(crate) window_attributes_hook: Option<WindowBuilderHook>,
pub(crate) window_handle_hook: Option<WindowHandleHook>,
pub(crate) on_close: Option<OnCloseHook>,
}
impl Debug for WindowConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowConfig")
.field("size", &self.size)
.field("min_size", &self.min_size)
.field("max_size", &self.max_size)
.field("decorations", &self.decorations)
.field("title", &self.title)
.field("transparent", &self.transparent)
.field("background", &self.background)
.field("resizable", &self.resizable)
.field("icon", &self.icon)
.field("app_id", &self.app_id)
.finish()
}
}
impl WindowConfig {
pub fn new(app: impl Into<AppComponent>) -> Self {
Self::new_with_defaults(app.into())
}
pub fn new_app(app: impl App + 'static) -> Self {
Self::new_with_defaults(AppComponent::new(app))
}
fn new_with_defaults(app: impl Into<AppComponent>) -> Self {
Self {
app: app.into(),
size: (700.0, 500.0),
min_size: None,
max_size: None,
decorations: true,
title: "Freya",
transparent: false,
background: Color::WHITE,
resizable: true,
icon: None,
app_id: None,
window_attributes_hook: None,
window_handle_hook: None,
on_close: None,
}
}
pub fn with_size(mut self, width: f64, height: f64) -> Self {
self.size = (width, height);
self
}
pub fn with_min_size(mut self, min_width: f64, min_height: f64) -> Self {
self.min_size = Some((min_width, min_height));
self
}
pub fn with_max_size(mut self, max_width: f64, max_height: f64) -> Self {
self.max_size = Some((max_width, max_height));
self
}
pub fn with_decorations(mut self, decorations: bool) -> Self {
self.decorations = decorations;
self
}
pub fn with_title(mut self, title: &'static str) -> Self {
self.title = title;
self
}
pub fn with_transparency(mut self, transparency: bool) -> Self {
self.transparent = transparency;
self
}
pub fn with_background(mut self, background: impl Into<Color>) -> Self {
self.background = background.into();
self
}
pub fn with_resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
pub fn with_icon(mut self, icon: Icon) -> Self {
self.icon = Some(icon);
self
}
pub fn with_app_id(mut self, app_id: impl Into<String>) -> Self {
self.app_id = Some(app_id.into());
self
}
pub fn with_window_attributes(
mut self,
window_attributes_hook: impl FnOnce(WindowAttributes, &ActiveEventLoop) -> WindowAttributes
+ 'static
+ Send
+ Sync,
) -> Self {
self.window_attributes_hook = Some(Box::new(window_attributes_hook));
self
}
pub fn with_window_handle(
mut self,
window_handle_hook: impl FnOnce(&mut Window) + 'static + Send + Sync,
) -> Self {
self.window_handle_hook = Some(Box::new(window_handle_hook));
self
}
pub fn with_on_close(
mut self,
on_close: impl FnMut(crate::renderer::RendererContext, WindowId) -> CloseDecision
+ 'static
+ Send,
) -> Self {
self.on_close = Some(Box::new(on_close));
self
}
}
pub type EmbeddedFonts = Vec<(Cow<'static, str>, Bytes)>;
#[cfg(feature = "tray")]
pub type TrayIconGetter = Box<dyn FnOnce() -> tray_icon::TrayIcon + Send>;
#[cfg(feature = "tray")]
pub type TrayHandler =
Box<dyn FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)>;
pub type TaskHandler =
Box<dyn FnOnce(crate::renderer::LaunchProxy) -> Pin<Box<dyn Future<Output = ()>>> + 'static>;
pub struct LaunchConfig {
pub(crate) windows_configs: Vec<WindowConfig>,
#[cfg(feature = "tray")]
pub(crate) tray: (Option<TrayIconGetter>, Option<TrayHandler>),
pub(crate) plugins: PluginsManager,
pub(crate) embedded_fonts: EmbeddedFonts,
pub(crate) fallback_fonts: Vec<Cow<'static, str>>,
pub(crate) tasks: Vec<TaskHandler>,
pub(crate) exit_on_close: bool,
pub(crate) event_loop: Option<winit::event_loop::EventLoop<crate::renderer::NativeEvent>>,
pub(crate) gpu_resource_cache_limit: usize,
}
impl Default for LaunchConfig {
fn default() -> Self {
LaunchConfig {
windows_configs: Vec::default(),
#[cfg(feature = "tray")]
tray: (None, None),
plugins: PluginsManager::default(),
embedded_fonts: Default::default(),
fallback_fonts: default_fonts(),
tasks: Vec::new(),
exit_on_close: true,
event_loop: None,
gpu_resource_cache_limit: 1024 * 1024 * 1024,
}
}
}
impl LaunchConfig {
pub fn new() -> LaunchConfig {
LaunchConfig::default()
}
pub fn window_icon(icon: &[u8]) -> Icon {
let reader = ImageReader::new(Cursor::new(icon))
.with_guessed_format()
.expect("Cursor io never fails");
let image = reader
.decode()
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
}
#[cfg(feature = "tray")]
pub fn tray_icon(icon: &[u8]) -> tray_icon::Icon {
let reader = ImageReader::new(Cursor::new(icon))
.with_guessed_format()
.expect("Cursor io never fails");
let image = reader
.decode()
.expect("Failed to open icon path")
.into_rgba8();
let (width, height) = image.dimensions();
let rgba = image.into_raw();
tray_icon::Icon::from_rgba(rgba, width, height).expect("Failed to open icon")
}
}
impl LaunchConfig {
pub fn with_window(mut self, window_config: WindowConfig) -> Self {
self.windows_configs.push(window_config);
self
}
#[cfg(feature = "tray")]
pub fn with_tray(
mut self,
tray_icon: impl FnOnce() -> tray_icon::TrayIcon + 'static + Send,
tray_handler: impl FnMut(crate::tray_icon::TrayEvent, crate::renderer::RendererContext)
+ 'static,
) -> Self {
self.tray = (Some(Box::new(tray_icon)), Some(Box::new(tray_handler)));
self
}
pub fn with_plugin(mut self, plugin: impl FreyaPlugin + 'static) -> Self {
self.plugins.add_plugin(plugin);
self
}
pub fn with_font(
mut self,
font_name: impl Into<Cow<'static, str>>,
font: impl Into<Bytes>,
) -> Self {
self.embedded_fonts.push((font_name.into(), font.into()));
self
}
pub fn with_fallback_font(mut self, font_family: impl Into<Cow<'static, str>>) -> Self {
self.fallback_fonts.push(font_family.into());
self
}
pub fn with_default_font(mut self, font_name: impl Into<Cow<'static, str>>) -> Self {
self.fallback_fonts.insert(0, font_name.into());
self
}
pub fn with_exit_on_close(mut self, exit_on_close: bool) -> Self {
self.exit_on_close = exit_on_close;
self
}
pub fn with_future<F, Fut>(mut self, task: F) -> Self
where
F: FnOnce(LaunchProxy) -> Fut + 'static,
Fut: Future<Output = ()> + 'static,
{
self.tasks
.push(Box::new(move |proxy| Box::pin(task(proxy))));
self
}
pub fn with_event_loop(
mut self,
event_loop: winit::event_loop::EventLoop<crate::renderer::NativeEvent>,
) -> Self {
self.event_loop = Some(event_loop);
self
}
pub fn with_gpu_resource_cache_limit(mut self, gpu_resource_cache_limit: usize) -> Self {
self.gpu_resource_cache_limit = gpu_resource_cache_limit;
self
}
}