use std::collections::HashMap;
use std::sync::Arc;
use platform_core::{
Event, EventHandler, FullscreenMode, MultiSurfacePlatform, Platform, PlatformError, SurfaceId,
Window, WindowConfig, WindowPosition,
};
use winit::application::ApplicationHandler;
use winit::event::{StartCause, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Fullscreen, WindowAttributes, WindowId, WindowLevel};
use platform_winit::{SurfaceIntent, WinitWindow, map_window_event};
enum UserEvent {
Accessibility(accesskit_winit::Event),
#[cfg(target_os = "linux")]
ColorScheme(bool),
Wake,
}
impl From<accesskit_winit::Event> for UserEvent {
fn from(event: accesskit_winit::Event) -> Self {
UserEvent::Accessibility(event)
}
}
pub struct WinitPlatform {
event_loop: EventLoop<UserEvent>,
}
impl WinitPlatform {
pub fn try_new() -> Result<Self, PlatformError> {
Ok(Self {
event_loop: EventLoop::<UserEvent>::with_user_event()
.build()
.map_err(|e| PlatformError(e.to_string()))?,
})
}
}
struct WinitRunner<H: EventHandler<WinitWindow>> {
handler: H,
window: Option<WinitWindow>,
config: WindowConfig,
cursor_position: (f64, f64),
scale_factor: f64,
modifiers: platform_core::ModifiersState,
timer_has_fired: bool,
a11y: Option<accesskit_winit::Adapter>,
a11y_proxy: winit::event_loop::EventLoopProxy<UserEvent>,
a11y_nodes: Vec<platform_core::AccessNode>,
}
impl<H: EventHandler<WinitWindow>> WinitRunner<H> {
fn on_accessibility(&mut self, event: accesskit_winit::WindowEvent) {
use accesskit_winit::WindowEvent as AkEvent;
match event {
AkEvent::InitialTreeRequested => self.publish_accessibility(),
AkEvent::ActionRequested(request) => {
let Some((id, activate)) =
crate::accessibility::requested_focus_id(&request, &self.a11y_nodes)
else {
return;
};
self.handler.on_accessibility_action(id, activate);
self.publish_accessibility();
}
AkEvent::AccessibilityDeactivated => self.a11y_nodes.clear(),
}
}
fn publish_accessibility(&mut self) {
let Some(adapter) = &mut self.a11y else {
return;
};
let nodes = self.handler.accessibility();
let title = self.config.title.clone();
adapter.update_if_active(|| crate::accessibility::tree_update(&nodes, &title));
self.a11y_nodes = nodes;
}
}
impl<H: EventHandler<WinitWindow>> ApplicationHandler<UserEvent> for WinitRunner<H> {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
match event {
UserEvent::Accessibility(event) => self.on_accessibility(event.window_event),
#[cfg(target_os = "linux")]
UserEvent::ColorScheme(dark) => {
if let Some(window) = &self.window {
self.handler
.on_event(Event::ColorSchemeChanged { dark }, window);
}
}
UserEvent::Wake => {
if let Some(window) = &self.window {
window.request_redraw();
}
}
}
}
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
self.timer_has_fired = matches!(cause, StartCause::ResumeTimeReached { .. });
self.handler.new_events();
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if let Some(d) = self.handler.about_to_wait() {
if self.timer_has_fired {
if let Some(window) = &self.window {
window.request_redraw();
}
}
event_loop.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + d));
} else {
event_loop.set_control_flow(ControlFlow::Wait);
}
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let Some(window) = create_window_from_config(event_loop, &self.config) else {
return;
};
if let Some(dark) = initial_prefers_dark(&window) {
self.handler
.on_event(Event::ColorSchemeChanged { dark }, &window);
}
if !self.handler.on_resume(&window) {
event_loop.exit();
return;
}
self.a11y = Some(accesskit_winit::Adapter::with_event_loop_proxy(
event_loop,
&window.0,
self.a11y_proxy.clone(),
));
window.0.set_visible(true);
window.request_redraw();
self.window = Some(window);
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
let Some(window) = self.window.clone() else {
return;
};
if let Some(adapter) = &mut self.a11y {
adapter.process_event(&window.0, &event);
}
let redrawn = matches!(event, WindowEvent::RedrawRequested);
let outcome = dispatch_window_event(
&mut self.handler,
&window,
&mut self.cursor_position,
&mut self.scale_factor,
&mut self.modifiers,
event,
);
if redrawn {
self.publish_accessibility();
}
if matches!(outcome, WindowEventOutcome::CloseRequested) || self.handler.take_exit_request()
{
event_loop.exit();
}
}
}
enum WindowEventOutcome {
Continue,
CloseRequested,
}
fn create_window_from_config(
event_loop: &ActiveEventLoop,
config: &WindowConfig,
) -> Option<WinitWindow> {
let mut attributes = WindowAttributes::default()
.with_visible(false)
.with_title(config.title.as_str())
.with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height))
.with_resizable(config.is_resizable)
.with_decorations(config.has_decorations)
.with_transparent(config.is_transparent);
if let Some((w, h)) = config.min_size {
attributes = attributes.with_min_inner_size(winit::dpi::LogicalSize::new(w, h));
}
if let Some((w, h)) = config.max_size {
attributes = attributes.with_max_inner_size(winit::dpi::LogicalSize::new(w, h));
}
match config.fullscreen {
FullscreenMode::Disabled => {}
FullscreenMode::Borderless | FullscreenMode::Exclusive => {
attributes = attributes.with_fullscreen(Some(Fullscreen::Borderless(None)));
}
}
if let WindowPosition::At(x, y) = config.position {
attributes = attributes.with_position(winit::dpi::PhysicalPosition::new(x, y));
}
if config.is_always_on_top {
attributes = attributes.with_window_level(WindowLevel::AlwaysOnTop);
}
match event_loop.create_window(attributes) {
Ok(w) => Some(WinitWindow(std::sync::Arc::new(w))),
Err(e) => {
tracing::error!(error = %e, "failed to create window");
None
}
}
}
fn dispatch_window_event<H: EventHandler<WinitWindow>>(
handler: &mut H,
window: &WinitWindow,
cursor_position: &mut (f64, f64),
scale_factor: &mut f64,
modifiers: &mut platform_core::ModifiersState,
event: WindowEvent,
) -> WindowEventOutcome {
match map_window_event(event, cursor_position, scale_factor, modifiers) {
SurfaceIntent::Event(e) => handler.on_event(e, window),
SurfaceIntent::Resized(e) => {
handler.on_event(e, window);
window.request_redraw();
}
SurfaceIntent::Redraw => handler.on_redraw(window),
SurfaceIntent::Close(e) => {
handler.on_event(e, window);
return WindowEventOutcome::CloseRequested;
}
SurfaceIntent::Ignore => {}
}
WindowEventOutcome::Continue
}
impl Platform for WinitPlatform {
type Window = WinitWindow;
fn run<H: EventHandler<Self::Window>>(
self,
config: WindowConfig,
handler: H,
) -> Result<(), PlatformError> {
let mut runner = WinitRunner {
handler,
window: None,
config,
cursor_position: (0.0, 0.0),
scale_factor: 1.0,
modifiers: platform_core::ModifiersState::default(),
timer_has_fired: false,
a11y: None,
a11y_proxy: self.event_loop.create_proxy(),
a11y_nodes: Vec::new(),
};
let wake_proxy = self.event_loop.create_proxy();
platform_core::set_loop_waker(std::sync::Arc::new(move || {
let _ = wake_proxy.send_event(UserEvent::Wake);
}));
#[cfg(target_os = "linux")]
{
let proxy = self.event_loop.create_proxy();
crate::color_scheme::spawn_watch(move |dark| {
let _ = proxy.send_event(UserEvent::ColorScheme(dark));
});
}
self.event_loop
.run_app(&mut runner)
.map_err(|e| PlatformError(e.to_string()))
}
}
fn initial_prefers_dark(window: &WinitWindow) -> Option<bool> {
let winit = window.prefers_dark();
#[cfg(target_os = "linux")]
{
winit.or_else(crate::color_scheme::portal_prefers_dark)
}
#[cfg(not(target_os = "linux"))]
{
winit
}
}
struct DynamicRequest {
config: WindowConfig,
handler: Box<dyn EventHandler<WinitWindow>>,
close: Arc<std::sync::atomic::AtomicBool>,
}
thread_local! {
static DYNAMIC_QUEUE: std::cell::RefCell<Vec<DynamicRequest>> =
const { std::cell::RefCell::new(Vec::new()) };
}
pub fn request_dynamic_surface(
config: WindowConfig,
handler: Box<dyn EventHandler<WinitWindow>>,
) -> Arc<std::sync::atomic::AtomicBool> {
let close = Arc::new(std::sync::atomic::AtomicBool::new(false));
DYNAMIC_QUEUE.with(|q| {
q.borrow_mut().push(DynamicRequest {
config,
handler,
close: Arc::clone(&close),
})
});
close
}
fn drain_dynamic_requests() -> Vec<DynamicRequest> {
DYNAMIC_QUEUE.with(|q| std::mem::take(&mut *q.borrow_mut()))
}
struct SurfaceRunner {
handler: Box<dyn EventHandler<WinitWindow>>,
window: WinitWindow,
cursor_position: (f64, f64),
scale_factor: f64,
modifiers: platform_core::ModifiersState,
pace: Option<std::time::Duration>,
close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
resumed: bool,
a11y: Option<accesskit_winit::Adapter>,
a11y_nodes: Vec<platform_core::AccessNode>,
title: String,
}
impl SurfaceRunner {
fn publish_accessibility(&mut self) {
let Some(adapter) = &mut self.a11y else {
return;
};
let nodes = self.handler.accessibility();
let title = self.title.clone();
adapter.update_if_active(|| crate::accessibility::tree_update(&nodes, &title));
self.a11y_nodes = nodes;
}
}
fn resume_surface(surface: &mut SurfaceRunner) -> bool {
let window = surface.window.clone();
surface.handler.new_events();
let built = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if let Some(dark) = initial_prefers_dark(&window) {
surface
.handler
.on_event(Event::ColorSchemeChanged { dark }, &window);
}
surface.handler.on_resume(&window)
}));
surface.pace = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
surface.handler.about_to_wait()
}))
.unwrap_or(None);
matches!(built, Ok(true))
}
type BoxedFactory = Box<dyn Fn(SurfaceId) -> Box<dyn EventHandler<WinitWindow>>>;
struct WinitMultiRunner {
factory: BoxedFactory,
pending: Vec<(SurfaceId, WindowConfig)>,
surfaces: HashMap<WindowId, SurfaceRunner>,
created: bool,
a11y_proxy: winit::event_loop::EventLoopProxy<UserEvent>,
timer_has_fired: bool,
}
impl WinitMultiRunner {
fn spawn_surface(
&mut self,
event_loop: &ActiveEventLoop,
config: WindowConfig,
handler: Box<dyn EventHandler<WinitWindow>>,
close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
resume_now: bool,
) {
let Some(window) = ({
let _gpu = renderer_core::gpu_sync::lifecycle_guard();
create_window_from_config(event_loop, &config)
}) else {
return;
};
let window_id = window.0.id();
let a11y = accesskit_winit::Adapter::with_event_loop_proxy(
event_loop,
&window.0,
self.a11y_proxy.clone(),
);
let mut surface = SurfaceRunner {
handler,
window,
cursor_position: (0.0, 0.0),
scale_factor: 1.0,
modifiers: platform_core::ModifiersState::default(),
pace: None,
close_flag,
resumed: false,
a11y: Some(a11y),
a11y_nodes: Vec::new(),
title: config.title.clone(),
};
if resume_now {
if !resume_surface(&mut surface) {
tracing::error!("surface on_resume failed or panicked; skipping it");
return;
}
surface.window.request_redraw();
surface.resumed = true;
}
self.surfaces.insert(window_id, surface);
}
}
impl ApplicationHandler<UserEvent> for WinitMultiRunner {
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
match event {
UserEvent::Accessibility(event) => {
use accesskit_winit::WindowEvent as AkEvent;
let Some(surface) = self.surfaces.get_mut(&event.window_id) else {
return;
};
match event.window_event {
AkEvent::InitialTreeRequested => surface.publish_accessibility(),
AkEvent::ActionRequested(request) => {
let Some((id, activate)) =
crate::accessibility::requested_focus_id(&request, &surface.a11y_nodes)
else {
return;
};
surface.handler.on_accessibility_action(id, activate);
surface.publish_accessibility();
}
AkEvent::AccessibilityDeactivated => surface.a11y_nodes.clear(),
}
}
#[cfg(target_os = "linux")]
UserEvent::ColorScheme(dark) => {
for surface in self.surfaces.values_mut() {
surface.handler.new_events();
surface
.handler
.on_event(Event::ColorSchemeChanged { dark }, &surface.window);
surface.pace = surface.handler.about_to_wait();
}
}
UserEvent::Wake => {
for surface in self.surfaces.values() {
surface.window.request_redraw();
}
}
}
}
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
self.timer_has_fired = matches!(cause, StartCause::ResumeTimeReached { .. });
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
for req in drain_dynamic_requests() {
self.spawn_surface(event_loop, req.config, req.handler, Some(req.close), false);
}
let to_close: Vec<WindowId> = self
.surfaces
.iter()
.filter(|(_, s)| {
s.close_flag
.as_ref()
.is_some_and(|f| f.load(std::sync::atomic::Ordering::Relaxed))
})
.map(|(&id, _)| id)
.collect();
for id in to_close {
if let Some(mut removed) = self.surfaces.remove(&id) {
removed.handler.on_suspend();
let _gpu = renderer_core::gpu_sync::lifecycle_guard();
drop(removed);
}
}
if self.created && self.surfaces.is_empty() {
event_loop.exit();
return;
}
let mut next_wake: Option<std::time::Duration> = None;
for surface in self.surfaces.values() {
if let Some(d) = surface.pace {
if self.timer_has_fired {
surface.window.request_redraw();
}
next_wake = Some(next_wake.map_or(d, |cur| cur.min(d)));
}
}
match next_wake {
Some(d) => {
event_loop.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + d))
}
None => event_loop.set_control_flow(ControlFlow::Wait),
}
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.created {
return;
}
self.created = true;
for (id, config) in std::mem::take(&mut self.pending) {
let handler = (self.factory)(id);
self.spawn_surface(event_loop, config, handler, None, true);
}
if self.surfaces.is_empty() {
event_loop.exit();
}
}
fn window_event(&mut self, _event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
let Some(surface) = self.surfaces.get_mut(&id) else {
return;
};
if !surface.resumed {
let configured =
matches!(&event, WindowEvent::Resized(s) if s.width > 0 && s.height > 0);
if !configured {
return;
}
if resume_surface(surface) {
surface.resumed = true;
} else {
if let Some(removed) = self.surfaces.remove(&id) {
let _gpu = renderer_core::gpu_sync::lifecycle_guard();
drop(removed);
}
return;
}
}
let window = surface.window.clone();
let redrawn = matches!(event, WindowEvent::RedrawRequested);
if let Some(adapter) = &mut surface.a11y {
adapter.process_event(&window.0, &event);
}
surface.handler.new_events();
let dispatched = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
dispatch_window_event(
&mut surface.handler,
&window,
&mut surface.cursor_position,
&mut surface.scale_factor,
&mut surface.modifiers,
event,
)
}));
let paced = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
surface.handler.about_to_wait()
}));
surface.pace = paced.as_ref().copied().unwrap_or(None);
let panicked = dispatched.is_err() || paced.is_err();
if redrawn && !panicked {
surface.publish_accessibility();
}
let close = matches!(dispatched, Ok(WindowEventOutcome::CloseRequested));
let exit_requested = !panicked && surface.handler.take_exit_request();
if panicked {
tracing::error!(?id, "surface panicked; unmounting it");
}
if panicked || close || exit_requested {
if let Some(mut removed) = self.surfaces.remove(&id) {
if !panicked {
removed.handler.on_suspend();
}
let _gpu = renderer_core::gpu_sync::lifecycle_guard();
drop(removed);
}
tracing::debug!(
?id,
close,
exit_requested,
panicked,
remaining = self.surfaces.len(),
"surface closed"
);
}
}
}
impl MultiSurfacePlatform for WinitPlatform {
type Window = WinitWindow;
fn run_surfaces<H, F>(
self,
surfaces: Vec<(SurfaceId, WindowConfig)>,
factory: F,
) -> Result<(), PlatformError>
where
H: EventHandler<WinitWindow> + 'static,
F: Fn(SurfaceId) -> H + 'static,
{
let factory: BoxedFactory =
Box::new(move |id| Box::new(factory(id)) as Box<dyn EventHandler<WinitWindow>>);
let mut runner = WinitMultiRunner {
factory,
pending: surfaces,
surfaces: HashMap::new(),
created: false,
a11y_proxy: self.event_loop.create_proxy(),
timer_has_fired: false,
};
let wake_proxy = self.event_loop.create_proxy();
platform_core::set_loop_waker(std::sync::Arc::new(move || {
let _ = wake_proxy.send_event(UserEvent::Wake);
}));
#[cfg(target_os = "linux")]
{
let proxy = self.event_loop.create_proxy();
crate::color_scheme::spawn_watch(move |dark| {
let _ = proxy.send_event(UserEvent::ColorScheme(dark));
});
}
self.event_loop
.run_app(&mut runner)
.map_err(|e| PlatformError(e.to_string()))
}
}