use crate::ecs::Point;
use crate::ecs::drag::DragConstraint;
use crate::ecs::drag::capture_guard::CaptureGuard;
use crate::ecs::pointer::PhysicalPoint;
use bevy_ecs::entity::Entity;
use std::cell::RefCell;
use std::time::Instant;
use windows::Win32::Foundation::HWND;
#[derive(Debug)]
pub enum DragState {
Idle,
Preparing {
entity: Entity,
start_pos: PhysicalPoint,
start_time: Instant,
capture_guard: CaptureGuard,
},
JustStarted {
entity: Entity,
start_pos: PhysicalPoint,
current_pos: PhysicalPoint,
start_time: Instant,
capture_guard: CaptureGuard,
},
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>,
capture_guard: CaptureGuard,
},
JustEnded {
entity: Entity,
position: PhysicalPoint,
cancelled: bool,
},
}
#[derive(Debug, Clone)]
pub enum DragStateSnapshot {
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,
},
}
impl DragStateSnapshot {
pub fn is_button_held(&self) -> bool {
matches!(
self,
Self::Preparing { .. } | Self::JustStarted { .. } | Self::Dragging { .. }
)
}
}
impl DragState {
pub fn is_button_held(&self) -> bool {
matches!(
self,
Self::Preparing { .. } | Self::JustStarted { .. } | Self::Dragging { .. }
)
}
pub fn snapshot(&self) -> DragStateSnapshot {
match self {
DragState::Idle => DragStateSnapshot::Idle,
DragState::Preparing {
entity,
start_pos,
start_time,
..
} => DragStateSnapshot::Preparing {
entity: *entity,
start_pos: *start_pos,
start_time: *start_time,
},
DragState::JustStarted {
entity,
start_pos,
current_pos,
start_time,
..
} => DragStateSnapshot::JustStarted {
entity: *entity,
start_pos: *start_pos,
current_pos: *current_pos,
start_time: *start_time,
},
DragState::Dragging {
entity,
start_pos,
current_pos,
prev_pos,
start_time,
hwnd,
initial_window_pos,
move_window,
constraint,
..
} => DragStateSnapshot::Dragging {
entity: *entity,
start_pos: *start_pos,
current_pos: *current_pos,
prev_pos: *prev_pos,
start_time: *start_time,
hwnd: *hwnd,
initial_window_pos: *initial_window_pos,
move_window: *move_window,
constraint: *constraint,
},
DragState::JustEnded {
entity,
position,
cancelled,
} => DragStateSnapshot::JustEnded {
entity: *entity,
position: *position,
cancelled: *cancelled,
},
}
}
}
thread_local! {
static DRAG_STATE: RefCell<DragState> = const { 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 snapshot_drag_state() -> DragStateSnapshot {
read_drag_state(|state| state.snapshot())
}
#[inline]
pub fn start_preparing(entity: Entity, pos: PhysicalPoint, hwnd: HWND) {
if read_drag_state(DragState::is_button_held) {
tracing::debug!("[drag] Already dragging, ignoring new button press");
return;
}
let capture_guard = CaptureGuard::acquire(hwnd);
let _previous = update_drag_state(|state| {
std::mem::replace(
state,
DragState::Preparing {
entity,
start_pos: pos,
start_time: Instant::now(),
capture_guard,
},
)
});
tracing::debug!(
entity = ?entity,
x = pos.x,
y = pos.y,
hwnd = format!("0x{:X}", hwnd.0 as usize),
"[start_preparing] DragState -> Preparing (with capture)"
);
}
#[inline]
pub fn start_dragging(current_pos: PhysicalPoint) {
update_drag_state(|state| {
if !matches!(state, DragState::Preparing { .. }) {
return;
}
let old = std::mem::replace(state, DragState::Idle);
if let DragState::Preparing {
entity,
start_pos,
start_time,
capture_guard,
} = old
{
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"
);
*state = DragState::JustStarted {
entity,
start_pos,
current_pos,
start_time,
capture_guard,
};
}
});
}
#[inline]
pub fn update_dragging(
current_pos: PhysicalPoint,
drag_context: Option<&crate::ecs::drag::WindowDragContextResource>,
) {
update_drag_state(|state| {
match state {
DragState::JustStarted { .. } => {
let old = std::mem::replace(state, DragState::Idle);
if let DragState::JustStarted {
entity,
start_pos,
start_time,
capture_guard,
..
} = old
{
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,
start_pos,
current_pos,
prev_pos: current_pos,
start_time,
hwnd,
initial_window_pos,
move_window,
constraint,
capture_guard,
};
}
}
DragState::Dragging { .. } => {
let old = std::mem::replace(state, DragState::Idle);
if let DragState::Dragging {
entity,
start_pos,
current_pos: old_pos,
start_time,
hwnd,
initial_window_pos,
move_window,
constraint,
capture_guard,
..
} = old
{
*state = DragState::Dragging {
entity,
start_pos,
current_pos,
prev_pos: old_pos,
start_time,
hwnd,
initial_window_pos,
move_window,
constraint,
capture_guard,
};
}
}
_ => {}
}
});
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DragClose {
Closed { entity: Entity, notified: bool },
OtherWindow,
NotActive,
}
fn held_parts(state: &mut DragState) -> Option<(&mut CaptureGuard, PhysicalPoint, PhysicalPoint)> {
match state {
DragState::Preparing {
capture_guard,
start_pos,
..
} => Some((capture_guard, *start_pos, *start_pos)),
DragState::JustStarted {
capture_guard,
start_pos,
current_pos,
..
}
| DragState::Dragging {
capture_guard,
start_pos,
current_pos,
..
} => Some((capture_guard, *start_pos, *current_pos)),
DragState::Idle | DragState::JustEnded { .. } => None,
}
}
fn close_drag(
pick: impl FnOnce(&mut DragState) -> Result<(PhysicalPoint, bool), DragClose>,
) -> DragClose {
let (result, _guard) = update_drag_state(|state| {
let (position, cancelled) = match pick(state) {
Ok(v) => v,
Err(result) => return (result, None),
};
let (entity, started, capture_guard) = match std::mem::replace(state, DragState::Idle) {
DragState::Preparing {
entity,
capture_guard,
..
} => (entity, false, capture_guard),
DragState::JustStarted {
entity,
capture_guard,
..
}
| DragState::Dragging {
entity,
capture_guard,
..
} => (entity, true, capture_guard),
other => {
tracing::error!(event = "drag_close_unexpected_state", state = ?other);
debug_assert!(false, "close_drag: pick が押していない状態で Ok を返した");
*state = other;
return (DragClose::NotActive, None);
}
};
*state = DragState::JustEnded {
entity,
position,
cancelled,
};
let notified =
started && crate::ecs::drag::accumulator::push_ended_seed(entity, position, cancelled);
tracing::debug!(
entity = ?entity,
x = position.x,
y = position.y,
cancelled,
notified,
"[drag] Dragging ended"
);
(DragClose::Closed { entity, notified }, Some(capture_guard))
});
result
}
#[inline]
pub fn end_dragging(position: PhysicalPoint, cancelled: bool) -> DragClose {
close_drag(|state| {
held_parts(state)
.map(|_| (position, cancelled))
.ok_or(DragClose::NotActive)
})
}
#[inline]
pub fn cancel_dragging() -> DragClose {
close_drag(|state| {
held_parts(state)
.map(|(_, start_pos, _)| (start_pos, true))
.ok_or(DragClose::NotActive)
})
}
#[inline]
pub fn cancel_dragging_on_capture_lost() -> DragClose {
close_drag(|state| {
held_parts(state)
.map(|(capture_guard, start_pos, _)| {
capture_guard.mark_released();
(start_pos, true)
})
.ok_or(DragClose::NotActive)
})
}
#[inline]
pub fn end_dragging_on_release(hwnd: HWND, position: Option<PhysicalPoint>) -> DragClose {
close_drag(|state| {
let (capture_guard, _, last_pos) = held_parts(state).ok_or(DragClose::NotActive)?;
if capture_guard.hwnd() != hwnd {
return Err(DragClose::OtherWindow);
}
Ok((position.unwrap_or(last_pos), false))
})
}
#[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
}
})
}
#[cfg(test)]
mod tests;