|
| 1 | +use crate::{ |
| 2 | + event::Event, |
| 3 | + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, |
| 4 | +}; |
| 5 | + |
| 6 | +/// The return status for `pump_events` |
| 7 | +pub enum PumpStatus { |
| 8 | + /// Continue running external loop. |
| 9 | + Continue, |
| 10 | + /// Exit external loop. |
| 11 | + Exit(i32), |
| 12 | +} |
| 13 | + |
| 14 | +/// Additional methods on [`EventLoop`] for pumping events within an external event loop |
| 15 | +pub trait EventLoopExtPumpEvents { |
| 16 | + /// A type provided by the user that can be passed through [`Event::UserEvent`]. |
| 17 | + type UserEvent; |
| 18 | + |
| 19 | + /// Pump the `EventLoop` to check for and dispatch pending events. |
| 20 | + /// |
| 21 | + /// This API is designed to enable applications to integrate Winit into an |
| 22 | + /// external event loop, for platforms that can support this. |
| 23 | + /// |
| 24 | + /// The given `timeout` limits how long it may block waiting for new events. |
| 25 | + /// |
| 26 | + /// Passing a `timeout` of `Some(Duration::ZERO)` would ensure your external |
| 27 | + /// event loop is never blocked but you would likely need to consider how |
| 28 | + /// to throttle your own external loop. |
| 29 | + /// |
| 30 | + /// Passing a `timeout` of `None` means that it may wait indefinitely for new |
| 31 | + /// events before returning control back to the external loop. |
| 32 | + /// |
| 33 | + /// ## Example |
| 34 | + /// |
| 35 | + /// ```rust,no_run |
| 36 | + /// # // Copied from examples/window_pump_events.rs |
| 37 | + /// # #[cfg(any( |
| 38 | + /// # windows_platform, |
| 39 | + /// # macos_platform, |
| 40 | + /// # x11_platform, |
| 41 | + /// # wayland_platform, |
| 42 | + /// # android_platform, |
| 43 | + /// # ))] |
| 44 | + /// fn main() -> std::process::ExitCode { |
| 45 | + /// # use std::{process::ExitCode, thread::sleep, time::Duration}; |
| 46 | + /// # |
| 47 | + /// # use simple_logger::SimpleLogger; |
| 48 | + /// # use winit::{ |
| 49 | + /// # event::{Event, WindowEvent}, |
| 50 | + /// # event_loop::EventLoop, |
| 51 | + /// # platform::pump_events::{EventLoopExtPumpEvents, PumpStatus}, |
| 52 | + /// # window::WindowBuilder, |
| 53 | + /// # }; |
| 54 | + /// let mut event_loop = EventLoop::new(); |
| 55 | + /// # |
| 56 | + /// # SimpleLogger::new().init().unwrap(); |
| 57 | + /// let window = WindowBuilder::new() |
| 58 | + /// .with_title("A fantastic window!") |
| 59 | + /// .build(&event_loop) |
| 60 | + /// .unwrap(); |
| 61 | + /// |
| 62 | + /// 'main: loop { |
| 63 | + /// let status = event_loop.pump_events(|event, _, control_flow| { |
| 64 | + /// # if let Event::WindowEvent { event, .. } = &event { |
| 65 | + /// # // Print only Window events to reduce noise |
| 66 | + /// # println!("{event:?}"); |
| 67 | + /// # } |
| 68 | + /// # |
| 69 | + /// match event { |
| 70 | + /// Event::WindowEvent { |
| 71 | + /// event: WindowEvent::CloseRequested, |
| 72 | + /// window_id, |
| 73 | + /// } if window_id == window.id() => control_flow.set_exit(), |
| 74 | + /// Event::MainEventsCleared => { |
| 75 | + /// window.request_redraw(); |
| 76 | + /// } |
| 77 | + /// _ => (), |
| 78 | + /// } |
| 79 | + /// }); |
| 80 | + /// if let PumpStatus::Exit(exit_code) = status { |
| 81 | + /// break 'main ExitCode::from(exit_code as u8); |
| 82 | + /// } |
| 83 | + /// |
| 84 | + /// // Sleep for 1/60 second to simulate application work |
| 85 | + /// // |
| 86 | + /// // Since `pump_events` doesn't block it will be important to |
| 87 | + /// // throttle the loop in the app somehow. |
| 88 | + /// println!("Update()"); |
| 89 | + /// sleep(Duration::from_millis(16)); |
| 90 | + /// } |
| 91 | + /// } |
| 92 | + /// ``` |
| 93 | + /// |
| 94 | + /// **Note:** This is not a portable API, and its usage involves a number of |
| 95 | + /// caveats and trade offs that should be considered before using this API! |
| 96 | + /// |
| 97 | + /// You almost certainly shouldn't use this API, unless you absolutely know it's |
| 98 | + /// the only practical option you have. |
| 99 | + /// |
| 100 | + /// ## Synchronous events |
| 101 | + /// |
| 102 | + /// Some events _must_ only be handled synchronously via the closure that |
| 103 | + /// is passed to Winit so that the handler will also be synchronized with |
| 104 | + /// the window system and operating system. |
| 105 | + /// |
| 106 | + /// This is because some events are driven by a window system callback |
| 107 | + /// where the window systems expects the application to have handled the |
| 108 | + /// event before returning. |
| 109 | + /// |
| 110 | + /// **These events can not be buffered and handled outside of the closure |
| 111 | + /// passed to Winit.** |
| 112 | + /// |
| 113 | + /// As a general rule it is not recommended to ever buffer events to handle |
| 114 | + /// them outside of the closure passed to Winit since it's difficult to |
| 115 | + /// provide guarantees about which events are safe to buffer across all |
| 116 | + /// operating systems. |
| 117 | + /// |
| 118 | + /// Notable events that will certainly create portability problems if |
| 119 | + /// buffered and handled outside of Winit include: |
| 120 | + /// - `RedrawRequested` events, used to schedule rendering. |
| 121 | + /// |
| 122 | + /// macOS for example uses a `drawRect` callback to drive rendering |
| 123 | + /// within applications and expects rendering to be finished before |
| 124 | + /// the `drawRect` callback returns. |
| 125 | + /// |
| 126 | + /// For portability it's strongly recommended that applications should |
| 127 | + /// keep their rendering inside the closure provided to Winit. |
| 128 | + /// - Any lifecycle events, such as `Suspended` / `Resumed`. |
| 129 | + /// |
| 130 | + /// The handling of these events needs to be synchronized with the |
| 131 | + /// operating system and it would never be appropriate to buffer a |
| 132 | + /// notification that your application has been suspended or resumed and |
| 133 | + /// then handled that later since there would always be a chance that |
| 134 | + /// other lifecycle events occur while the event is buffered. |
| 135 | + /// |
| 136 | + /// ## Supported Platforms |
| 137 | + /// - Windows |
| 138 | + /// - Linux |
| 139 | + /// - MacOS |
| 140 | + /// - Android |
| 141 | + /// |
| 142 | + /// ## Unsupported Platforms |
| 143 | + /// - **Web:** This API is fundamentally incompatible with the event-based way in which |
| 144 | + /// Web browsers work because it's not possible to have a long-running external |
| 145 | + /// loop that would block the browser and there is nothing that can be |
| 146 | + /// polled to ask for new new events. Events are delivered via callbacks based |
| 147 | + /// on an event loop that is internal to the browser itself. |
| 148 | + /// - **iOS:** It's not possible to stop and start an `NSApplication` repeatedly on iOS so |
| 149 | + /// there's no way to support the same approach to polling as on MacOS. |
| 150 | + /// |
| 151 | + /// ## Platform-specific |
| 152 | + /// - **Windows**: The implementation will use `PeekMessage` when checking for |
| 153 | + /// window messages to avoid blocking your external event loop. |
| 154 | + /// |
| 155 | + /// - **MacOS**: The implementation works in terms of stopping the global `NSApp` |
| 156 | + /// whenever the application `RunLoop` indicates that it is preparing to block |
| 157 | + /// and wait for new events. |
| 158 | + /// |
| 159 | + /// This is very different to the polling APIs that are available on other |
| 160 | + /// platforms (the lower level polling primitives on MacOS are private |
| 161 | + /// implementation details for `NSApp` which aren't accessible to application |
| 162 | + /// developers) |
| 163 | + /// |
| 164 | + /// It's likely this will be less efficient than polling on other OSs and |
| 165 | + /// it also means the `NSApp` is stopped while outside of the Winit |
| 166 | + /// event loop - and that's observable (for example to crates like `rfd`) |
| 167 | + /// because the `NSApp` is global state. |
| 168 | + /// |
| 169 | + /// If you render outside of Winit you are likely to see window resizing artifacts |
| 170 | + /// since MacOS expects applications to render synchronously during any `drawRect` |
| 171 | + /// callback. |
| 172 | + fn pump_events<F>(&mut self, event_handler: F) -> PumpStatus |
| 173 | + where |
| 174 | + F: FnMut( |
| 175 | + Event<'_, Self::UserEvent>, |
| 176 | + &EventLoopWindowTarget<Self::UserEvent>, |
| 177 | + &mut ControlFlow, |
| 178 | + ); |
| 179 | +} |
| 180 | + |
| 181 | +impl<T> EventLoopExtPumpEvents for EventLoop<T> { |
| 182 | + type UserEvent = T; |
| 183 | + |
| 184 | + fn pump_events<F>(&mut self, event_handler: F) -> PumpStatus |
| 185 | + where |
| 186 | + F: FnMut( |
| 187 | + Event<'_, Self::UserEvent>, |
| 188 | + &EventLoopWindowTarget<Self::UserEvent>, |
| 189 | + &mut ControlFlow, |
| 190 | + ), |
| 191 | + { |
| 192 | + self.event_loop.pump_events(event_handler) |
| 193 | + } |
| 194 | +} |
0 commit comments