use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(web_platform))]
use std::time::Instant;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
#[cfg(web_platform)]
use web_time::Instant;
use crate::dpi::{PhysicalPosition, PhysicalSize};
use crate::error::ExternalError;
use crate::event_loop::AsyncRequestSerial;
use crate::keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState};
use crate::platform_impl;
#[cfg(doc)]
use crate::window::Window;
use crate::window::{ActivationToken, Theme, WindowId};
#[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> {
NewEvents(StartCause),
WindowEvent {
window_id: WindowId,
event: WindowEvent,
},
DeviceEvent {
device_id: DeviceId,
event: DeviceEvent,
},
UserEvent(T),
Suspended,
Resumed,
AboutToWait,
LoopExiting,
MemoryWarning,
Opened {
urls: Vec<String>,
},
}
impl<T> Event<T> {
#[allow(clippy::result_large_err)]
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
AboutToWait => Ok(AboutToWait),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
MemoryWarning => Ok(MemoryWarning),
Opened { urls } => Ok(Opened { urls }),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StartCause {
ResumeTimeReached { start: Instant, requested_resume: Instant },
WaitCancelled { start: Instant, requested_resume: Option<Instant> },
Poll,
Init,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WindowEvent {
#[cfg_attr(not(any(x11_platform, wayland_platform)), allow(rustdoc::broken_intra_doc_links))]
ActivationTokenDone { serial: AsyncRequestSerial, token: ActivationToken },
Resized(PhysicalSize<u32>),
Moved(PhysicalPosition<i32>),
CloseRequested,
Destroyed,
DroppedFile(PathBuf),
HoveredFile(PathBuf),
HoveredFileCancelled,
Focused(bool),
KeyboardInput {
device_id: DeviceId,
event: KeyEvent,
is_synthetic: bool,
},
ModifiersChanged(Modifiers),
Ime(Ime),
CursorMoved {
device_id: DeviceId,
position: PhysicalPosition<f64>,
},
CursorEntered { device_id: DeviceId },
CursorLeft { device_id: DeviceId },
MouseWheel { device_id: DeviceId, delta: MouseScrollDelta, phase: TouchPhase },
MouseInput { device_id: DeviceId, state: ElementState, button: MouseButton },
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,
},
TouchpadPressure { device_id: DeviceId, pressure: f32, stage: i64 },
AxisMotion { device_id: DeviceId, axis: AxisId, value: f64 },
Touch(Touch),
ScaleFactorChanged {
scale_factor: f64,
inner_size_writer: InnerSizeWriter,
},
ThemeChanged(Theme),
Occluded(bool),
RedrawRequested,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
impl DeviceId {
pub const unsafe fn dummy() -> Self {
#[allow(unused_unsafe)]
DeviceId(unsafe { platform_impl::DeviceId::dummy() })
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum DeviceEvent {
Added,
Removed,
MouseMotion {
delta: (f64, f64),
},
MouseWheel {
delta: MouseScrollDelta,
},
Motion {
axis: AxisId,
value: f64,
},
Button {
button: ButtonId,
state: ElementState,
},
Key(RawKeyEvent),
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RawKeyEvent {
pub physical_key: keyboard::PhysicalKey,
pub state: ElementState,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeyEvent {
pub physical_key: keyboard::PhysicalKey,
#[cfg_attr(
not(any(windows_platform, macos_platform, x11_platform, wayland_platform)),
allow(rustdoc::broken_intra_doc_links)
)]
pub logical_key: keyboard::Key,
pub text: Option<SmolStr>,
pub location: keyboard::KeyLocation,
pub state: ElementState,
pub repeat: bool,
pub(crate) platform_specific: platform_impl::KeyEventExtra,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Modifiers {
pub(crate) state: ModifiersState,
pub(crate) pressed_mods: ModifiersKeys,
}
impl Modifiers {
pub fn state(&self) -> ModifiersState {
self.state
}
pub fn lshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LSHIFT)
}
pub fn rshift_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RSHIFT)
}
pub fn lalt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LALT)
}
pub fn ralt_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RALT)
}
pub fn lcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LCONTROL)
}
pub fn rcontrol_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RCONTROL)
}
pub fn lsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::LSUPER)
}
pub fn rsuper_state(&self) -> ModifiersKeyState {
self.mod_state(ModifiersKeys::RSUPER)
}
fn mod_state(&self, modifier: ModifiersKeys) -> ModifiersKeyState {
if self.pressed_mods.contains(modifier) {
ModifiersKeyState::Pressed
} else {
ModifiersKeyState::Unknown
}
}
}
impl From<ModifiersState> for Modifiers {
fn from(value: ModifiersState) -> Self {
Self { state: value, pressed_mods: Default::default() }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Ime {
Enabled,
Preedit(String, Option<(usize, usize)>),
Commit(String),
Disabled,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase {
Started,
Moved,
Ended,
Cancelled,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Touch {
pub device_id: DeviceId,
pub phase: TouchPhase,
pub location: PhysicalPosition<f64>,
pub force: Option<Force>,
pub id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Force {
Calibrated {
force: f64,
max_possible_force: f64,
altitude_angle: Option<f64>,
},
Normalized(f64),
}
impl Force {
pub fn normalized(&self) -> f64 {
match self {
Force::Calibrated { force, max_possible_force, altitude_angle } => {
let force = match altitude_angle {
Some(altitude_angle) => force / altitude_angle.sin(),
None => *force,
};
force / max_possible_force
},
Force::Normalized(force) => *force,
}
}
}
pub type AxisId = u32;
pub type ButtonId = u32;
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ElementState {
Pressed,
Released,
}
impl ElementState {
pub fn is_pressed(self) -> bool {
self == ElementState::Pressed
}
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseButton {
Left,
Right,
Middle,
Back,
Forward,
Other(u16),
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MouseScrollDelta {
LineDelta(f32, f32),
PixelDelta(PhysicalPosition<f64>),
}
#[derive(Debug, Clone)]
pub struct InnerSizeWriter {
pub(crate) new_inner_size: Weak<Mutex<PhysicalSize<u32>>>,
}
impl InnerSizeWriter {
#[cfg(not(orbital_platform))]
pub(crate) fn new(new_inner_size: Weak<Mutex<PhysicalSize<u32>>>) -> Self {
Self { new_inner_size }
}
pub fn request_inner_size(
&mut self,
new_inner_size: PhysicalSize<u32>,
) -> Result<(), ExternalError> {
if let Some(inner) = self.new_inner_size.upgrade() {
*inner.lock().unwrap() = new_inner_size;
Ok(())
} else {
Err(ExternalError::Ignored)
}
}
}
impl PartialEq for InnerSizeWriter {
fn eq(&self, other: &Self) -> bool {
self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr()
}
}
#[cfg(test)]
mod tests {
use crate::dpi::PhysicalPosition;
use crate::event;
use std::collections::{BTreeSet, HashSet};
macro_rules! foreach_event {
($closure:expr) => {{
#[allow(unused_mut)]
let mut x = $closure;
let did = unsafe { event::DeviceId::dummy() };
#[allow(deprecated)]
{
use crate::event::Event::*;
use crate::event::Ime::Enabled;
use crate::event::WindowEvent::*;
use crate::window::WindowId;
let wid = unsafe { WindowId::dummy() };
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
x(LoopExiting);
x(Suspended);
x(Resumed);
let with_window_event = |wev| x(WindowEvent { window_id: wid, event: wev });
with_window_event(CloseRequested);
with_window_event(Destroyed);
with_window_event(Focused(true));
with_window_event(Moved((0, 0).into()));
with_window_event(Resized((0, 0).into()));
with_window_event(DroppedFile("x.txt".into()));
with_window_event(HoveredFile("x.txt".into()));
with_window_event(HoveredFileCancelled);
with_window_event(Ime(Enabled));
with_window_event(CursorMoved { device_id: did, position: (0, 0).into() });
with_window_event(ModifiersChanged(event::Modifiers::default()));
with_window_event(CursorEntered { device_id: did });
with_window_event(CursorLeft { device_id: did });
with_window_event(MouseWheel {
device_id: did,
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(MouseInput {
device_id: did,
state: event::ElementState::Pressed,
button: event::MouseButton::Other(0),
});
with_window_event(PinchGesture {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(DoubleTapGesture { device_id: did });
with_window_event(RotationGesture {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(PanGesture {
device_id: did,
delta: PhysicalPosition::<f32>::new(0.0, 0.0),
phase: event::TouchPhase::Started,
});
with_window_event(TouchpadPressure { device_id: did, pressure: 0.0, stage: 0 });
with_window_event(AxisMotion { device_id: did, axis: 0, value: 0.0 });
with_window_event(Touch(event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
location: (0.0, 0.0).into(),
id: 0,
force: Some(event::Force::Normalized(0.0)),
}));
with_window_event(ThemeChanged(crate::window::Theme::Light));
with_window_event(Occluded(true));
}
#[allow(deprecated)]
{
use event::DeviceEvent::*;
let with_device_event =
|dev_ev| x(event::Event::DeviceEvent { device_id: did, event: dev_ev });
with_device_event(Added);
with_device_event(Removed);
with_device_event(MouseMotion { delta: (0.0, 0.0).into() });
with_device_event(MouseWheel {
delta: event::MouseScrollDelta::LineDelta(0.0, 0.0),
});
with_device_event(Motion { axis: 0, value: 0.0 });
with_device_event(Button { button: 0, state: event::ElementState::Pressed });
}
}};
}
#[allow(clippy::redundant_clone)]
#[test]
fn test_event_clone() {
foreach_event!(|event: event::Event<()>| {
let event2 = event.clone();
assert_eq!(event, event2);
})
}
#[test]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
let event2 = event.map_nonuser_event::<()>();
if is_user {
assert_eq!(event2, Err(event::Event::UserEvent(())));
} else {
assert!(event2.is_ok());
}
})
}
#[test]
fn test_force_normalize() {
let force = event::Force::Normalized(0.0);
assert_eq!(force.normalized(), 0.0);
let force2 =
event::Force::Calibrated { force: 5.0, max_possible_force: 2.5, altitude_angle: None };
assert_eq!(force2.normalized(), 2.0);
let force3 = event::Force::Calibrated {
force: 5.0,
max_possible_force: 2.5,
altitude_angle: Some(std::f64::consts::PI / 2.0),
};
assert_eq!(force3.normalized(), 2.0);
}
#[allow(clippy::clone_on_copy)]
#[test]
fn ensure_attrs_do_not_panic() {
foreach_event!(|event: event::Event<()>| {
let _ = format!("{:?}", event);
});
let _ = event::StartCause::Init.clone();
let did = unsafe { crate::event::DeviceId::dummy() }.clone();
HashSet::new().insert(did);
let mut set = [did, did, did];
set.sort_unstable();
let mut set2 = BTreeSet::new();
set2.insert(did);
set2.insert(did);
HashSet::new().insert(event::TouchPhase::Started.clone());
HashSet::new().insert(event::MouseButton::Left.clone());
HashSet::new().insert(event::Ime::Enabled);
let _ = event::Touch {
device_id: did,
phase: event::TouchPhase::Started,
location: (0.0, 0.0).into(),
id: 0,
force: Some(event::Force::Normalized(0.0)),
}
.clone();
let _ =
event::Force::Calibrated { force: 0.0, max_possible_force: 0.0, altitude_angle: None }
.clone();
}
}