mirror of
https://github.com/danbulant/appit
synced 2026-06-14 03:41:12 +00:00
421 lines
13 KiB
Rust
421 lines
13 KiB
Rust
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<dyn FnOnce(OpenedWindow) + Send + 'static>;
|
|
|
|
pub trait ApplicationSealed<AppMessage>
|
|
where
|
|
AppMessage: Message,
|
|
{
|
|
fn open(
|
|
&mut self,
|
|
window: WindowAttributes,
|
|
sender: Arc<mpsc::SyncSender<WindowMessage<AppMessage::Window>>>,
|
|
spawner: WindowSpawner,
|
|
) -> Result<Option<OpenedWindow>, OsError>;
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct OpenedWindow(pub(crate) Arc<Mutex<Option<Arc<winit::window::Window>>>>);
|
|
|
|
impl OpenedWindow {
|
|
pub fn winit(&self) -> Option<Arc<winit::window::Window>> {
|
|
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<AppMessage>
|
|
where
|
|
AppMessage: Message,
|
|
{
|
|
OpenWindow {
|
|
attrs: WindowAttributes,
|
|
sender: Arc<mpsc::SyncSender<WindowMessage<AppMessage::Window>>>,
|
|
open_sender: mpsc::SyncSender<Result<OpenedWindow, OsError>>,
|
|
spawner: WindowSpawner,
|
|
},
|
|
CloseWindow(WindowId),
|
|
WindowPanic(WindowId),
|
|
User {
|
|
message: AppMessage,
|
|
response_sender: mpsc::SyncSender<AppMessage::Response>,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum WindowMessage<User> {
|
|
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<u32>),
|
|
|
|
/// The position of the window has changed. Contains the window's new position.
|
|
///
|
|
/// ## Platform-specific
|
|
///
|
|
/// - **iOS / Android / Web / Wayland:** Unsupported.
|
|
Moved(PhysicalPosition<i32>),
|
|
|
|
/// 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<f64>,
|
|
},
|
|
|
|
/// 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<f32>,
|
|
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<WaitForRedraw>) {
|
|
(
|
|
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 <https://github.com/rust-windowing/winit/issues/3080>
|
|
} => {
|
|
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,
|
|
)
|
|
}
|
|
}
|