use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub enum AppEvent {
Resized { width: u32, height: u32 },
CloseRequested,
Focused(bool),
Key(KeyEvent),
Mouse(MouseEvent),
Ime(ImeEvent),
RedrawRequested,
}
#[derive(Debug, Clone, Default)]
pub struct EventResponse {
pub consumed: bool,
pub exit: bool,
pub set_title: Option<String>,
pub toggle_fullscreen: bool,
pub set_cursor_visible: Option<bool>,
}
impl EventResponse {
#[must_use]
pub fn consumed() -> Self {
Self {
consumed: true,
..Default::default()
}
}
#[must_use]
pub fn ignored() -> Self {
Self::default()
}
}
impl From<bool> for EventResponse {
fn from(consumed: bool) -> Self {
Self {
consumed,
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub enum ImeEvent {
Enabled,
Preedit(String, Option<(usize, usize)>),
Commit(String),
Disabled,
}
#[derive(Debug, Clone)]
pub struct KeyEvent {
pub key: KeyCode,
pub pressed: bool,
pub modifiers: Modifiers,
pub text: Option<String>,
}
#[derive(Debug, Clone)]
pub enum MouseEvent {
Moved {
x: f64,
y: f64,
},
Button {
button: MouseButton,
pressed: bool,
x: f64,
y: f64,
modifiers: Modifiers,
},
Scroll {
dx: f64,
dy: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MouseButton {
Left,
Right,
Middle,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Modifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
pub meta: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KeyCode {
Char(char),
Enter,
Escape,
Backspace,
Delete,
Tab,
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
F(u8),
Space,
Unknown,
}
impl KeyCode {
#[must_use]
pub fn from_winit(key: &winit::keyboard::Key) -> Self {
use winit::keyboard::{Key as WKey, NamedKey};
match key {
WKey::Named(named) => match named {
NamedKey::Enter => Self::Enter,
NamedKey::Escape => Self::Escape,
NamedKey::Backspace => Self::Backspace,
NamedKey::Delete => Self::Delete,
NamedKey::Tab => Self::Tab,
NamedKey::ArrowUp => Self::Up,
NamedKey::ArrowDown => Self::Down,
NamedKey::ArrowLeft => Self::Left,
NamedKey::ArrowRight => Self::Right,
NamedKey::Home => Self::Home,
NamedKey::End => Self::End,
NamedKey::PageUp => Self::PageUp,
NamedKey::PageDown => Self::PageDown,
NamedKey::Space => Self::Space,
NamedKey::F1 => Self::F(1),
NamedKey::F2 => Self::F(2),
NamedKey::F3 => Self::F(3),
NamedKey::F4 => Self::F(4),
NamedKey::F5 => Self::F(5),
NamedKey::F6 => Self::F(6),
NamedKey::F7 => Self::F(7),
NamedKey::F8 => Self::F(8),
NamedKey::F9 => Self::F(9),
NamedKey::F10 => Self::F(10),
NamedKey::F11 => Self::F(11),
NamedKey::F12 => Self::F(12),
_ => Self::Unknown,
},
WKey::Character(c) => {
let mut chars = c.chars();
match (chars.next(), chars.next()) {
(Some(ch), None) => Self::Char(ch),
_ => Self::Unknown,
}
}
_ => Self::Unknown,
}
}
}
impl Modifiers {
#[must_use]
pub fn from_winit(state: &winit::keyboard::ModifiersState) -> Self {
Self {
shift: state.shift_key(),
ctrl: state.control_key(),
alt: state.alt_key(),
meta: state.super_key(),
}
}
#[must_use]
pub fn any(&self) -> bool {
self.shift || self.ctrl || self.alt || self.meta
}
}
#[derive(Debug, Clone)]
pub enum InputEvent {
Key(KeyEvent),
Mouse(MouseEvent),
}
#[cfg(test)]
mod tests {
use super::*;
use winit::keyboard::{Key as WKey, NamedKey};
#[test]
fn modifiers_any() {
let none = Modifiers::default();
assert!(!none.any());
let shift = Modifiers {
shift: true,
..Default::default()
};
assert!(shift.any());
}
#[test]
fn modifiers_any_covers_every_field() {
for modifier in [
Modifiers { shift: true, ..Default::default() },
Modifiers { ctrl: true, ..Default::default() },
Modifiers { alt: true, ..Default::default() },
Modifiers { meta: true, ..Default::default() },
] {
assert!(modifier.any(), "any() false for {modifier:?}");
}
}
#[test]
fn modifiers_any_with_combinations() {
let all = Modifiers { shift: true, ctrl: true, alt: true, meta: true };
assert!(all.any());
let ctrl_alt = Modifiers { ctrl: true, alt: true, ..Default::default() };
assert!(ctrl_alt.any());
}
#[test]
fn modifiers_serde_roundtrip() {
let original = Modifiers { shift: true, ctrl: false, alt: true, meta: false };
let json = serde_json::to_string(&original).unwrap();
assert!(json.contains("\"shift\":true"));
assert!(json.contains("\"alt\":true"));
let back: Modifiers = serde_json::from_str(&json).unwrap();
assert_eq!(back.shift, original.shift);
assert_eq!(back.ctrl, original.ctrl);
assert_eq!(back.alt, original.alt);
assert_eq!(back.meta, original.meta);
}
#[test]
fn key_code_char() {
let k = KeyCode::Char('a');
assert_eq!(k, KeyCode::Char('a'));
assert_ne!(k, KeyCode::Char('b'));
}
#[test]
fn key_code_from_winit_named_keys() {
let cases: &[(WKey, KeyCode)] = &[
(WKey::Named(NamedKey::Enter), KeyCode::Enter),
(WKey::Named(NamedKey::Escape), KeyCode::Escape),
(WKey::Named(NamedKey::Backspace), KeyCode::Backspace),
(WKey::Named(NamedKey::Delete), KeyCode::Delete),
(WKey::Named(NamedKey::Tab), KeyCode::Tab),
(WKey::Named(NamedKey::ArrowUp), KeyCode::Up),
(WKey::Named(NamedKey::ArrowDown), KeyCode::Down),
(WKey::Named(NamedKey::ArrowLeft), KeyCode::Left),
(WKey::Named(NamedKey::ArrowRight), KeyCode::Right),
(WKey::Named(NamedKey::Home), KeyCode::Home),
(WKey::Named(NamedKey::End), KeyCode::End),
(WKey::Named(NamedKey::PageUp), KeyCode::PageUp),
(WKey::Named(NamedKey::PageDown), KeyCode::PageDown),
(WKey::Named(NamedKey::Space), KeyCode::Space),
];
for (input, expected) in cases {
assert_eq!(KeyCode::from_winit(input), *expected, "for {input:?}");
}
}
#[test]
fn key_code_from_winit_function_keys() {
let fkeys = [
(NamedKey::F1, 1u8), (NamedKey::F2, 2), (NamedKey::F3, 3),
(NamedKey::F4, 4), (NamedKey::F5, 5), (NamedKey::F6, 6),
(NamedKey::F7, 7), (NamedKey::F8, 8), (NamedKey::F9, 9),
(NamedKey::F10, 10), (NamedKey::F11, 11), (NamedKey::F12, 12),
];
for (named, n) in fkeys {
let got = KeyCode::from_winit(&WKey::Named(named));
assert_eq!(got, KeyCode::F(n), "F-key mapped wrong: {named:?}");
}
}
#[test]
fn key_code_from_winit_character_single() {
let k: winit::keyboard::SmolStr = "q".into();
let got = KeyCode::from_winit(&WKey::Character(k));
assert_eq!(got, KeyCode::Char('q'));
}
#[test]
fn key_code_from_winit_character_multi_char() {
let k: winit::keyboard::SmolStr = "ab".into();
let got = KeyCode::from_winit(&WKey::Character(k));
assert_eq!(got, KeyCode::Unknown);
}
#[test]
fn key_code_from_winit_character_empty() {
let k: winit::keyboard::SmolStr = "".into();
let got = KeyCode::from_winit(&WKey::Character(k));
assert_eq!(got, KeyCode::Unknown);
}
#[test]
fn key_code_from_winit_unmapped_named_is_unknown() {
let got = KeyCode::from_winit(&WKey::Named(NamedKey::CapsLock));
assert_eq!(got, KeyCode::Unknown);
}
#[test]
fn event_response_consumed_sets_only_consumed() {
let r = EventResponse::consumed();
assert!(r.consumed);
assert!(!r.exit);
assert!(r.set_title.is_none());
assert!(!r.toggle_fullscreen);
assert!(r.set_cursor_visible.is_none());
}
#[test]
fn event_response_ignored_is_default() {
let r = EventResponse::ignored();
assert!(!r.consumed);
assert!(!r.exit);
assert!(r.set_title.is_none());
assert!(!r.toggle_fullscreen);
assert!(r.set_cursor_visible.is_none());
}
#[test]
fn event_response_from_bool() {
let consumed: EventResponse = true.into();
assert!(consumed.consumed);
assert!(!consumed.exit);
let ignored: EventResponse = false.into();
assert!(!ignored.consumed);
}
#[test]
fn event_response_default_matches_ignored() {
let d = EventResponse::default();
let i = EventResponse::ignored();
assert_eq!(d.consumed, i.consumed);
assert_eq!(d.exit, i.exit);
assert_eq!(d.set_title, i.set_title);
assert_eq!(d.toggle_fullscreen, i.toggle_fullscreen);
assert_eq!(d.set_cursor_visible, i.set_cursor_visible);
}
#[test]
fn modifiers_default_is_all_false() {
let m = Modifiers::default();
assert!(!m.shift);
assert!(!m.ctrl);
assert!(!m.alt);
assert!(!m.meta);
}
}