1use sim_kernel::{Expr, Symbol};
10use sim_value::build;
11
12use crate::gesture::Hit;
13use crate::model::{Origin, intent};
14
15pub trait WristInputCapabilities {
20 fn has_input(&self, name: &str) -> bool;
22}
23
24impl WristInputCapabilities for [Symbol] {
25 fn has_input(&self, name: &str) -> bool {
26 self.iter()
27 .any(|symbol| symbol.namespace.is_none() && symbol.name.as_ref() == name)
28 }
29}
30
31impl WristInputCapabilities for Vec<Symbol> {
32 fn has_input(&self, name: &str) -> bool {
33 self.as_slice().has_input(name)
34 }
35}
36
37impl<T: WristInputCapabilities + ?Sized> WristInputCapabilities for &T {
38 fn has_input(&self, name: &str) -> bool {
39 (**self).has_input(name)
40 }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct WristInputTiming {
46 pub debounce_ms: u64,
48 pub tap_sequence_ms: u64,
50 pub long_press_ms: u64,
52 pub raise_stable_ms: u64,
54}
55
56impl Default for WristInputTiming {
57 fn default() -> Self {
58 Self {
59 debounce_ms: 80,
60 tap_sequence_ms: 450,
61 long_press_ms: 650,
62 raise_stable_ms: 180,
63 }
64 }
65}
66
67#[derive(Clone, Debug, PartialEq)]
69pub enum WristRawInput {
70 Button {
72 id: Symbol,
74 held_ms: u64,
76 at_ms: u64,
78 },
79 Crown {
81 delta: i32,
83 press: bool,
85 target: Expr,
87 at_ms: u64,
89 },
90 Tap {
92 count: u8,
94 target: Expr,
96 span_ms: u64,
98 at_ms: u64,
100 },
101 Raise {
103 target: Expr,
105 stable_ms: u64,
107 at_ms: u64,
109 },
110 Touch {
112 hit: Hit,
114 at_ms: u64,
116 },
117}
118
119#[derive(Clone, Debug, Default)]
121pub struct WristIntentReducer {
122 timing: WristInputTiming,
123 last_accepted_ms: Option<u64>,
124}
125
126impl WristIntentReducer {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn with_timing(timing: WristInputTiming) -> Self {
134 Self {
135 timing,
136 last_accepted_ms: None,
137 }
138 }
139
140 pub fn reduce<C: WristInputCapabilities + ?Sized>(
143 &mut self,
144 origin: Origin,
145 raw: WristRawInput,
146 profile: &C,
147 ) -> Option<Expr> {
148 let at_ms = raw.at_ms();
149 let intent = match raw {
150 WristRawInput::Button { id, held_ms, .. } if profile.has_input("button") => {
151 if held_ms >= self.timing.long_press_ms {
152 Some(intent("dismiss", origin, vec![]))
153 } else {
154 Some(invoke(
155 origin,
156 Expr::Symbol(id.clone()),
157 "button-press",
158 vec![Expr::Symbol(id)],
159 ))
160 }
161 }
162 WristRawInput::Crown {
163 delta,
164 press,
165 target,
166 ..
167 } if has_any_input(profile, &["crown", "rotary"]) => {
168 if press {
169 Some(invoke(origin, target, "crown-press", vec![]))
170 } else if delta != 0 {
171 Some(move_selection(origin, target, delta))
172 } else {
173 None
174 }
175 }
176 WristRawInput::Tap {
177 count,
178 target,
179 span_ms,
180 ..
181 } if profile.has_input("tap") && span_ms <= self.timing.tap_sequence_ms => {
182 tap_pattern(origin, count, target)
183 }
184 WristRawInput::Raise {
185 target, stable_ms, ..
186 } if profile.has_input("raise") && stable_ms >= self.timing.raise_stable_ms => {
187 Some(select_one(origin, target))
188 }
189 WristRawInput::Touch { hit, .. } if profile.has_input("touch") => hit
190 .target
191 .clone()
192 .map(|target| move_selection(origin, target, 1)),
193 _ => None,
194 }?;
195 self.accepts(at_ms).then_some(intent)
196 }
197
198 fn accepts(&mut self, at_ms: u64) -> bool {
199 if let Some(last) = self.last_accepted_ms
200 && at_ms.saturating_sub(last) < self.timing.debounce_ms
201 {
202 return false;
203 }
204 self.last_accepted_ms = Some(at_ms);
205 true
206 }
207}
208
209impl WristRawInput {
210 fn at_ms(&self) -> u64 {
211 match self {
212 WristRawInput::Button { at_ms, .. }
213 | WristRawInput::Crown { at_ms, .. }
214 | WristRawInput::Tap { at_ms, .. }
215 | WristRawInput::Raise { at_ms, .. }
216 | WristRawInput::Touch { at_ms, .. } => *at_ms,
217 }
218 }
219}
220
221fn has_any_input<C: WristInputCapabilities + ?Sized>(profile: &C, names: &[&str]) -> bool {
222 names.iter().any(|name| profile.has_input(name))
223}
224
225fn tap_pattern(origin: Origin, count: u8, target: Expr) -> Option<Expr> {
226 match count {
227 1 => Some(select_one(origin, target)),
228 2 => Some(invoke(origin, target, "double-tap", vec![])),
229 3 => Some(intent(
230 "edit",
231 origin,
232 vec![("target", target), ("path", Expr::List(Vec::new()))],
233 )),
234 _ => None,
235 }
236}
237
238fn select_one(origin: Origin, target: Expr) -> Expr {
239 intent(
240 "select",
241 origin,
242 vec![("targets", Expr::List(vec![target]))],
243 )
244}
245
246fn invoke(origin: Origin, target: Expr, op: &str, args: Vec<Expr>) -> Expr {
247 intent(
248 "invoke",
249 origin,
250 vec![
251 ("target", target),
252 ("op", Expr::Symbol(Symbol::qualified("watch/input", op))),
253 ("args", Expr::List(args)),
254 ],
255 )
256}
257
258fn move_selection(origin: Origin, target: Expr, delta: i32) -> Expr {
259 intent(
260 "move",
261 origin,
262 vec![
263 ("node", target),
264 (
265 "at",
266 build::map(vec![("selection-delta", build::float(f64::from(delta)))]),
267 ),
268 ],
269 )
270}