1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub enum InputPrimitive {
9 Rotate {
10 delta: f64,
11 },
12 Press,
13 Release,
14 LongPress,
15 Swipe {
16 direction: Direction,
17 },
18 Slide {
19 value: f64,
20 },
21 Hover {
22 proximity: f64,
23 },
24 Touch {
25 area: TouchArea,
26 },
27 LongTouch {
28 area: TouchArea,
29 },
30 KeyPress {
31 key: u32,
32 },
33 Button {
36 id: u8,
37 },
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum Direction {
43 Up,
44 Down,
45 Left,
46 Right,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum TouchArea {
52 Top,
53 Bottom,
54 Left,
55 Right,
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[serde(tag = "type", rename_all = "snake_case")]
62pub enum Intent {
63 Play,
64 Pause,
65 PlayPause,
66 Stop,
67 Next,
68 Previous,
69 VolumeChange { delta: f64 },
70 VolumeSet { value: f64 },
71 Mute,
72 Unmute,
73 SeekRelative { seconds: f64 },
74 SeekAbsolute { seconds: f64 },
75 BrightnessChange { delta: f64 },
76 BrightnessSet { value: f64 },
77 ColorTemperatureChange { delta: f64 },
78 PowerToggle,
79 PowerOn,
80 PowerOff,
81}
82
83impl InputPrimitive {
84 pub fn matches_route(&self, route_input: &str) -> bool {
87 match (self, route_input) {
88 (InputPrimitive::Rotate { .. }, "rotate") => true,
89 (InputPrimitive::Press, "press") => true,
90 (InputPrimitive::Release, "release") => true,
91 (InputPrimitive::LongPress, "long_press") => true,
92 (InputPrimitive::Slide { .. }, "slide") => true,
93 (InputPrimitive::Hover { .. }, "hover") => true,
94 (InputPrimitive::Swipe { direction }, s) => matches!(
95 (direction, s),
96 (Direction::Up, "swipe_up")
97 | (Direction::Down, "swipe_down")
98 | (Direction::Left, "swipe_left")
99 | (Direction::Right, "swipe_right")
100 ),
101 (InputPrimitive::Touch { area }, s) => matches!(
102 (area, s),
103 (TouchArea::Top, "touch_top")
104 | (TouchArea::Bottom, "touch_bottom")
105 | (TouchArea::Left, "touch_left")
106 | (TouchArea::Right, "touch_right")
107 ),
108 (InputPrimitive::LongTouch { area }, s) => matches!(
109 (area, s),
110 (TouchArea::Top, "long_touch_top")
111 | (TouchArea::Bottom, "long_touch_bottom")
112 | (TouchArea::Left, "long_touch_left")
113 | (TouchArea::Right, "long_touch_right")
114 ),
115 (InputPrimitive::Button { id }, s) => s
116 .strip_prefix("button_")
117 .and_then(|n| n.parse::<u8>().ok())
118 .is_some_and(|parsed| parsed == *id),
119 _ => false,
120 }
121 }
122
123 pub fn continuous_value(&self) -> Option<f64> {
125 match self {
126 InputPrimitive::Rotate { delta } | InputPrimitive::Slide { value: delta } => {
127 Some(*delta)
128 }
129 _ => None,
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn rotate_matches_rotate_route() {
140 let r = InputPrimitive::Rotate { delta: 0.03 };
141 assert!(r.matches_route("rotate"));
142 assert!(!r.matches_route("press"));
143 }
144
145 #[test]
146 fn swipe_direction_matters() {
147 let s = InputPrimitive::Swipe {
148 direction: Direction::Right,
149 };
150 assert!(s.matches_route("swipe_right"));
151 assert!(!s.matches_route("swipe_left"));
152 assert!(!s.matches_route("swipe_up"));
153 }
154
155 #[test]
156 fn button_id_matches_numbered_route() {
157 let b = InputPrimitive::Button { id: 1 };
158 assert!(b.matches_route("button_1"));
159 assert!(!b.matches_route("button_2"));
160 assert!(!b.matches_route("button_"));
161 assert!(!b.matches_route("press"));
162 }
163
164 #[test]
165 fn button_route_rejects_non_numeric_suffix() {
166 let b = InputPrimitive::Button { id: 3 };
167 assert!(!b.matches_route("button_x"));
168 assert!(!b.matches_route("button_3a"));
169 assert!(b.matches_route("button_3"));
170 }
171}