1use sim_kernel::Expr;
12
13use crate::model::{IntentError, Origin, intent};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum HitRole {
18 Blank,
20 Node,
22 Port,
24 Button,
26 Field,
28 Edge,
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Hit {
36 pub role: HitRole,
38 pub target: Option<Expr>,
40 pub detail: Vec<(String, Expr)>,
42}
43
44impl Hit {
45 pub fn blank() -> Self {
47 Self {
48 role: HitRole::Blank,
49 target: None,
50 detail: Vec::new(),
51 }
52 }
53
54 pub fn on(role: HitRole, target: Expr) -> Self {
56 Self {
57 role,
58 target: Some(target),
59 detail: Vec::new(),
60 }
61 }
62
63 pub fn with(mut self, key: &str, value: Expr) -> Self {
65 self.detail.push((key.to_owned(), value));
66 self
67 }
68
69 fn detail(&self, key: &str) -> Option<&Expr> {
70 self.detail
71 .iter()
72 .find_map(|(name, value)| (name == key).then_some(value))
73 }
74}
75
76#[derive(Clone, Debug, PartialEq)]
78pub struct PointerEvent {
79 pub phase: PointerPhase,
81 pub x: f64,
83 pub y: f64,
85 pub hit: Hit,
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub enum PointerPhase {
92 Down,
94 Move,
96 Up,
98}
99
100#[derive(Clone, Debug, PartialEq)]
103pub enum RawGesture {
104 Tap {
106 hit: Hit,
108 },
109 Drag {
111 from: Hit,
113 to: Hit,
115 at: (f64, f64),
117 },
118 Key {
120 command: String,
122 hit: Hit,
124 },
125}
126
127#[derive(Debug, Default)]
130pub struct GestureRecognizer {
131 down: Option<(Hit, f64, f64)>,
132 moved: bool,
133 last: (f64, f64),
134}
135
136impl GestureRecognizer {
137 pub fn new() -> Self {
139 Self::default()
140 }
141
142 pub fn pointer(&mut self, event: PointerEvent) -> Option<RawGesture> {
144 match event.phase {
145 PointerPhase::Down => {
146 self.down = Some((event.hit, event.x, event.y));
147 self.moved = false;
148 self.last = (event.x, event.y);
149 None
150 }
151 PointerPhase::Move => {
152 if let Some((_, start_x, start_y)) = &self.down {
153 if (event.x - start_x).abs() > DRAG_THRESHOLD
154 || (event.y - start_y).abs() > DRAG_THRESHOLD
155 {
156 self.moved = true;
157 }
158 self.last = (event.x, event.y);
159 }
160 None
161 }
162 PointerPhase::Up => {
163 let (from, _, _) = self.down.take()?;
164 let at = (event.x, event.y);
165 if self.moved {
166 Some(RawGesture::Drag {
167 from,
168 to: event.hit,
169 at,
170 })
171 } else {
172 Some(RawGesture::Tap { hit: from })
173 }
174 }
175 }
176 }
177
178 pub fn key(command: &str, hit: Hit) -> RawGesture {
180 RawGesture::Key {
181 command: command.to_owned(),
182 hit,
183 }
184 }
185}
186
187const DRAG_THRESHOLD: f64 = 3.0;
188
189pub fn intent_from_gesture(
193 operator: Origin,
194 pane: &str,
195 raw: &RawGesture,
196) -> Result<Expr, IntentError> {
197 match raw {
198 RawGesture::Tap { hit } => tap_intent(operator, hit),
199 RawGesture::Drag { from, to, at } => drag_intent(operator, from, to, *at),
200 RawGesture::Key { command, hit } => key_intent(operator, pane, command, hit),
201 }
202}
203
204fn ungesturable(message: &str) -> IntentError {
205 IntentError {
206 path: vec!["gesture".to_owned()],
207 message: message.to_owned(),
208 }
209}
210
211fn pane_field(pane: &str) -> Expr {
212 sim_value::build::sym(pane)
213}
214
215fn at_field(at: (f64, f64)) -> Expr {
216 sim_value::build::map(vec![
217 ("x", sim_value::build::float(at.0)),
218 ("y", sim_value::build::float(at.1)),
219 ])
220}
221
222fn require_target(hit: &Hit, message: &str) -> Result<Expr, IntentError> {
223 hit.target.clone().ok_or_else(|| ungesturable(message))
224}
225
226fn tap_intent(operator: Origin, hit: &Hit) -> Result<Expr, IntentError> {
227 match hit.role {
228 HitRole::Button => {
229 let target = require_target(hit, "tap on a control with no target")?;
230 let control = hit
231 .detail("control")
232 .cloned()
233 .ok_or_else(|| ungesturable("button hit is missing a 'control'"))?;
234 Ok(intent(
235 "tap",
236 operator,
237 vec![("target", target), ("control", control)],
238 ))
239 }
240 HitRole::Node | HitRole::Port | HitRole::Edge | HitRole::Field => {
241 let target = require_target(hit, "selectable hit with no target")?;
242 Ok(intent(
243 "select",
244 operator,
245 vec![("targets", Expr::List(vec![target]))],
246 ))
247 }
248 HitRole::Blank => Ok(intent(
249 "select",
250 operator,
251 vec![("targets", Expr::List(vec![]))],
252 )),
253 }
254}
255
256fn drag_intent(
257 operator: Origin,
258 from: &Hit,
259 to: &Hit,
260 at: (f64, f64),
261) -> Result<Expr, IntentError> {
262 match (&from.role, &to.role) {
263 (HitRole::Port, HitRole::Port) => {
264 let from_port = port_descriptor(from)?;
265 let to_port = port_descriptor(to)?;
266 Ok(intent(
267 "wire",
268 operator,
269 vec![("from", from_port), ("to", to_port)],
270 ))
271 }
272 (HitRole::Node, _) => {
273 let node = require_target(from, "drag of a node with no target")?;
274 Ok(intent(
275 "move",
276 operator,
277 vec![("node", node), ("at", at_field(at))],
278 ))
279 }
280 _ => Err(ungesturable("drag has no meaning between these elements")),
281 }
282}
283
284fn port_descriptor(hit: &Hit) -> Result<Expr, IntentError> {
285 let node = hit
286 .detail("node")
287 .cloned()
288 .ok_or_else(|| ungesturable("port hit is missing a 'node'"))?;
289 let port = hit
290 .detail("port")
291 .cloned()
292 .ok_or_else(|| ungesturable("port hit is missing a 'port'"))?;
293 Ok(sim_value::build::map(vec![("node", node), ("port", port)]))
294}
295
296fn key_intent(operator: Origin, pane: &str, command: &str, hit: &Hit) -> Result<Expr, IntentError> {
297 match command {
298 "delete" => {
299 let target = require_target(hit, "delete with no target under the pointer")?;
300 Ok(intent(
301 "delete",
302 operator,
303 vec![("targets", Expr::List(vec![target]))],
304 ))
305 }
306 "commit" => Ok(intent("commit", operator, vec![("pane", pane_field(pane))])),
307 "cancel" => Ok(intent("cancel", operator, vec![("pane", pane_field(pane))])),
308 other => Err(ungesturable(&format!(
309 "no Intent bound to command '{other}'"
310 ))),
311 }
312}