// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial #![doc = include_str!("README.md")] #![doc(html_logo_url = "https://slint-ui.com/logo/slint-logo-square-light.svg")] extern crate alloc; use i_slint_core::platform::EventLoopProxy; use i_slint_core::window::WindowAdapter; use renderer::WinitCompatibleRenderer; use std::rc::Rc; mod glwindow; use glwindow::*; use i_slint_core::platform::PlatformError; pub(crate) mod event_loop; mod renderer { use std::rc::{Rc, Weak}; use i_slint_core::api::PhysicalSize; use i_slint_core::window::WindowAdapter; pub(crate) trait WinitCompatibleRenderer { const NAME: &'static str; fn new(window_adapter_weak: &Weak) -> Self; fn show( &self, window: &Rc, #[cfg(target_arch = "wasm32")] canvas_id: &str, ); fn hide(&self); fn render(&self, size: PhysicalSize); fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer; fn resize_event(&self, size: PhysicalSize); #[cfg(target_arch = "wasm32")] fn html_canvas_element(&self) -> web_sys::HtmlCanvasElement; } #[cfg(feature = "renderer-winit-femtovg")] pub(crate) mod femtovg; #[cfg(enable_skia_renderer)] pub(crate) mod skia; #[cfg(feature = "renderer-winit-software")] pub(crate) mod sw; } #[cfg(target_arch = "wasm32")] pub(crate) mod wasm_input_helper; #[cfg(target_arch = "wasm32")] pub fn create_gl_window_with_canvas_id(canvas_id: String) -> Rc { GLWindow::::new(canvas_id) } fn window_factory_fn() -> Rc { GLWindow::::new( #[cfg(target_arch = "wasm32")] "canvas".into(), ) } cfg_if::cfg_if! { if #[cfg(feature = "renderer-winit-femtovg")] { type DefaultRenderer = renderer::femtovg::FemtoVGRenderer; } else if #[cfg(enable_skia_renderer)] { type DefaultRenderer = renderer::skia::SkiaRenderer; } else if #[cfg(feature = "renderer-winit-software")] { type DefaultRenderer = renderer::sw::WinitSoftwareRenderer; } else { compile_error!("Please select a feature to build with the winit backend: `renderer-winit-femtovg`, `renderer-winit-skia`, `renderer-winit-skia-opengl` or `renderer-winit-software`"); } } #[doc(hidden)] #[cold] #[cfg(not(target_arch = "wasm32"))] pub fn use_modules() {} pub type NativeWidgets = (); pub type NativeGlobals = (); pub const HAS_NATIVE_STYLE: bool = false; pub mod native_widgets {} pub struct Backend { window_factory_fn: fn() -> Rc, } impl Backend { pub fn new(renderer_name: Option<&str>) -> Self { let window_factory_fn = match renderer_name { #[cfg(feature = "renderer-winit-femtovg")] Some("gl") | Some("femtovg") => window_factory_fn::, #[cfg(enable_skia_renderer)] Some("skia") => window_factory_fn::, #[cfg(feature = "renderer-winit-software")] Some("sw") | Some("software") => { window_factory_fn:: } None => window_factory_fn::, Some(renderer_name) => { eprintln!( "slint winit: unrecognized renderer {}, falling back to {}", renderer_name, DefaultRenderer::NAME ); window_factory_fn:: } }; Self { window_factory_fn } } } impl i_slint_core::platform::Platform for Backend { fn create_window_adapter(&self) -> Result, PlatformError> { Ok((self.window_factory_fn)()) } #[doc(hidden)] fn set_event_loop_quit_on_last_window_closed(&self, quit_on_last_window_closed: bool) { event_loop::QUIT_ON_LAST_WINDOW_CLOSED .store(quit_on_last_window_closed, std::sync::atomic::Ordering::Relaxed); } fn run_event_loop(&self) -> Result<(), PlatformError> { crate::event_loop::run() } fn new_event_loop_proxy(&self) -> Option> { struct Proxy; impl EventLoopProxy for Proxy { fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> { crate::event_loop::with_window_target(|event_loop| { event_loop .event_loop_proxy() .send_event(crate::event_loop::CustomEvent::Exit) .map_err(|_| i_slint_core::api::EventLoopError::EventLoopTerminated) }) } fn invoke_from_event_loop( &self, event: Box, ) -> Result<(), i_slint_core::api::EventLoopError> { let e = crate::event_loop::CustomEvent::UserEvent(event); #[cfg(not(target_arch = "wasm32"))] crate::event_loop::GLOBAL_PROXY .get_or_init(Default::default) .lock() .unwrap() .send_event(e)?; #[cfg(target_arch = "wasm32")] { crate::event_loop::GLOBAL_PROXY.with(|global_proxy| { let mut maybe_proxy = global_proxy.borrow_mut(); let proxy = maybe_proxy.get_or_insert_with(Default::default); // Calling send_event is usually done by winit at the bottom of the stack, // in event handlers, and thus winit might decide to process the event // immediately within that stack. // To prevent re-entrancy issues that might happen by getting the application // event processed on top of the current stack, set winit in Poll mode so that // events are queued and process on top of a clean stack during a requested animation // frame a few moments later. // This also allows batching multiple post_event calls and redraw their state changes // all at once. proxy .send_event(crate::event_loop::CustomEvent::WakeEventLoopWorkaround)?; proxy.send_event(e)?; Ok(()) })? } Ok(()) } } Some(Box::new(Proxy)) } fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) { crate::event_loop::with_window_target(|event_loop_target| { event_loop_target.clipboard(clipboard)?.set_contents(text.into()).ok() }); } fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option { crate::event_loop::with_window_target(|event_loop_target| { event_loop_target.clipboard(clipboard)?.get_contents().ok() }) } }