use crate::ext_event::{ExtEventHost, ExtEventSink};
use crate::kurbo::{Point, Size};
use crate::menu::MenuManager;
use crate::shell::{Application, Error as PlatformError, WindowBuilder, WindowHandle, WindowLevel};
use crate::widget::LabelText;
use crate::win_handler::{AppHandler, AppState};
use crate::window::WindowId;
use crate::{AppDelegate, Data, Env, LocalizedString, Menu, Widget};
use tracing::warn;
use druid_shell::WindowState;
type EnvSetupFn<T> = dyn FnOnce(&mut Env, &T);
pub struct AppLauncher<T> {
windows: Vec<WindowDesc<T>>,
env_setup: Option<Box<EnvSetupFn<T>>>,
l10n_resources: Option<(Vec<String>, String)>,
delegate: Option<Box<dyn AppDelegate<T>>>,
ext_event_host: ExtEventHost,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WindowSizePolicy {
Content,
User,
}
#[derive(PartialEq)]
pub struct WindowConfig {
pub(crate) size_policy: WindowSizePolicy,
pub(crate) size: Option<Size>,
pub(crate) min_size: Option<Size>,
pub(crate) position: Option<Point>,
pub(crate) resizable: Option<bool>,
pub(crate) transparent: Option<bool>,
pub(crate) show_titlebar: Option<bool>,
pub(crate) level: Option<WindowLevel>,
pub(crate) always_on_top: Option<bool>,
pub(crate) state: Option<WindowState>,
}
pub struct WindowDesc<T> {
pub(crate) pending: PendingWindow<T>,
pub(crate) config: WindowConfig,
pub id: WindowId,
}
pub struct PendingWindow<T> {
pub(crate) root: Box<dyn Widget<T>>,
pub(crate) title: LabelText<T>,
pub(crate) transparent: bool,
pub(crate) menu: Option<MenuManager<T>>,
pub(crate) size_policy: WindowSizePolicy, }
impl<T: Data> PendingWindow<T> {
pub fn new<W>(root: W) -> PendingWindow<T>
where
W: Widget<T> + 'static,
{
PendingWindow {
root: Box::new(root),
title: LocalizedString::new("app-name").into(),
menu: MenuManager::platform_default(),
transparent: false,
size_policy: WindowSizePolicy::User,
}
}
pub fn title(mut self, title: impl Into<LabelText<T>>) -> Self {
self.title = title.into();
self
}
pub fn transparent(mut self, transparent: bool) -> Self {
self.transparent = transparent;
self
}
pub fn menu(
mut self,
menu: impl FnMut(Option<WindowId>, &T, &Env) -> Menu<T> + 'static,
) -> Self {
self.menu = Some(MenuManager::new(menu));
self
}
}
impl<T: Data> AppLauncher<T> {
pub fn with_window(window: WindowDesc<T>) -> Self {
AppLauncher {
windows: vec![window],
env_setup: None,
l10n_resources: None,
delegate: None,
ext_event_host: ExtEventHost::new(),
}
}
pub fn configure_env(mut self, f: impl Fn(&mut Env, &T) + 'static) -> Self {
self.env_setup = Some(Box::new(f));
self
}
pub fn delegate(mut self, delegate: impl AppDelegate<T> + 'static) -> Self {
self.delegate = Some(Box::new(delegate));
self
}
#[doc(hidden)]
#[deprecated(since = "0.8.0", note = "Use log_to_console instead")]
pub fn use_simple_logger(self) -> Self {
self.log_to_console()
}
pub fn start_console_logging(self, enable: bool) -> Self {
if !enable {
return self;
}
#[cfg(not(target_arch = "wasm32"))]
{
use tracing_subscriber::prelude::*;
let filter_layer = tracing_subscriber::filter::LevelFilter::DEBUG;
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(true);
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.init();
}
#[cfg(target_arch = "wasm32")]
{
console_error_panic_hook::set_once();
let config = tracing_wasm::WASMLayerConfigBuilder::new()
.set_max_level(tracing::Level::DEBUG)
.build();
tracing_wasm::set_as_global_default_with_config(config)
}
self
}
pub fn log_to_console(self) -> Self {
self.start_console_logging(true)
}
pub fn localization_resources(mut self, resources: Vec<String>, base_dir: String) -> Self {
self.l10n_resources = Some((resources, base_dir));
self
}
pub fn get_external_handle(&self) -> ExtEventSink {
self.ext_event_host.make_sink()
}
pub fn launch(mut self, data: T) -> Result<(), PlatformError> {
let app = Application::new()?;
let mut env = self
.l10n_resources
.map(|it| Env::with_i10n(it.0, &it.1))
.unwrap_or_else(Env::with_default_i10n);
if let Some(f) = self.env_setup.take() {
f(&mut env, &data);
}
let mut state = AppState::new(
app.clone(),
data,
env,
self.delegate.take(),
self.ext_event_host,
);
for desc in self.windows {
let window = desc.build_native(&mut state)?;
window.show();
}
let handler = AppHandler::new(state);
app.run(Some(Box::new(handler)));
Ok(())
}
}
impl Default for WindowConfig {
fn default() -> Self {
WindowConfig {
size_policy: WindowSizePolicy::User,
size: None,
min_size: None,
position: None,
resizable: None,
show_titlebar: None,
transparent: None,
level: None,
always_on_top: None,
state: None,
}
}
}
impl WindowConfig {
pub fn window_size_policy(mut self, size_policy: WindowSizePolicy) -> Self {
#[cfg(windows)]
{
if size_policy == WindowSizePolicy::Content {
self.size = Some(Size::new(1., 1.))
}
}
self.size_policy = size_policy;
self
}
pub fn window_size(mut self, size: impl Into<Size>) -> Self {
self.size = Some(size.into());
self
}
pub fn with_min_size(mut self, size: impl Into<Size>) -> Self {
self.min_size = Some(size.into());
self
}
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = Some(resizable);
self
}
pub fn show_titlebar(mut self, show_titlebar: bool) -> Self {
self.show_titlebar = Some(show_titlebar);
self
}
pub fn set_position(mut self, position: Point) -> Self {
self.position = Some(position);
self
}
pub fn set_level(mut self, level: WindowLevel) -> Self {
self.level = Some(level);
self
}
pub fn set_always_on_top(mut self, always_on_top: bool) -> Self {
self.always_on_top = Some(always_on_top);
self
}
pub fn set_window_state(mut self, state: WindowState) -> Self {
self.state = Some(state);
self
}
pub fn transparent(mut self, transparent: bool) -> Self {
self.transparent = Some(transparent);
self
}
pub fn apply_to_builder(&self, builder: &mut WindowBuilder) {
if let Some(resizable) = self.resizable {
builder.resizable(resizable);
}
if let Some(show_titlebar) = self.show_titlebar {
builder.show_titlebar(show_titlebar);
}
if let Some(size) = self.size {
builder.set_size(size);
} else if let WindowSizePolicy::Content = self.size_policy {
builder.set_size(Size::new(0., 0.));
}
if let Some(position) = self.position {
builder.set_position(position);
}
if let Some(transparent) = self.transparent {
builder.set_transparent(transparent);
}
if let Some(level) = self.level.clone() {
builder.set_level(level)
}
if let Some(always_on_top) = self.always_on_top {
builder.set_always_on_top(always_on_top)
}
if let Some(state) = self.state {
builder.set_window_state(state);
}
if let Some(min_size) = self.min_size {
builder.set_min_size(min_size);
}
}
pub fn apply_to_handle(&self, win_handle: &mut WindowHandle) {
if let Some(resizable) = self.resizable {
win_handle.resizable(resizable);
}
if let Some(show_titlebar) = self.show_titlebar {
win_handle.show_titlebar(show_titlebar);
}
if let Some(size) = self.size {
win_handle.set_size(size);
}
if let Some(position) = self.position {
win_handle.set_position(position);
}
if self.level.is_some() {
warn!("Applying a level can only be done on window builders");
}
if let Some(state) = self.state {
win_handle.set_window_state(state);
}
}
}
impl<T: Data> WindowDesc<T> {
pub fn new<W>(root: W) -> WindowDesc<T>
where
W: Widget<T> + 'static,
{
WindowDesc {
pending: PendingWindow::new(root),
config: WindowConfig::default(),
id: WindowId::next(),
}
}
pub fn title(mut self, title: impl Into<LabelText<T>>) -> Self {
self.pending = self.pending.title(title);
self
}
pub fn menu(
mut self,
menu: impl FnMut(Option<WindowId>, &T, &Env) -> Menu<T> + 'static,
) -> Self {
self.pending = self.pending.menu(menu);
self
}
pub fn window_size_policy(mut self, size_policy: WindowSizePolicy) -> Self {
#[cfg(windows)]
{
if size_policy == WindowSizePolicy::Content {
self.config.size = Some(Size::new(1., 1.))
}
}
self.config.size_policy = size_policy;
self
}
pub fn window_size(mut self, size: impl Into<Size>) -> Self {
self.config.size = Some(size.into());
self
}
pub fn with_min_size(mut self, size: impl Into<Size>) -> Self {
self.config = self.config.with_min_size(size);
self
}
pub fn resizable(mut self, resizable: bool) -> Self {
self.config = self.config.resizable(resizable);
self
}
pub fn show_titlebar(mut self, show_titlebar: bool) -> Self {
self.config = self.config.show_titlebar(show_titlebar);
self
}
pub fn transparent(mut self, transparent: bool) -> Self {
self.config = self.config.transparent(transparent);
self.pending = self.pending.transparent(transparent);
self
}
pub fn set_position(mut self, position: impl Into<Point>) -> Self {
self.config = self.config.set_position(position.into());
self
}
pub fn set_level(mut self, level: WindowLevel) -> Self {
self.config = self.config.set_level(level);
self
}
pub fn set_always_on_top(mut self, always_on_top: bool) -> Self {
self.config = self.config.set_always_on_top(always_on_top);
self
}
pub fn set_window_state(mut self, state: WindowState) -> Self {
self.config = self.config.set_window_state(state);
self
}
pub fn with_config(mut self, config: WindowConfig) -> Self {
self.config = config;
self
}
pub(crate) fn build_native(
self,
state: &mut AppState<T>,
) -> Result<WindowHandle, PlatformError> {
state.build_native_window(self.id, self.pending, self.config)
}
}