#![doc = include_str!("README.md")]
#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
#![warn(missing_docs)]
extern crate alloc;
use event_loop::CustomEvent;
use i_slint_core::platform::EventLoopProxy;
use i_slint_core::window::WindowAdapter;
use renderer::WinitCompatibleRenderer;
use std::rc::Rc;
#[cfg(not(target_arch = "wasm32"))]
use std::rc::Weak;
#[cfg(not(target_arch = "wasm32"))]
mod clipboard;
mod drag_resize_window;
mod winitwindowadapter;
use i_slint_core::platform::PlatformError;
use winitwindowadapter::*;
pub(crate) mod event_loop;
pub use winit;
#[non_exhaustive]
#[derive(Debug)]
pub struct SlintUserEvent(CustomEvent);
pub enum WinitWindowEventResult {
Propagate,
PreventDefault,
}
mod renderer {
use std::rc::Rc;
use i_slint_core::platform::PlatformError;
pub trait WinitCompatibleRenderer {
fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>;
fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
fn occluded(&self, _: bool) {}
fn suspend(&self) -> Result<(), PlatformError>;
fn resume(
&self,
window_attributes: winit::window::WindowAttributes,
) -> Result<Rc<winit::window::Window>, PlatformError>;
fn is_suspended(&self) -> bool;
}
#[cfg(feature = "renderer-femtovg")]
pub(crate) mod femtovg;
#[cfg(enable_skia_renderer)]
pub(crate) mod skia;
#[cfg(feature = "renderer-software")]
pub(crate) mod sw;
}
#[cfg(enable_accesskit)]
mod accesskit;
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm_input_helper;
#[cfg(all(target_arch = "wasm32", feature = "renderer-femtovg"))]
pub fn create_gl_window_with_canvas_id(
canvas_id: &str,
) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
let attrs = WinitWindowAdapter::window_attributes(canvas_id)?;
let adapter =
WinitWindowAdapter::new(renderer::femtovg::GlutinFemtoVGRenderer::new_suspended(), attrs)?;
Ok(adapter)
}
cfg_if::cfg_if! {
if #[cfg(feature = "renderer-femtovg")] {
const DEFAULT_RENDERER_NAME: &str = "FemtoVG";
} else if #[cfg(enable_skia_renderer)] {
const DEFAULT_RENDERER_NAME: &'static str = "Skia";
} else if #[cfg(feature = "renderer-software")] {
const DEFAULT_RENDERER_NAME: &'static str = "Software";
} else {
compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
}
}
fn default_renderer_factory() -> Box<dyn WinitCompatibleRenderer> {
cfg_if::cfg_if! {
if #[cfg(enable_skia_renderer)] {
renderer::skia::WinitSkiaRenderer::new_suspended()
} else if #[cfg(feature = "renderer-femtovg")] {
renderer::femtovg::GlutinFemtoVGRenderer::new_suspended()
} else if #[cfg(feature = "renderer-software")] {
renderer::sw::WinitSoftwareRenderer::new_suspended()
} else {
compile_error!("Please select a feature to build with the winit backend: `renderer-femtovg`, `renderer-skia`, `renderer-skia-opengl`, `renderer-skia-vulkan` or `renderer-software`");
}
}
}
fn try_create_window_with_fallback_renderer(
attrs: winit::window::WindowAttributes,
_proxy: &winit::event_loop::EventLoopProxy<SlintUserEvent>,
) -> Option<Rc<WinitWindowAdapter>> {
[
#[cfg(any(
feature = "renderer-skia",
feature = "renderer-skia-opengl",
feature = "renderer-skia-vulkan"
))]
renderer::skia::WinitSkiaRenderer::new_suspended,
#[cfg(feature = "renderer-femtovg")]
renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
#[cfg(feature = "renderer-software")]
renderer::sw::WinitSoftwareRenderer::new_suspended,
]
.into_iter()
.find_map(|renderer_factory| {
WinitWindowAdapter::new(
renderer_factory(),
attrs.clone(),
#[cfg(enable_accesskit)]
_proxy.clone(),
)
.ok()
})
}
#[doc(hidden)]
pub type NativeWidgets = ();
#[doc(hidden)]
pub type NativeGlobals = ();
#[doc(hidden)]
pub const HAS_NATIVE_STYLE: bool = false;
#[doc(hidden)]
pub mod native_widgets {}
pub struct BackendBuilder {
window_attributes_hook:
Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
renderer_name: Option<String>,
event_loop_builder: Option<winit::event_loop::EventLoopBuilder<SlintUserEvent>>,
}
impl BackendBuilder {
pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self {
self.renderer_name = Some(name.into());
self
}
pub fn with_window_attributes_hook(
mut self,
hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static,
) -> Self {
self.window_attributes_hook = Some(Box::new(hook));
self
}
pub fn with_event_loop_builder(
mut self,
event_loop_builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
) -> Self {
self.event_loop_builder = Some(event_loop_builder);
self
}
pub fn build(self) -> Result<Backend, PlatformError> {
let event_loop_builder =
self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event);
let nre = crate::event_loop::NotRunningEventLoop::new(Some(event_loop_builder))?;
let proxy = nre.instance.create_proxy();
#[cfg(not(target_arch = "wasm32"))]
let clipboard = Rc::downgrade(&nre.clipboard);
crate::event_loop::MAYBE_LOOP_INSTANCE.with(|loop_instance| {
*loop_instance.borrow_mut() = Some(nre);
});
let renderer_factory_fn = match self.renderer_name.as_deref() {
#[cfg(feature = "renderer-femtovg")]
Some("gl") | Some("femtovg") => renderer::femtovg::GlutinFemtoVGRenderer::new_suspended,
#[cfg(enable_skia_renderer)]
Some("skia") => renderer::skia::WinitSkiaRenderer::new_suspended,
#[cfg(enable_skia_renderer)]
Some("skia-opengl") => renderer::skia::WinitSkiaRenderer::new_opengl_suspended,
#[cfg(all(enable_skia_renderer, not(target_os = "android")))]
Some("skia-software") => renderer::skia::WinitSkiaRenderer::new_software_suspended,
#[cfg(feature = "renderer-software")]
Some("sw") | Some("software") => renderer::sw::WinitSoftwareRenderer::new_suspended,
None => default_renderer_factory,
Some(renderer_name) => {
eprintln!(
"slint winit: unrecognized renderer {}, falling back to {}",
renderer_name, DEFAULT_RENDERER_NAME
);
default_renderer_factory
}
};
Ok(Backend {
renderer_factory_fn,
event_loop_state: Default::default(),
window_attributes_hook: self.window_attributes_hook,
#[cfg(not(target_arch = "wasm32"))]
clipboard: clipboard.into(),
proxy,
})
}
}
#[doc = concat!("This struct implements the Slint Platform trait. Use this in conjunction with [`slint::platform::set_platform`](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/platform/fn.set_platform.html) to initialize.")]
pub struct Backend {
renderer_factory_fn: fn() -> Box<dyn WinitCompatibleRenderer>,
event_loop_state: std::cell::RefCell<Option<crate::event_loop::EventLoopState>>,
proxy: winit::event_loop::EventLoopProxy<SlintUserEvent>,
pub window_attributes_hook:
Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>,
#[cfg(not(target_arch = "wasm32"))]
clipboard: Weak<std::cell::RefCell<clipboard::ClipboardPair>>,
}
impl Backend {
#[doc = concat!("Creates a new winit backend with the default renderer that's compiled in. See the [backend documentation](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/index.html#backends) for")]
pub fn new() -> Result<Self, PlatformError> {
Self::builder().build()
}
#[doc = concat!("Creates a new winit backend with the renderer specified by name. See the [backend documentation](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/index.html#backends) for")]
pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> {
let mut builder = Self::builder();
if let Some(name) = renderer_name {
builder = builder.with_renderer_name(name.to_string());
}
builder.build()
}
pub fn builder() -> BackendBuilder {
BackendBuilder {
window_attributes_hook: None,
renderer_name: None,
event_loop_builder: None,
}
}
}
fn send_event_via_global_event_loop_proxy(
event: SlintUserEvent,
) -> Result<(), i_slint_core::api::EventLoopError> {
#[cfg(not(target_arch = "wasm32"))]
crate::event_loop::GLOBAL_PROXY
.get_or_init(Default::default)
.lock()
.unwrap()
.send_event(event)?;
#[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);
proxy.send_event(SlintUserEvent(CustomEvent::WakeEventLoopWorkaround))?;
proxy.send_event(event)?;
Ok(())
})?
}
Ok(())
}
impl i_slint_core::platform::Platform for Backend {
fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
let mut attrs = WinitWindowAdapter::window_attributes(
#[cfg(target_arch = "wasm32")]
"canvas".into(),
)?;
if let Some(hook) = &self.window_attributes_hook {
attrs = hook(attrs);
}
let adapter = WinitWindowAdapter::new(
(self.renderer_factory_fn)(),
attrs.clone(),
#[cfg(enable_accesskit)]
self.proxy.clone(),
)
.or_else(|e| {
try_create_window_with_fallback_renderer(attrs, &self.proxy)
.ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}"))
})?;
Ok(adapter)
}
fn run_event_loop(&self) -> Result<(), PlatformError> {
let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_default();
let new_state = loop_state.run()?;
*self.event_loop_state.borrow_mut() = Some(new_state);
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
fn process_events(
&self,
timeout: core::time::Duration,
_: i_slint_core::InternalToken,
) -> Result<core::ops::ControlFlow<()>, PlatformError> {
let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_default();
let (new_state, status) = loop_state.pump_events(Some(timeout))?;
*self.event_loop_state.borrow_mut() = Some(new_state);
match status {
winit::platform::pump_events::PumpStatus::Continue => {
Ok(core::ops::ControlFlow::Continue(()))
}
winit::platform::pump_events::PumpStatus::Exit(code) => {
if code == 0 {
Ok(core::ops::ControlFlow::Break(()))
} else {
Err(format!("Event loop exited with non-zero code {code}").into())
}
}
}
}
fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
struct Proxy;
impl EventLoopProxy for Proxy {
fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> {
send_event_via_global_event_loop_proxy(SlintUserEvent(CustomEvent::Exit))
}
fn invoke_from_event_loop(
&self,
event: Box<dyn FnOnce() + Send>,
) -> Result<(), i_slint_core::api::EventLoopError> {
let e = SlintUserEvent(CustomEvent::UserEvent(event));
send_event_via_global_event_loop_proxy(e)
}
}
Some(Box::new(Proxy))
}
#[cfg(target_arch = "wasm32")]
fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard);
}
#[cfg(not(target_arch = "wasm32"))]
fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) {
let Some(clipboard_pair) = self.clipboard.upgrade() else { return };
let mut pair = clipboard_pair.borrow_mut();
if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) {
clipboard.set_contents(text.into()).ok();
}
}
#[cfg(target_arch = "wasm32")]
fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
crate::wasm_input_helper::get_clipboard_text(clipboard)
}
#[cfg(not(target_arch = "wasm32"))]
fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> {
let clipboard_pair = self.clipboard.upgrade()?;
let mut pair = clipboard_pair.borrow_mut();
clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok())
}
}
#[cfg(target_arch = "wasm32")]
pub fn spawn_event_loop() -> Result<(), PlatformError> {
crate::event_loop::spawn()
}
mod private {
pub trait WinitWindowAccessorSealed {}
}
#[doc = concat!("This helper trait can be used to obtain access to the [`winit::window::Window`] for a given [`slint::Window`](https://slint.dev/releases/", env!("CARGO_PKG_VERSION"), "/docs/rust/slint/struct.window).")]
pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed {
fn has_winit_window(&self) -> bool;
fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T)
-> Option<T>;
fn on_winit_window_event(
&self,
callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> WinitWindowEventResult
+ 'static,
);
}
impl WinitWindowAccessor for i_slint_core::api::Window {
fn has_winit_window(&self) -> bool {
i_slint_core::window::WindowInner::from_pub(self)
.window_adapter()
.internal(i_slint_core::InternalToken)
.and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
.map_or(false, |adapter| adapter.winit_window().is_some())
}
fn with_winit_window<T>(
&self,
callback: impl FnOnce(&winit::window::Window) -> T,
) -> Option<T> {
i_slint_core::window::WindowInner::from_pub(self)
.window_adapter()
.internal(i_slint_core::InternalToken)
.and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
.and_then(|adapter| adapter.winit_window().map(|w| callback(&w)))
}
fn on_winit_window_event(
&self,
mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> WinitWindowEventResult
+ 'static,
) {
i_slint_core::window::WindowInner::from_pub(&self)
.window_adapter()
.internal(i_slint_core::InternalToken)
.and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>())
.map(|adapter| {
adapter
.window_event_filter
.set(Some(Box::new(move |window, event| callback(window, event))));
});
}
}
impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {}
#[cfg(test)]
mod testui {
slint::slint! {
export component App inherits Window {
Text { text: "Ok"; }
}
}
}
#[cfg(not(any(target_arch = "wasm32", target_os = "macos", target_os = "ios")))]
#[test]
fn test_window_accessor_and_rwh() {
slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap();
use testui::*;
let app = App::new().unwrap();
let slint_window = app.window();
assert!(slint_window.has_winit_window());
let handle = slint_window.window_handle();
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
assert!(handle.window_handle().is_ok());
assert!(handle.display_handle().is_ok());
}