use blinc_core::layer::{Point, Rect};
#[derive(Clone, Debug)]
pub struct HitRegion {
pub id: String,
pub rect: Rect,
}
impl HitRegion {
pub fn new(id: impl Into<String>, rect: Rect) -> Self {
Self {
id: id.into(),
rect,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct InteractionState {
pub hovered: Option<String>,
pub active: Option<String>,
pub drag_start: Option<Point>,
pub did_drag: bool,
}
#[derive(Clone, Debug)]
pub struct CanvasEvent {
pub content_point: Point,
pub screen_point: Point,
pub region_id: Option<String>,
}
#[derive(Clone, Debug)]
pub struct CanvasDragEvent {
pub content_point: Point,
pub content_delta: Point,
pub screen_point: Point,
pub region_id: String,
}
pub fn hit_test(regions: &[HitRegion], content_point: Point) -> Option<String> {
regions
.iter()
.rev()
.find(|r| r.rect.contains(content_point))
.map(|r| r.id.clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hit_test_empty() {
assert!(hit_test(&[], Point::new(0.0, 0.0)).is_none());
}
#[test]
fn test_hit_test_single_hit() {
let regions = vec![HitRegion::new("a", Rect::new(10.0, 10.0, 50.0, 50.0))];
assert_eq!(hit_test(®ions, Point::new(20.0, 20.0)), Some("a".into()));
}
#[test]
fn test_hit_test_miss() {
let regions = vec![HitRegion::new("a", Rect::new(10.0, 10.0, 50.0, 50.0))];
assert!(hit_test(®ions, Point::new(100.0, 100.0)).is_none());
}
#[test]
fn test_hit_test_topmost_wins() {
let regions = vec![
HitRegion::new("bottom", Rect::new(0.0, 0.0, 100.0, 100.0)),
HitRegion::new("top", Rect::new(20.0, 20.0, 60.0, 60.0)),
];
assert_eq!(
hit_test(®ions, Point::new(30.0, 30.0)),
Some("top".into())
);
}
#[test]
fn test_hit_test_non_overlapping() {
let regions = vec![
HitRegion::new("left", Rect::new(0.0, 0.0, 50.0, 50.0)),
HitRegion::new("right", Rect::new(100.0, 0.0, 50.0, 50.0)),
];
assert_eq!(
hit_test(®ions, Point::new(25.0, 25.0)),
Some("left".into())
);
assert_eq!(
hit_test(®ions, Point::new(125.0, 25.0)),
Some("right".into())
);
assert!(hit_test(®ions, Point::new(75.0, 25.0)).is_none());
}
}