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::Event(Event::ModifiersChanged {
289 modifiers: *modifiers,
290 })
291 }
292 WindowEvent::KeyboardInput { event, .. } => {
293 let key = match &event.logical_key {
294 WinitKey::Character(c) => match c.as_str().chars().next() {
295 Some(ch) => platform_core::Key::Char(ch),
296 None => return SurfaceIntent::Ignore,
297 },
298 WinitKey::Named(named) => match platform_winit::map_named_key(*named) {
299 Some(nk) => platform_core::Key::Named(nk),
300 None => return SurfaceIntent::Ignore,
301 },
302 _ => return SurfaceIntent::Ignore,
303 };
304 let mods = *modifiers;
305 SurfaceIntent::Event(match event.state {
306 ElementState::Pressed => Event::KeyPressed {
307 key,
308 modifiers: mods,
309 },
310 ElementState::Released => Event::KeyReleased {
311 key,
312 modifiers: mods,
313 },
314 })
315 }
316 WindowEvent::ThemeChanged(theme) => SurfaceIntent::Event(Event::ColorSchemeChanged {
317 dark: theme == winit::window::Theme::Dark,
318 }),
319 _ => SurfaceIntent::Ignore,
320 }
321}
322
323fn dispatch_window_event<H: EventHandler<WinitWindow>>(
326 handler: &mut H,
327 window: &WinitWindow,
328 cursor_position: &mut (f64, f64),
329 scale_factor: &mut f64,
330 modifiers: &mut platform_core::ModifiersState,
331 event: WindowEvent,
332) -> WindowEventOutcome {
333 match map_window_event(event, cursor_position, scale_factor, modifiers) {
334 SurfaceIntent::Event(e) => handler.on_event(e, window),
335 SurfaceIntent::Resized(e) => {
336 handler.on_event(e, window);
337 window.request_redraw();
338 }
339 SurfaceIntent::Redraw => handler.on_redraw(window),
340 SurfaceIntent::Close(e) => {
341 handler.on_event(e, window);
342 return WindowEventOutcome::CloseRequested;
343 }
344 SurfaceIntent::Ignore => {}
345 }
346 WindowEventOutcome::Continue
347}
348
349impl Platform for WinitPlatform {
350 type Window = WinitWindow;
351
352 fn run<H: EventHandler<Self::Window>>(
353 self,
354 config: WindowConfig,
355 handler: H,
356 ) -> Result<(), PlatformError> {
357 let mut runner = WinitRunner {
358 handler,
359 window: None,
360 config,
361 cursor_position: (0.0, 0.0),
362 scale_factor: 1.0,
363 modifiers: platform_core::ModifiersState::default(),
364 timer_has_fired: false,
365 };
366 let wake_proxy = self.event_loop.create_proxy();
369 platform_core::set_loop_waker(std::sync::Arc::new(move || {
370 let _ = wake_proxy.send_event(UserEvent::Wake);
371 }));
372 #[cfg(target_os = "linux")]
375 {
376 let proxy = self.event_loop.create_proxy();
377 crate::color_scheme::spawn_watch(move |dark| {
378 let _ = proxy.send_event(UserEvent::ColorScheme(dark));
379 });
380 }
381 self.event_loop
382 .run_app(&mut runner)
383 .map_err(|e| PlatformError(e.to_string()))
384 }
385}
386
387fn initial_prefers_dark(window: &WinitWindow) -> Option<bool> {
390 let winit = window.prefers_dark();
391 #[cfg(target_os = "linux")]
392 {
393 winit.or_else(crate::color_scheme::portal_prefers_dark)
394 }
395 #[cfg(not(target_os = "linux"))]
396 {
397 winit
398 }
399}
400
401struct DynamicRequest {
418 config: WindowConfig,
419 handler: Box<dyn EventHandler<WinitWindow>>,
420 close: Arc<std::sync::atomic::AtomicBool>,
421}
422
423thread_local! {
424 static DYNAMIC_QUEUE: std::cell::RefCell<Vec<DynamicRequest>> =
425 const { std::cell::RefCell::new(Vec::new()) };
426}
427
428pub fn request_dynamic_surface(
433 config: WindowConfig,
434 handler: Box<dyn EventHandler<WinitWindow>>,
435) -> Arc<std::sync::atomic::AtomicBool> {
436 let close = Arc::new(std::sync::atomic::AtomicBool::new(false));
437 DYNAMIC_QUEUE.with(|q| {
438 q.borrow_mut().push(DynamicRequest {
439 config,
440 handler,
441 close: Arc::clone(&close),
442 })
443 });
444 close
445}
446
447fn drain_dynamic_requests() -> Vec<DynamicRequest> {
448 DYNAMIC_QUEUE.with(|q| std::mem::take(&mut *q.borrow_mut()))
449}
450
451struct SurfaceRunner {
455 handler: Box<dyn EventHandler<WinitWindow>>,
456 window: WinitWindow,
457 cursor_position: (f64, f64),
458 scale_factor: f64,
459 modifiers: platform_core::ModifiersState,
460 pace: Option<std::time::Duration>,
461 close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
463 resumed: bool,
467}
468
469fn resume_surface(surface: &mut SurfaceRunner) -> bool {
473 let window = surface.window.clone();
474 surface.handler.new_events();
475 let built = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
476 if let Some(dark) = initial_prefers_dark(&window) {
477 surface
478 .handler
479 .on_event(Event::ColorSchemeChanged { dark }, &window);
480 }
481 surface.handler.on_resume(&window)
482 }));
483 surface.pace = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
484 surface.handler.about_to_wait()
485 }))
486 .unwrap_or(None);
487 matches!(built, Ok(true))
488}
489
490type BoxedFactory = Box<dyn Fn(SurfaceId) -> Box<dyn EventHandler<WinitWindow>>>;
493
494struct WinitMultiRunner {
495 factory: BoxedFactory,
496 pending: Vec<(SurfaceId, WindowConfig)>,
497 surfaces: HashMap<WindowId, SurfaceRunner>,
498 created: bool,
499 timer_has_fired: bool,
501}
502
503impl WinitMultiRunner {
504 fn spawn_surface(
509 &mut self,
510 event_loop: &ActiveEventLoop,
511 config: WindowConfig,
512 handler: Box<dyn EventHandler<WinitWindow>>,
513 close_flag: Option<Arc<std::sync::atomic::AtomicBool>>,
514 resume_now: bool,
515 ) {
516 let Some(window) = ({
520 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
521 create_window_from_config(event_loop, &config)
522 }) else {
523 return;
524 };
525 let window_id = window.0.id();
526 let mut surface = SurfaceRunner {
527 handler,
528 window,
529 cursor_position: (0.0, 0.0),
530 scale_factor: 1.0,
531 modifiers: platform_core::ModifiersState::default(),
532 pace: None,
533 close_flag,
534 resumed: false,
535 };
536 if resume_now {
537 if !resume_surface(&mut surface) {
538 tracing::error!("surface on_resume failed or panicked; skipping it");
539 return;
540 }
541 surface.window.request_redraw();
542 surface.resumed = true;
543 }
544 self.surfaces.insert(window_id, surface);
545 }
546}
547
548impl ApplicationHandler<UserEvent> for WinitMultiRunner {
549 fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) {
550 match event {
551 UserEvent::ColorScheme(dark) => {
552 for surface in self.surfaces.values_mut() {
554 surface.handler.new_events();
555 surface
556 .handler
557 .on_event(Event::ColorSchemeChanged { dark }, &surface.window);
558 surface.pace = surface.handler.about_to_wait();
559 }
560 }
561 UserEvent::Wake => {
562 for surface in self.surfaces.values() {
565 surface.window.request_redraw();
566 }
567 }
568 }
569 }
570
571 fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
572 self.timer_has_fired = matches!(cause, StartCause::ResumeTimeReached { .. });
574 }
575
576 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
577 for req in drain_dynamic_requests() {
580 self.spawn_surface(event_loop, req.config, req.handler, Some(req.close), false);
581 }
582 let to_close: Vec<WindowId> = self
583 .surfaces
584 .iter()
585 .filter(|(_, s)| {
586 s.close_flag
587 .as_ref()
588 .is_some_and(|f| f.load(std::sync::atomic::Ordering::Relaxed))
589 })
590 .map(|(&id, _)| id)
591 .collect();
592 for id in to_close {
593 if let Some(mut removed) = self.surfaces.remove(&id) {
594 removed.handler.on_suspend();
595 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
598 drop(removed);
599 }
600 }
601 if self.created && self.surfaces.is_empty() {
602 event_loop.exit();
603 return;
604 }
605
606 let mut next_wake: Option<std::time::Duration> = None;
609 for surface in self.surfaces.values() {
610 if let Some(d) = surface.pace {
611 if self.timer_has_fired {
612 surface.window.request_redraw();
613 }
614 next_wake = Some(next_wake.map_or(d, |cur| cur.min(d)));
615 }
616 }
617 match next_wake {
618 Some(d) => {
619 event_loop.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + d))
620 }
621 None => event_loop.set_control_flow(ControlFlow::Wait),
622 }
623 }
624
625 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
626 if self.created {
629 return;
630 }
631 self.created = true;
632 for (id, config) in std::mem::take(&mut self.pending) {
633 let handler = (self.factory)(id);
635 self.spawn_surface(event_loop, config, handler, None, true);
636 }
637 if self.surfaces.is_empty() {
638 event_loop.exit();
639 }
640 }
641
642 fn window_event(&mut self, _event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
643 let Some(surface) = self.surfaces.get_mut(&id) else {
644 return;
645 };
646 if !surface.resumed {
647 let configured =
652 matches!(&event, WindowEvent::Resized(s) if s.width > 0 && s.height > 0);
653 if !configured {
654 return;
655 }
656 if resume_surface(surface) {
657 surface.resumed = true;
658 } else {
661 if let Some(removed) = self.surfaces.remove(&id) {
664 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
665 drop(removed);
666 }
667 return;
668 }
669 }
670 let window = surface.window.clone();
672 surface.handler.new_events();
673 let dispatched = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
678 dispatch_window_event(
679 &mut surface.handler,
680 &window,
681 &mut surface.cursor_position,
682 &mut surface.scale_factor,
683 &mut surface.modifiers,
684 event,
685 )
686 }));
687 let paced = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
688 surface.handler.about_to_wait()
689 }));
690 surface.pace = paced.as_ref().copied().unwrap_or(None);
691 let panicked = dispatched.is_err() || paced.is_err();
692 let close = matches!(dispatched, Ok(WindowEventOutcome::CloseRequested));
693 let exit_requested = !panicked && surface.handler.take_exit_request();
695 if panicked {
696 tracing::error!(?id, "surface panicked; unmounting it");
697 }
698 if panicked || close || exit_requested {
700 if let Some(mut removed) = self.surfaces.remove(&id) {
701 if !panicked {
704 removed.handler.on_suspend();
705 }
706 let _gpu = renderer_core::gpu_sync::lifecycle_guard();
711 drop(removed);
712 }
713 tracing::debug!(
714 ?id,
715 close,
716 exit_requested,
717 panicked,
718 remaining = self.surfaces.len(),
719 "surface closed"
720 );
721 }
725 }
726}
727
728impl MultiSurfacePlatform for WinitPlatform {
729 type Window = WinitWindow;
730
731 fn run_surfaces<H, F>(
732 self,
733 surfaces: Vec<(SurfaceId, WindowConfig)>,
734 factory: F,
735 ) -> Result<(), PlatformError>
736 where
737 H: EventHandler<WinitWindow> + 'static,
738 F: Fn(SurfaceId) -> H + 'static,
739 {
740 let factory: BoxedFactory =
742 Box::new(move |id| Box::new(factory(id)) as Box<dyn EventHandler<WinitWindow>>);
743 let mut runner = WinitMultiRunner {
744 factory,
745 pending: surfaces,
746 surfaces: HashMap::new(),
747 created: false,
748 timer_has_fired: false,
749 };
750 let wake_proxy = self.event_loop.create_proxy();
754 platform_core::set_loop_waker(std::sync::Arc::new(move || {
755 let _ = wake_proxy.send_event(UserEvent::Wake);
756 }));
757 #[cfg(target_os = "linux")]
759 {
760 let proxy = self.event_loop.create_proxy();
761 crate::color_scheme::spawn_watch(move |dark| {
762 let _ = proxy.send_event(UserEvent::ColorScheme(dark));
763 });
764 }
765 self.event_loop
766 .run_app(&mut runner)
767 .map_err(|e| PlatformError(e.to_string()))
768 }
769}