use sim_kernel::Expr;
use crate::model::{IntentError, Origin, intent};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HitRole {
Blank,
Node,
Port,
Button,
Field,
Edge,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Hit {
pub role: HitRole,
pub target: Option<Expr>,
pub detail: Vec<(String, Expr)>,
}
impl Hit {
pub fn blank() -> Self {
Self {
role: HitRole::Blank,
target: None,
detail: Vec::new(),
}
}
pub fn on(role: HitRole, target: Expr) -> Self {
Self {
role,
target: Some(target),
detail: Vec::new(),
}
}
pub fn with(mut self, key: &str, value: Expr) -> Self {
self.detail.push((key.to_owned(), value));
self
}
fn detail(&self, key: &str) -> Option<&Expr> {
self.detail
.iter()
.find_map(|(name, value)| (name == key).then_some(value))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PointerEvent {
pub phase: PointerPhase,
pub x: f64,
pub y: f64,
pub hit: Hit,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PointerPhase {
Down,
Move,
Up,
}
#[derive(Clone, Debug, PartialEq)]
pub enum RawGesture {
Tap {
hit: Hit,
},
Drag {
from: Hit,
to: Hit,
at: (f64, f64),
},
Key {
command: String,
hit: Hit,
},
}
#[derive(Debug, Default)]
pub struct GestureRecognizer {
down: Option<(Hit, f64, f64)>,
moved: bool,
last: (f64, f64),
}
impl GestureRecognizer {
pub fn new() -> Self {
Self::default()
}
pub fn pointer(&mut self, event: PointerEvent) -> Option<RawGesture> {
match event.phase {
PointerPhase::Down => {
self.down = Some((event.hit, event.x, event.y));
self.moved = false;
self.last = (event.x, event.y);
None
}
PointerPhase::Move => {
if let Some((_, start_x, start_y)) = &self.down {
if (event.x - start_x).abs() > DRAG_THRESHOLD
|| (event.y - start_y).abs() > DRAG_THRESHOLD
{
self.moved = true;
}
self.last = (event.x, event.y);
}
None
}
PointerPhase::Up => {
let (from, _, _) = self.down.take()?;
let at = (event.x, event.y);
if self.moved {
Some(RawGesture::Drag {
from,
to: event.hit,
at,
})
} else {
Some(RawGesture::Tap { hit: from })
}
}
}
}
pub fn key(command: &str, hit: Hit) -> RawGesture {
RawGesture::Key {
command: command.to_owned(),
hit,
}
}
}
const DRAG_THRESHOLD: f64 = 3.0;
pub fn intent_from_gesture(
operator: Origin,
pane: &str,
raw: &RawGesture,
) -> Result<Expr, IntentError> {
match raw {
RawGesture::Tap { hit } => tap_intent(operator, hit),
RawGesture::Drag { from, to, at } => drag_intent(operator, from, to, *at),
RawGesture::Key { command, hit } => key_intent(operator, pane, command, hit),
}
}
fn ungesturable(message: &str) -> IntentError {
IntentError {
path: vec!["gesture".to_owned()],
message: message.to_owned(),
}
}
fn pane_field(pane: &str) -> Expr {
sim_value::build::sym(pane)
}
fn at_field(at: (f64, f64)) -> Expr {
sim_value::build::map(vec![
("x", sim_value::build::float(at.0)),
("y", sim_value::build::float(at.1)),
])
}
fn require_target(hit: &Hit, message: &str) -> Result<Expr, IntentError> {
hit.target.clone().ok_or_else(|| ungesturable(message))
}
fn tap_intent(operator: Origin, hit: &Hit) -> Result<Expr, IntentError> {
match hit.role {
HitRole::Button => {
let target = require_target(hit, "tap on a control with no target")?;
let control = hit
.detail("control")
.cloned()
.ok_or_else(|| ungesturable("button hit is missing a 'control'"))?;
Ok(intent(
"tap",
operator,
vec![("target", target), ("control", control)],
))
}
HitRole::Node | HitRole::Port | HitRole::Edge | HitRole::Field => {
let target = require_target(hit, "selectable hit with no target")?;
Ok(intent(
"select",
operator,
vec![("targets", Expr::List(vec![target]))],
))
}
HitRole::Blank => Ok(intent(
"select",
operator,
vec![("targets", Expr::List(vec![]))],
)),
}
}
fn drag_intent(
operator: Origin,
from: &Hit,
to: &Hit,
at: (f64, f64),
) -> Result<Expr, IntentError> {
match (&from.role, &to.role) {
(HitRole::Port, HitRole::Port) => {
let from_port = port_descriptor(from)?;
let to_port = port_descriptor(to)?;
Ok(intent(
"wire",
operator,
vec![("from", from_port), ("to", to_port)],
))
}
(HitRole::Node, _) => {
let node = require_target(from, "drag of a node with no target")?;
Ok(intent(
"move",
operator,
vec![("node", node), ("at", at_field(at))],
))
}
_ => Err(ungesturable("drag has no meaning between these elements")),
}
}
fn port_descriptor(hit: &Hit) -> Result<Expr, IntentError> {
let node = hit
.detail("node")
.cloned()
.ok_or_else(|| ungesturable("port hit is missing a 'node'"))?;
let port = hit
.detail("port")
.cloned()
.ok_or_else(|| ungesturable("port hit is missing a 'port'"))?;
Ok(sim_value::build::map(vec![("node", node), ("port", port)]))
}
fn key_intent(operator: Origin, pane: &str, command: &str, hit: &Hit) -> Result<Expr, IntentError> {
match command {
"delete" => {
let target = require_target(hit, "delete with no target under the pointer")?;
Ok(intent(
"delete",
operator,
vec![("targets", Expr::List(vec![target]))],
))
}
"commit" => Ok(intent("commit", operator, vec![("pane", pane_field(pane))])),
"cancel" => Ok(intent("cancel", operator, vec![("pane", pane_field(pane))])),
other => Err(ungesturable(&format!(
"no Intent bound to command '{other}'"
))),
}
}