use super::types::DockZone;
use crate::tree::{LayoutRect, NodeId};
use astrelis_core::math::Vec2;
pub const DEFAULT_EDGE_THRESHOLD: f32 = 0.25;
#[derive(Debug, Clone)]
pub struct DropZoneDetector {
pub edge_threshold: f32,
}
impl Default for DropZoneDetector {
fn default() -> Self {
Self {
edge_threshold: DEFAULT_EDGE_THRESHOLD,
}
}
}
impl DropZoneDetector {
pub fn new() -> Self {
Self::default()
}
pub fn with_edge_threshold(mut self, threshold: f32) -> Self {
self.edge_threshold = threshold.clamp(0.1, 0.5);
self
}
pub fn detect_zone(&self, cursor: Vec2, bounds: LayoutRect) -> Option<DockZone> {
if bounds.width <= 0.0 || bounds.height <= 0.0 {
return None;
}
if !bounds.contains(cursor) {
return None;
}
let rel_x = (cursor.x - bounds.x) / bounds.width;
let rel_y = (cursor.y - bounds.y) / bounds.height;
if rel_x < self.edge_threshold {
Some(DockZone::Left)
} else if rel_x > (1.0 - self.edge_threshold) {
Some(DockZone::Right)
} else if rel_y < self.edge_threshold {
Some(DockZone::Top)
} else if rel_y > (1.0 - self.edge_threshold) {
Some(DockZone::Bottom)
} else {
Some(DockZone::Center)
}
}
pub fn preview_bounds(&self, zone: DockZone, target: LayoutRect) -> LayoutRect {
match zone {
DockZone::Left => LayoutRect {
x: target.x,
y: target.y,
width: target.width * self.edge_threshold * 2.0, height: target.height,
},
DockZone::Right => {
let width = target.width * self.edge_threshold * 2.0;
LayoutRect {
x: target.x + target.width - width,
y: target.y,
width,
height: target.height,
}
}
DockZone::Top => LayoutRect {
x: target.x,
y: target.y,
width: target.width,
height: target.height * self.edge_threshold * 2.0,
},
DockZone::Bottom => {
let height = target.height * self.edge_threshold * 2.0;
LayoutRect {
x: target.x,
y: target.y + target.height - height,
width: target.width,
height,
}
}
DockZone::Center => {
target
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DropTarget {
pub container_id: NodeId,
pub zone: DockZone,
pub insert_index: Option<usize>,
}
impl DropTarget {
pub fn new(container_id: NodeId, zone: DockZone) -> Self {
Self {
container_id,
zone,
insert_index: None,
}
}
pub fn with_insert_index(mut self, index: usize) -> Self {
self.insert_index = Some(index);
self
}
pub fn is_edge_drop(&self) -> bool {
!matches!(self.zone, DockZone::Center)
}
pub fn is_center_drop(&self) -> bool {
matches!(self.zone, DockZone::Center)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_left_zone() {
let detector = DropZoneDetector::new();
let bounds = LayoutRect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0,
};
assert_eq!(
detector.detect_zone(Vec2::new(10.0, 50.0), bounds),
Some(DockZone::Left)
);
}
#[test]
fn test_detect_right_zone() {
let detector = DropZoneDetector::new();
let bounds = LayoutRect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0,
};
assert_eq!(
detector.detect_zone(Vec2::new(90.0, 50.0), bounds),
Some(DockZone::Right)
);
}
#[test]
fn test_detect_center_zone() {
let detector = DropZoneDetector::new();
let bounds = LayoutRect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0,
};
assert_eq!(
detector.detect_zone(Vec2::new(50.0, 50.0), bounds),
Some(DockZone::Center)
);
}
#[test]
fn test_detect_outside_bounds() {
let detector = DropZoneDetector::new();
let bounds = LayoutRect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0,
};
assert_eq!(detector.detect_zone(Vec2::new(150.0, 50.0), bounds), None);
}
#[test]
fn test_preview_bounds_left() {
let detector = DropZoneDetector::new();
let target = LayoutRect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0,
};
let preview = detector.preview_bounds(DockZone::Left, target);
assert_eq!(preview.x, 0.0);
assert_eq!(preview.y, 0.0);
assert_eq!(preview.width, 50.0); assert_eq!(preview.height, 100.0);
}
#[test]
fn test_preview_bounds_center() {
let detector = DropZoneDetector::new();
let target = LayoutRect {
x: 10.0,
y: 20.0,
width: 100.0,
height: 80.0,
};
let preview = detector.preview_bounds(DockZone::Center, target);
assert_eq!(preview.x, target.x);
assert_eq!(preview.y, target.y);
assert_eq!(preview.width, target.width);
assert_eq!(preview.height, target.height);
}
#[test]
fn test_custom_edge_threshold() {
let detector = DropZoneDetector::new().with_edge_threshold(0.3);
let bounds = LayoutRect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0,
};
assert_eq!(
detector.detect_zone(Vec2::new(28.0, 50.0), bounds),
Some(DockZone::Left)
);
assert_eq!(
detector.detect_zone(Vec2::new(35.0, 50.0), bounds),
Some(DockZone::Center)
);
}
}