use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::time::Instant;
use bevy_ecs::message::Messages;
use windows::Win32::UI::WindowsAndMessaging::{
WM_ACTIVATE, WM_KEYDOWN, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MOUSEMOVE,
};
use super::wndproc_bridge_drag_test_support::{
CANCELMODE, CANCELS, CAPTURE_LOST, Cancel, DEACTIVATE, ESC, MOVED, PRESS, Rig, client_lparam,
rests_cancelled,
};
use crate::ecs::drag::{DragEndEvent, DraggingState, WindowDragging, snapshot_drag_state};
use crate::ecs::pointer::PhysicalPoint;
use crate::ecs::window::{KeepDirectlyAbove, SinkObservationPending};
use crate::ecs::world::{FrameCount, UISetup, tick_wake};
fn cancel_after_threshold_ends_once(kind: Cancel) {
let rig = Rig::new();
rig.start_drag();
let ret = rig.send_while_borrowed(kind.msg, kind.wparam, 0);
assert!(ret.is_none(), "{}: 入口は既定の手続きへ委ねる", kind.what);
assert!(
rests_cancelled(),
"{}: 再入で届いてもドラッグは取り消しの印つきで休む: {:?}",
kind.what,
snapshot_drag_state()
);
let ends = rig.dispatch_ends();
assert_eq!(
ends.len(),
1,
"{}: 再入の取り消しでも終了の知らせは 1 件: {ends:?}",
kind.what
);
let end = &ends[0];
assert_eq!(end.target, rig.target, "{}: 対象", kind.what);
assert!(end.cancelled, "{}: 取り消しの印つき", kind.what);
assert_eq!(end.position, PRESS, "{}: 位置は押した位置", kind.what);
}
#[test]
fn t01_reentrant_esc_after_threshold_ends_once_cancelled() {
cancel_after_threshold_ends_once(ESC);
}
#[test]
fn t01_reentrant_cancelmode_after_threshold_ends_once_cancelled() {
cancel_after_threshold_ends_once(CANCELMODE);
}
#[test]
fn t01_reentrant_deactivate_after_threshold_ends_once_cancelled() {
cancel_after_threshold_ends_once(DEACTIVATE);
}
#[test]
fn t01_reentrant_capture_lost_after_threshold_ends_once_cancelled() {
cancel_after_threshold_ends_once(CAPTURE_LOST);
}
fn cancel_after_threshold_clears_drag_markers(kind: Cancel) {
let rig = Rig::new();
rig.start_drag();
assert!(
rig.target_has::<DraggingState>() && rig.target_has::<WindowDragging>(),
"開始を配った後はドラッグ中の印が付いている"
);
rig.send_while_borrowed(kind.msg, kind.wparam, 0);
rig.dispatch_ends();
assert!(
!rig.target_has::<DraggingState>(),
"{}: 配った後 DraggingState が外れている",
kind.what
);
assert!(
!rig.target_has::<WindowDragging>(),
"{}: 配った後 WindowDragging が外れている",
kind.what
);
}
#[test]
fn t03_reentrant_esc_clears_drag_markers() {
cancel_after_threshold_clears_drag_markers(ESC);
}
#[test]
fn t03_reentrant_cancelmode_clears_drag_markers() {
cancel_after_threshold_clears_drag_markers(CANCELMODE);
}
#[test]
fn t03_reentrant_deactivate_clears_drag_markers() {
cancel_after_threshold_clears_drag_markers(DEACTIVATE);
}
#[test]
fn t03_reentrant_capture_lost_clears_drag_markers() {
cancel_after_threshold_clears_drag_markers(CAPTURE_LOST);
}
fn cancel_before_threshold_rests_without_end(kind: Cancel) {
let rig = Rig::new();
rig.press();
rig.send_while_borrowed(kind.msg, kind.wparam, 0);
assert!(
rests_cancelled(),
"{}: 閾値前でも再入の取り消しで状態は休む: {:?}",
kind.what,
snapshot_drag_state()
);
let ends = rig.dispatch_ends();
assert_eq!(
ends.len(),
0,
"{}: 閾値前の取り消しで終了の知らせは 0 件: {ends:?}",
kind.what
);
}
#[test]
fn t04_reentrant_esc_before_threshold_rests_without_end() {
cancel_before_threshold_rests_without_end(ESC);
}
#[test]
fn t04_reentrant_cancelmode_before_threshold_rests_without_end() {
cancel_before_threshold_rests_without_end(CANCELMODE);
}
#[test]
fn t04_reentrant_deactivate_before_threshold_rests_without_end() {
cancel_before_threshold_rests_without_end(DEACTIVATE);
}
#[test]
fn t04_reentrant_capture_lost_before_threshold_rests_without_end() {
cancel_before_threshold_rests_without_end(CAPTURE_LOST);
}
fn ends_per_dispatch(borrowed: bool) -> (usize, usize) {
let rig = Rig::new();
rig.start_drag();
if borrowed {
rig.send_while_borrowed(CANCELMODE.msg, CANCELMODE.wparam, 0);
} else {
rig.send(CANCELMODE.msg, CANCELMODE.wparam, 0);
}
(rig.dispatch_ends().len(), rig.dispatch_ends().len())
}
#[test]
fn t07_reentrant_end_is_dispatched_on_the_first_dispatch_like_the_ordinary_path() {
let _lock = crate::ecs::world::TICK_WAKE_TEST_LOCK
.lock()
.unwrap_or_else(|p| p.into_inner());
assert_eq!(ends_per_dispatch(false), (1, 0), "借用なしの道");
let rig = Rig::new();
rig.start_drag();
let _ = tick_wake::take(Instant::now());
rig.send_while_borrowed(CANCELMODE.msg, CANCELMODE.wparam, 0);
let woke = tick_wake::take(Instant::now());
let first = rig.dispatch_ends().len();
let second = rig.dispatch_ends().len();
assert_eq!(
(first, second),
(1, 0),
"再入の終了の知らせは借用を返した後の 1 回目の配る段で 1 件・2 回目は 0 件"
);
let expected = tick_wake::wake_bits_for_message(CANCELMODE.msg);
assert!(
woke.bits & expected.bits() == expected.bits(),
"再入の扱いの後、配送表の入口と同じ起床の旗が立っている: {:?}",
woke.bits
);
}
thread_local! {
static STASHED: RefCell<Option<Rc<Rig>>> = const { RefCell::new(None) };
static ARMED: Cell<bool> = const { Cell::new(false) };
static FIRED: Cell<u32> = const { Cell::new(0) };
}
fn deactivate_through_the_entrance_mid_tick() {
if !ARMED.with(|a| a.replace(false)) {
return;
}
let rig = STASHED.with(|s| s.borrow().clone());
if let Some(rig) = rig {
rig.send(DEACTIVATE.msg, DEACTIVATE.wparam, 0);
FIRED.with(|f| f.set(f.get() + 1));
}
}
fn tick(rig: &Rig) -> Vec<DragEndEvent> {
let mut w = rig.world.borrow_mut();
assert!(w.try_tick_world(), "画面更新が回る");
w.world_mut()
.resource_mut::<Messages<DragEndEvent>>()
.drain()
.collect()
}
fn stashed_dragging_rig() -> Rc<Rig> {
let rig = Rc::new(Rig::new());
rig.world
.borrow_mut()
.add_systems(UISetup, deactivate_through_the_entrance_mid_tick);
rig.start_drag();
STASHED.with(|s| *s.borrow_mut() = Some(rig.clone()));
FIRED.with(|f| f.set(0));
rig
}
#[test]
fn t07b_deactivate_inside_a_real_tick_ends_on_the_next_tick() {
let _lock = crate::ecs::world::TICK_WAKE_TEST_LOCK
.lock()
.unwrap_or_else(|p| p.into_inner());
{
let rig = stashed_dragging_rig();
assert_eq!(tick(&rig).len(), 0, "借用なしの道: 1 回目は何も起きない");
rig.send(DEACTIVATE.msg, DEACTIVATE.wparam, 0);
assert_eq!(tick(&rig).len(), 1, "借用なしの道: 2 回目で 1 件");
STASHED.with(|s| *s.borrow_mut() = None);
}
let rig = stashed_dragging_rig();
let _ = tick_wake::take(Instant::now());
ARMED.with(|a| a.set(true));
let first = tick(&rig);
assert_eq!(
FIRED.with(|f| f.get()),
1,
"テスト用システムが 1 回目の途中で入口を呼んだ"
);
let woke = tick_wake::take(Instant::now());
assert_eq!(
first.len(),
0,
"1 回目の終わりでは終了の知らせ 0 件: {first:?}"
);
assert!(
rests_cancelled(),
"1 回目の終わりで状態は取り消しの印つきで休んでいる: {:?}",
snapshot_drag_state()
);
let expected = tick_wake::wake_bits_for_message(DEACTIVATE.msg);
assert!(
woke.bits & expected.bits() == expected.bits(),
"1 回目の終わりで再入の扱いの起床の旗が立っている: {:?}",
woke.bits
);
let second = tick(&rig);
assert_eq!(second.len(), 1, "2 回目で終了の知らせ 1 件: {second:?}");
assert!(second[0].cancelled, "取り消しの印つき");
assert_eq!(second[0].target, rig.target, "対象");
assert!(
!rig.target_has::<DraggingState>(),
"2 回目の後 DraggingState が外れている"
);
assert!(
!rig.target_has::<WindowDragging>(),
"2 回目の後 WindowDragging が外れている"
);
STASHED.with(|s| *s.borrow_mut() = None);
}
const NOT_DRAG_MESSAGES: [(&str, u32, usize); 4] = [
("WM_MOUSEMOVE", WM_MOUSEMOVE, 0),
("WM_LBUTTONDOWN", WM_LBUTTONDOWN, 0),
("ESC でない WM_KEYDOWN(A)", WM_KEYDOWN, 0x41),
("活性化の WM_ACTIVATE(WA_ACTIVE)", WM_ACTIVATE, 1),
];
fn not_drag_messages_change_nothing(rig: &Rig, phase: &str) {
let acc = rig.accumulator();
let before_flush = acc.flush().expect("累積器の錠は毒化していない");
let before_state = format!("{:?}", snapshot_drag_state());
let ((), events) = log_capture_kit::capture(|| {
for (what, msg, wparam) in NOT_DRAG_MESSAGES {
let ret = rig.send_while_borrowed(msg, wparam, client_lparam(MOVED));
assert!(ret.is_none(), "{phase}: {what} は借用中は None");
}
});
assert_eq!(
format!("{:?}", snapshot_drag_state()),
before_state,
"{phase}: 5 種でないメッセージは状態を変えない"
);
let after = acc.flush().expect("累積器の錠は毒化していない");
assert!(
after.transitions.is_empty(),
"{phase}: 遷移は積まれない: {:?}",
after.transitions
);
assert_eq!(
after.delta,
PhysicalPoint::new(0, 0),
"{phase}: 動きは積まれない"
);
assert_eq!(
after.current_dragging_entity, before_flush.current_dragging_entity,
"{phase}: ドラッグ中の対象は変わらない"
);
assert_eq!(
after.current_position, before_flush.current_position,
"{phase}: 位置は変わらない"
);
let handled = events
.iter()
.filter(|e| e.field_str("event") == Some("drag_reentry_handled"))
.count();
assert_eq!(handled, 0, "{phase}: drag_reentry_handled の記録は 0 件");
}
#[test]
fn t08_non_drag_messages_while_borrowed_change_nothing() {
let rig = Rig::new();
rig.press();
not_drag_messages_change_nothing(&rig, "準備中");
let rig = Rig::new();
rig.start_drag();
not_drag_messages_change_nothing(&rig, "ドラッグ中");
}
#[test]
fn t13_reentrant_handling_returns_none_keeps_the_borrow_and_skips_side_work() {
let release = Cancel {
what: "左ボタンを離す(WM_LBUTTONUP)",
msg: WM_LBUTTONUP,
wparam: 0,
};
for kind in CANCELS.into_iter().chain([release]) {
let rig = Rig::new();
let balloon = rig
.world
.borrow_mut()
.world_mut()
.spawn(KeepDirectlyAbove { peer: rig.target })
.id();
rig.start_drag();
let frames = rig.world.borrow().world().resource::<FrameCount>().0;
{
let _held = rig.world.borrow_mut();
let ret = rig.send(kind.msg, kind.wparam, client_lparam(MOVED));
assert!(ret.is_none(), "{}: 再入の扱いは None を返す", kind.what);
assert!(
rig.world.try_borrow().is_err(),
"{}: 扱いの後も借用は握られたまま(横取りしない)",
kind.what
);
}
let w = rig.world.borrow();
assert_eq!(
w.world().resource::<FrameCount>().0,
frames,
"{}: 画面更新の回数は増えない",
kind.what
);
assert!(
!w.world()
.entity(balloon)
.contains::<SinkObservationPending>(),
"{}: 再入では沈降の観測の目印が付かない",
kind.what
);
}
let rig = Rig::new();
let balloon = rig
.world
.borrow_mut()
.world_mut()
.spawn(KeepDirectlyAbove { peer: rig.target })
.id();
rig.send(DEACTIVATE.msg, DEACTIVATE.wparam, 0);
assert!(
rig.world
.borrow()
.world()
.entity(balloon)
.contains::<SinkObservationPending>(),
"借用なしの非活性化では目印が付く"
);
}