diff --git a/Cargo.lock b/Cargo.lock index f24a6c0db..04908a70c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,7 @@ dependencies = [ "accesskit_windows", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", + "softbuffer", "winit", ] @@ -584,6 +585,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ctor-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" + [[package]] name = "cursor-icon" version = "1.1.0" @@ -1431,9 +1438,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polling" @@ -1822,6 +1829,38 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "softbuffer" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d623bff5d06f60d738990980d782c8c866997d9194cfe79ecad00aa2f76826dd" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "cfg_aliases", + "core-graphics", + "fastrand", + "foreign-types", + "js-sys", + "log", + "memmap2", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle 0.6.2", + "redox_syscall 0.5.13", + "rustix 0.38.44", + "tiny-xlib", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.52.0", + "x11rb", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1909,6 +1948,19 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading", + "pkg-config", + "tracing", +] + [[package]] name = "tokio" version = "1.44.2" diff --git a/platforms/winit/Cargo.toml b/platforms/winit/Cargo.toml index 61680b881..077e5d9e2 100644 --- a/platforms/winit/Cargo.toml +++ b/platforms/winit/Cargo.toml @@ -41,3 +41,10 @@ version = "0.30.5" default-features = false features = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] +[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dev-dependencies] +softbuffer = { version = "0.4.0", default-features = false, features = [ + "x11", + "x11-dlopen", + "wayland", + "wayland-dlopen", +] } diff --git a/platforms/winit/examples/mixed_handlers.rs b/platforms/winit/examples/mixed_handlers.rs index ef5246f55..1064d289a 100644 --- a/platforms/winit/examples/mixed_handlers.rs +++ b/platforms/winit/examples/mixed_handlers.rs @@ -1,3 +1,6 @@ +#[path = "util/fill.rs"] +mod fill; + use accesskit::{ Action, ActionRequest, ActivationHandler, Live, Node, NodeId, Rect, Role, Tree, TreeUpdate, }; @@ -205,8 +208,12 @@ impl ApplicationHandler for Application { adapter.process_event(&window.window, &event); match event { WindowEvent::CloseRequested => { + fill::cleanup_window(&window.window); self.window = None; } + WindowEvent::RedrawRequested => { + fill::fill_window(&window.window); + } WindowEvent::KeyboardInput { event: KeyEvent { @@ -270,7 +277,9 @@ impl ApplicationHandler for Application { } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - if self.window.is_none() { + if let Some(window) = self.window.as_ref() { + window.window.request_redraw(); + } else { event_loop.exit(); } } diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index 39ccd28e5..4d4a942c7 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -1,3 +1,6 @@ +#[path = "util/fill.rs"] +mod fill; + use accesskit::{Action, ActionRequest, Live, Node, NodeId, Rect, Role, Tree, TreeUpdate}; use accesskit_winit::{Adapter, Event as AccessKitEvent, WindowEvent as AccessKitWindowEvent}; use std::error::Error; @@ -182,8 +185,12 @@ impl ApplicationHandler for Application { adapter.process_event(&window.window, &event); match event { WindowEvent::CloseRequested => { + fill::cleanup_window(&window.window); self.window = None; } + WindowEvent::RedrawRequested => { + fill::fill_window(&window.window); + } WindowEvent::KeyboardInput { event: KeyEvent { @@ -246,7 +253,9 @@ impl ApplicationHandler for Application { } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - if self.window.is_none() { + if let Some(window) = self.window.as_ref() { + window.window.request_redraw(); + } else { event_loop.exit(); } } diff --git a/platforms/winit/examples/util/fill.rs b/platforms/winit/examples/util/fill.rs new file mode 100644 index 000000000..27625e2e9 --- /dev/null +++ b/platforms/winit/examples/util/fill.rs @@ -0,0 +1,123 @@ +// Adapted from winit's examples/util/fill.rs. + +//! Fill the window buffer with a solid color. +//! +//! Launching a window without drawing to it has unpredictable results varying from platform to +//! platform. In order to have well-defined examples, this module provides an easy way to +//! fill the window buffer with a solid color. +//! +//! The `softbuffer` crate is used, largely because of its ease of use. `glutin` or `wgpu` could +//! also be used to fill the window buffer, but they are more complicated to use. + +pub use platform::cleanup_window; +pub use platform::fill_window; + +#[cfg(not(any(target_os = "android", target_os = "ios")))] +mod platform { + use std::cell::RefCell; + use std::collections::HashMap; + use std::mem; + use std::mem::ManuallyDrop; + use std::num::NonZeroU32; + + use softbuffer::{Context, Surface}; + use winit::window::{Window, WindowId}; + + thread_local! { + // NOTE: You should never do things like that, create context and drop it before + // you drop the event loop. We do this for brevity to not blow up examples. We use + // ManuallyDrop to prevent destructors from running. + // + // A static, thread-local map of graphics contexts to open windows. + static GC: ManuallyDrop>> = const { ManuallyDrop::new(RefCell::new(None)) }; + } + + /// The graphics context used to draw to a window. + struct GraphicsContext { + /// The global softbuffer context. + context: RefCell>, + + /// The hash map of window IDs to surfaces. + surfaces: HashMap>, + } + + impl GraphicsContext { + fn new(w: &Window) -> Self { + Self { + context: RefCell::new( + Context::new(unsafe { mem::transmute::<&'_ Window, &'static Window>(w) }) + .expect("Failed to create a softbuffer context"), + ), + surfaces: HashMap::new(), + } + } + + fn create_surface( + &mut self, + window: &Window, + ) -> &mut Surface<&'static Window, &'static Window> { + self.surfaces.entry(window.id()).or_insert_with(|| { + Surface::new(&self.context.borrow(), unsafe { + mem::transmute::<&'_ Window, &'static Window>(window) + }) + .expect("Failed to create a softbuffer surface") + }) + } + + fn destroy_surface(&mut self, window: &Window) { + self.surfaces.remove(&window.id()); + } + } + + pub fn fill_window(window: &Window) { + GC.with(|gc| { + let size = window.inner_size(); + let (Some(width), Some(height)) = + (NonZeroU32::new(size.width), NonZeroU32::new(size.height)) + else { + return; + }; + + // Either get the last context used or create a new one. + let mut gc = gc.borrow_mut(); + let surface = gc + .get_or_insert_with(|| GraphicsContext::new(window)) + .create_surface(window); + + // Fill a buffer with a solid color. + const DARK_GRAY: u32 = 0xff181818; + + surface + .resize(width, height) + .expect("Failed to resize the softbuffer surface"); + + let mut buffer = surface + .buffer_mut() + .expect("Failed to get the softbuffer buffer"); + buffer.fill(DARK_GRAY); + buffer + .present() + .expect("Failed to present the softbuffer buffer"); + }) + } + + pub fn cleanup_window(window: &Window) { + GC.with(|gc| { + let mut gc = gc.borrow_mut(); + if let Some(context) = gc.as_mut() { + context.destroy_surface(window); + } + }); + } +} + +#[cfg(any(target_os = "android", target_os = "ios"))] +mod platform { + pub fn fill_window(_window: &winit::window::Window) { + // No-op on mobile platforms. + } + + pub fn cleanup_window(_window: &winit::window::Window) { + // No-op on mobile platforms. + } +}