use std::cell::RefCell;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use devtools_core::DevPlugin;
use platform_core::{EventHandler, PlatformError, SurfaceId, WindowConfig};
use platform_desktop::{DesktopPathsProvider, WinitPlatform, WinitWindow, request_dynamic_surface};
use renderer_core::Color;
use services_core::AppPathsProvider;
use ui_core::{Component, Surface, SurfacePlacement, SurfaceRoot, SurfaceScaffold, SurfaceSize};
use crate::app::App;
use crate::app_config::AppConfig;
use crate::surface::{SurfaceContent, SurfaceControl, SurfaceHost, SurfaceToken, set_surface_host};
#[cfg(telar_hot_reload)]
pub(super) fn apply_dev_window_overrides(config: &mut platform_core::WindowConfig) {
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_TITLE") {
config.title = v;
}
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_WIDTH") {
if let Ok(n) = v.parse() {
config.width = n;
}
}
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_HEIGHT") {
if let Ok(n) = v.parse() {
config.height = n;
}
}
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_DECORATIONS") {
config.has_decorations = v == "1";
}
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_RESIZABLE") {
config.is_resizable = v == "1";
}
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_TRANSPARENT") {
config.is_transparent = v == "1";
}
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_FULLSCREEN") {
config.fullscreen = match v.as_str() {
"borderless" => platform_core::FullscreenMode::Borderless,
"exclusive" => platform_core::FullscreenMode::Exclusive,
_ => platform_core::FullscreenMode::Disabled,
};
}
if let Ok(v) = std::env::var("TELAR_DEV_WINDOW_POSITION") {
config.position = parse_dev_window_position(&v);
}
}
#[cfg(telar_hot_reload)]
fn parse_dev_window_position(value: &str) -> platform_core::WindowPosition {
let value = value.trim();
if let Some((x, y)) = value.split_once(',')
&& let (Ok(x), Ok(y)) = (x.trim().parse::<i32>(), y.trim().parse::<i32>())
{
return platform_core::WindowPosition::At(x, y);
}
platform_core::WindowPosition::Centered
}
fn run_desktop_with_plugin<A: App, D: DevPlugin>(config: AppConfig, app: A, app_name: &str) {
let paths: Box<dyn AppPathsProvider> = Box::new(DesktopPathsProvider);
let platform = match WinitPlatform::try_new() {
Ok(p) => p,
Err(e) => {
tracing::error!("Failed to create event loop: {e}");
std::process::exit(1);
}
};
let AppConfig {
window,
font_paths,
font_data,
} = config;
#[cfg(telar_hot_reload)]
let window = {
let mut window = window;
apply_dev_window_overrides(&mut window);
window
};
let config = AppConfig {
window,
font_paths,
font_data,
};
if let Err(e) = super::run_with_platform::<_, A, D>(platform, config, paths, app, app_name) {
tracing::error!("Event loop exited with error: {e}");
std::process::exit(1);
}
}
pub fn run_app_with_name<A: App>(config: AppConfig, app: A, app_name: &str) {
#[cfg(feature = "dev")]
{
if std::env::var("TELAR_DEVTOOLS").as_deref() == Ok("0") {
run_desktop_with_plugin::<A, ()>(config, app, app_name);
} else {
run_desktop_with_plugin::<A, telar_devtools::DevTools>(config, app, app_name);
}
}
#[cfg(not(feature = "dev"))]
run_desktop_with_plugin::<A, ()>(config, app, app_name);
}
pub fn run_multi_app_with_name<A, AF>(
surfaces: Vec<(SurfaceId, AppConfig)>,
app_factory: AF,
app_name: &str,
) -> Result<(), PlatformError>
where
A: App,
AF: Fn(SurfaceId) -> A + Send + Sync + 'static,
{
let platform = WinitPlatform::try_new()?;
super::run_multi_with_platform(
platform,
surfaces,
|_id| Box::new(DesktopPathsProvider) as Box<dyn AppPathsProvider>,
app_factory,
app_name,
)
}
fn window_config_for(placement: &SurfacePlacement) -> WindowConfig {
let (width, height) = match placement.size {
SurfaceSize::Fixed(w, h) => (w, h),
SurfaceSize::Auto => (900, 660),
};
WindowConfig {
title: "Surface".to_string(),
width,
height,
has_decorations: false,
is_transparent: true,
..Default::default()
}
}
struct HostedSurfaceApp {
placement: SurfacePlacement,
content: SurfaceContent,
}
impl App for HostedSurfaceApp {
fn root(&self) -> Box<dyn Component> {
let content = (self.content)();
if self.placement.needs_scaffold() {
Box::new(
SurfaceScaffold::new(&self.placement, content, None)
.expect("surface scaffold build failed")
.animate_in(),
)
} else {
Box::new(
SurfaceRoot::new(content)
.expect("surface root build failed")
.animate_in(),
)
}
}
fn clear_color(&self) -> Option<Color> {
None
}
fn window_config(&self) -> Option<WindowConfig> {
Some(window_config_for(&self.placement))
}
}
struct WinitSurfaceHost;
impl SurfaceHost for WinitSurfaceHost {
fn open(&self, placement: SurfacePlacement, content: SurfaceContent) -> SurfaceToken {
let window_config = window_config_for(&placement);
let app = HostedSurfaceApp { placement, content };
let paths: Box<dyn AppPathsProvider> = Box::new(DesktopPathsProvider);
let prefs = crate::prefs::UserPrefs::load("telar-surface", paths.as_ref());
let backend = prefs
.backend
.unwrap_or_else(crate::config::compile_time_backend);
let mut handler = super::handler::build_app_handler::<WinitWindow, ()>(
Box::new(app),
paths,
Vec::new(),
Vec::new(),
backend,
prefs,
"telar-surface".to_string(),
);
handler.surface = Some(Surface::new());
let boxed: Box<dyn EventHandler<WinitWindow>> = Box::new(handler);
let close = request_dynamic_surface(window_config, boxed);
SurfaceToken::new(Box::new(WinitSurfaceControl { close }))
}
}
struct WinitSurfaceControl {
close: Arc<AtomicBool>,
}
impl SurfaceControl for WinitSurfaceControl {
fn close(&self) {
self.close.store(true, Ordering::Relaxed);
}
fn is_closing(&self) -> bool {
self.close.load(Ordering::Relaxed)
}
}
pub fn open_window<A: App>(app: A) -> SurfaceToken {
let window_config = app.window_config().unwrap_or_default();
let paths: Box<dyn AppPathsProvider> = Box::new(DesktopPathsProvider);
let prefs = crate::prefs::UserPrefs::load("telar-window", paths.as_ref());
let backend = prefs
.backend
.unwrap_or_else(crate::config::compile_time_backend);
let mut handler = super::handler::build_app_handler::<WinitWindow, ()>(
Box::new(app),
paths,
Vec::new(),
Vec::new(),
backend,
prefs,
"telar-window".to_string(),
);
handler.surface = Some(Surface::new());
let boxed: Box<dyn EventHandler<WinitWindow>> = Box::new(handler);
let close = request_dynamic_surface(window_config, boxed);
SurfaceToken::new(Box::new(WinitSurfaceControl { close }))
}
pub fn run_app_windowed<A: App>(config: AppConfig, app: A, app_name: &str) {
set_surface_host(Box::new(WinitSurfaceHost));
let platform = match WinitPlatform::try_new() {
Ok(p) => p,
Err(e) => {
tracing::error!("Failed to create event loop: {e}");
std::process::exit(1);
}
};
let window = app.window_config().unwrap_or_else(|| config.window.clone());
let config = AppConfig { window, ..config };
let app = RefCell::new(Some(app));
let result = super::run_multi_with_platform(
platform,
vec![(SurfaceId(0), config)],
|_id| Box::new(DesktopPathsProvider) as Box<dyn AppPathsProvider>,
move |_id| {
app.borrow_mut()
.take()
.expect("windowed app factory is called once")
},
app_name,
);
if let Err(e) = result {
tracing::error!("Event loop exited with error: {e}");
std::process::exit(1);
}
}