use crate::ecs::drag::DragConstraint;
use crate::ecs::pointer::PhysicalPoint;
use bevy_ecs::entity::Entity;
use std::cell::RefCell;
use std::time::Instant;
use windows::Win32::Foundation::{HWND, POINT};
#[derive(Debug, Clone)]
pub enum DragState {
Idle,
Preparing {
entity: Entity,
start_pos: PhysicalPoint,
start_time: Instant,
},
JustStarted {
entity: Entity,
start_pos: PhysicalPoint,
current_pos: PhysicalPoint,
start_time: Instant,
},
Dragging {
entity: Entity,
start_pos: PhysicalPoint,
current_pos: PhysicalPoint,
prev_pos: PhysicalPoint,
start_time: Instant,
hwnd: HWND,
initial_window_pos: POINT,
move_window: bool,
constraint: Option<DragConstraint>,
},
JustEnded {
entity: Entity,
position: PhysicalPoint,
cancelled: bool,
},
}
thread_local! {
static DRAG_STATE: RefCell<DragState> = RefCell::new(DragState::Idle);
}
pub fn update_drag_state<F, R>(f: F) -> R
where
F: FnOnce(&mut DragState) -> R,
{
DRAG_STATE.with(|state| {
let mut state = state.borrow_mut();
f(&mut *state)
})
}
pub fn read_drag_state<F, R>(f: F) -> R
where
F: FnOnce(&DragState) -> R,
{
DRAG_STATE.with(|state| {
let state = state.borrow();
f(&*state)
})
}
#[inline]
pub fn start_preparing(entity: Entity, pos: PhysicalPoint) {
update_drag_state(|state| {
if matches!(
state,
DragState::Preparing { .. }
| DragState::JustStarted { .. }
| DragState::Dragging { .. }
) {
tracing::debug!("[drag] Already dragging, ignoring new button press");
return;
}
*state = DragState::Preparing {
entity,
start_pos: pos,
start_time: Instant::now(),
};
tracing::debug!(
entity = ?entity,
x = pos.x,
y = pos.y,
"[start_preparing] DragState -> Preparing"
);
});
}
#[inline]
pub fn start_dragging(current_pos: PhysicalPoint) {
update_drag_state(|state| {
if let DragState::Preparing {
entity,
start_pos,
start_time,
} = *state
{
*state = DragState::JustStarted {
entity,
start_pos,
current_pos,
start_time,
};
tracing::debug!(
entity = ?entity,
start_x = start_pos.x,
start_y = start_pos.y,
current_x = current_pos.x,
current_y = current_pos.y,
"[drag] Dragging started"
);
}
});
}
#[inline]
pub fn update_dragging(
current_pos: PhysicalPoint,
drag_context: Option<&crate::ecs::drag::WindowDragContextResource>,
) {
update_drag_state(|state| match state {
DragState::JustStarted {
entity,
start_pos,
start_time,
..
} => {
let (hwnd, initial_window_pos, move_window, constraint) =
if let Some(ctx_res) = drag_context {
if let Some(ctx) = ctx_res.get() {
(
ctx.hwnd.unwrap_or(HWND::default()),
ctx.initial_window_pos.unwrap_or(POINT { x: 0, y: 0 }),
ctx.move_window,
ctx.constraint,
)
} else {
(HWND::default(), POINT { x: 0, y: 0 }, false, None)
}
} else {
(HWND::default(), POINT { x: 0, y: 0 }, false, None)
};
tracing::debug!(
entity = ?*entity,
hwnd = format!("0x{:X}", hwnd.0 as usize),
initial_x = initial_window_pos.x,
initial_y = initial_window_pos.y,
move_window = move_window,
"[update_dragging] JustStarted -> Dragging with WindowDragContext"
);
*state = DragState::Dragging {
entity: *entity,
start_pos: *start_pos,
current_pos,
prev_pos: current_pos,
start_time: *start_time,
hwnd,
initial_window_pos,
move_window,
constraint,
};
}
DragState::Dragging {
current_pos: old_pos,
..
} => {
let prev_pos = *old_pos;
if let DragState::Dragging {
entity,
start_pos,
start_time,
hwnd,
initial_window_pos,
move_window,
constraint,
..
} = state.clone()
{
*state = DragState::Dragging {
entity,
start_pos,
current_pos,
prev_pos,
start_time,
hwnd,
initial_window_pos,
move_window,
constraint,
};
}
}
_ => {}
});
}
#[inline]
pub fn end_dragging(position: PhysicalPoint, cancelled: bool) {
update_drag_state(|state| match state {
DragState::Preparing { entity, .. }
| DragState::JustStarted { entity, .. }
| DragState::Dragging { entity, .. } => {
let entity = *entity;
*state = DragState::JustEnded {
entity,
position,
cancelled,
};
tracing::debug!(
entity = ?entity,
x = position.x,
y = position.y,
cancelled,
"[drag] Dragging ended"
);
}
_ => {}
});
}
#[inline]
pub fn cancel_dragging() {
update_drag_state(|state| match state {
DragState::Preparing {
entity, start_pos, ..
}
| DragState::JustStarted {
entity, start_pos, ..
}
| DragState::Dragging {
entity, start_pos, ..
} => {
let entity = *entity;
let position = *start_pos;
*state = DragState::JustEnded {
entity,
position,
cancelled: true,
};
tracing::debug!(
entity = ?entity,
"[drag] Dragging cancelled"
);
}
_ => {}
});
}
#[inline]
pub fn reset_to_idle() {
update_drag_state(|state| {
if matches!(state, DragState::JustEnded { .. }) {
*state = DragState::Idle;
}
});
}
#[inline]
pub fn check_threshold(current_pos: PhysicalPoint, threshold: i32) -> bool {
read_drag_state(|state| {
if let DragState::Preparing { start_pos, .. } = state {
let dx = current_pos.x - start_pos.x;
let dy = current_pos.y - start_pos.y;
let distance_sq = dx * dx + dy * dy;
let threshold_sq = threshold * threshold;
let result = distance_sq >= threshold_sq;
tracing::debug!(
dx,
dy,
distance_sq,
threshold_sq,
result,
"[check_threshold]"
);
result
} else {
tracing::warn!(state = ?state, "[check_threshold] Not in Preparing state");
false
}
})
}