use std::path::PathBuf;
use std::sync::{Mutex, Weak};
#[cfg(not(wasm_platform))]
use std::time::Instant;
use smol_str::SmolStr;
#[cfg(wasm_platform)]
use web_time::Instant;
use crate::error::ExternalError;
#[cfg(doc)]
use crate::window::Window;
use crate::{
dpi::{PhysicalPosition, PhysicalSize},
event_loop::AsyncRequestSerial,
keyboard::{self, ModifiersKeyState, ModifiersKeys, ModifiersState},
platform_impl,
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,
}
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),
}
}
}
#[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_platfrom)),
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,
},
TouchpadMagnify {
device_id: DeviceId,
delta: f64,
phase: TouchPhase,
},
SmartMagnify { device_id: DeviceId },
TouchpadRotate {
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::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::*, Ime::Enabled, 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(TouchpadMagnify {
device_id: did,
delta: 0.0,
phase: event::TouchPhase::Started,
});
with_window_event(SmartMagnify { device_id: did });
with_window_event(TouchpadRotate {
device_id: did,
delta: 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();
}
}