appit/src/private.rs
2024-09-06 11:31:23 -07:00

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,
)
}
}