use std::path::PathBuf; use std::sync::{mpsc, Arc, Mutex, PoisonError}; use std::time::Duration; use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::error::OsError; use winit::event::{ AxisId, DeviceId, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, Touch, TouchPhase, }; use winit::event_loop::AsyncRequestSerial; use winit::window::{ActivationToken, Theme, WindowId}; use crate::window::WindowAttributes; use crate::Message; pub type WindowSpawner = Box; pub trait ApplicationSealed where AppMessage: Message, { fn open( &mut self, window: WindowAttributes, sender: Arc>>, spawner: WindowSpawner, ) -> Result, OsError>; } #[derive(Clone, Debug)] pub struct OpenedWindow(pub(crate) Arc>>>); impl OpenedWindow { pub fn winit(&self) -> Option> { self.0 .lock() .unwrap_or_else(PoisonError::into_inner) .clone() } pub(crate) fn close(&self) { *self.0.lock().unwrap_or_else(PoisonError::into_inner) = None; } } pub enum EventLoopMessage where AppMessage: Message, { OpenWindow { attrs: WindowAttributes, sender: Arc>>, open_sender: mpsc::SyncSender>, spawner: WindowSpawner, }, CloseWindow(WindowId), WindowPanic(WindowId), User { message: AppMessage, response_sender: mpsc::SyncSender, }, PreventShutdown, AllowShutdown, } #[derive(Debug)] pub enum WindowMessage { User(User), Event(WindowEvent), } #[derive(Debug)] pub struct RedrawGuard(mpsc::SyncSender<()>); impl Drop for RedrawGuard { fn drop(&mut self) { let _ignored = self.0.send(()); } } pub struct WaitForRedraw(mpsc::Receiver<()>); impl WaitForRedraw { pub fn wait(self, timeout: Duration) { let _result = self.0.recv_timeout(timeout); } } impl RedrawGuard { pub fn new() -> (Self, WaitForRedraw) { let (sender, receiver) = mpsc::sync_channel(1); (Self(sender), WaitForRedraw(receiver)) } } #[derive(Debug)] pub enum WindowEvent { RedrawRequested(RedrawGuard), /// The size of the window has changed. Contains the client area's new dimensions. Resized(PhysicalSize), /// The position of the window has changed. Contains the window's new position. /// /// ## Platform-specific /// /// - **iOS / Android / Web / Wayland:** Unsupported. Moved(PhysicalPosition), /// The window has been requested to close. CloseRequested, /// The window has been destroyed. Destroyed, /// A file has been dropped into the window. /// /// When the user drops multiple files at once, this event will be emitted for each file /// separately. DroppedFile(PathBuf), /// A file is being hovered over the window. /// /// When the user hovers multiple files at once, this event will be emitted for each file /// separately. HoveredFile(PathBuf), /// A file was hovered, but has exited the window. /// /// There will be a single `HoveredFileCancelled` event triggered even if multiple files were /// hovered. HoveredFileCancelled, /// The window received a unicode character. /// /// See also the [`Ime`](Self::Ime) event for more complex character sequences. ReceivedCharacter(char), /// The window gained or lost focus. /// /// The parameter is true if the window has gained focus, and false if it has lost focus. Focused(bool), /// An event from the keyboard has been received. KeyboardInput { device_id: DeviceId, event: KeyEvent, /// If `true`, the event was generated synthetically by winit /// in one of the following circumstances: /// /// * Synthetic key press events are generated for all keys pressed /// when a window gains focus. Likewise, synthetic key release events /// are generated for all keys pressed when a window goes out of focus. /// ***Currently, this is only functional on X11 and Windows*** /// /// Otherwise, this value is always `false`. is_synthetic: bool, }, /// The keyboard modifiers have changed. /// /// ## Platform-specific /// /// - **Web:** This API is currently unimplemented on the web. This isn't by design - it's an /// issue, and it should get fixed - but it's the current state of the API. ModifiersChanged(Modifiers), /// An event from an input method. /// /// **Note:** You have to explicitly enable this event using [`Window::set_ime_allowed`]. /// /// ## Platform-specific /// /// - **iOS / Android / Web:** Unsupported. Ime(Ime), /// The cursor has moved on the window. CursorMoved { device_id: DeviceId, /// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is /// limited by the display area and it may have been transformed by the OS to implement effects such as cursor /// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control. position: PhysicalPosition, }, /// The cursor has entered the window. CursorEntered { device_id: DeviceId, }, /// The cursor has left the window. CursorLeft { device_id: DeviceId, }, /// A mouse wheel movement or touchpad scroll occurred. MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase, }, /// An mouse button press has been received. MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton, }, /// Touchpad pressure event. /// /// At the moment, only supported on Apple forcetouch-capable macbooks. /// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad /// is being pressed) and stage (integer representing the click level). TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64, }, /// Motion on some analog axis. May report data redundant to other, more specific events. AxisMotion { device_id: DeviceId, axis: AxisId, value: f64, }, /// Touch event has been received Touch(Touch), /// The window's scale factor has changed. /// /// The following user actions can cause DPI changes: /// /// * Changing the display's resolution. /// * Changing the display's scale factor (e.g. in Control Panel on Windows). /// * Moving the window to a display with a different scale factor. /// /// After this event callback has been processed, the window will be resized to whatever value /// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested /// by the OS, but it can be changed to any value. /// /// For more information about DPI in general, see the [`dpi`](crate::dpi) module. ScaleFactorChanged { scale_factor: f64, }, /// The system window theme has changed. /// /// Applications might wish to react to this to change the theme of the content of the window /// when the system changes the window theme. /// /// ## Platform-specific /// /// At the moment this is only supported on Windows. ThemeChanged(Theme), /// The window has been occluded (completely hidden from view). /// /// This is different to window visibility as it depends on whether the window is closed, /// minimised, set invisible, or fully occluded by another window. /// /// Platform-specific behavior: /// - **iOS / Android / Web / Wayland / Windows:** Unsupported. Occluded(bool), PinchGesture { device_id: DeviceId, delta: f64, phase: TouchPhase, }, PanGesture { device_id: DeviceId, delta: PhysicalPosition, phase: TouchPhase, }, DoubleTapGesture { device_id: DeviceId, }, RotationGesture { device_id: DeviceId, delta: f32, phase: TouchPhase, }, /// The activation token was delivered back and now could be used. /// /// Delivered in response to [`request_activation_token`]. ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken, }, } impl WindowEvent { #[allow(clippy::too_many_lines)] // it's a match statement pub fn from_winit(event: winit::event::WindowEvent) -> (Self, Option) { ( match event { winit::event::WindowEvent::RedrawRequested => { let (guard, wait) = RedrawGuard::new(); return (Self::RedrawRequested(guard), Some(wait)) }, winit::event::WindowEvent::Resized(size) => Self::Resized(size), winit::event::WindowEvent::Moved(pos) => Self::Moved(pos), winit::event::WindowEvent::CloseRequested => Self::CloseRequested, winit::event::WindowEvent::Destroyed => Self::Destroyed, winit::event::WindowEvent::DroppedFile(path) => Self::DroppedFile(path), winit::event::WindowEvent::HoveredFile(path) => Self::HoveredFile(path), winit::event::WindowEvent::HoveredFileCancelled => Self::HoveredFileCancelled, winit::event::WindowEvent::Focused(focused) => Self::Focused(focused), winit::event::WindowEvent::KeyboardInput { device_id, event, is_synthetic, } => Self::KeyboardInput { device_id, event, is_synthetic, }, winit::event::WindowEvent::ModifiersChanged(modifiers) => { Self::ModifiersChanged(modifiers) } winit::event::WindowEvent::Ime(ime) => Self::Ime(ime), winit::event::WindowEvent::CursorMoved { device_id, position, .. } => Self::CursorMoved { device_id, position, }, winit::event::WindowEvent::CursorEntered { device_id } => { Self::CursorEntered { device_id } } winit::event::WindowEvent::CursorLeft { device_id } => Self::CursorLeft { device_id }, winit::event::WindowEvent::MouseWheel { device_id, delta, phase, .. } => Self::MouseWheel { device_id, delta, phase, }, winit::event::WindowEvent::MouseInput { device_id, state, button, .. } => Self::MouseInput { device_id, state, button, }, winit::event::WindowEvent::TouchpadPressure { device_id, pressure, stage, } => Self::TouchpadPressure { device_id, pressure, stage, }, winit::event::WindowEvent::AxisMotion { device_id, axis, value, } => Self::AxisMotion { device_id, axis, value, }, winit::event::WindowEvent::Touch(touch) => Self::Touch(touch), winit::event::WindowEvent::ScaleFactorChanged { scale_factor, .. // TODO use the suggested size from the writer } => { Self::ScaleFactorChanged { scale_factor, } }, winit::event::WindowEvent::ThemeChanged(theme) => Self::ThemeChanged(theme), winit::event::WindowEvent::Occluded(occluded) => Self::Occluded(occluded), winit::event::WindowEvent::PinchGesture { device_id, delta, phase, } => Self::PinchGesture { device_id, delta, phase, }, winit::event::WindowEvent::PanGesture { device_id, delta, phase } => { Self::PanGesture { device_id, delta, phase, } } winit::event::WindowEvent::DoubleTapGesture { device_id } => { Self::DoubleTapGesture { device_id } } winit::event::WindowEvent::RotationGesture { device_id, delta, phase, } => Self::RotationGesture { device_id, delta, phase, }, winit::event::WindowEvent::ActivationTokenDone { serial, token } => { Self::ActivationTokenDone { serial, token } } }, None, ) } }