use super::registry::PanelKey;
use crate::ecs::CursorShape;
pub(crate) const BORDER: f32 = 6.0;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Edges {
pub left: bool,
pub right: bool,
pub top: bool,
pub bottom: bool,
}
impl Edges {
pub(crate) fn any(self) -> bool {
self.left || self.right || self.top || self.bottom
}
}
pub(crate) fn hit_test(o: [f32; 2], s: [f32; 2], t: f32, mx: f32, my: f32) -> Option<Edges> {
if mx < o[0] || mx >= o[0] + s[0] || my < o[1] || my >= o[1] + s[1] {
return None;
}
let edges = Edges {
left: mx - o[0] <= t,
right: o[0] + s[0] - mx <= t,
top: my - o[1] <= t,
bottom: o[1] + s[1] - my <= t,
};
edges.any().then_some(edges)
}
pub(crate) fn cursor_shape(e: Edges) -> CursorShape {
let horizontal = e.left || e.right;
let vertical = e.top || e.bottom;
match (horizontal, vertical) {
(true, true) => {
if (e.left && e.top) || (e.right && e.bottom) {
CursorShape::ResizeNWSE
} else {
CursorShape::ResizeNESW
}
}
(true, false) => CursorShape::ResizeEW,
(false, true) => CursorShape::ResizeNS,
(false, false) => CursorShape::Default,
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Resize {
pub key: PanelKey,
pub edges: Edges,
pub grab_mouse: [f32; 2],
pub start_origin: [f32; 2],
pub start_size: [f32; 2],
}
pub(crate) fn apply(
r: &Resize,
mouse: [f32; 2],
min: [f32; 2],
max: [f32; 2],
vp: [f32; 2],
top: f32,
) -> ([f32; 2], [f32; 2]) {
let dx = mouse[0] - r.grab_mouse[0];
let dy = mouse[1] - r.grab_mouse[1];
let (mut x, mut y) = (r.start_origin[0], r.start_origin[1]);
let (mut w, mut h) = (r.start_size[0], r.start_size[1]);
if r.edges.right {
let cap = max[0].min(vp[0] - x).max(min[0]);
w = (r.start_size[0] + dx).clamp(min[0], cap);
}
if r.edges.left {
let right = r.start_origin[0] + r.start_size[0];
let cap = max[0].min(right).max(min[0]);
w = (r.start_size[0] - dx).clamp(min[0], cap);
x = right - w;
}
if r.edges.bottom {
let cap = max[1].min(vp[1] - y).max(min[1]);
h = (r.start_size[1] + dy).clamp(min[1], cap);
}
if r.edges.top {
let bottom = r.start_origin[1] + r.start_size[1];
let cap = max[1].min(bottom - top).max(min[1]);
h = (r.start_size[1] - dy).clamp(min[1], cap);
y = bottom - h;
}
([x, y], [w, h])
}
#[cfg(test)]
mod tests {
use super::*;
const O: [f32; 2] = [100.0, 100.0];
const S: [f32; 2] = [200.0, 300.0];
const T: f32 = 6.0;
#[test]
fn interior_and_outside_miss() {
assert_eq!(hit_test(O, S, T, 200.0, 250.0), None);
assert_eq!(hit_test(O, S, T, 50.0, 250.0), None);
assert_eq!(hit_test(O, S, T, 350.0, 250.0), None);
assert_eq!(hit_test(O, S, T, 200.0, 50.0), None);
assert_eq!(hit_test(O, S, T, 200.0, 500.0), None);
}
#[test]
fn each_edge_and_corner_resolves() {
let left = hit_test(O, S, T, 102.0, 250.0).unwrap();
assert_eq!(cursor_shape(left), CursorShape::ResizeEW);
assert!(left.left && !left.right && !left.top && !left.bottom);
let right = hit_test(O, S, T, 298.0, 250.0).unwrap();
assert_eq!(cursor_shape(right), CursorShape::ResizeEW);
let top = hit_test(O, S, T, 200.0, 102.0).unwrap();
assert_eq!(cursor_shape(top), CursorShape::ResizeNS);
let bottom = hit_test(O, S, T, 200.0, 398.0).unwrap();
assert_eq!(cursor_shape(bottom), CursorShape::ResizeNS);
assert_eq!(
cursor_shape(hit_test(O, S, T, 102.0, 102.0).unwrap()),
CursorShape::ResizeNWSE
);
assert_eq!(
cursor_shape(hit_test(O, S, T, 298.0, 398.0).unwrap()),
CursorShape::ResizeNWSE
);
assert_eq!(
cursor_shape(hit_test(O, S, T, 298.0, 102.0).unwrap()),
CursorShape::ResizeNESW
);
assert_eq!(
cursor_shape(hit_test(O, S, T, 102.0, 398.0).unwrap()),
CursorShape::ResizeNESW
);
}
#[test]
fn no_edges_is_the_arrow() {
assert_eq!(cursor_shape(Edges::default()), CursorShape::Default);
}
fn resize(edges: Edges) -> Resize {
Resize {
key: PanelKey::Assets,
edges,
grab_mouse: [200.0, 250.0],
start_origin: O,
start_size: S,
}
}
const VP: [f32; 2] = [1280.0, 720.0];
const TOP: f32 = 30.0;
const MIN: [f32; 2] = [120.0, 80.0];
const UNBOUNDED: [f32; 2] = [f32::INFINITY, f32::INFINITY];
#[test]
fn right_and_bottom_grow_from_the_fixed_origin() {
let r = resize(Edges {
right: true,
bottom: true,
..Default::default()
});
let (o, s) = apply(&r, [260.0, 300.0], MIN, UNBOUNDED, VP, TOP);
assert_eq!(o, O, "origin stays put for a right / bottom drag");
assert_eq!(s, [S[0] + 60.0, S[1] + 50.0]);
}
#[test]
fn left_and_top_move_the_origin_and_hold_the_far_edge() {
let r = resize(Edges {
left: true,
top: true,
..Default::default()
});
let (o, s) = apply(&r, [160.0, 220.0], MIN, UNBOUNDED, VP, TOP);
assert_eq!(o, [60.0, 70.0]);
assert_eq!(s, [S[0] + 40.0, S[1] + 30.0]);
assert_eq!(
[o[0] + s[0], o[1] + s[1]],
[O[0] + S[0], O[1] + S[1]],
"the far corner is pinned"
);
}
#[test]
fn never_shrinks_below_min() {
let r = resize(Edges {
right: true,
top: true,
..Default::default()
});
let big_min = [180.0, 200.0];
let (o, s) = apply(&r, [0.0, 700.0], big_min, UNBOUNDED, VP, TOP);
assert_eq!(s[0], big_min[0], "width floors at min");
assert_eq!(s[1], big_min[1], "height floors at min");
assert_eq!(o[1] + s[1], O[1] + S[1]);
}
#[test]
fn never_grows_past_max() {
let r = resize(Edges {
right: true,
bottom: true,
..Default::default()
});
let max = [260.0, 340.0];
let (_, s) = apply(&r, [5000.0, 5000.0], MIN, max, VP, TOP);
assert_eq!(s, max, "each dimension caps at max");
}
#[test]
fn clamps_to_the_screen_and_top_bar() {
let r = resize(Edges {
right: true,
bottom: true,
..Default::default()
});
let (_, s) = apply(&r, [5000.0, 5000.0], MIN, UNBOUNDED, VP, TOP);
assert_eq!(s[0], VP[0] - O[0]);
assert_eq!(s[1], VP[1] - O[1]);
let r = resize(Edges {
top: true,
..Default::default()
});
let (o, _) = apply(&r, [200.0, -5000.0], MIN, UNBOUNDED, VP, TOP);
assert_eq!(o[1], TOP);
}
}