Skip to content

slint crate: Expose parts of the winit backend's previously private API #8315

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 2, 2025
14 changes: 14 additions & 0 deletions api/rs/slint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,18 @@ backend-android-activity-05 = [
## ```
unstable-wgpu-24 = ["i-slint-core/unstable-wgpu-24", "i-slint-backend-selector/unstable-wgpu-24", "dep:wgpu-24"]

## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees, because winit releases new major
## versions more frequently than Slint. This feature as well as the APIs changed or removed in future minor releases of Slint, likely to be replaced
## by a feature with a similar name but the winit version suffix being bumped.
##
## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements)
## in your `Cargo.toml` when enabling this feature:
##
## ```toml
## slint = { version = "~1.12", features = ["unstable-winit-030"] }
## ```
unstable-winit-030 = ["backend-winit", "dep:i-slint-backend-winit", "i-slint-backend-selector/unstable-winit-030"]

[dependencies]
i-slint-core = { workspace = true }
slint-macros = { workspace = true }
Expand All @@ -212,6 +224,8 @@ unicode-segmentation = { workspace = true }

wgpu-24 = { workspace = true, optional = true }

i-slint-backend-winit = { workspace = true, optional = true }

[target.'cfg(not(target_os = "android"))'.dependencies]
# FemtoVG is disabled on android because it doesn't compile without setting RUST_FONTCONFIG_DLOPEN=on
# end even then wouldn't work because it can't load fonts
Expand Down
63 changes: 63 additions & 0 deletions api/rs/slint/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,66 @@ pub mod wgpu_24 {
pub use i_slint_core::graphics::wgpu_24::*;
pub use wgpu_24 as wgpu;
}

#[cfg(feature = "unstable-winit-030")]
pub mod winit_030 {
//! Winit 0.30.x specific types and re-exports.
//!
//! *Note*: This module is behind a feature flag and may be removed or changed in future minor releases,
//! as new major Winit releases become available.
//!
//! Use the types and traits in this module in combination with other APIs to access additional window properties,
//! create custom windows, or hook into the winit event loop.
//!
//! For example, use the [`WinitWindowAccessor`] to obtain access to the underling [`winit::window::Window`]:
//!
//! `Cargo.toml`:
//! ```toml
//! slint = { version = "~1.12", features = ["unstable-winit-030"] }
//! ```
//!
//! `main.rs`:
//! ```rust,no_run
//! // Bring winit and accessor traits into scope.
//! use slint::winit_030::{WinitWindowAccessor, winit};
//!
//! slint::slint!{
//! import { VerticalBox, Button } from "std-widgets.slint";
//! export component HelloWorld inherits Window {
//! callback clicked;
//! VerticalBox {
//! Text {
//! text: "hello world";
//! color: green;
//! }
//! Button {
//! text: "Click me";
//! clicked => { root.clicked(); }
//! }
//! }
//! }
//! }
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Make sure the winit backed is selected:
//! slint::BackendSelector::new()
//! .backend_name("winit".into())
//! .select()?;
//!
//! let app = HelloWorld::new()?;
//! let app_weak = app.as_weak();
//! app.on_clicked(move || {
//! // access the winit window
//! let app = app_weak.unwrap();
//! app.window().with_winit_window(|winit_window: &winit::window::Window| {
//! eprintln!("window id = {:#?}", winit_window.id());
//! });
//! });
//! app.run()?;
//! Ok(())
//! }
//! ```

pub use i_slint_backend_winit::{
winit, EventLoopBuilder, SlintEvent, WinitWindowAccessor, WinitWindowEventResult,
};
}
5 changes: 5 additions & 0 deletions internal/backends/selector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ unstable-wgpu-24 = [
"i-slint-backend-winit?/unstable-wgpu-24",
]

unstable-winit-030 = ["i-slint-backend-winit"]

# note that default enable the i-slint-backend-qt, but not its enable feature
default = ["i-slint-backend-qt", "backend-winit"]

Expand All @@ -77,3 +79,6 @@ i-slint-backend-linuxkms = { workspace = true, features = ["default"], optional

[build-dependencies]
i-slint-common = { workspace = true }

[dev-dependencies]
slint = { path = "../../../api/rs/slint", default-features = false, features = ["std", "compat-1-2", "backend-winit", "renderer-software", "unstable-winit-030"] }
65 changes: 65 additions & 0 deletions internal/backends/selector/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ pub struct BackendSelector {
backend: Option<String>,
renderer: Option<String>,
selected: bool,
#[cfg(feature = "unstable-winit-030")]
winit_window_attributes_hook: Option<
Box<
dyn Fn(
i_slint_backend_winit::winit::window::WindowAttributes,
) -> i_slint_backend_winit::winit::window::WindowAttributes,
>,
>,
#[cfg(feature = "unstable-winit-030")]
winit_event_loop_builder: Option<i_slint_backend_winit::EventLoopBuilder>,
}

impl BackendSelector {
Expand Down Expand Up @@ -111,6 +121,49 @@ impl BackendSelector {
self
}

/// Configures this builder to use the specified winit hook that will be called before a Window is created.
///
/// It can be used to adjust settings of window that will be created.
///
/// # Example
///
/// ```rust,no_run
/// let mut backend = slint::BackendSelector::new()
/// .with_winit_030_window_attributes_hook(|attributes| attributes.with_content_protected(true))
/// .select()
/// .unwrap();
/// ```
///
/// *Note*: This function is behind a feature flag and may be removed or changed in future minor releases,
/// as new major Winit releases become available.
#[must_use]
#[cfg(feature = "unstable-winit-030")]
pub fn with_winit_030_window_attributes_hook(
mut self,
hook: impl Fn(
i_slint_backend_winit::winit::window::WindowAttributes,
) -> i_slint_backend_winit::winit::window::WindowAttributes
+ 'static,
) -> Self {
self.winit_window_attributes_hook = Some(Box::new(hook));
self
}

/// Configures this builder to use the specified winit event loop builder when creating the event
/// loop.
///
/// *Note*: This function is behind a feature flag and may be removed or changed in future minor releases,
/// as new major Winit releases become available.
#[must_use]
#[cfg(feature = "unstable-winit-030")]
pub fn with_winit_030_event_loop_builder(
mut self,
event_loop_builder: i_slint_backend_winit::EventLoopBuilder,
) -> Self {
self.winit_event_loop_builder = Some(event_loop_builder);
self
}

/// Adds the requirement that the selected renderer must match the given name. This is
/// equivalent to setting the `SLINT_BACKEND=name` environment variable and requires
/// that the corresponding renderer feature is enabled. For example, to select the Skia renderer,
Expand Down Expand Up @@ -168,6 +221,18 @@ impl BackendSelector {
None => builder,
};

#[cfg(feature = "unstable-winit-030")]
let builder = match self.winit_window_attributes_hook.take() {
Some(hook) => builder.with_window_attributes_hook(hook),
None => builder,
};

#[cfg(feature = "unstable-winit-030")]
let builder = match self.winit_event_loop_builder.take() {
Some(event_loop_builder) => builder.with_event_loop_builder(event_loop_builder),
None => builder,
};

Box::new(builder.build()?)
}
#[cfg(feature = "i-slint-backend-qt")]
Expand Down
8 changes: 4 additions & 4 deletions internal/backends/winit/accesskit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use i_slint_core::SharedString;
use i_slint_core::{properties::PropertyTracker, window::WindowAdapter};

use super::WinitWindowAdapter;
use crate::SlintUserEvent;
use crate::SlintEvent;
use winit::event_loop::EventLoopProxy;

/// The AccessKit adapter tries to keep the given window adapter's item tree in sync with accesskit's node tree.
Expand Down Expand Up @@ -52,7 +52,7 @@ impl AccessKitAdapter {
pub fn new(
window_adapter_weak: Weak<WinitWindowAdapter>,
winit_window: &winit::window::Window,
proxy: EventLoopProxy<SlintUserEvent>,
proxy: EventLoopProxy<SlintEvent>,
) -> Self {
Self {
inner: accesskit_winit::Adapter::with_event_loop_proxy(winit_window, proxy),
Expand Down Expand Up @@ -728,9 +728,9 @@ struct CachedNode {
tracker: Pin<Box<PropertyTracker>>,
}

impl From<accesskit_winit::Event> for SlintUserEvent {
impl From<accesskit_winit::Event> for SlintEvent {
fn from(value: accesskit_winit::Event) -> Self {
SlintUserEvent(crate::event_loop::CustomEvent::Accesskit(value))
SlintEvent(crate::event_loop::CustomEvent::Accesskit(value))
}
}

Expand Down
14 changes: 6 additions & 8 deletions internal/backends/winit/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
use crate::drag_resize_window::{handle_cursor_move_for_resize, handle_resize};
use crate::WinitWindowEventResult;
use crate::{SharedBackendData, SlintUserEvent};
use crate::{SharedBackendData, SlintEvent};
use corelib::graphics::euclid;
use corelib::input::{KeyEvent, KeyEventType, MouseEvent};
use corelib::items::{ColorScheme, PointerEventButton};
Expand All @@ -26,13 +26,11 @@ use winit::event_loop::ActiveEventLoop;
use winit::event_loop::ControlFlow;
use winit::window::ResizeDirection;
pub(crate) struct NotRunningEventLoop {
pub(crate) instance: winit::event_loop::EventLoop<SlintUserEvent>,
pub(crate) instance: winit::event_loop::EventLoop<SlintEvent>,
}

impl NotRunningEventLoop {
pub(crate) fn new(
mut builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
) -> Result<Self, PlatformError> {
pub(crate) fn new(mut builder: crate::EventLoopBuilder) -> Result<Self, PlatformError> {
#[cfg(all(unix, not(target_vendor = "apple")))]
{
#[cfg(feature = "wayland")]
Expand Down Expand Up @@ -77,7 +75,7 @@ pub(crate) enum ActiveOrInactiveEventLoop<'a> {
#[allow(unused)]
Active(&'a ActiveEventLoop),
#[allow(unused)]
Inactive(&'a winit::event_loop::EventLoop<SlintUserEvent>),
Inactive(&'a winit::event_loop::EventLoop<SlintEvent>),
}

pub(crate) trait EventLoopInterface {
Expand Down Expand Up @@ -175,7 +173,7 @@ impl EventLoopState {
}
}

impl winit::application::ApplicationHandler<SlintUserEvent> for EventLoopState {
impl winit::application::ApplicationHandler<SlintEvent> for EventLoopState {
fn resumed(&mut self, active_event_loop: &ActiveEventLoop) {
for (_, window_weak) in self.shared_backend_data.active_windows.borrow().iter() {
if let Some(w) = window_weak.upgrade() {
Expand Down Expand Up @@ -462,7 +460,7 @@ impl winit::application::ApplicationHandler<SlintUserEvent> for EventLoopState {
}
}

fn user_event(&mut self, event_loop: &ActiveEventLoop, event: SlintUserEvent) {
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: SlintEvent) {
match event.0 {
CustomEvent::UserEvent(user_callback) => user_callback(),
CustomEvent::Exit => event_loop.exit(),
Expand Down
28 changes: 13 additions & 15 deletions internal/backends/winit/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ pub use winit;
/// Internal type used by the winit backend for thread communication and window system updates.
#[non_exhaustive]
#[derive(Debug)]
pub struct SlintUserEvent(CustomEvent);
pub struct SlintEvent(CustomEvent);

/// Convenience alias for the event loop builder used by Slint.
pub type EventLoopBuilder = winit::event_loop::EventLoopBuilder<SlintEvent>;

/// Returned by callbacks passed to [`Window::on_winit_window_event`](WinitWindowAccessor::on_winit_window_event)
/// to determine if winit events should propagate to the Slint event loop.
Expand Down Expand Up @@ -119,7 +122,7 @@ fn default_renderer_factory(
fn try_create_window_with_fallback_renderer(
shared_backend_data: &Rc<SharedBackendData>,
attrs: winit::window::WindowAttributes,
_proxy: &winit::event_loop::EventLoopProxy<SlintUserEvent>,
_proxy: &winit::event_loop::EventLoopProxy<SlintEvent>,
#[cfg(all(muda, target_os = "macos"))] muda_enable_default_menu_bar: bool,
) -> Option<Rc<WinitWindowAdapter>> {
[
Expand Down Expand Up @@ -168,7 +171,7 @@ 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>>,
event_loop_builder: Option<EventLoopBuilder>,
#[cfg(all(muda, target_os = "macos"))]
muda_enable_default_menu_bar_bar: bool,
#[cfg(target_family = "wasm")]
Expand Down Expand Up @@ -216,10 +219,7 @@ impl BackendBuilder {
/// Configures this builder to use the specified event loop builder when creating the event
/// loop during a subsequent call to [`Self::build`].
#[must_use]
pub fn with_event_loop_builder(
mut self,
event_loop_builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
) -> Self {
pub fn with_event_loop_builder(mut self, event_loop_builder: EventLoopBuilder) -> Self {
self.event_loop_builder = Some(event_loop_builder);
self
}
Expand Down Expand Up @@ -372,13 +372,11 @@ pub(crate) struct SharedBackendData {
#[cfg(not(target_arch = "wasm32"))]
clipboard: std::cell::RefCell<clipboard::ClipboardPair>,
not_running_event_loop: RefCell<Option<crate::event_loop::NotRunningEventLoop>>,
event_loop_proxy: winit::event_loop::EventLoopProxy<SlintUserEvent>,
event_loop_proxy: winit::event_loop::EventLoopProxy<SlintEvent>,
}

impl SharedBackendData {
fn new(
builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>,
) -> Result<Self, PlatformError> {
fn new(builder: EventLoopBuilder) -> Result<Self, PlatformError> {
#[cfg(not(target_arch = "wasm32"))]
use raw_window_handle::HasDisplayHandle;

Expand Down Expand Up @@ -587,11 +585,11 @@ impl i_slint_core::platform::Platform for Backend {
}

fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
struct Proxy(winit::event_loop::EventLoopProxy<SlintUserEvent>);
struct Proxy(winit::event_loop::EventLoopProxy<SlintEvent>);
impl EventLoopProxy for Proxy {
fn quit_event_loop(&self) -> Result<(), EventLoopError> {
self.0
.send_event(SlintUserEvent(CustomEvent::Exit))
.send_event(SlintEvent(CustomEvent::Exit))
.map_err(|_| EventLoopError::EventLoopTerminated)
}

Expand All @@ -610,11 +608,11 @@ impl i_slint_core::platform::Platform for Backend {
// all at once.
#[cfg(target_arch = "wasm32")]
self.0
.send_event(SlintUserEvent(CustomEvent::WakeEventLoopWorkaround))
.send_event(SlintEvent(CustomEvent::WakeEventLoopWorkaround))
.map_err(|_| EventLoopError::EventLoopTerminated)?;

self.0
.send_event(SlintUserEvent(CustomEvent::UserEvent(event)))
.send_event(SlintEvent(CustomEvent::UserEvent(event)))
.map_err(|_| EventLoopError::EventLoopTerminated)
}
}
Expand Down
6 changes: 3 additions & 3 deletions internal/backends/winit/muda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

use super::WinitWindowAdapter;
use crate::SlintUserEvent;
use crate::SlintEvent;
use core::pin::Pin;
use i_slint_core::items::MenuEntry;
use i_slint_core::menus::MenuVTable;
Expand Down Expand Up @@ -36,13 +36,13 @@ impl MudaAdapter {
pub fn setup(
menubar: &vtable::VBox<MenuVTable>,
winit_window: &Window,
proxy: EventLoopProxy<SlintUserEvent>,
proxy: EventLoopProxy<SlintEvent>,
window_adapter_weak: Weak<WinitWindowAdapter>,
) -> Self {
let menu = muda::Menu::new();

muda::MenuEvent::set_event_handler(Some(move |e| {
let _ = proxy.send_event(SlintUserEvent(crate::event_loop::CustomEvent::Muda(e)));
let _ = proxy.send_event(SlintEvent(crate::event_loop::CustomEvent::Muda(e)));
}));

#[cfg(target_os = "windows")]
Expand Down
Loading
Loading