use crate::layout::docking::DockPanel;
use super::app::{App, AppConfig, NoPanel};
use super::multi_window::{WindowSpec, WindowKey};
pub use crate::platform::types::{CornerStyle, RgbaIcon, RenderBackend};
pub trait AnyFactory: Send + Sync + 'static {
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any + Send + Sync>;
}
pub type RenderSurfaceFactory = dyn AnyFactory;
impl<T: Send + Sync + 'static> AnyFactory for T {
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any + Send + Sync> {
self
}
}
#[derive(Debug)]
pub enum BuildError {
IconDecode(String),
}
impl std::fmt::Display for BuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BuildError::IconDecode(msg) => {
write!(f, "icon PNG decode failed: {msg}")
}
}
}
}
impl std::error::Error for BuildError {}
pub struct TraySpec {
pub tooltip: Option<String>,
pub items: Vec<(String, String, bool)>, }
pub struct BuiltApp<A: App<P>, P: DockPanel> {
#[doc(hidden)]
pub app: A,
#[doc(hidden)]
pub config: AppConfig,
#[doc(hidden)]
pub backend: Option<RenderBackend>,
#[doc(hidden)]
pub factory: Option<Box<dyn AnyFactory>>,
#[doc(hidden)]
pub tray: Option<TraySpec>,
#[doc(hidden)]
pub windows: Vec<WindowSpec>,
#[doc(hidden)]
pub _phantom: std::marker::PhantomData<P>,
}
pub struct AppBuilder<A, P = NoPanel>
where
A: App<P>,
P: DockPanel,
{
app: A,
config: AppConfig,
backend: Option<RenderBackend>,
factory: Option<Box<dyn AnyFactory>>,
tray: Option<TraySpec>,
windows: Vec<WindowSpec>,
_phantom: std::marker::PhantomData<P>,
}
impl<A, P> AppBuilder<A, P>
where
A: App<P>,
P: DockPanel + Default + Clone + 'static,
{
pub fn new(app: A) -> Self {
Self {
app,
config: AppConfig::default(),
backend: None,
factory: None,
tray: None,
windows: Vec::new(),
_phantom: std::marker::PhantomData,
}
}
pub fn window(mut self, spec: WindowSpec) -> Self {
self.windows.push(spec);
self
}
pub fn tray(mut self, tooltip: impl Into<String>) -> Self {
self.tray = Some(TraySpec {
tooltip: Some(tooltip.into()),
items: Vec::new(),
});
self
}
pub fn tray_item(mut self, id: impl Into<String>, label: impl Into<String>) -> Self {
if let Some(ref mut t) = self.tray {
t.items.push((id.into(), label.into(), true));
}
self
}
pub fn tray_item_disabled(mut self, id: impl Into<String>, label: impl Into<String>) -> Self {
if let Some(ref mut t) = self.tray {
t.items.push((id.into(), label.into(), false));
}
self
}
pub fn config(mut self, config: AppConfig) -> Self {
self.config = config;
self
}
pub fn title(mut self, t: impl Into<String>) -> Self {
self.config.title = t.into();
self
}
pub fn size(mut self, w: u32, h: u32) -> Self {
self.config.initial_size = (w, h);
self
}
pub fn min_size(mut self, min: Option<(u32, u32)>) -> Self {
self.config.min_size = min;
self
}
pub fn decorations(mut self, on: bool) -> Self {
self.config.decorations = on;
self
}
pub fn multi_window(mut self, on: bool) -> Self {
self.config.multi_window = on;
self
}
pub fn fps_limit(mut self, fps: u32) -> Self {
self.config.fps_limit = fps;
self
}
pub fn msaa(mut self, samples: u8) -> Self {
self.config.msaa_samples = samples;
self
}
pub fn vsync(mut self, on: bool) -> Self {
self.config.vsync = on;
self
}
pub fn agent_api(mut self, port: u16) -> Self {
self.config.agent_api_port = Some(port);
self
}
pub fn default_tick_rate(mut self, rate: crate::render::TickRate) -> Self {
self.config.default_tick_rate = rate;
self
}
pub fn background(mut self, argb: u32) -> Self {
self.config.background = argb;
self
}
pub fn single_instance(mut self, name: Option<impl Into<String>>) -> Self {
self.config.single_instance = name.map(Into::into);
self
}
pub fn border_color(mut self, color: Option<u32>) -> Self {
self.config.border_color = color;
self
}
pub fn corner_style(mut self, style: CornerStyle) -> Self {
self.config.corner_style = style;
self
}
pub fn shadow(mut self, on: bool) -> Self {
self.config.shadow = Some(on);
self
}
pub fn icon(mut self, icon: RgbaIcon) -> Self {
self.config.icon = Some(icon);
self
}
pub fn icon_from_png(mut self, png_bytes: &[u8]) -> Result<Self, BuildError> {
let icon = decode_png_to_rgba(png_bytes)
.map_err(|e| BuildError::IconDecode(e))?;
self.config.icon = Some(icon);
Ok(self)
}
pub fn backend(mut self, backend: RenderBackend) -> Self {
self.backend = Some(backend);
self
}
pub fn surface_factory<T: Send + Sync + 'static>(mut self, factory: Box<T>) -> Self {
self.factory = Some(factory as Box<dyn AnyFactory>);
self
}
pub fn build(mut self) -> Result<BuiltApp<A, P>, BuildError> {
if self.windows.is_empty() {
let default = WindowSpec::new(
WindowKey::new("main"),
if self.config.title.is_empty() { "uzor".to_string() }
else { self.config.title.clone() },
)
.size(self.config.initial_size.0, self.config.initial_size.1)
.decorations(self.config.decorations)
.background(self.config.background);
let default = if let Some((mw, mh)) = self.config.min_size {
default.min_size(mw, mh)
} else {
default
};
self.windows.push(default);
}
Ok(BuiltApp {
app: self.app,
config: self.config,
backend: self.backend,
factory: self.factory,
tray: self.tray,
windows: self.windows,
_phantom: std::marker::PhantomData,
})
}
}
#[cfg(feature = "framework-png")]
fn decode_png_to_rgba(png_bytes: &[u8]) -> Result<RgbaIcon, String> {
use image::ImageDecoder;
use std::io::Cursor;
let decoder = image::codecs::png::PngDecoder::new(Cursor::new(png_bytes))
.map_err(|e| e.to_string())?;
let (width, height) = decoder.dimensions();
let total_bytes = decoder.total_bytes() as usize;
let mut raw = vec![0u8; total_bytes];
decoder
.read_image(&mut raw)
.map_err(|e| e.to_string())?;
let rgba: Vec<u8> = if total_bytes == (width * height * 4) as usize {
raw
} else {
let img = image::load_from_memory_with_format(png_bytes, image::ImageFormat::Png)
.map_err(|e| e.to_string())?;
img.into_rgba8().into_raw()
};
Ok(RgbaIcon::from_rgba(width, height, rgba))
}
#[cfg(not(feature = "framework-png"))]
fn decode_png_to_rgba(_png_bytes: &[u8]) -> Result<RgbaIcon, String> {
Err("PNG icon decoding requires the 'framework-png' feature".to_string())
}