use teksilo_canvas::{Point, Rect};
pub(crate) fn point_in_safe_triangle(p: Point, apex: Point, submenu: Rect) -> bool {
if submenu.width <= 0.0 || submenu.height <= 0.0 {
return false;
}
let near_x = if apex.x < submenu.x {
submenu.x
} else if apex.x > submenu.x + submenu.width {
submenu.x + submenu.width
} else {
return false;
};
let top = Point::new(near_x, submenu.y);
let bottom = Point::new(near_x, submenu.y + submenu.height);
point_in_triangle(p, apex, top, bottom)
}
fn point_in_triangle(p: Point, a: Point, b: Point, c: Point) -> bool {
let s1 = cross(p, a, b);
let s2 = cross(p, b, c);
let s3 = cross(p, c, a);
let neg = (s1 < 0.0) || (s2 < 0.0) || (s3 < 0.0);
let pos = (s1 > 0.0) || (s2 > 0.0) || (s3 > 0.0);
!(neg && pos)
}
fn cross(p: Point, a: Point, b: Point) -> f32 {
(b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)
}
#[cfg(test)]
mod tests {
use super::*;
fn submenu_to_the_right() -> Rect {
Rect::new(160.0, 100.0, 200.0, 240.0)
}
fn submenu_to_the_left() -> Rect {
Rect::new(0.0, 100.0, 200.0, 240.0)
}
#[test]
fn apex_inside_triangle_returns_true() {
let sub = submenu_to_the_right();
let apex = Point::new(80.0, 200.0); assert!(point_in_safe_triangle(apex, apex, sub));
}
#[test]
fn pointer_directly_toward_submenu_top_corner_is_inside() {
let sub = submenu_to_the_right();
let apex = Point::new(80.0, 220.0);
let midpoint_toward_top_left = Point::new(120.0, 160.0);
assert!(point_in_safe_triangle(midpoint_toward_top_left, apex, sub));
}
#[test]
fn pointer_directly_toward_submenu_bottom_corner_is_inside() {
let sub = submenu_to_the_right();
let apex = Point::new(80.0, 220.0);
let midpoint_toward_bottom_left = Point::new(120.0, 280.0);
assert!(point_in_safe_triangle(
midpoint_toward_bottom_left,
apex,
sub
));
}
#[test]
fn pointer_above_apex_outside() {
let sub = submenu_to_the_right();
let apex = Point::new(80.0, 220.0);
assert!(!point_in_safe_triangle(Point::new(80.0, 50.0), apex, sub));
}
#[test]
fn pointer_below_apex_outside() {
let sub = submenu_to_the_right();
let apex = Point::new(80.0, 220.0);
assert!(!point_in_safe_triangle(Point::new(80.0, 400.0), apex, sub));
}
#[test]
fn pointer_left_of_apex_outside_when_submenu_is_right() {
let sub = submenu_to_the_right();
let apex = Point::new(80.0, 220.0);
assert!(!point_in_safe_triangle(Point::new(0.0, 220.0), apex, sub));
}
#[test]
fn submenu_on_left_pointer_toward_top_corner_is_inside() {
let sub = submenu_to_the_left();
let apex = Point::new(280.0, 220.0);
let p = Point::new(220.0, 160.0);
assert!(point_in_safe_triangle(p, apex, sub));
}
#[test]
fn submenu_on_left_pointer_drifting_further_left_is_outside() {
let sub = submenu_to_the_left();
let apex = Point::new(280.0, 220.0);
let p = Point::new(-50.0, 220.0);
assert!(!point_in_safe_triangle(p, apex, sub));
}
#[test]
fn submenu_on_left_pointer_right_of_apex_outside() {
let sub = submenu_to_the_left();
let apex = Point::new(280.0, 220.0);
assert!(!point_in_safe_triangle(Point::new(360.0, 220.0), apex, sub));
}
#[test]
fn zero_width_submenu_returns_false() {
let sub = Rect::new(160.0, 100.0, 0.0, 240.0);
let apex = Point::new(80.0, 220.0);
assert!(!point_in_safe_triangle(Point::new(120.0, 220.0), apex, sub));
}
#[test]
fn zero_height_submenu_returns_false() {
let sub = Rect::new(160.0, 100.0, 200.0, 0.0);
let apex = Point::new(80.0, 220.0);
assert!(!point_in_safe_triangle(Point::new(220.0, 100.0), apex, sub));
}
#[test]
fn apex_inside_submenu_horizontal_extent_returns_false() {
let sub = submenu_to_the_right();
let apex_inside_sub_x = Point::new(180.0, 50.0);
assert!(!point_in_safe_triangle(
Point::new(180.0, 200.0),
apex_inside_sub_x,
sub
));
}
}