use sim_kernel::{Expr, Symbol};
use sim_value::build;
use crate::gesture::Hit;
use crate::model::{Origin, intent};
pub trait GlassesInputCapabilities {
fn has_input(&self, name: &str) -> bool;
}
impl GlassesInputCapabilities for [Symbol] {
fn has_input(&self, name: &str) -> bool {
self.iter()
.any(|symbol| symbol.namespace.is_none() && symbol.name.as_ref() == name)
}
}
impl GlassesInputCapabilities for Vec<Symbol> {
fn has_input(&self, name: &str) -> bool {
self.as_slice().has_input(name)
}
}
impl<T: GlassesInputCapabilities + ?Sized> GlassesInputCapabilities for &T {
fn has_input(&self, name: &str) -> bool {
(**self).has_input(name)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GlassesInputTiming {
pub debounce_ms: u64,
pub gaze_stable_ms: u64,
pub gaze_dwell_ms: u64,
pub head_stable_ms: u64,
pub hand_stable_ms: u64,
pub tap_sequence_ms: u64,
pub long_press_ms: u64,
}
impl Default for GlassesInputTiming {
fn default() -> Self {
Self {
debounce_ms: 80,
gaze_stable_ms: 120,
gaze_dwell_ms: 550,
head_stable_ms: 140,
hand_stable_ms: 80,
tap_sequence_ms: 450,
long_press_ms: 650,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GazePhase {
Enter,
Hold,
Dwell,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HeadGesture {
Nod,
Shake,
TiltLeft,
TiltRight,
TiltUp,
TiltDown,
}
impl HeadGesture {
fn token(self) -> &'static str {
match self {
HeadGesture::Nod => "head-nod",
HeadGesture::Shake => "head-shake",
HeadGesture::TiltLeft => "head-tilt-left",
HeadGesture::TiltRight => "head-tilt-right",
HeadGesture::TiltUp => "head-tilt-up",
HeadGesture::TiltDown => "head-tilt-down",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ControllerAction {
Press,
Move {
dx: i32,
dy: i32,
},
Edit,
}
#[derive(Clone, Debug, PartialEq)]
pub enum GlassesRawInput {
Gaze {
phase: GazePhase,
hit: Hit,
stable_ms: u64,
vio_stable: bool,
at_ms: u64,
},
Head {
kind: HeadGesture,
target: Expr,
stable_ms: u64,
vio_stable: bool,
at_ms: u64,
},
HandRay {
hit: Hit,
stable_ms: u64,
vio_stable: bool,
at_ms: u64,
},
Pinch {
hit: Hit,
stable_ms: u64,
vio_stable: bool,
at_ms: u64,
},
Tap {
count: u8,
target: Expr,
span_ms: u64,
held_ms: u64,
at_ms: u64,
},
Button {
id: Symbol,
target: Option<Expr>,
held_ms: u64,
at_ms: u64,
},
Controller {
id: Symbol,
action: ControllerAction,
target: Expr,
at_ms: u64,
},
}
#[derive(Clone, Debug, Default)]
pub struct GlassesIntentReducer {
timing: GlassesInputTiming,
last_accepted_ms: Option<u64>,
}
impl GlassesIntentReducer {
pub fn new() -> Self {
Self::default()
}
pub fn with_timing(timing: GlassesInputTiming) -> Self {
Self {
timing,
last_accepted_ms: None,
}
}
pub fn reduce<C: GlassesInputCapabilities + ?Sized>(
&mut self,
origin: Origin,
raw: GlassesRawInput,
profile: &C,
) -> Option<Expr> {
let at_ms = raw.at_ms();
let intent = match raw {
GlassesRawInput::Gaze {
phase,
hit,
stable_ms,
vio_stable,
..
} if profile.has_input("gaze") && vio_stable => {
self.gaze_intent(origin, phase, hit, stable_ms)
}
GlassesRawInput::Head {
kind,
target,
stable_ms,
vio_stable,
..
} if profile.has_input("head")
&& vio_stable
&& stable_ms >= self.timing.head_stable_ms =>
{
head_intent(origin, kind, target)
}
GlassesRawInput::HandRay {
hit,
stable_ms,
vio_stable,
..
} if has_any_input(profile, &["hand", "hand-ray"])
&& vio_stable
&& stable_ms >= self.timing.hand_stable_ms =>
{
hit.target
.clone()
.map(|target| move_focus(origin, target, "hand-ray"))
}
GlassesRawInput::Pinch {
hit,
stable_ms,
vio_stable,
..
} if has_any_input(profile, &["hand", "pinch"])
&& vio_stable
&& stable_ms >= self.timing.hand_stable_ms =>
{
hit.target
.clone()
.map(|target| invoke(origin, target, "pinch", vec![]))
}
GlassesRawInput::Tap {
count,
target,
span_ms,
held_ms,
..
} if profile.has_input("tap") => {
tap_intent(origin, count, target, span_ms, held_ms, self.timing)
}
GlassesRawInput::Button {
id,
target,
held_ms,
..
} if profile.has_input("button") => {
if held_ms >= self.timing.long_press_ms {
Some(intent("dismiss", origin, vec![]))
} else {
let target = target.unwrap_or_else(|| Expr::Symbol(id.clone()));
Some(invoke(
origin,
target,
"button-press",
vec![Expr::Symbol(id)],
))
}
}
GlassesRawInput::Controller {
id, action, target, ..
} if profile.has_input("controller") => controller_intent(origin, id, action, target),
_ => None,
}?;
self.accepts(at_ms).then_some(intent)
}
fn gaze_intent(
&self,
origin: Origin,
phase: GazePhase,
hit: Hit,
stable_ms: u64,
) -> Option<Expr> {
let target = hit.target?;
match phase {
GazePhase::Dwell if stable_ms >= self.timing.gaze_dwell_ms => {
Some(select_one(origin, target))
}
GazePhase::Enter | GazePhase::Hold if stable_ms >= self.timing.gaze_stable_ms => {
Some(move_focus(origin, target, phase.token()))
}
_ => None,
}
}
fn accepts(&mut self, at_ms: u64) -> bool {
if let Some(last) = self.last_accepted_ms
&& at_ms.saturating_sub(last) < self.timing.debounce_ms
{
return false;
}
self.last_accepted_ms = Some(at_ms);
true
}
}
impl GazePhase {
fn token(self) -> &'static str {
match self {
GazePhase::Enter => "gaze-enter",
GazePhase::Hold => "gaze-hold",
GazePhase::Dwell => "gaze-dwell",
}
}
}
impl GlassesRawInput {
fn at_ms(&self) -> u64 {
match self {
GlassesRawInput::Gaze { at_ms, .. }
| GlassesRawInput::Head { at_ms, .. }
| GlassesRawInput::HandRay { at_ms, .. }
| GlassesRawInput::Pinch { at_ms, .. }
| GlassesRawInput::Tap { at_ms, .. }
| GlassesRawInput::Button { at_ms, .. }
| GlassesRawInput::Controller { at_ms, .. } => *at_ms,
}
}
}
fn has_any_input<C: GlassesInputCapabilities + ?Sized>(profile: &C, names: &[&str]) -> bool {
names.iter().any(|name| profile.has_input(name))
}
fn head_intent(origin: Origin, kind: HeadGesture, target: Expr) -> Option<Expr> {
match kind {
HeadGesture::Nod => Some(invoke(origin, target, kind.token(), vec![])),
HeadGesture::Shake => Some(intent("dismiss", origin, vec![])),
HeadGesture::TiltLeft
| HeadGesture::TiltRight
| HeadGesture::TiltUp
| HeadGesture::TiltDown => Some(move_focus(origin, target, kind.token())),
}
}
fn tap_intent(
origin: Origin,
count: u8,
target: Expr,
span_ms: u64,
held_ms: u64,
timing: GlassesInputTiming,
) -> Option<Expr> {
if count == 1 && held_ms >= timing.long_press_ms {
return Some(edit(origin, target));
}
if span_ms > timing.tap_sequence_ms {
return None;
}
match count {
1 => Some(select_one(origin, target)),
2 => Some(invoke(origin, target, "double-tap", vec![])),
_ => None,
}
}
fn controller_intent(
origin: Origin,
id: Symbol,
action: ControllerAction,
target: Expr,
) -> Option<Expr> {
match action {
ControllerAction::Press => Some(invoke(
origin,
target,
"controller-press",
vec![Expr::Symbol(id)],
)),
ControllerAction::Move { dx, dy } if dx != 0 || dy != 0 => {
Some(move_by(origin, target, "controller-move", dx, dy))
}
ControllerAction::Edit => Some(edit(origin, target)),
_ => None,
}
}
fn select_one(origin: Origin, target: Expr) -> Expr {
intent(
"select",
origin,
vec![("targets", Expr::List(vec![target]))],
)
}
fn invoke(origin: Origin, target: Expr, op: &str, args: Vec<Expr>) -> Expr {
intent(
"invoke",
origin,
vec![
("target", target),
("op", Expr::Symbol(Symbol::qualified("glasses/input", op))),
("args", Expr::List(args)),
],
)
}
fn edit(origin: Origin, target: Expr) -> Expr {
intent(
"edit",
origin,
vec![("target", target), ("path", Expr::List(Vec::new()))],
)
}
fn move_focus(origin: Origin, target: Expr, source: &str) -> Expr {
intent(
"move",
origin,
vec![
("node", target),
("at", build::map(vec![("source", build::sym(source))])),
],
)
}
fn move_by(origin: Origin, target: Expr, source: &str, dx: i32, dy: i32) -> Expr {
intent(
"move",
origin,
vec![
("node", target),
(
"at",
build::map(vec![
("source", build::sym(source)),
("dx", build::float(f64::from(dx))),
("dy", build::float(f64::from(dy))),
]),
),
],
)
}