mod accumulator;
mod capture_guard;
mod context;
mod dispatch;
mod reentry;
mod state;
mod systems;
pub use accumulator::{
DragAccumulator, DragAccumulatorResource, DragTransition, FlushResult, install_drag_accumulator,
};
pub use capture_guard::CaptureGuard;
pub use context::{WindowDragContext, WindowDragContextResource};
pub use dispatch::{
DragEndEvent, DragEvent, DragStartEvent, OnDrag, OnDragEnd, OnDragStart, dispatch_drag_events,
};
pub(crate) use reentry::handle_message_while_world_busy;
pub use state::{
DragClose, DragState, DragStateSnapshot, cancel_dragging, cancel_dragging_on_capture_lost,
check_threshold, end_dragging, end_dragging_on_release, read_drag_state, snapshot_drag_state,
start_dragging, start_preparing, update_drag_state, update_dragging,
};
pub use systems::{cleanup_drag_state, rearm_tick_while_dragging};
use crate::ecs::pointer::PhysicalPoint;
use bevy_ecs::prelude::*;
#[derive(Component, Clone, Debug)]
#[component(storage = "SparseSet")]
pub struct DragConfig {
pub threshold: i32,
pub enabled: bool,
pub move_window: bool,
pub left_button: bool,
pub right_button: bool,
pub middle_button: bool,
}
impl Default for DragConfig {
fn default() -> Self {
Self {
threshold: 5,
enabled: true,
move_window: true,
left_button: true,
right_button: false,
middle_button: false,
}
}
}
#[derive(Component, Debug, Clone, Copy)]
#[component(storage = "SparseSet")]
pub struct DraggingState {
pub drag_start_pos: PhysicalPoint,
pub initial_inset: (f32, f32),
}
#[derive(Component, Clone, Copy, Debug)]
#[component(storage = "SparseSet")]
pub struct DragConstraint {
pub min_x: Option<i32>,
pub max_x: Option<i32>,
pub min_y: Option<i32>,
pub max_y: Option<i32>,
}
impl DragConstraint {
pub fn apply(&self, x: i32, y: i32) -> (i32, i32) {
let x = self.min_x.map_or(x, |min| x.max(min));
let x = self.max_x.map_or(x, |max| x.min(max));
let y = self.min_y.map_or(y, |min| y.max(min));
let y = self.max_y.map_or(y, |max| y.min(max));
(x, y)
}
}
#[derive(Component)]
#[component(storage = "SparseSet")]
pub struct WindowDragging;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_drag_config_default_values() {
let c = DragConfig::default();
assert_eq!(c.threshold, 5);
assert!(c.enabled);
assert!(c.move_window);
assert!(c.left_button, "左ボタンは既定で有効");
assert!(!c.right_button, "右ボタンは既定で無効");
assert!(!c.middle_button, "中ボタンは既定で無効");
}
#[test]
fn test_drag_constraint_apply_no_bounds_passthrough() {
let constraint = DragConstraint {
min_x: None,
max_x: None,
min_y: None,
max_y: None,
};
assert_eq!(constraint.apply(123, -456), (123, -456));
}
#[test]
fn test_drag_constraint_apply_within_bounds_unchanged() {
let constraint = DragConstraint {
min_x: Some(0),
max_x: Some(100),
min_y: Some(0),
max_y: Some(100),
};
assert_eq!(constraint.apply(50, 60), (50, 60));
}
#[test]
fn test_drag_constraint_apply_clamps_to_min() {
let constraint = DragConstraint {
min_x: Some(10),
max_x: None,
min_y: Some(20),
max_y: None,
};
assert_eq!(constraint.apply(-5, 0), (10, 20));
}
#[test]
fn test_drag_constraint_apply_clamps_to_max() {
let constraint = DragConstraint {
min_x: None,
max_x: Some(200),
min_y: None,
max_y: Some(300),
};
assert_eq!(constraint.apply(999, 999), (200, 300));
}
#[test]
fn test_drag_constraint_apply_axis_independent() {
let constraint = DragConstraint {
min_x: Some(0),
max_x: Some(10),
min_y: None,
max_y: None,
};
assert_eq!(constraint.apply(50, -777), (10, -777));
}
}