1#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum UiEvent<'a> {
21 Click { x: f32, y: f32 },
22 Drag { x: f32, y: f32 },
23 Release { x: f32, y: f32 },
24 Move { x: f32, y: f32 },
25 Scroll { dy: f32 },
26 Key { key: i32, mods: i32 },
27 Char(char),
28 Open { w: f32, h: f32 },
29 Close,
30 Other(&'a str),
31}
32
33pub mod keys {
35 pub const SPACE: i32 = 32;
36 pub const A: i32 = 65;
37 pub const ESCAPE: i32 = 256;
38 pub const ENTER: i32 = 257;
39 pub const TAB: i32 = 258;
40 pub const BACKSPACE: i32 = 259;
41 pub const DELETE: i32 = 261;
42 pub const RIGHT: i32 = 262;
43 pub const LEFT: i32 = 263;
44 pub const DOWN: i32 = 264;
45 pub const UP: i32 = 265;
46 pub const PAGE_UP: i32 = 266;
47 pub const PAGE_DOWN: i32 = 267;
48 pub const HOME: i32 = 268;
49 pub const END: i32 = 269;
50 pub const KP_ENTER: i32 = 335;
51 pub const MOD_SHIFT: i32 = 0x1;
52 pub const MOD_CONTROL: i32 = 0x2;
53 pub const MOD_ALT: i32 = 0x4;
54 pub const MOD_SUPER: i32 = 0x8;
55}
56
57fn xy(rest: &str) -> Option<(f32, f32)> {
58 let mut it = rest.split(':');
59 let x = it.next()?.parse().ok()?;
60 let y = it.next()?.parse().ok()?;
61 it.next().is_none().then_some((x, y))
62}
63
64impl<'a> UiEvent<'a> {
65 pub fn parse(raw: &'a str) -> Self {
67 Self::try_parse(raw).unwrap_or(UiEvent::Other(raw))
68 }
69
70 fn try_parse(raw: &'a str) -> Option<Self> {
71 if raw == "close" {
72 return Some(UiEvent::Close);
73 }
74 let (head, rest) = raw.split_once(':')?;
75 Some(match head {
76 "click" => xy(rest).map(|(x, y)| UiEvent::Click { x, y })?,
77 "drag" => xy(rest).map(|(x, y)| UiEvent::Drag { x, y })?,
78 "release" => xy(rest).map(|(x, y)| UiEvent::Release { x, y })?,
79 "move" => xy(rest).map(|(x, y)| UiEvent::Move { x, y })?,
80 "open" => xy(rest).map(|(w, h)| UiEvent::Open { w, h })?,
81 "scroll" => UiEvent::Scroll {
82 dy: rest.parse().ok()?,
83 },
84 "key" => {
85 let mut it = rest.split(':');
86 let key = it.next()?.parse().ok()?;
87 let mods = match it.next() {
88 Some(m) => m.parse().ok()?,
89 None => 0,
90 };
91 UiEvent::Key { key, mods }
92 }
93 "char" => UiEvent::Char(rest.parse::<u32>().ok().and_then(char::from_u32)?),
94 _ => return None,
95 })
96 }
97
98 pub fn pos(&self) -> Option<(f32, f32)> {
100 match *self {
101 UiEvent::Click { x, y }
102 | UiEvent::Drag { x, y }
103 | UiEvent::Release { x, y }
104 | UiEvent::Move { x, y } => Some((x, y)),
105 _ => None,
106 }
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn parses_every_host_format() {
116 assert_eq!(
117 UiEvent::parse("click:1.5:2.0"),
118 UiEvent::Click { x: 1.5, y: 2.0 }
119 );
120 assert_eq!(UiEvent::parse("drag:3:4"), UiEvent::Drag { x: 3.0, y: 4.0 });
121 assert_eq!(
122 UiEvent::parse("release:3:4"),
123 UiEvent::Release { x: 3.0, y: 4.0 }
124 );
125 assert_eq!(
126 UiEvent::parse("move:3.0:4.5"),
127 UiEvent::Move { x: 3.0, y: 4.5 }
128 );
129 assert_eq!(UiEvent::parse("scroll:-2.00"), UiEvent::Scroll { dy: -2.0 });
130 assert_eq!(
131 UiEvent::parse("key:256:0"),
132 UiEvent::Key { key: 256, mods: 0 }
133 );
134 assert_eq!(UiEvent::parse("key:65"), UiEvent::Key { key: 65, mods: 0 });
135 assert_eq!(UiEvent::parse("char:1078"), UiEvent::Char('ж'));
136 assert_eq!(
137 UiEvent::parse("open:320:240"),
138 UiEvent::Open { w: 320.0, h: 240.0 }
139 );
140 assert_eq!(UiEvent::parse("close"), UiEvent::Close);
141 }
142
143 #[test]
144 fn garbage_is_other() {
145 for raw in [
146 "",
147 "click",
148 "click:x:1",
149 "click:1:2:3",
150 "scroll:",
151 "char:-1",
152 "done",
153 "key:a:0",
154 ] {
155 assert_eq!(UiEvent::parse(raw), UiEvent::Other(raw), "{raw}");
156 }
157 }
158}