use crate::ecs::pointer::PhysicalPoint;
use bevy_ecs::prelude::*;
use std::cell::RefCell;
use std::sync::{Arc, Mutex};
use std::time::Instant;
#[derive(Debug, Clone)]
pub enum DragTransition {
Started {
entity: Entity,
start_pos: PhysicalPoint,
timestamp: Instant,
},
Ended {
entity: Entity,
end_pos: PhysicalPoint,
cancelled: bool,
},
}
#[derive(Debug)]
pub struct DragAccumulator {
accumulated_delta: PhysicalPoint,
current_dragging_entity: Option<Entity>,
current_pos: PhysicalPoint,
pending_transitions: Vec<DragTransition>,
}
impl DragAccumulator {
pub fn new() -> Self {
Self {
accumulated_delta: PhysicalPoint::new(0, 0),
current_dragging_entity: None,
current_pos: PhysicalPoint::new(0, 0),
pending_transitions: Vec::new(),
}
}
pub fn accumulate_delta(&mut self, delta: PhysicalPoint) {
self.accumulated_delta.x += delta.x;
self.accumulated_delta.y += delta.y;
}
pub fn update_position(&mut self, pos: PhysicalPoint) {
self.current_pos = pos;
}
pub fn set_transition(&mut self, transition: DragTransition) {
match &transition {
DragTransition::Started { entity, .. } => {
self.current_dragging_entity = Some(*entity);
}
DragTransition::Ended {
entity, cancelled, ..
} => {
if self.current_dragging_entity.is_none() {
tracing::debug!(
entity = ?entity,
cancelled,
"[DragAccumulator] Ended without Started dropped"
);
return;
}
self.current_dragging_entity = None;
}
}
self.pending_transitions.push(transition);
}
pub fn flush(&mut self) -> FlushResult {
let delta = self.accumulated_delta;
let transitions = std::mem::take(&mut self.pending_transitions);
let entity = self.current_dragging_entity;
let position = self.current_pos;
self.accumulated_delta = PhysicalPoint::new(0, 0);
FlushResult {
delta,
transitions,
current_dragging_entity: entity,
current_position: position,
}
}
}
impl Default for DragAccumulator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct FlushResult {
pub delta: PhysicalPoint,
pub transitions: Vec<DragTransition>,
pub current_dragging_entity: Option<Entity>,
pub current_position: PhysicalPoint,
}
#[derive(Resource, Clone)]
pub struct DragAccumulatorResource {
inner: Arc<Mutex<DragAccumulator>>,
}
impl DragAccumulatorResource {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(DragAccumulator::new())),
}
}
pub fn accumulate_delta(&self, delta: PhysicalPoint) {
if let Ok(mut acc) = self.inner.lock() {
acc.accumulate_delta(delta);
}
}
pub fn update_position(&self, pos: PhysicalPoint) {
if let Ok(mut acc) = self.inner.lock() {
acc.update_position(pos);
}
}
pub fn set_transition(&self, transition: DragTransition) {
match self.inner.lock() {
Ok(mut acc) => acc.set_transition(transition),
Err(_) => tracing::warn!(
event = "drag_accumulator_poisoned",
op = "set_transition",
transition = ?transition,
"[DragAccumulator] mutex poisoned, transition dropped"
),
}
}
pub fn flush(&self) -> Option<FlushResult> {
match self.inner.lock() {
Ok(mut acc) => Some(acc.flush()),
Err(_) => {
tracing::warn!(
event = "drag_accumulator_poisoned",
op = "flush",
"[DragAccumulator] mutex poisoned, nothing flushed"
);
None
}
}
}
}
impl Default for DragAccumulatorResource {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static WNDPROC_ACCUMULATOR: RefCell<Option<DragAccumulatorResource>> =
const { RefCell::new(None) };
}
pub fn install_drag_accumulator(world: &mut World) {
let accumulator = DragAccumulatorResource::new();
WNDPROC_ACCUMULATOR.with(|h| *h.borrow_mut() = Some(accumulator.clone()));
world.insert_resource(accumulator);
}
pub(crate) fn push_ended_seed(entity: Entity, end_pos: PhysicalPoint, cancelled: bool) -> bool {
let Some(accumulator) = WNDPROC_ACCUMULATOR.with(|h| h.borrow().clone()) else {
tracing::warn!(
event = "drag_end_seed_unreachable",
entity = ?entity,
cancelled,
"[DragAccumulator] no wndproc handle on this thread, Ended seed not pushed"
);
return false;
};
accumulator.set_transition(DragTransition::Ended {
entity,
end_pos,
cancelled,
});
true
}
#[cfg(test)]
#[path = "accumulator_tests.rs"]
mod accumulator_tests;