1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
3 | |
4 | #![doc = include_str!("README.md" )] |
5 | #![doc (html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg" )] |
6 | #![warn (missing_docs)] |
7 | |
8 | extern crate alloc; |
9 | |
10 | use event_loop::{CustomEvent, EventLoopState, NotRunningEventLoop}; |
11 | use i_slint_core::api::EventLoopError; |
12 | use i_slint_core::graphics::RequestedGraphicsAPI; |
13 | use i_slint_core::platform::{EventLoopProxy, PlatformError}; |
14 | use i_slint_core::window::WindowAdapter; |
15 | use renderer::WinitCompatibleRenderer; |
16 | use std::cell::RefCell; |
17 | use std::collections::HashMap; |
18 | use std::rc::Rc; |
19 | use std::rc::Weak; |
20 | |
21 | #[cfg (not(target_arch = "wasm32" ))] |
22 | mod clipboard; |
23 | mod drag_resize_window; |
24 | mod winitwindowadapter; |
25 | use winitwindowadapter::*; |
26 | pub(crate) mod event_loop; |
27 | |
28 | /// Re-export of the winit crate. |
29 | pub use winit; |
30 | |
31 | /// Internal type used by the winit backend for thread communication and window system updates. |
32 | #[non_exhaustive ] |
33 | #[derive (Debug)] |
34 | pub struct SlintUserEvent(CustomEvent); |
35 | |
36 | /// Returned by callbacks passed to [`Window::on_winit_window_event`](WinitWindowAccessor::on_winit_window_event) |
37 | /// to determine if winit events should propagate to the Slint event loop. |
38 | pub enum WinitWindowEventResult { |
39 | /// The winit event should propagate normally. |
40 | Propagate, |
41 | /// The winit event shouldn't be processed further. |
42 | PreventDefault, |
43 | } |
44 | |
45 | mod renderer { |
46 | use std::rc::Rc; |
47 | |
48 | use i_slint_core::{graphics::RequestedGraphicsAPI, platform::PlatformError}; |
49 | |
50 | pub trait WinitCompatibleRenderer { |
51 | fn render(&self, window: &i_slint_core::api::Window) -> Result<(), PlatformError>; |
52 | |
53 | fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer; |
54 | // Got WindowEvent::Occluded |
55 | fn occluded(&self, _: bool) {} |
56 | |
57 | fn suspend(&self) -> Result<(), PlatformError>; |
58 | |
59 | // Got winit::Event::Resumed |
60 | fn resume( |
61 | &self, |
62 | event_loop: &dyn crate::event_loop::EventLoopInterface, |
63 | window_attributes: winit::window::WindowAttributes, |
64 | requested_graphics_api: Option<RequestedGraphicsAPI>, |
65 | ) -> Result<Rc<winit::window::Window>, PlatformError>; |
66 | |
67 | fn is_suspended(&self) -> bool; |
68 | } |
69 | |
70 | #[cfg (feature = "renderer-femtovg" )] |
71 | pub(crate) mod femtovg; |
72 | #[cfg (enable_skia_renderer)] |
73 | pub(crate) mod skia; |
74 | |
75 | #[cfg (feature = "renderer-software" )] |
76 | pub(crate) mod sw; |
77 | } |
78 | |
79 | #[cfg (enable_accesskit)] |
80 | mod accesskit; |
81 | #[cfg (muda)] |
82 | mod muda; |
83 | #[cfg (not(use_winit_theme))] |
84 | mod xdg_color_scheme; |
85 | |
86 | #[cfg (target_arch = "wasm32" )] |
87 | pub(crate) mod wasm_input_helper; |
88 | |
89 | cfg_if::cfg_if! { |
90 | if #[cfg(feature = "renderer-femtovg" )] { |
91 | const DEFAULT_RENDERER_NAME: &str = "FemtoVG" ; |
92 | } else if #[cfg(enable_skia_renderer)] { |
93 | const DEFAULT_RENDERER_NAME: &'static str = "Skia" ; |
94 | } else if #[cfg(feature = "renderer-software" )] { |
95 | const DEFAULT_RENDERER_NAME: &'static str = "Software" ; |
96 | } else { |
97 | 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`" ); |
98 | } |
99 | } |
100 | |
101 | fn default_renderer_factory() -> Box<dyn WinitCompatibleRenderer> { |
102 | cfg_if::cfg_if! { |
103 | if #[cfg(enable_skia_renderer)] { |
104 | renderer::skia::WinitSkiaRenderer::new_suspended() |
105 | } else if #[cfg(feature = "renderer-femtovg" )] { |
106 | renderer::femtovg::GlutinFemtoVGRenderer::new_suspended() |
107 | } else if #[cfg(feature = "renderer-software" )] { |
108 | renderer::sw::WinitSoftwareRenderer::new_suspended() |
109 | } else { |
110 | 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`" ); |
111 | } |
112 | } |
113 | } |
114 | |
115 | fn try_create_window_with_fallback_renderer( |
116 | shared_backend_data: &Rc<SharedBackendData>, |
117 | attrs: winit::window::WindowAttributes, |
118 | _proxy: &winit::event_loop::EventLoopProxy<SlintUserEvent>, |
119 | #[cfg (all(muda, target_os = "macos" ))] muda_enable_default_menu_bar: bool, |
120 | ) -> Option<Rc<WinitWindowAdapter>> { |
121 | [ |
122 | #[cfg (any( |
123 | feature = "renderer-skia" , |
124 | feature = "renderer-skia-opengl" , |
125 | feature = "renderer-skia-vulkan" |
126 | ))] |
127 | renderer::skia::WinitSkiaRenderer::new_suspended, |
128 | #[cfg (feature = "renderer-femtovg" )] |
129 | renderer::femtovg::GlutinFemtoVGRenderer::new_suspended, |
130 | #[cfg (feature = "renderer-software" )] |
131 | renderer::sw::WinitSoftwareRenderer::new_suspended, |
132 | ] |
133 | .into_iter() |
134 | .find_map(|renderer_factory| { |
135 | WinitWindowAdapter::new( |
136 | shared_backend_data.clone(), |
137 | renderer_factory(), |
138 | attrs.clone(), |
139 | None, |
140 | #[cfg (any(enable_accesskit, muda))] |
141 | _proxy.clone(), |
142 | #[cfg (all(muda, target_os = "macos" ))] |
143 | muda_enable_default_menu_bar, |
144 | ) |
145 | .ok() |
146 | }) |
147 | } |
148 | |
149 | #[doc (hidden)] |
150 | pub type NativeWidgets = (); |
151 | #[doc (hidden)] |
152 | pub type NativeGlobals = (); |
153 | #[doc (hidden)] |
154 | pub const HAS_NATIVE_STYLE: bool = false; |
155 | #[doc (hidden)] |
156 | pub mod native_widgets {} |
157 | |
158 | /// Use the BackendBuilder to configure the properties of the Winit Backend before creating it. |
159 | /// Create the builder using [`Backend::builder()`], then configure it for example with [`Self::with_renderer_name`], |
160 | /// and build the backend using [`Self::build`]. |
161 | pub struct BackendBuilder { |
162 | allow_fallback: bool, |
163 | requested_graphics_api: Option<RequestedGraphicsAPI>, |
164 | window_attributes_hook: |
165 | Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>, |
166 | renderer_name: Option<String>, |
167 | event_loop_builder: Option<winit::event_loop::EventLoopBuilder<SlintUserEvent>>, |
168 | #[cfg (all(muda, target_os = "macos" ))] |
169 | muda_enable_default_menu_bar_bar: bool, |
170 | #[cfg (target_family = "wasm" )] |
171 | spawn_event_loop: bool, |
172 | } |
173 | |
174 | impl BackendBuilder { |
175 | /// Configures this builder to require a renderer that supports the specified graphics API. |
176 | #[must_use ] |
177 | pub fn request_graphics_api(mut self, graphics_api: RequestedGraphicsAPI) -> Self { |
178 | self.requested_graphics_api = Some(graphics_api); |
179 | self |
180 | } |
181 | |
182 | /// Configures this builder to use the specified renderer name when building the backend later. |
183 | /// Pass `renderer-software` for example to configure the backend to use the Slint software renderer. |
184 | #[must_use ] |
185 | pub fn with_renderer_name(mut self, name: impl Into<String>) -> Self { |
186 | self.renderer_name = Some(name.into()); |
187 | self |
188 | } |
189 | |
190 | /// Configures this builder to use the specified hook that will be called before a Window is created. |
191 | /// |
192 | /// It can be used to adjust settings of window that will be created. |
193 | /// |
194 | /// # Example |
195 | /// |
196 | /// ```rust,no_run |
197 | /// let mut backend = i_slint_backend_winit::Backend::builder() |
198 | /// .with_window_attributes_hook(|attributes| attributes.with_content_protected(true)) |
199 | /// .build() |
200 | /// .unwrap(); |
201 | /// slint::platform::set_platform(Box::new(backend)); |
202 | /// ``` |
203 | #[must_use ] |
204 | pub fn with_window_attributes_hook( |
205 | mut self, |
206 | hook: impl Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes + 'static, |
207 | ) -> Self { |
208 | self.window_attributes_hook = Some(Box::new(hook)); |
209 | self |
210 | } |
211 | |
212 | /// Configures this builder to use the specified event loop builder when creating the event |
213 | /// loop during a subsequent call to [`Self::build`]. |
214 | #[must_use ] |
215 | pub fn with_event_loop_builder( |
216 | mut self, |
217 | event_loop_builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>, |
218 | ) -> Self { |
219 | self.event_loop_builder = Some(event_loop_builder); |
220 | self |
221 | } |
222 | |
223 | /// Configures this builder to enable or disable the default menu bar. |
224 | /// By default, the menu bar is provided by Slint. Set this to false |
225 | /// if you're providing your own menu bar. |
226 | /// Note that an application provided menu bar will be overriden by a `MenuBar` |
227 | /// declared in Slint code. |
228 | #[must_use ] |
229 | #[cfg (all(muda, target_os = "macos" ))] |
230 | pub fn with_default_menu_bar(mut self, enable: bool) -> Self { |
231 | self.muda_enable_default_menu_bar_bar = enable; |
232 | self |
233 | } |
234 | |
235 | #[cfg (target_family = "wasm" )] |
236 | /// Configures this builder to spawn the event loop using [`winit::platform::web::EventLoopExtWebSys::spawn()`] |
237 | /// run `run_event_loop()` is called. |
238 | pub fn with_spawn_event_loop(mut self, enable: bool) -> Self { |
239 | self.spawn_event_loop = enable; |
240 | self |
241 | } |
242 | |
243 | /// Builds the backend with the parameters configured previously. Set the resulting backend |
244 | /// with `slint::platform::set_platform()`: |
245 | /// |
246 | /// # Example |
247 | /// |
248 | /// ```rust,no_run |
249 | /// let mut backend = i_slint_backend_winit::Backend::builder() |
250 | /// .with_renderer_name("renderer-software" ) |
251 | /// .build() |
252 | /// .unwrap(); |
253 | /// slint::platform::set_platform(Box::new(backend)); |
254 | /// ``` |
255 | pub fn build(self) -> Result<Backend, PlatformError> { |
256 | #[allow (unused_mut)] |
257 | let mut event_loop_builder = |
258 | self.event_loop_builder.unwrap_or_else(winit::event_loop::EventLoop::with_user_event); |
259 | |
260 | // Never use winit's menu bar. Either we provide one ourselves with muda, or |
261 | // the user provides one. |
262 | #[cfg (all(feature = "muda" , target_os = "macos" ))] |
263 | winit::platform::macos::EventLoopBuilderExtMacOS::with_default_menu( |
264 | &mut event_loop_builder, |
265 | false, |
266 | ); |
267 | |
268 | // Initialize the winit event loop and propagate errors if for example `DISPLAY` or `WAYLAND_DISPLAY` isn't set. |
269 | |
270 | let shared_data = Rc::new(SharedBackendData::new(event_loop_builder)?); |
271 | |
272 | let renderer_factory_fn = match ( |
273 | self.renderer_name.as_deref(), |
274 | self.requested_graphics_api.as_ref(), |
275 | ) { |
276 | #[cfg (feature = "renderer-femtovg" )] |
277 | (Some("gl" ), maybe_graphics_api) | (Some("femtovg" ), maybe_graphics_api) => { |
278 | // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc. |
279 | if let Some(api) = maybe_graphics_api { |
280 | i_slint_core::graphics::RequestedOpenGLVersion::try_from(api.clone())?; |
281 | } |
282 | renderer::femtovg::GlutinFemtoVGRenderer::new_suspended |
283 | } |
284 | #[cfg (enable_skia_renderer)] |
285 | (Some("skia" ), maybe_graphics_api) => { |
286 | renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(maybe_graphics_api)? |
287 | } |
288 | #[cfg (all(enable_skia_renderer, supports_opengl))] |
289 | (Some("skia-opengl" ), maybe_graphics_api @ _) => { |
290 | // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc. |
291 | if let Some(api) = maybe_graphics_api { |
292 | i_slint_core::graphics::RequestedOpenGLVersion::try_from(api.clone())?; |
293 | } |
294 | renderer::skia::WinitSkiaRenderer::new_opengl_suspended |
295 | } |
296 | #[cfg (all(enable_skia_renderer, not(target_os = "android" )))] |
297 | (Some("skia-software" ), None) => { |
298 | renderer::skia::WinitSkiaRenderer::new_software_suspended |
299 | } |
300 | #[cfg (feature = "renderer-software" )] |
301 | (Some("sw" ), None) | (Some("software" ), None) => { |
302 | renderer::sw::WinitSoftwareRenderer::new_suspended |
303 | } |
304 | (None, None) => default_renderer_factory, |
305 | (Some(renderer_name), _) => { |
306 | if self.allow_fallback { |
307 | eprintln!( |
308 | "slint winit: unrecognized renderer {renderer_name}, falling back to {DEFAULT_RENDERER_NAME}" |
309 | ); |
310 | default_renderer_factory |
311 | } else { |
312 | return Err(PlatformError::NoPlatform); |
313 | } |
314 | } |
315 | (None, Some(_requested_graphics_api)) => { |
316 | cfg_if::cfg_if! { |
317 | if #[cfg(enable_skia_renderer)] { |
318 | renderer::skia::WinitSkiaRenderer::factory_for_graphics_api(Some(_requested_graphics_api))? |
319 | } else if #[cfg(feature = "renderer-femtovg" )] { |
320 | // If a graphics API was requested, double check that it's GL. FemtoVG doesn't support Metal, etc. |
321 | i_slint_core::graphics::RequestedOpenGLVersion::try_from(_requested_graphics_api.clone())?; |
322 | renderer::femtovg::GlutinFemtoVGRenderer::new_suspended |
323 | } else { |
324 | return Err(format!("Graphics API use requested by the compile-time enabled renderers don't support that" ).into()) |
325 | } |
326 | } |
327 | } |
328 | }; |
329 | |
330 | Ok(Backend { |
331 | requested_graphics_api: self.requested_graphics_api, |
332 | renderer_factory_fn, |
333 | event_loop_state: Default::default(), |
334 | window_attributes_hook: self.window_attributes_hook, |
335 | shared_data, |
336 | #[cfg (all(muda, target_os = "macos" ))] |
337 | muda_enable_default_menu_bar_bar: self.muda_enable_default_menu_bar_bar, |
338 | #[cfg (target_family = "wasm" )] |
339 | spawn_event_loop: self.spawn_event_loop, |
340 | }) |
341 | } |
342 | } |
343 | |
344 | pub(crate) struct SharedBackendData { |
345 | active_windows: RefCell<HashMap<winit::window::WindowId, Weak<WinitWindowAdapter>>>, |
346 | #[cfg (not(target_arch = "wasm32" ))] |
347 | clipboard: std::cell::RefCell<clipboard::ClipboardPair>, |
348 | not_running_event_loop: RefCell<Option<crate::event_loop::NotRunningEventLoop>>, |
349 | event_loop_proxy: winit::event_loop::EventLoopProxy<SlintUserEvent>, |
350 | } |
351 | |
352 | impl SharedBackendData { |
353 | fn new( |
354 | builder: winit::event_loop::EventLoopBuilder<SlintUserEvent>, |
355 | ) -> Result<Self, PlatformError> { |
356 | #[cfg (not(target_arch = "wasm32" ))] |
357 | use raw_window_handle::HasDisplayHandle; |
358 | |
359 | let nre = NotRunningEventLoop::new(builder)?; |
360 | let event_loop_proxy = nre.instance.create_proxy(); |
361 | #[cfg (not(target_arch = "wasm32" ))] |
362 | let clipboard = crate::clipboard::create_clipboard( |
363 | &nre.instance |
364 | .display_handle() |
365 | .map_err(|display_err| PlatformError::OtherError(display_err.into()))?, |
366 | ); |
367 | Ok(Self { |
368 | active_windows: Default::default(), |
369 | #[cfg (not(target_arch = "wasm32" ))] |
370 | clipboard: RefCell::new(clipboard), |
371 | not_running_event_loop: RefCell::new(Some(nre)), |
372 | event_loop_proxy, |
373 | }) |
374 | } |
375 | |
376 | pub(crate) fn with_event_loop<T>( |
377 | &self, |
378 | callback: impl FnOnce( |
379 | &dyn crate::event_loop::EventLoopInterface, |
380 | ) -> Result<T, Box<dyn std::error::Error + Send + Sync>>, |
381 | ) -> Result<T, Box<dyn std::error::Error + Send + Sync>> { |
382 | if crate::event_loop::CURRENT_WINDOW_TARGET.is_set() { |
383 | crate::event_loop::CURRENT_WINDOW_TARGET.with(|current_target| callback(current_target)) |
384 | } else { |
385 | match self.not_running_event_loop.borrow().as_ref() { |
386 | Some(event_loop) => callback(event_loop), |
387 | None => { |
388 | Err(PlatformError::from("Event loop functions called without event loop" ) |
389 | .into()) |
390 | } |
391 | } |
392 | } |
393 | } |
394 | |
395 | pub fn register_window(&self, id: winit::window::WindowId, window: Rc<WinitWindowAdapter>) { |
396 | self.active_windows.borrow_mut().insert(id, Rc::downgrade(&window)); |
397 | } |
398 | |
399 | pub fn unregister_window(&self, id: winit::window::WindowId) { |
400 | self.active_windows.borrow_mut().remove(&id); |
401 | } |
402 | |
403 | pub fn window_by_id(&self, id: winit::window::WindowId) -> Option<Rc<WinitWindowAdapter>> { |
404 | self.active_windows.borrow().get(&id).and_then(|weakref| weakref.upgrade()) |
405 | } |
406 | } |
407 | |
408 | #[i_slint_core_macros::slint_doc ] |
409 | /// This struct implements the Slint Platform trait. |
410 | /// Use this in conjunction with [`slint::platform::set_platform`](slint:rust:slint/platform/fn.set_platform.html) to initialize. |
411 | /// Slint to use winit for all windowing system interaction. |
412 | /// |
413 | /// ```rust,no_run |
414 | /// use i_slint_backend_winit::Backend; |
415 | /// slint::platform::set_platform(Box::new(Backend::new().unwrap())); |
416 | /// ``` |
417 | pub struct Backend { |
418 | requested_graphics_api: Option<RequestedGraphicsAPI>, |
419 | renderer_factory_fn: fn() -> Box<dyn WinitCompatibleRenderer>, |
420 | event_loop_state: std::cell::RefCell<Option<crate::event_loop::EventLoopState>>, |
421 | shared_data: Rc<SharedBackendData>, |
422 | |
423 | /// This hook is called before a Window is created. |
424 | /// |
425 | /// It can be used to adjust settings of window that will be created |
426 | /// |
427 | /// See also [`BackendBuilder::with_window_attributes_hook`]. |
428 | /// |
429 | /// # Example |
430 | /// |
431 | /// ```rust,no_run |
432 | /// let mut backend = i_slint_backend_winit::Backend::new().unwrap(); |
433 | /// backend.window_attributes_hook = Some(Box::new(|attributes| attributes.with_content_protected(true))); |
434 | /// slint::platform::set_platform(Box::new(backend)); |
435 | /// ``` |
436 | pub window_attributes_hook: |
437 | Option<Box<dyn Fn(winit::window::WindowAttributes) -> winit::window::WindowAttributes>>, |
438 | |
439 | #[cfg (all(muda, target_os = "macos" ))] |
440 | muda_enable_default_menu_bar_bar: bool, |
441 | |
442 | #[cfg (target_family = "wasm" )] |
443 | spawn_event_loop: bool, |
444 | } |
445 | |
446 | impl Backend { |
447 | #[i_slint_core_macros::slint_doc ] |
448 | /// Creates a new winit backend with the default renderer that's compiled in. |
449 | /// |
450 | /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer. |
451 | pub fn new() -> Result<Self, PlatformError> { |
452 | Self::builder().build() |
453 | } |
454 | |
455 | #[i_slint_core_macros::slint_doc ] |
456 | /// Creates a new winit backend with the renderer specified by name. |
457 | /// |
458 | /// See the [backend documentation](slint:backends_and_renderers) for details on how to select the default renderer. |
459 | /// |
460 | /// If the renderer name is `None` or the name is not recognized, the default renderer is selected. |
461 | pub fn new_with_renderer_by_name(renderer_name: Option<&str>) -> Result<Self, PlatformError> { |
462 | let mut builder = Self::builder(); |
463 | if let Some(name) = renderer_name { |
464 | builder = builder.with_renderer_name(name.to_string()); |
465 | } |
466 | builder.build() |
467 | } |
468 | |
469 | /// Creates a new BackendBuilder for configuring aspects of the Winit backend before |
470 | /// setting it as the platform backend. |
471 | pub fn builder() -> BackendBuilder { |
472 | BackendBuilder { |
473 | allow_fallback: true, |
474 | requested_graphics_api: None, |
475 | window_attributes_hook: None, |
476 | renderer_name: None, |
477 | event_loop_builder: None, |
478 | #[cfg (all(muda, target_os = "macos" ))] |
479 | muda_enable_default_menu_bar_bar: true, |
480 | #[cfg (target_family = "wasm" )] |
481 | spawn_event_loop: false, |
482 | } |
483 | } |
484 | } |
485 | |
486 | impl i_slint_core::platform::Platform for Backend { |
487 | fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> { |
488 | let mut attrs = WinitWindowAdapter::window_attributes()?; |
489 | |
490 | if let Some(hook) = &self.window_attributes_hook { |
491 | attrs = hook(attrs); |
492 | } |
493 | |
494 | let adapter = WinitWindowAdapter::new( |
495 | self.shared_data.clone(), |
496 | (self.renderer_factory_fn)(), |
497 | attrs.clone(), |
498 | self.requested_graphics_api.clone(), |
499 | #[cfg (any(enable_accesskit, muda))] |
500 | self.shared_data.event_loop_proxy.clone(), |
501 | #[cfg (all(muda, target_os = "macos" ))] |
502 | self.muda_enable_default_menu_bar_bar, |
503 | ) |
504 | .or_else(|e| { |
505 | try_create_window_with_fallback_renderer( |
506 | &self.shared_data, |
507 | attrs, |
508 | &self.shared_data.event_loop_proxy.clone(), |
509 | #[cfg (all(muda, target_os = "macos" ))] |
510 | self.muda_enable_default_menu_bar_bar, |
511 | ) |
512 | .ok_or_else(|| format!("Winit backend failed to find a suitable renderer: {e}" )) |
513 | })?; |
514 | Ok(adapter) |
515 | } |
516 | |
517 | fn run_event_loop(&self) -> Result<(), PlatformError> { |
518 | let loop_state = self |
519 | .event_loop_state |
520 | .borrow_mut() |
521 | .take() |
522 | .unwrap_or_else(|| EventLoopState::new(self.shared_data.clone())); |
523 | #[cfg (target_family = "wasm" )] |
524 | { |
525 | if self.spawn_event_loop { |
526 | return loop_state.spawn(); |
527 | } |
528 | } |
529 | let new_state = loop_state.run()?; |
530 | *self.event_loop_state.borrow_mut() = Some(new_state); |
531 | Ok(()) |
532 | } |
533 | |
534 | #[cfg (all(not(target_arch = "wasm32" ), not(target_os = "ios" )))] |
535 | fn process_events( |
536 | &self, |
537 | timeout: core::time::Duration, |
538 | _: i_slint_core::InternalToken, |
539 | ) -> Result<core::ops::ControlFlow<()>, PlatformError> { |
540 | let loop_state = self |
541 | .event_loop_state |
542 | .borrow_mut() |
543 | .take() |
544 | .unwrap_or_else(|| EventLoopState::new(self.shared_data.clone())); |
545 | let (new_state, status) = loop_state.pump_events(Some(timeout))?; |
546 | *self.event_loop_state.borrow_mut() = Some(new_state); |
547 | match status { |
548 | winit::platform::pump_events::PumpStatus::Continue => { |
549 | Ok(core::ops::ControlFlow::Continue(())) |
550 | } |
551 | winit::platform::pump_events::PumpStatus::Exit(code) => { |
552 | if code == 0 { |
553 | Ok(core::ops::ControlFlow::Break(())) |
554 | } else { |
555 | Err(format!("Event loop exited with non-zero code {code}" ).into()) |
556 | } |
557 | } |
558 | } |
559 | } |
560 | |
561 | fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> { |
562 | struct Proxy(winit::event_loop::EventLoopProxy<SlintUserEvent>); |
563 | impl EventLoopProxy for Proxy { |
564 | fn quit_event_loop(&self) -> Result<(), EventLoopError> { |
565 | self.0 |
566 | .send_event(SlintUserEvent(CustomEvent::Exit)) |
567 | .map_err(|_| EventLoopError::EventLoopTerminated) |
568 | } |
569 | |
570 | fn invoke_from_event_loop( |
571 | &self, |
572 | event: Box<dyn FnOnce() + Send>, |
573 | ) -> Result<(), EventLoopError> { |
574 | // Calling send_event is usually done by winit at the bottom of the stack, |
575 | // in event handlers, and thus winit might decide to process the event |
576 | // immediately within that stack. |
577 | // To prevent re-entrancy issues that might happen by getting the application |
578 | // event processed on top of the current stack, set winit in Poll mode so that |
579 | // events are queued and process on top of a clean stack during a requested animation |
580 | // frame a few moments later. |
581 | // This also allows batching multiple post_event calls and redraw their state changes |
582 | // all at once. |
583 | #[cfg (target_arch = "wasm32" )] |
584 | self.0 |
585 | .send_event(SlintUserEvent(CustomEvent::WakeEventLoopWorkaround)) |
586 | .map_err(|_| EventLoopError::EventLoopTerminated)?; |
587 | |
588 | self.0 |
589 | .send_event(SlintUserEvent(CustomEvent::UserEvent(event))) |
590 | .map_err(|_| EventLoopError::EventLoopTerminated) |
591 | } |
592 | } |
593 | Some(Box::new(Proxy(self.shared_data.event_loop_proxy.clone()))) |
594 | } |
595 | |
596 | #[cfg (target_arch = "wasm32" )] |
597 | fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) { |
598 | crate::wasm_input_helper::set_clipboard_text(text.into(), clipboard); |
599 | } |
600 | |
601 | #[cfg (not(target_arch = "wasm32" ))] |
602 | fn set_clipboard_text(&self, text: &str, clipboard: i_slint_core::platform::Clipboard) { |
603 | let mut pair = self.shared_data.clipboard.borrow_mut(); |
604 | if let Some(clipboard) = clipboard::select_clipboard(&mut pair, clipboard) { |
605 | clipboard.set_contents(text.into()).ok(); |
606 | } |
607 | } |
608 | |
609 | #[cfg (target_arch = "wasm32" )] |
610 | fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> { |
611 | crate::wasm_input_helper::get_clipboard_text(clipboard) |
612 | } |
613 | |
614 | #[cfg (not(target_arch = "wasm32" ))] |
615 | fn clipboard_text(&self, clipboard: i_slint_core::platform::Clipboard) -> Option<String> { |
616 | let mut pair = self.shared_data.clipboard.borrow_mut(); |
617 | clipboard::select_clipboard(&mut pair, clipboard).and_then(|c| c.get_contents().ok()) |
618 | } |
619 | } |
620 | |
621 | mod private { |
622 | pub trait WinitWindowAccessorSealed {} |
623 | } |
624 | |
625 | #[i_slint_core_macros::slint_doc ] |
626 | /// This helper trait can be used to obtain access to the [`winit::window::Window`] for a given |
627 | /// [`slint::Window`](slint:rust:slint/struct.window).")] |
628 | pub trait WinitWindowAccessor: private::WinitWindowAccessorSealed { |
629 | /// Returns true if a [`winit::window::Window`] exists for this window. This is the case if the window is |
630 | /// backed by this winit backend. |
631 | fn has_winit_window(&self) -> bool; |
632 | /// Invokes the specified callback with a reference to the [`winit::window::Window`] that exists for this Slint window |
633 | /// and returns `Some(T)`; otherwise `None`. |
634 | fn with_winit_window<T>(&self, callback: impl FnOnce(&winit::window::Window) -> T) |
635 | -> Option<T>; |
636 | /// Registers a window event filter callback for this Slint window. |
637 | /// |
638 | /// The callback is invoked in the winit event loop whenever a window event is received with a reference to the |
639 | /// [`slint::Window`](i_slint_core::api::Window) and the [`winit::event::WindowEvent`]. The return value of the |
640 | /// callback specifies whether Slint should handle this event. |
641 | /// |
642 | /// If this window [is not backed by winit](WinitWindowAccessor::has_winit_window), this function is a no-op. |
643 | fn on_winit_window_event( |
644 | &self, |
645 | callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> WinitWindowEventResult |
646 | + 'static, |
647 | ); |
648 | |
649 | /// Creates a non Slint aware window with winit |
650 | fn create_winit_window( |
651 | &self, |
652 | window_attributes: winit::window::WindowAttributes, |
653 | ) -> Result<winit::window::Window, winit::error::OsError>; |
654 | } |
655 | |
656 | impl WinitWindowAccessor for i_slint_core::api::Window { |
657 | fn has_winit_window(&self) -> bool { |
658 | i_slint_core::window::WindowInner::from_pub(self) |
659 | .window_adapter() |
660 | .internal(i_slint_core::InternalToken) |
661 | .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>()) |
662 | .is_some_and(|adapter| adapter.winit_window().is_some()) |
663 | } |
664 | |
665 | fn with_winit_window<T>( |
666 | &self, |
667 | callback: impl FnOnce(&winit::window::Window) -> T, |
668 | ) -> Option<T> { |
669 | i_slint_core::window::WindowInner::from_pub(self) |
670 | .window_adapter() |
671 | .internal(i_slint_core::InternalToken) |
672 | .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>()) |
673 | .and_then(|adapter| adapter.winit_window().map(|w| callback(&w))) |
674 | } |
675 | |
676 | fn on_winit_window_event( |
677 | &self, |
678 | mut callback: impl FnMut(&i_slint_core::api::Window, &winit::event::WindowEvent) -> WinitWindowEventResult |
679 | + 'static, |
680 | ) { |
681 | if let Some(adapter) = i_slint_core::window::WindowInner::from_pub(self) |
682 | .window_adapter() |
683 | .internal(i_slint_core::InternalToken) |
684 | .and_then(|wa| wa.as_any().downcast_ref::<WinitWindowAdapter>()) |
685 | { |
686 | adapter |
687 | .window_event_filter |
688 | .set(Some(Box::new(move |window, event| callback(window, event)))); |
689 | } |
690 | } |
691 | |
692 | /// Creates a non Slint aware window with winit |
693 | fn create_winit_window( |
694 | &self, |
695 | window_attributes: winit::window::WindowAttributes, |
696 | ) -> Result<winit::window::Window, winit::error::OsError> { |
697 | i_slint_core::window::WindowInner::from_pub(self) |
698 | .window_adapter() |
699 | .internal(i_slint_core::InternalToken) |
700 | .unwrap() |
701 | .as_any() |
702 | .downcast_ref::<WinitWindowAdapter>() |
703 | .unwrap() |
704 | .shared_backend_data |
705 | .with_event_loop(|eli| Ok(eli.create_window(window_attributes))) |
706 | .unwrap() |
707 | } |
708 | } |
709 | |
710 | impl private::WinitWindowAccessorSealed for i_slint_core::api::Window {} |
711 | |
712 | #[cfg (test)] |
713 | mod testui { |
714 | slint::slint! { |
715 | export component App inherits Window { |
716 | Text { text: "Ok" ; } |
717 | } |
718 | } |
719 | } |
720 | |
721 | // Sorry, can't test with rust test harness and multiple threads. |
722 | #[cfg (not(any(target_arch = "wasm32" , target_os = "macos" , target_os = "ios" )))] |
723 | #[test ] |
724 | fn test_window_accessor_and_rwh() { |
725 | slint::platform::set_platform(Box::new(crate::Backend::new().unwrap())).unwrap(); |
726 | |
727 | use testui::*; |
728 | let app = App::new().unwrap(); |
729 | let slint_window = app.window(); |
730 | assert!(slint_window.has_winit_window()); |
731 | let handle = slint_window.window_handle(); |
732 | use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; |
733 | assert!(handle.window_handle().is_ok()); |
734 | assert!(handle.display_handle().is_ok()); |
735 | } |
736 | |