lgui-platform-winit 0.2.0

Winit desktop backend for LGUI
use super::*;

#[cfg(feature = "store")]
use lgui_store::StoreApplicationExt as _;

#[derive(Debug)]
pub struct WinitApplicationError(pub(super) String);

impl fmt::Display for WinitApplicationError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(&self.0)
    }
}

impl std::error::Error for WinitApplicationError {}

impl From<winit::error::EventLoopError> for WinitApplicationError {
    fn from(error: winit::error::EventLoopError) -> Self {
        Self(error.to_string())
    }
}

#[derive(Clone, Copy, Debug)]
pub struct WinitApplication {
    pub(super) preference: GraphicsPreference,
}

impl WinitApplication {
    pub fn new(preference: GraphicsPreference) -> Self {
        Self { preference }
    }
}

impl ApplicationBackend for WinitApplication {
    type Error = WinitApplicationError;

    fn run(
        self,
        options: WindowOptions,
        view: AppView,
        context: ApplicationContext,
    ) -> Result<(), Self::Error> {
        if !graphics_preference_supported(self.preference) {
            return Err(WinitApplicationError(format!(
                "the winit backend does not support the explicit {} driver on this target",
                self.preference.as_str()
            )));
        }

        let event_loop = EventLoop::<WinitUserEvent>::with_user_event()
            .build()
            .map_err(|error| WinitApplicationError(error.to_string()))?;
        let proxy = event_loop.create_proxy();
        lgui_core::backend::install_window_commands(&context.windows(), {
            let proxy = proxy.clone();
            move |command| {
                let _ = proxy.send_event(WinitUserEvent::Window(command));
            }
        });
        let application_handle = ApplicationHandle::new(
            {
                let proxy = proxy.clone();
                move |task| {
                    let _ = proxy.send_event(WinitUserEvent::Task(task));
                }
            },
            {
                let proxy = proxy.clone();
                move || {
                    let _ = proxy.send_event(WinitUserEvent::RequestAllFrames);
                }
            },
        );
        context.resources().provide(application_handle.clone());
        #[cfg(feature = "images")]
        if context
            .try_resource::<lgui_assets::ImageCacheHandle>()
            .is_none()
        {
            let loader = context
                .try_resource::<lgui_assets::RemoteImageLoaderHandle>()
                .map(|loader| (*loader).clone())
                .unwrap_or_else(lgui_assets::http_image_loader);
            let image_wake = application_handle.clone();
            let budget = context
                .memory()
                .options()
                .domain_budget(lgui_core::memory::CacheDomain::EncodedImage);
            let cache = lgui_assets::backend::async_image_cache(
                loader,
                move || image_wake.request_frame(),
                budget,
                context.memory().clone(),
            );
            let stats_cache = cache.clone();
            let trim_cache = cache.clone();
            let registration =
                context
                    .memory()
                    .register(lgui_core::memory::DomainRegistration::new(
                        lgui_core::memory::CacheDomain::EncodedImage,
                        context.memory().next_instance_id(),
                        "application:winit-images",
                        lgui_core::memory::CacheAdapter::managed(
                            move || {
                                let stats = stats_cache.stats();
                                lgui_core::memory::CacheUsage {
                                    cache_bytes: stats.resident_bytes,
                                    cpu_bytes: stats.resident_bytes,
                                    pinned_bytes: stats.pinned_bytes,
                                    entries: stats.entries,
                                    hits: stats.hits,
                                    misses: stats.misses,
                                    evictions: stats.evictions,
                                    largest_entry_bytes: stats.largest_entry_bytes,
                                    in_flight: stats.in_flight,
                                    ..Default::default()
                                }
                            },
                            move |request| {
                                let before = trim_cache.stats().resident_bytes;
                                trim_cache.trim_to(request.target_bytes);
                                lgui_core::memory::TrimResult {
                                    before_bytes: before,
                                    after_bytes: trim_cache.stats().resident_bytes,
                                }
                            },
                            {
                                let cache = cache.clone();
                                move |budget| cache.set_budget(budget)
                            },
                        ),
                    ));
            lgui_core::backend::retain_memory_registration(&context, registration);
            context.resources().provide(cache);
        }
        #[cfg(feature = "store")]
        context.stores().set_wake({
            let proxy = proxy.clone();
            Arc::new(move || {
                let _ = proxy.send_event(WinitUserEvent::RequestAllFrames);
            })
        });
        #[cfg(all(feature = "notifications-win32", target_os = "windows"))]
        lgui_platform_win32::install_notification_service(&context)
            .map_err(|error| WinitApplicationError(error.to_string()))?;
        #[cfg(feature = "renderer-skia")]
        let _text_environment = lgui_core::backend::install_text_environment(
            &context,
            lgui_render_skia::skia_text_system_handle(),
        );

        let display = event_loop.owned_display_handle();
        let soft_context =
            Arc::new(SoftContext::new(display).map_err(|error| {
                WinitApplicationError(format!("create software display: {error}"))
            })?);
        let main_id = options.id.clone();
        let mut handler = WinitHost {
            initial: Some((options, view)),
            main_id,
            context,
            proxy,
            soft_context,
            windows: HashMap::new(),
            ids: HashMap::new(),
            scale_preference: None,
            preference: self.preference,
            #[cfg(all(feature = "tray-win32", target_os = "windows"))]
            tray_host: None,
            exit_requested: false,
            backgrounded: false,
        };
        event_loop.run_app(&mut handler)?;
        Ok(())
    }
}