faststep 0.1.0

UIKit-inspired embedded UI framework built on embedded-graphics
Documentation
use embedded_graphics::primitives::Rectangle;

use crate::{Button, TouchEvent, TouchPhase};

/// Result returned by shared multi-button touch tracking.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ButtonTouchResponse<K> {
    /// Activated button key, if any.
    pub action: Option<K>,
    /// Whether the touch sequence was captured.
    pub captured: bool,
    /// Whether visual state changed and needs redraw.
    pub redraw: bool,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ActiveButton<K> {
    key: K,
    frame: Rectangle,
    highlighted: bool,
}

/// Shared touch tracker for a set of buttons.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ButtonTouchState<K> {
    active: Option<ActiveButton<K>>,
}

impl<K: Copy + Eq> ButtonTouchState<K> {
    /// Creates an empty touch state.
    pub const fn new() -> Self {
        Self { active: None }
    }

    /// Clears the active button, if any.
    pub fn clear(&mut self) {
        self.active = None;
    }

    /// Returns whether the supplied button is currently highlighted.
    pub fn is_highlighted(&self, button: &Button<'_, K>) -> bool {
        matches!(
            self.active,
            Some(active)
                if active.highlighted
                    && active.key == button.spec.key
                    && active.frame == button.frame
        )
    }

    /// Applies a touch sample to a button slice.
    pub fn handle_touch<'a>(
        &mut self,
        touch: TouchEvent,
        buttons: &[Button<'a, K>],
    ) -> ButtonTouchResponse<K> {
        let was_active = self.active;
        match touch.phase {
            TouchPhase::Start => {
                self.active = buttons.iter().find_map(|button| {
                    touch.within(button.frame).then_some(ActiveButton {
                        key: button.spec.key,
                        frame: button.frame,
                        highlighted: true,
                    })
                });
                ButtonTouchResponse {
                    action: None,
                    captured: self.active.is_some(),
                    redraw: was_active != self.active,
                }
            }
            TouchPhase::Move => self.move_touch(touch),
            TouchPhase::End => self.finish_touch(touch),
            TouchPhase::Cancel => self.cancel_touch(),
        }
    }

    fn move_touch(&mut self, touch: TouchEvent) -> ButtonTouchResponse<K> {
        let Some(mut active) = self.active else {
            return ButtonTouchResponse {
                action: None,
                captured: false,
                redraw: false,
            };
        };
        let highlighted = touch.within(active.frame);
        let redraw = highlighted != active.highlighted;
        active.highlighted = highlighted;
        self.active = Some(active);
        ButtonTouchResponse {
            action: None,
            captured: true,
            redraw,
        }
    }

    fn finish_touch(&mut self, touch: TouchEvent) -> ButtonTouchResponse<K> {
        let Some(active) = self.active.take() else {
            return ButtonTouchResponse {
                action: None,
                captured: false,
                redraw: false,
            };
        };
        ButtonTouchResponse {
            action: (active.highlighted && touch.within(active.frame)).then_some(active.key),
            captured: true,
            redraw: active.highlighted,
        }
    }

    fn cancel_touch(&mut self) -> ButtonTouchResponse<K> {
        let Some(active) = self.active.take() else {
            return ButtonTouchResponse {
                action: None,
                captured: false,
                redraw: false,
            };
        };
        ButtonTouchResponse {
            action: None,
            captured: true,
            redraw: active.highlighted,
        }
    }
}