use cast::CastApprox;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{EventCx, IsUsed, TimerHandle, Unused, Used};
#[allow(unused)] use super::{EventState, GrabMode};
use super::{Key, KeyEvent, NamedKey, PhysicalKey, Press, PressStart};
use crate::geom::{Affine, Offset, Vec2};
#[allow(unused)] use crate::{Events, window::Popup};
use crate::{Id, dir::Direction, window::WindowId};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Ime<'a> {
Enabled,
Disabled,
Preedit {
text: &'a str,
cursor: Option<(usize, usize)>,
},
Commit { text: &'a str },
DeleteSurrounding {
before_bytes: usize,
after_bytes: usize,
},
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum Event<'a> {
Command(Command, Option<PhysicalKey>),
Key(&'a KeyEvent, bool),
Ime(Ime<'a>),
Scroll(ScrollDelta),
Pan(Affine),
PointerMove { press: Press },
PressStart(PressStart),
PressMove { press: Press, delta: Vec2 },
PressEnd { press: Press, success: bool },
Timer(TimerHandle),
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
PopupClosed(WindowId),
NavFocus(FocusSource),
LostNavFocus,
SelFocus(FocusSource),
LostSelFocus,
KeyFocus,
LostKeyFocus,
MouseOver(bool),
}
impl<'a> std::ops::Add<Offset> for Event<'a> {
type Output = Self;
#[inline]
fn add(mut self, offset: Offset) -> Self {
self += offset;
self
}
}
impl<'a> std::ops::AddAssign<Offset> for Event<'a> {
fn add_assign(&mut self, offset: Offset) {
match self {
Event::PointerMove { press } => {
press.coord += offset;
}
Event::PressStart(press) => {
*press += offset;
}
Event::PressMove { press, .. } => {
press.coord += offset;
}
Event::PressEnd { press, .. } => {
press.coord += offset;
}
_ => (),
}
}
}
impl<'a> Event<'a> {
pub fn on_click<F: FnOnce(&mut EventCx)>(self, cx: &mut EventCx, id: Id, f: F) -> IsUsed {
match self {
Event::Command(cmd, code) if cmd.is_activate() => {
cx.depress_with_key(id, code);
f(cx);
Used
}
Event::PressStart(press) if press.is_primary() => press.grab_click(id).complete(cx),
Event::PressEnd { press, success, .. } => {
if success && id == press.id {
f(cx);
}
Used
}
_ => Unused,
}
}
pub fn pass_when_disabled(&self) -> bool {
use Event::*;
match self {
Command(_, _) => false,
Key(_, _) | Scroll(_) => false,
PointerMove { .. } | PressStart(_) => false,
Pan { .. } | PressMove { .. } | PressEnd { .. } => true,
Timer(_) | PopupClosed(_) => true,
NavFocus { .. } | SelFocus(_) | KeyFocus | MouseOver(true) => false,
LostNavFocus | LostKeyFocus | LostSelFocus | MouseOver(false) => true,
Ime(super::Ime::Disabled) => true,
Ime(_) => false,
}
}
pub fn is_reusable(&self) -> bool {
use Event::*;
match self {
Command(_, code) => code.is_some(),
Scroll(_) | Pan { .. } => true,
PointerMove { .. } | PressStart(_) => true,
Key(_, _) | Ime(_) => false,
PressMove { .. } | PressEnd { .. } => false,
Timer(_) => false,
PopupClosed(_) => false,
NavFocus { .. } | LostNavFocus => false,
SelFocus(_) | LostSelFocus => false,
KeyFocus | LostKeyFocus => false,
MouseOver(_) => false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum Command {
Escape,
Activate,
Enter,
Space,
Tab,
ViewUp,
ViewDown,
Left,
Right,
Up,
Down,
WordLeft,
WordRight,
Home,
End,
DocHome,
DocEnd,
PageUp,
PageDown,
Snapshot,
ScrollLock,
Pause,
Insert,
Delete,
DelBack,
DelWord,
DelWordBack,
Deselect,
SelectAll,
Find,
FindReplace,
FindNext,
FindPrevious,
Bold,
Italic,
Underline,
Link,
Cut,
Copy,
Paste,
Undo,
Redo,
New,
Open,
Save,
Print,
NavNext,
NavPrevious,
NavParent,
NavDown,
TabNew,
TabNext,
TabPrevious,
Help,
Rename,
Refresh,
Debug,
SpellCheck,
ContextMenu,
Menu,
Fullscreen,
Close,
Exit,
}
impl Command {
pub fn new(key: &Key) -> Option<Self> {
match key {
Key::Named(named) => Some(match named {
NamedKey::ScrollLock => Command::ScrollLock,
NamedKey::Enter => Command::Enter,
NamedKey::Tab => Command::Tab,
NamedKey::ArrowDown => Command::Down,
NamedKey::ArrowLeft => Command::Left,
NamedKey::ArrowRight => Command::Right,
NamedKey::ArrowUp => Command::Up,
NamedKey::End => Command::End,
NamedKey::Home => Command::Home,
NamedKey::PageDown => Command::PageDown,
NamedKey::PageUp => Command::PageUp,
NamedKey::Backspace => Command::DelBack,
NamedKey::Clear => Command::Deselect,
NamedKey::Copy => Command::Copy,
NamedKey::Cut => Command::Cut,
NamedKey::Delete => Command::Delete,
NamedKey::Insert => Command::Insert,
NamedKey::Paste => Command::Paste,
NamedKey::Redo | NamedKey::Again => Command::Redo,
NamedKey::Undo => Command::Undo,
NamedKey::ContextMenu => Command::ContextMenu,
NamedKey::Escape => Command::Escape,
NamedKey::Execute => Command::Activate,
NamedKey::Find => Command::Find,
NamedKey::Help => Command::Help,
NamedKey::Pause => Command::Pause,
NamedKey::Select => Command::SelectAll,
NamedKey::PrintScreen => Command::Snapshot,
NamedKey::New => Command::New,
NamedKey::Open => Command::Open,
NamedKey::Print => Command::Print,
NamedKey::Save => Command::Save,
NamedKey::SpellCheck => Command::SpellCheck,
NamedKey::BrowserBack | NamedKey::GoBack => Command::NavPrevious,
NamedKey::BrowserForward => Command::NavNext,
NamedKey::BrowserRefresh => Command::Refresh,
NamedKey::Exit => Command::Exit,
_ => return None,
}),
Key::Character(s) if s == " " => Some(Command::Space),
_ => None,
}
}
pub fn is_activate(self) -> bool {
use Command::*;
matches!(self, Activate | Enter | Space)
}
pub fn suitable_for_sel_focus(self) -> bool {
use Command::*;
matches!(self, Escape | Cut | Copy | Deselect)
}
pub fn as_direction(self) -> Option<Direction> {
match self {
Command::Left => Some(Direction::Left),
Command::Right => Some(Direction::Right),
Command::Up => Some(Direction::Up),
Command::Down => Some(Direction::Down),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FocusSource {
Pointer,
Key,
Synthetic,
}
impl FocusSource {
pub fn key_or_synthetic(self) -> bool {
match self {
FocusSource::Pointer => false,
FocusSource::Key | FocusSource::Synthetic => true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ScrollDelta {
Lines(f32, f32),
PixelDelta(Vec2),
}
impl ScrollDelta {
pub fn is_vertical(self) -> bool {
match self {
ScrollDelta::Lines(0.0, _) => true,
ScrollDelta::PixelDelta(Vec2(0.0, _)) => true,
_ => false,
}
}
pub fn is_horizontal(self) -> bool {
match self {
ScrollDelta::Lines(_, 0.0) => true,
ScrollDelta::PixelDelta(Vec2(_, 0.0)) => true,
_ => false,
}
}
pub fn as_offset(self, cx: &EventState) -> Vec2 {
match self {
ScrollDelta::Lines(x, y) => cx.config().event().scroll_distance((x, y)),
ScrollDelta::PixelDelta(d) => d,
}
}
pub fn as_factor(self, _: &EventState) -> f32 {
match self {
ScrollDelta::Lines(_, y) => -0.5 * y,
ScrollDelta::PixelDelta(Vec2(_, y)) => -0.01 * y,
}
}
pub fn as_offset_or_factor(self, cx: &EventState) -> Result<Vec2, f32> {
if cx.modifiers().control_key() {
Err(self.as_factor(cx))
} else {
Ok(self.as_offset(cx))
}
}
pub fn as_factor_or_offset(self, cx: &EventState) -> Result<f32, Vec2> {
if matches!(self, ScrollDelta::Lines(_, _)) || cx.modifiers().control_key() {
Ok(self.as_factor(cx))
} else {
Err(self.as_offset(cx))
}
}
pub fn as_wheel_action(self, cx: &EventState) -> Option<i32> {
match self {
ScrollDelta::Lines(_, y) if cx.config().event().mouse_wheel_actions() => {
y.try_cast_approx().ok()
}
_ => None,
}
}
}
#[cfg(test)]
#[test]
fn sizes() {
use core::mem::size_of;
assert_eq!(size_of::<Command>(), 1);
assert_eq!(size_of::<PhysicalKey>(), 8);
assert_eq!(size_of::<KeyEvent>(), 128);
assert_eq!(size_of::<ScrollDelta>(), 12);
assert_eq!(size_of::<Affine>(), 32);
assert_eq!(size_of::<Press>(), 24);
assert_eq!(size_of::<TimerHandle>(), 8);
assert_eq!(size_of::<WindowId>(), 4);
assert_eq!(size_of::<FocusSource>(), 1);
assert_eq!(size_of::<Event>(), 40);
}