cranpose 0.1.162

Cranpose runtime and UI facade
Documentation
use winit::event::{ButtonSource, FingerId, PointerKind, PointerSource as WinitPointerSource};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum TouchRoute {
    Primary,
    Secondary(u64),
    Untracked,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct TouchRelease {
    pub(crate) secondaries: Vec<u64>,
    pub(crate) releases_primary: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum TouchLeave {
    CancelGesture,
    ReleaseSecondary(u64),
    Ignore,
}

#[derive(Debug, Default)]
pub(crate) struct TouchPointerRouter {
    primary: Option<FingerId>,
    secondaries: Vec<(FingerId, u64)>,
    next_secondary_id: u64,
}

fn button_finger(button: &ButtonSource) -> Option<FingerId> {
    match button {
        ButtonSource::Touch { finger_id, .. } => Some(*finger_id),
        _ => None,
    }
}

fn source_finger(source: &WinitPointerSource) -> Option<FingerId> {
    match source {
        WinitPointerSource::Touch { finger_id, .. } => Some(*finger_id),
        _ => None,
    }
}

fn kind_finger(kind: &PointerKind) -> Option<FingerId> {
    match kind {
        PointerKind::Touch(finger_id) => Some(*finger_id),
        _ => None,
    }
}

impl TouchPointerRouter {
    fn is_idle(&self) -> bool {
        self.primary.is_none() && self.secondaries.is_empty()
    }

    fn secondary_id(&self, finger: FingerId) -> Option<u64> {
        self.secondaries
            .iter()
            .find(|(tracked, _)| *tracked == finger)
            .map(|(_, id)| *id)
    }

    fn end_gesture(&mut self) {
        self.primary = None;
        self.secondaries.clear();
        self.next_secondary_id = 0;
    }

    pub(crate) fn press(&mut self, button: &ButtonSource) -> TouchRoute {
        let Some(finger) = button_finger(button) else {
            return TouchRoute::Primary;
        };

        match self.primary {
            None => {
                self.primary = Some(finger);
                TouchRoute::Primary
            }
            Some(primary) if primary == finger => TouchRoute::Primary,
            Some(_) => {
                if let Some(id) = self.secondary_id(finger) {
                    return TouchRoute::Secondary(id);
                }
                self.next_secondary_id += 1;
                let id = self.next_secondary_id;
                self.secondaries.push((finger, id));
                TouchRoute::Secondary(id)
            }
        }
    }

    pub(crate) fn moved(&self, source: &WinitPointerSource) -> TouchRoute {
        let Some(finger) = source_finger(source) else {
            return TouchRoute::Primary;
        };
        if self.primary == Some(finger) {
            return TouchRoute::Primary;
        }
        match self.secondary_id(finger) {
            Some(id) => TouchRoute::Secondary(id),
            None => TouchRoute::Untracked,
        }
    }

    pub(crate) fn release(&mut self, button: &ButtonSource) -> TouchRelease {
        let primary_only = TouchRelease {
            secondaries: Vec::new(),
            releases_primary: true,
        };
        let Some(finger) = button_finger(button) else {
            return primary_only;
        };

        if self.primary == Some(finger) {
            let mut secondaries: Vec<u64> = self.secondaries.iter().map(|(_, id)| *id).collect();
            secondaries.sort_unstable();
            self.end_gesture();
            return TouchRelease {
                secondaries,
                releases_primary: true,
            };
        }

        if let Some(id) = self.secondary_id(finger) {
            self.secondaries.retain(|(tracked, _)| *tracked != finger);
            return TouchRelease {
                secondaries: vec![id],
                releases_primary: false,
            };
        }

        if self.is_idle() {
            primary_only
        } else {
            TouchRelease::default()
        }
    }

    pub(crate) fn left(&mut self, kind: &PointerKind) -> TouchLeave {
        let Some(finger) = kind_finger(kind) else {
            self.end_gesture();
            return TouchLeave::CancelGesture;
        };

        if self.primary == Some(finger) {
            self.end_gesture();
            return TouchLeave::CancelGesture;
        }

        if let Some(id) = self.secondary_id(finger) {
            self.secondaries.retain(|(tracked, _)| *tracked != finger);
            return TouchLeave::ReleaseSecondary(id);
        }

        if self.is_idle() {
            TouchLeave::CancelGesture
        } else {
            TouchLeave::Ignore
        }
    }
}

#[cfg(test)]
#[path = "tests/winit_touch_tests.rs"]
mod tests;