1use std::collections::HashMap;
2use std::sync::Arc;
3
4use platform_core::{
5 Event, EventHandler, FullscreenMode, MultiSurfacePlatform, Platform, PlatformError,
6 PointerButton, PointerSource, ScrollDelta, SurfaceId, Window, WindowConfig, WindowPosition,
7};
8use winit::application::ApplicationHandler;
9use winit::event::{ElementState, MouseScrollDelta, StartCause, Touch, TouchPhase, WindowEvent};
10use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
11use winit::keyboard::Key as WinitKey;
12use winit::window::{Fullscreen, WindowAttributes, WindowId, WindowLevel};
13
14use platform_winit::WinitWindow;
15
16enum UserEvent {
18 ColorScheme(bool),
20 Wake,
23}
24
25pub struct WinitPlatform {
26 event_loop: EventLoop<UserEvent>,
27}
28
29impl WinitPlatform {
30 pub fn try_new() -> Result<Self, PlatformError> {
31 Ok(Self {
32 event_loop: EventLoop::<UserEvent>::with_user_event()
33 .build()
34 .map_err(|e| PlatformError(e.to_string()))?,
35 })
36 }
37}
38
39struct WinitRunner<H: EventHandler<WinitWindow>> {
40 handler: H,
41 window: Option<WinitWindow>,
42 config: WindowConfig,
43 cursor_position: (f64, f64),
44 scale_factor: f64,
45 modifiers: platform_core::ModifiersState,
46 timer_has_fired: bool,
48}
49
50impl<H: EventHandler<WinitWindow>> ApplicationHandler<UserEvent> for WinitRunner<H> {
51 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
52 match event {
53 UserEvent::ColorScheme(dark) => {
54 if let Some(window) = &self.window {
55 self.handler
56 .on_event(Event::ColorSchemeChanged { dark }, window);
57 }
58 }
59 UserEvent::Wake => {
60 if let Some(window) = &self.window {
61 window.request_redraw();
62 }
63 }
64 }
65 }
66
67 fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
68 self.timer_has_fired = matches!(cause, StartCause::ResumeTimeReached { .. });
69 self.handler.new_events();
70 }
71
72 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
73 if let Some(d) = self.handler.about_to_wait() {
74 if self.timer_has_fired {
76 if let Some(window) = &self.window {
77 window.request_redraw();
78 }
79 }
80 event_loop.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + d));
81 } else {
82 event_loop.set_control_flow(ControlFlow::Wait);
83 }
84 }
85
86 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
87 let Some(window) = create_window_from_config(event_loop, &self.config) else {
88 return;
89 };
90 if let Some(dark) = initial_prefers_dark(&window) {
93 self.handler
94 .on_event(Event::ColorSchemeChanged { dark }, &window);
95 }
96 if !self.handler.on_resume(&window) {
97 event_loop.exit();
98 return;
99 }
100 window.request_redraw();
101 self.window = Some(window);
102 }
103
104 fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
105 let Some(window) = self.window.clone() else {
108 return;
109 };
110 let outcome = dispatch_window_event(
111 &mut self.handler,
112 &window,
113 &mut self.cursor_position,
114 &mut self.scale_factor,
115 &mut self.modifiers,
116 event,
117 );
118 if matches!(outcome, WindowEventOutcome::CloseRequested) || self.handler.take_exit_request()
121 {
122 event_loop.exit();
123 }
124 }
125}
126
127enum WindowEventOutcome {
128 Continue,
129 CloseRequested,
130}
131
132fn create_window_from_config(
134 event_loop: &ActiveEventLoop,
135 config: &WindowConfig,
136) -> Option<WinitWindow> {
137 let mut attributes = WindowAttributes::default()
138 .with_title(config.title.as_str())
139 .with_inner_size(winit::dpi::LogicalSize::new(config.width, config.height))
140 .with_resizable(config.is_resizable)
141 .with_decorations(config.has_decorations)
142 .with_transparent(config.is_transparent);
143
144 if let Some((w, h)) = config.min_size {
145 attributes = attributes.with_min_inner_size(winit::dpi::LogicalSize::new(w, h));
146 }
147 if let Some((w, h)) = config.max_size {
148 attributes = attributes.with_max_inner_size(winit::dpi::LogicalSize::new(w, h));
149 }
150 match config.fullscreen {
151 FullscreenMode::Disabled => {}
152 FullscreenMode::Borderless | FullscreenMode::Exclusive => {
154 attributes = attributes.with_fullscreen(Some(Fullscreen::Borderless(None)));
155 }
156 }
157 if let WindowPosition::At(x, y) = config.position {
158 attributes = attributes.with_position(winit::dpi::PhysicalPosition::new(x, y));
159 }
160 if config.is_always_on_top {
161 attributes = attributes.with_window_level(WindowLevel::AlwaysOnTop);
162 }
163
164 match event_loop.create_window(attributes) {
165 Ok(w) => Some(WinitWindow(std::sync::Arc::new(w))),
166 Err(e) => {
167 tracing::error!(error = %e, "failed to create window");
168 None
169 }
170 }
171}
172
173enum SurfaceIntent {
178 Event(Event),
180 Resized(Event),
182 Redraw,
184 Close(Event),
186 Ignore,
188}
189
190fn map_window_event(
193 event: WindowEvent,
194 cursor_position: &mut (f64, f64),
195 scale_factor: &mut f64,
196 modifiers: &mut platform_core::ModifiersState,
197) -> SurfaceIntent {
198 match event {
199 WindowEvent::CloseRequested => SurfaceIntent::Close(Event::WindowCloseRequested),
200 WindowEvent::Resized(size) => SurfaceIntent::Resized(Event::WindowResized {
201 width: (size.width as f64 / *scale_factor).round() as u32,
202 height: (size.height as f64 / *scale_factor).round() as u32,
203 }),
204 WindowEvent::RedrawRequested => SurfaceIntent::Redraw,
205 WindowEvent::CursorMoved { position, .. } => {
206 let lx = position.x / *scale_factor;
207 let ly = position.y / *scale_factor;
208 *cursor_position = (lx, ly);
209 SurfaceIntent::Event(Event::PointerMoved {
210 x: lx,
211 y: ly,
212 source: PointerSource::Mouse,
213 })
214 }
215 WindowEvent::MouseInput { state, button, .. } => {
216 let Some(btn) = platform_winit::map_mouse_button(button) else {
217 return SurfaceIntent::Ignore;
218 };
219 let (x, y) = *cursor_position;
220 SurfaceIntent::Event(match state {
221 ElementState::Pressed => Event::PointerPressed {
222 x,
223 y,
224 button: btn,
225 source: PointerSource::Mouse,
226 },
227 ElementState::Released => Event::PointerReleased {
228 x,
229 y,
230 button: btn,
231 source: PointerSource::Mouse,
232 },
233 })
234 }
235 WindowEvent::Touch(Touch {
236 phase,
237 location,
238 id,
239 ..
240 }) => {
241 let x = location.x / *scale_factor;
242 let y = location.y / *scale_factor;
243 let source = PointerSource::Touch { id };
244 SurfaceIntent::Event(match phase {
245 TouchPhase::Started => Event::PointerPressed {
246 x,
247 y,
248 button: PointerButton::Primary,
249 source,
250 },
251 TouchPhase::Moved => Event::PointerMoved { x, y, source },
252 TouchPhase::Ended | TouchPhase::Cancelled => Event::PointerReleased {
253 x,
254 y,
255 button: PointerButton::Primary,
256 source,
257 },
258 })
259 }
260 WindowEvent::Focused(is_focused) => {
261 SurfaceIntent::Event(Event::FocusChanged { is_focused })
262 }
263 WindowEvent::CursorEntered { .. } => SurfaceIntent::Event(Event::CursorEntered),
264 WindowEvent::CursorLeft { .. } => SurfaceIntent::Event(Event::CursorLeft),
265 WindowEvent::ScaleFactorChanged {
266 scale_factor: new_scale,
267 ..
268 } => {
269 *scale_factor = new_scale;
270 SurfaceIntent::Event(Event::ScaleFactorChanged {
271 scale_factor: new_scale,
272 })
273 }
274 WindowEvent::MouseWheel { delta, .. } => {
275 let scroll_delta = match delta {
276 MouseScrollDelta::LineDelta(x, y) => ScrollDelta::Lines { x, y },
277 MouseScrollDelta::PixelDelta(pos) => ScrollDelta::Pixels {
278 x: (pos.x / *scale_factor) as f32,
279 y: (pos.y / *scale_factor) as f32,
280 },
281 };
282 SurfaceIntent::Event(Event::Scrolled {
283 delta: scroll_delta,
284 })
285 }
286 WindowEvent::ModifiersChanged(mods) => {
287 *modifiers = platform_winit::map_modifiers(&mods);
288 SurfaceIntent::Ignore
289 }
290 WindowEvent::KeyboardInput { event, .. } => {
291 let key = match &event.logical_key {
292 WinitKey::Character(c) => match c.as_str().chars().next() {
293 Some(ch) => platform_core::Key::Char(ch),
294 None => return SurfaceIntent::Ignore,
295 },
296 WinitKey::Named(named) => match platform_winit::map_named_key(*named) {
297 Some(nk) => platform_core::Key::Named(nk),
298 None => return SurfaceIntent::Ignore,
299 },
300 _ => return SurfaceIntent::Ignore,
301 };
302 let mods = *modifiers;
303 SurfaceIntent::Event(match event.state {
304 ElementState::Pressed => Event::KeyPressed {
305 key,
306 modifiers: mods,
307 },
308 ElementState::Released => Event::KeyReleased {
309 key,
310 modifiers: mods,
311 },
312 })
313 }
314 WindowEvent::ThemeChanged(theme) => SurfaceIntent::Event(Event::ColorSchemeChanged {
315 dark: theme == winit::window::Theme::Dark,
316 }),
317 _ => SurfaceIntent::Ignore,
318 }
319}
320
321fn dispatch_window_event<H: EventHandler<WinitWindow>>(
324 handler: &mut H,
325 window: &WinitWindow,
326 cursor_position: &mut (f64, f64),
327 scale_factor: &mut f64,
328 modifiers: &mut platform_core::ModifiersState,
329 event: WindowEvent,
330) -> WindowEventOutcome {
331 match map_window_event(event, cursor_position, scale_factor, modifiers) {
332 SurfaceIntent::Event(e) => handler.on_event(e, window),
333 SurfaceIntent::Resized(e) => {
334 handler.on_event(e, window);
335 window.request_redraw();
336 }
337 SurfaceIntent::Redraw => handler.on_redraw(window),
338 SurfaceIntent::Close(e) => {
339 handler.on_event(e, window);
340 return WindowEventOutcome::CloseRequested;
341 }
342 SurfaceIntent::Ignore => {}
343 }
344 WindowEventOutcome::Continue
345}
346
347impl Platform for WinitPlatform {
348 type Window = WinitWindow;
349
350 fn run<H: EventHandler<Self::Window>>(
351 self,
352 config: WindowConfig,
353 handler: H,
354 ) -> Result<(), PlatformError> {
355 let mut runner = WinitRunner {
356 handler,
357 window: None,
358 config,
359 cursor_position: (0.0, 0.0),
360 scale_factor: 1.0,
361 modifiers: platform_core::ModifiersState::default(),
362 timer_has_fired: false,
363 };
364 let wake_proxy = self.event_loop.create_proxy();
367 platform_core::set_loop_waker(std::sync::Arc::new(move || {
368 let _ = wake_proxy.send_event(UserEvent::Wake);
369 }));
370 #[cfg(target_os = "linux")]
373 {
374 let proxy = self.event_loop.create_proxy();
375 crate::color_scheme::spawn_watch(move |dark| {
376 let _ = proxy.send_event(UserEvent::ColorScheme(dark));
377 });
378 }
379 self.event_loop
380 .run_app(&mut runner)
381 .map_err(|e| PlatformError(e.to_string()))
382 }
383}
384
385fn initial_prefers_dark(window: &WinitWindow) -> Option<bool> {
388 let winit = window.prefers_dark();
389 #[cfg(target_os = "linux")]
390 {
391 winit.or_else(crate::color_scheme::portal_prefers_dark)
392 }
393 #[cfg(not(target_os = "linux"))]
394 {
395 winit
396 }
397}
398
399struct DynamicRequest {
416 config: WindowConfig,
417 handler: Box<dyn EventHandler<WinitWindow>>,
418 close: Arc<std::sync::atomic::AtomicBool>,
419}
420
421thread_local! {
422 static DYNAMIC_QUEUE: std::cell::RefCell<Vec<DynamicRequest>> =
423 const { std::cell::RefCell::new(Vec::new()) };
424}
425
426pub fn request_dynamic_surface(
431 config: WindowConfig,
432 handler: Box<dyn EventHandler<WinitWindow>>,
433) -> Arc<std::sync::atomic::AtomicBool> {
434 let close = Arc::new(std::sync::atomic::AtomicBool::new(false));
435 DYNAMIC_QUEUE.with(|q| {
436 q.borrow_mut().push(DynamicRequest {
437 config,
438 handler,
439 close: Arc::clone(&close),
440 })
441 });
442 close
443}
444
445fn drain_dynamic_requests() -> Vec<DynamicRequest> {
446 DYNAMIC_QUEUE.with(|q| std::mem::take(&mut *q.borrow_mut()))
447}
448
449struct SurfaceRunner {
453 handler: Box<dyn EventHandler<WinitWindow>>,
454 window: WinitWindow,
455 cursor_position: (f64, f64),
456 scale_factor: f64,
457 modifiers: platform_core::ModifiersState,
458 pace: Option<std::time::Duration>,
459 close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
461 resumed: bool,
465}
466
467fn resume_surface(surface: &mut SurfaceRunner) -> bool {
471 let window = surface.window.clone();
472 surface.handler.new_events();
473 let built = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
474 if let Some(dark) = initial_prefers_dark(&window) {
475 surface
476 .handler
477 .on_event(Event::ColorSchemeChanged { dark }, &window);
478 }
479 surface.handler.on_resume(&window)
480 }));
481 surface.pace = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
482 surface.handler.about_to_wait()
483 }))
484 .unwrap_or(None);
485 matches!(built, Ok(true))
486}
487
488type BoxedFactory = Box<dyn Fn(SurfaceId) -> Box<dyn EventHandler<WinitWindow>>>;
491
492struct WinitMultiRunner {
493 factory: BoxedFactory,
494 pending: Vec<(SurfaceId, WindowConfig)>,
495 surfaces: HashMap<WindowId, SurfaceRunner>,
496 created: bool,
497 timer_has_fired: bool,
499}
500
501impl WinitMultiRunner {
502 fn spawn_surface(
507 &mut self,
508 event_loop: &ActiveEventLoop,
509 config: WindowConfig,
510 handler: Box<dyn EventHandler<WinitWindow>>,
511 close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
512 resume_now: bool,
513 ) {
514 let Some(window) = ({
518 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
519 create_window_from_config(event_loop, &config)
520 }) else {
521 return;
522 };
523 let window_id = window.0.id();
524 let mut surface = SurfaceRunner {
525 handler,
526 window,
527 cursor_position: (0.0, 0.0),
528 scale_factor: 1.0,
529 modifiers: platform_core::ModifiersState::default(),
530 pace: None,
531 close_flag,
532 resumed: false,
533 };
534 if resume_now {
535 if !resume_surface(&mut surface) {
536 tracing::error!("surface on_resume failed or panicked; skipping it");
537 return;
538 }
539 surface.window.request_redraw();
540 surface.resumed = true;
541 }
542 self.surfaces.insert(window_id, surface);
543 }
544}
545
546impl ApplicationHandler<UserEvent> for WinitMultiRunner {
547 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
548 match event {
549 UserEvent::ColorScheme(dark) => {
550 for surface in self.surfaces.values_mut() {
552 surface.handler.new_events();
553 surface
554 .handler
555 .on_event(Event::ColorSchemeChanged { dark }, &surface.window);
556 surface.pace = surface.handler.about_to_wait();
557 }
558 }
559 UserEvent::Wake => {
560 for surface in self.surfaces.values() {
563 surface.window.request_redraw();
564 }
565 }
566 }
567 }
568
569 fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
570 self.timer_has_fired = matches!(cause, StartCause::ResumeTimeReached { .. });
572 }
573
574 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
575 for req in drain_dynamic_requests() {
578 self.spawn_surface(event_loop, req.config, req.handler, Some(req.close), false);
579 }
580 let to_close: Vec<WindowId> = self
581 .surfaces
582 .iter()
583 .filter(|(_, s)| {
584 s.close_flag
585 .as_ref()
586 .is_some_and(|f| f.load(std::sync::atomic::Ordering::Relaxed))
587 })
588 .map(|(&id, _)| id)
589 .collect();
590 for id in to_close {
591 if let Some(mut removed) = self.surfaces.remove(&id) {
592 removed.handler.on_suspend();
593 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
596 drop(removed);
597 }
598 }
599 if self.created && self.surfaces.is_empty() {
600 event_loop.exit();
601 return;
602 }
603
604 let mut next_wake: Option<std::time::Duration> = None;
607 for surface in self.surfaces.values() {
608 if let Some(d) = surface.pace {
609 if self.timer_has_fired {
610 surface.window.request_redraw();
611 }
612 next_wake = Some(next_wake.map_or(d, |cur| cur.min(d)));
613 }
614 }
615 match next_wake {
616 Some(d) => {
617 event_loop.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + d))
618 }
619 None => event_loop.set_control_flow(ControlFlow::Wait),
620 }
621 }
622
623 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
624 if self.created {
627 return;
628 }
629 self.created = true;
630 for (id, config) in std::mem::take(&mut self.pending) {
631 let handler = (self.factory)(id);
633 self.spawn_surface(event_loop, config, handler, None, true);
634 }
635 if self.surfaces.is_empty() {
636 event_loop.exit();
637 }
638 }
639
640 fn window_event(&mut self, _event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
641 let Some(surface) = self.surfaces.get_mut(&id) else {
642 return;
643 };
644 if !surface.resumed {
645 let configured =
650 matches!(&event, WindowEvent::Resized(s) if s.width > 0 && s.height > 0);
651 if !configured {
652 return;
653 }
654 if resume_surface(surface) {
655 surface.resumed = true;
656 } else {
659 if let Some(removed) = self.surfaces.remove(&id) {
662 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
663 drop(removed);
664 }
665 return;
666 }
667 }
668 let window = surface.window.clone();
670 surface.handler.new_events();
671 let dispatched = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
676 dispatch_window_event(
677 &mut surface.handler,
678 &window,
679 &mut surface.cursor_position,
680 &mut surface.scale_factor,
681 &mut surface.modifiers,
682 event,
683 )
684 }));
685 let paced = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
686 surface.handler.about_to_wait()
687 }));
688 surface.pace = paced.as_ref().copied().unwrap_or(None);
689 let panicked = dispatched.is_err() || paced.is_err();
690 let close = matches!(dispatched, Ok(WindowEventOutcome::CloseRequested));
691 let exit_requested = !panicked && surface.handler.take_exit_request();
693 if panicked {
694 tracing::error!(?id, "surface panicked; unmounting it");
695 }
696 if panicked || close || exit_requested {
698 if let Some(mut removed) = self.surfaces.remove(&id) {
699 if !panicked {
702 removed.handler.on_suspend();
703 }
704 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
709 drop(removed);
710 }
711 tracing::debug!(
712 ?id,
713 close,
714 exit_requested,
715 panicked,
716 remaining = self.surfaces.len(),
717 "surface closed"
718 );
719 }
723 }
724}
725
726impl MultiSurfacePlatform for WinitPlatform {
727 type Window = WinitWindow;
728
729 fn run_surfaces<H, F>(
730 self,
731 surfaces: Vec<(SurfaceId, WindowConfig)>,
732 factory: F,
733 ) -> Result<(), PlatformError>
734 where
735 H: EventHandler<WinitWindow> + 'static,
736 F: Fn(SurfaceId) -> H + 'static,
737 {
738 let factory: BoxedFactory =
740 Box::new(move |id| Box::new(factory(id)) as Box<dyn EventHandler<WinitWindow>>);
741 let mut runner = WinitMultiRunner {
742 factory,
743 pending: surfaces,
744 surfaces: HashMap::new(),
745 created: false,
746 timer_has_fired: false,
747 };
748 let wake_proxy = self.event_loop.create_proxy();
752 platform_core::set_loop_waker(std::sync::Arc::new(move || {
753 let _ = wake_proxy.send_event(UserEvent::Wake);
754 }));
755 #[cfg(target_os = "linux")]
757 {
758 let proxy = self.event_loop.create_proxy();
759 crate::color_scheme::spawn_watch(move |dark| {
760 let _ = proxy.send_event(UserEvent::ColorScheme(dark));
761 });
762 }
763 self.event_loop
764 .run_app(&mut runner)
765 .map_err(|e| PlatformError(e.to_string()))
766 }
767}