use std::cell::RefCell;
use std::rc::Rc;
use std::time::Instant;
use bevy_ecs::message::Messages;
use bevy_ecs::prelude::Entity;
use windows::Win32::Foundation::{HWND, LPARAM, WPARAM};
use windows::Win32::UI::Input::KeyboardAndMouse::VK_ESCAPE;
use windows::Win32::UI::WindowsAndMessaging::{
WM_ACTIVATE, WM_CANCELMODE, WM_CAPTURECHANGED, WM_KEYDOWN,
};
use crate::ecs::drag::{
DragAccumulatorResource, DragEndEvent, DragStateSnapshot, DragTransition, cancel_dragging,
dispatch_drag_events, snapshot_drag_state, start_dragging, start_preparing,
};
use crate::ecs::pointer::PhysicalPoint;
use crate::ecs::world::EcsWorld;
use crate::executor::util::WindowMessage;
#[test]
fn deactivation_right_after_the_threshold_cancels_the_drag() {
cancel_dragging();
let world = Rc::new(RefCell::new(EcsWorld::new()));
let (entity, accumulator) = {
let mut w = world.borrow_mut();
let w = w.world_mut();
let accumulator = w.resource::<DragAccumulatorResource>().clone();
(w.spawn_empty().id(), accumulator)
};
start_preparing(entity, PhysicalPoint::new(10, 20), HWND::default());
start_dragging(PhysicalPoint::new(40, 50));
assert!(matches!(
snapshot_drag_state(),
DragStateSnapshot::JustStarted { .. }
));
accumulator.set_transition(DragTransition::Started {
entity,
start_pos: PhysicalPoint::new(10, 20),
timestamp: Instant::now(),
});
accumulator.flush();
let message = WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg: WM_ACTIVATE,
wparam: WPARAM(0), lparam: LPARAM(0),
};
crate::ecs::dispatch_window_message(&world, entity, &message);
assert!(
matches!(
snapshot_drag_state(),
DragStateSnapshot::JustEnded {
cancelled: true,
..
}
),
"JustStarted のまま非活性化してもドラッグは中断される"
);
let transitions = accumulator
.flush()
.map(|flushed| flushed.transitions)
.unwrap_or_default();
assert!(
matches!(
transitions.as_slice(),
[DragTransition::Ended {
cancelled: true,
..
}]
),
"中断の遷移が積まれている: {transitions:?}"
);
}
fn setup_preparing() -> (Rc<RefCell<EcsWorld>>, Entity) {
cancel_dragging();
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = world.borrow_mut().world_mut().spawn_empty().id();
start_preparing(entity, PhysicalPoint::new(10, 20), HWND::default());
assert!(matches!(
snapshot_drag_state(),
DragStateSnapshot::Preparing { .. }
));
(world, entity)
}
fn send(world: &Rc<RefCell<EcsWorld>>, entity: Entity, msg: u32, wparam: usize) {
let message = WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg,
wparam: WPARAM(wparam),
lparam: LPARAM(0),
};
crate::ecs::dispatch_window_message(world, entity, &message);
}
fn dispatch_once(world: &Rc<RefCell<EcsWorld>>) -> Vec<DragEndEvent> {
let mut w = world.borrow_mut();
let w = w.world_mut();
dispatch_drag_events(w);
w.resource_mut::<Messages<DragEndEvent>>().drain().collect()
}
fn assert_cancel_before_threshold_dispatches_no_drag_end(msg: u32, wparam: usize, what: &str) {
let (world, entity) = setup_preparing();
send(&world, entity, msg, wparam);
assert!(
matches!(
snapshot_drag_state(),
DragStateSnapshot::JustEnded {
cancelled: true,
..
}
),
"閾値前の{what}で状態は JustEnded{{cancelled:true}} になる: {:?}",
snapshot_drag_state()
);
let ends = dispatch_once(&world);
assert_eq!(
ends.len(),
0,
"閾値前の{what}で終了の知らせは 0 件のはず: {ends:?}"
);
}
#[test]
fn esc_before_threshold_dispatches_no_drag_end() {
assert_cancel_before_threshold_dispatches_no_drag_end(WM_KEYDOWN, VK_ESCAPE.0 as usize, "ESC");
}
#[test]
fn cancelmode_before_threshold_dispatches_no_drag_end() {
assert_cancel_before_threshold_dispatches_no_drag_end(
WM_CANCELMODE,
0,
"メニューやダイアログの割り込み(WM_CANCELMODE)",
);
}
#[test]
fn capturechanged_before_threshold_dispatches_no_drag_end() {
assert_cancel_before_threshold_dispatches_no_drag_end(
WM_CAPTURECHANGED,
0,
"捕捉の喪失(WM_CAPTURECHANGED)",
);
}
#[test]
fn deactivation_before_threshold_dispatches_no_drag_end() {
let (world, entity) = setup_preparing();
send(&world, entity, WM_ACTIVATE, 0);
let ends = dispatch_once(&world);
assert_eq!(
ends.len(),
0,
"閾値前の非活性化で終了の知らせは 0 件: {ends:?}"
);
}