use std::time::{Duration, Instant};
use super::{LeafId, PanelRect, DropZone};
#[derive(Clone, Debug)]
pub struct DragSource {
pub panel_id: LeafId,
pub source_rect: PanelRect,
pub offset: (f32, f32),
}
#[derive(Clone, Debug)]
pub struct HoverTarget {
pub container_id: LeafId,
pub zone: DropZone,
pub preview_rect: PanelRect,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum LockState {
Unlocked,
SoftLock,
HardLock,
}
pub struct DragDropState {
drag_source: Option<DragSource>,
hover_target: Option<HoverTarget>,
lock: LockState,
lock_time: Option<Instant>,
}
impl DragDropState {
pub fn new() -> Self {
Self {
drag_source: None,
hover_target: None,
lock: LockState::Unlocked,
lock_time: None,
}
}
pub fn start_drag(&mut self, panel_id: LeafId, mouse_pos: (f32, f32), rect: PanelRect) {
self.drag_source = Some(DragSource {
panel_id,
source_rect: rect,
offset: (mouse_pos.0 - rect.x, mouse_pos.1 - rect.y),
});
}
pub fn update_hover(&mut self, container_id: LeafId, zone: DropZone, preview: PanelRect) {
if self.is_locked() {
return;
}
self.hover_target = Some(HoverTarget {
container_id,
zone,
preview_rect: preview,
});
}
pub fn clear_hover(&mut self) {
self.hover_target = None;
}
pub fn complete_drop(&mut self) -> Option<(LeafId, LeafId, DropZone)> {
if self.is_locked() {
return None;
}
let source = self.drag_source.take()?;
let target = self.hover_target.take()?;
self.lock = LockState::SoftLock;
self.lock_time = Some(Instant::now());
Some((source.panel_id, target.container_id, target.zone))
}
pub fn cancel_drag(&mut self) {
self.drag_source = None;
self.hover_target = None;
self.lock = LockState::Unlocked;
self.lock_time = None;
}
pub fn is_dragging(&self) -> bool {
self.drag_source.is_some()
}
pub fn is_locked(&self) -> bool {
match self.lock {
LockState::Unlocked => false,
LockState::HardLock => true,
LockState::SoftLock => {
if let Some(lock_time) = self.lock_time {
lock_time.elapsed() < Duration::from_millis(200)
} else {
false
}
}
}
}
pub fn update(&mut self) {
if self.lock == LockState::SoftLock {
if let Some(lock_time) = self.lock_time {
if lock_time.elapsed() >= Duration::from_millis(200) {
self.lock = LockState::Unlocked;
self.lock_time = None;
}
}
}
}
pub fn drag_source(&self) -> Option<&DragSource> {
self.drag_source.as_ref()
}
pub fn hover_target(&self) -> Option<&HoverTarget> {
self.hover_target.as_ref()
}
pub fn set_hard_lock(&mut self) {
self.lock = LockState::HardLock;
self.lock_time = Some(Instant::now());
}
pub fn unlock(&mut self) {
self.lock = LockState::Unlocked;
self.lock_time = None;
}
}
impl Default for DragDropState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct PanelDragState {
pub dragged_leaf_id: LeafId,
pub current_x: f32,
pub current_y: f32,
pub target_leaf_id: Option<LeafId>,
pub drop_zone: Option<DropZone>,
pub is_window_edge: bool,
}
impl PanelDragState {
pub fn new(dragged_leaf_id: LeafId, x: f32, y: f32) -> Self {
Self {
dragged_leaf_id,
current_x: x,
current_y: y,
target_leaf_id: None,
drop_zone: None,
is_window_edge: false,
}
}
pub fn update_position(&mut self, x: f32, y: f32) {
self.current_x = x;
self.current_y = y;
}
pub fn update_target(&mut self, target: LeafId, zone: DropZone, is_window_edge: bool) {
self.target_leaf_id = Some(target);
self.drop_zone = Some(zone);
self.is_window_edge = is_window_edge;
}
pub fn clear_target(&mut self) {
self.target_leaf_id = None;
self.drop_zone = None;
self.is_window_edge = false;
}
pub fn has_target(&self) -> bool {
self.target_leaf_id.is_some() && self.drop_zone.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drag_drop_lifecycle() {
let mut state = DragDropState::new();
assert!(!state.is_dragging());
let rect = PanelRect::new(0.0, 0.0, 100.0, 100.0);
state.start_drag(LeafId(1), (10.0, 10.0), rect);
assert!(state.is_dragging());
let preview = PanelRect::new(100.0, 0.0, 50.0, 100.0);
state.update_hover(LeafId(2), DropZone::Right, preview);
assert!(state.hover_target().is_some());
let result = state.complete_drop();
assert!(result.is_some());
assert!(!state.is_dragging());
assert!(state.is_locked()); }
#[test]
fn test_lock_prevents_hover_update() {
let mut state = DragDropState::new();
let rect = PanelRect::new(0.0, 0.0, 100.0, 100.0);
state.start_drag(LeafId(1), (10.0, 10.0), rect);
state.update_hover(LeafId(2), DropZone::Center, rect);
state.complete_drop();
assert!(state.is_locked());
state.start_drag(LeafId(3), (20.0, 20.0), rect);
state.update_hover(LeafId(4), DropZone::Left, rect);
assert!(state.drag_source().is_some());
}
#[test]
fn test_cancel_drag() {
let mut state = DragDropState::new();
let rect = PanelRect::new(0.0, 0.0, 100.0, 100.0);
state.start_drag(LeafId(1), (10.0, 10.0), rect);
assert!(state.is_dragging());
state.cancel_drag();
assert!(!state.is_dragging());
assert!(!state.is_locked());
}
#[test]
fn test_panel_drag_state() {
let mut state = PanelDragState::new(LeafId(1), 10.0, 20.0);
assert_eq!(state.dragged_leaf_id, LeafId(1));
assert_eq!(state.current_x, 10.0);
assert_eq!(state.current_y, 20.0);
assert!(!state.has_target());
state.update_position(30.0, 40.0);
assert_eq!(state.current_x, 30.0);
assert_eq!(state.current_y, 40.0);
state.update_target(LeafId(2), DropZone::Center, false);
assert!(state.has_target());
assert_eq!(state.target_leaf_id, Some(LeafId(2)));
assert_eq!(state.drop_zone, Some(DropZone::Center));
assert!(!state.is_window_edge);
state.clear_target();
assert!(!state.has_target());
assert_eq!(state.target_leaf_id, None);
assert_eq!(state.drop_zone, None);
}
#[test]
fn test_hard_lock() {
let mut state = DragDropState::new();
state.set_hard_lock();
assert!(state.is_locked());
assert_eq!(state.lock, LockState::HardLock);
state.update();
assert!(state.is_locked());
state.unlock();
assert!(!state.is_locked());
}
#[test]
fn test_soft_lock_timeout() {
let mut state = DragDropState::new();
let rect = PanelRect::new(0.0, 0.0, 100.0, 100.0);
state.start_drag(LeafId(1), (10.0, 10.0), rect);
state.update_hover(LeafId(2), DropZone::Center, rect);
state.complete_drop();
assert!(state.is_locked());
assert_eq!(state.lock, LockState::SoftLock);
state.update();
assert!(state.is_locked());
}
}