mod dpi_helpers;
mod drop_files;
mod keyboard;
mod lifecycle;
mod mouse_click;
mod mouse_dblclick_wheel;
mod mouse_move;
mod window_pos;
use crate::executor::util::WindowMessage;
use bevy_ecs::prelude::*;
use windows::Win32::Foundation::*;
use windows::Win32::UI::Controls::WM_MOUSELEAVE;
use windows::Win32::UI::WindowsAndMessaging::*;
use std::cell::RefCell;
use std::rc::Rc;
use crate::ecs::world::{EcsWorld, tick_wake};
pub(crate) fn dispatch_window_message(
world: &Rc<RefCell<EcsWorld>>,
entity: Entity,
msg: &WindowMessage,
) -> Option<LRESULT> {
let hwnd = msg.hwnd;
let wparam = msg.wparam;
let lparam = msg.lparam;
tick_wake::mark(tick_wake::wake_bits_for_message(msg.msg));
match msg.msg {
WM_ERASEBKGND => lifecycle::WM_ERASEBKGND(world, entity, hwnd, wparam, lparam),
WM_PAINT => lifecycle::WM_PAINT(world, entity, hwnd, wparam, lparam),
WM_CLOSE => lifecycle::WM_CLOSE(world, entity, hwnd, wparam, lparam),
WM_ENDSESSION => lifecycle::WM_ENDSESSION(world, entity, hwnd, wparam, lparam),
WM_WINDOWPOSCHANGED => window_pos::WM_WINDOWPOSCHANGED(world, entity, hwnd, wparam, lparam),
WM_DISPLAYCHANGE => lifecycle::WM_DISPLAYCHANGE(world, entity, hwnd, wparam, lparam),
WM_DPICHANGED => window_pos::WM_DPICHANGED(world, entity, hwnd, wparam, lparam),
WM_SETCURSOR => mouse_move::WM_SETCURSOR(world, entity, hwnd, wparam, lparam),
WM_NCHITTEST => mouse_move::WM_NCHITTEST(world, entity, hwnd, wparam, lparam),
WM_MOUSEMOVE => mouse_move::WM_MOUSEMOVE(world, entity, hwnd, wparam, lparam),
WM_MOUSELEAVE => mouse_move::WM_MOUSELEAVE(world, entity, hwnd, wparam, lparam),
WM_LBUTTONDOWN => mouse_click::WM_LBUTTONDOWN(world, entity, hwnd, wparam, lparam),
WM_LBUTTONUP => mouse_click::WM_LBUTTONUP(world, entity, hwnd, wparam, lparam),
WM_RBUTTONDOWN => mouse_click::WM_RBUTTONDOWN(world, entity, hwnd, wparam, lparam),
WM_RBUTTONUP => mouse_click::WM_RBUTTONUP(world, entity, hwnd, wparam, lparam),
WM_MBUTTONDOWN => mouse_click::WM_MBUTTONDOWN(world, entity, hwnd, wparam, lparam),
WM_MBUTTONUP => mouse_click::WM_MBUTTONUP(world, entity, hwnd, wparam, lparam),
WM_XBUTTONDOWN => mouse_click::WM_XBUTTONDOWN(world, entity, hwnd, wparam, lparam),
WM_XBUTTONUP => mouse_click::WM_XBUTTONUP(world, entity, hwnd, wparam, lparam),
WM_LBUTTONDBLCLK => {
mouse_dblclick_wheel::WM_LBUTTONDBLCLK(world, entity, hwnd, wparam, lparam)
}
WM_RBUTTONDBLCLK => {
mouse_dblclick_wheel::WM_RBUTTONDBLCLK(world, entity, hwnd, wparam, lparam)
}
WM_MBUTTONDBLCLK => {
mouse_dblclick_wheel::WM_MBUTTONDBLCLK(world, entity, hwnd, wparam, lparam)
}
WM_XBUTTONDBLCLK => {
mouse_dblclick_wheel::WM_XBUTTONDBLCLK(world, entity, hwnd, wparam, lparam)
}
WM_MOUSEWHEEL => mouse_dblclick_wheel::WM_MOUSEWHEEL(world, entity, hwnd, wparam, lparam),
WM_MOUSEHWHEEL => mouse_dblclick_wheel::WM_MOUSEHWHEEL(world, entity, hwnd, wparam, lparam),
WM_KEYDOWN => keyboard::WM_KEYDOWN(world, entity, hwnd, wparam, lparam),
WM_CANCELMODE => keyboard::WM_CANCELMODE(world, entity, hwnd, wparam, lparam),
WM_ACTIVATE => keyboard::WM_ACTIVATE(world, entity, hwnd, wparam, lparam),
WM_CAPTURECHANGED => keyboard::WM_CAPTURECHANGED(world, entity, hwnd, wparam, lparam),
WM_DROPFILES => drop_files::WM_DROPFILES(world, entity, hwnd, wparam, lparam),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecs::App;
use crate::ecs::world::EcsWorld;
fn msg(message: u32) -> WindowMessage {
WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg: message,
wparam: WPARAM(0),
lparam: LPARAM(0),
}
}
#[test]
fn dispatch_routes_erasebkgnd_to_some_lresult_1() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = world.borrow_mut().world_mut().spawn(()).id();
let ret = dispatch_window_message(&world, entity, &msg(WM_ERASEBKGND));
assert_eq!(
ret,
Some(LRESULT(1)),
"WM_ERASEBKGND は背景消去抑止の Some(LRESULT(1)) を返すべき"
);
}
#[test]
fn dispatch_returns_none_for_unhandled_message() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = world.borrow_mut().world_mut().spawn(()).id();
let ret = dispatch_window_message(&world, entity, &msg(WM_USER));
assert!(
ret.is_none(),
"配送表に無い WM_USER は None(DefWindowProcW 委譲)であるべき"
);
}
#[test]
fn dispatch_displaychange_marks_app_display_change() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = {
let mut w = world.borrow_mut();
w.world_mut().insert_resource(App::new());
w.world_mut().spawn(()).id()
};
assert!(
!world
.borrow()
.world()
.resource::<App>()
.display_configuration_changed(),
"初期状態は display change 未設定"
);
let ret = dispatch_window_message(&world, entity, &msg(WM_DISPLAYCHANGE));
assert!(ret.is_none(), "WM_DISPLAYCHANGE は None を返すべき");
assert!(
world
.borrow()
.world()
.resource::<App>()
.display_configuration_changed(),
"WM_DISPLAYCHANGE 配送後は display change が設定されるべき"
);
}
#[test]
fn dispatch_routes_lbuttondblclk_to_some_lresult_0() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = world.borrow_mut().world_mut().spawn(()).id();
let ret = dispatch_window_message(&world, entity, &msg(WM_LBUTTONDBLCLK));
assert_eq!(
ret,
Some(LRESULT(0)),
"WM_LBUTTONDBLCLK はダブルクリック処理後 Some(LRESULT(0)) を返すべき(panic 無し)"
);
}
#[test]
fn dispatch_routes_windowposchanged_to_none() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = world.borrow_mut().world_mut().spawn(()).id();
let windowpos = WINDOWPOS {
hwnd: HWND(std::ptr::null_mut()),
hwndInsertAfter: HWND(std::ptr::null_mut()),
x: 100,
y: 200,
cx: 640,
cy: 480,
flags: SET_WINDOW_POS_FLAGS(0),
};
let m = WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg: WM_WINDOWPOSCHANGED,
wparam: WPARAM(0),
lparam: LPARAM(&windowpos as *const WINDOWPOS as isize),
};
let ret = dispatch_window_message(&world, entity, &m);
assert!(
ret.is_none(),
"WM_WINDOWPOSCHANGED は None(DefWindowProcW 委譲)を返すべき"
);
}
#[test]
fn dispatch_routes_dpichanged_to_some_lresult_0() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = world.borrow_mut().world_mut().spawn(()).id();
let m = WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg: WM_DPICHANGED,
wparam: WPARAM(((96u32) << 16 | 96u32) as usize),
lparam: LPARAM(0),
};
let ret = dispatch_window_message(&world, entity, &m);
assert_eq!(
ret,
Some(LRESULT(0)),
"WM_DPICHANGED は処理後 Some(LRESULT(0)) を返すべき(panic 無し)"
);
}
#[test]
fn dispatch_activate_inactive_marks_the_pair_for_sink_observation() {
use crate::ecs::window::{KeepDirectlyAbove, SinkObservationPending};
let world = Rc::new(RefCell::new(EcsWorld::new()));
let (character, balloon) = {
let mut w = world.borrow_mut();
let w = w.world_mut();
let character = w.spawn(()).id();
let balloon = w.spawn(KeepDirectlyAbove { peer: character }).id();
(character, balloon)
};
let m = WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg: WM_ACTIVATE,
wparam: WPARAM(0), lparam: LPARAM(0),
};
let ret = dispatch_window_message(&world, character, &m);
assert!(
ret.is_none(),
"WM_ACTIVATE は None(DefWindowProcW 委譲)を返すべき"
);
assert!(
world
.borrow()
.world()
.entity(balloon)
.contains::<SinkObservationPending>(),
"非活性化の受理でペアの宣言側へ目印が付くべき"
);
let stray_world = Rc::new(RefCell::new(EcsWorld::new()));
let (stray_char, stray_balloon) = {
let mut w = stray_world.borrow_mut();
let w = w.world_mut();
let character = w.spawn(()).id();
let balloon = w.spawn(KeepDirectlyAbove { peer: character }).id();
(character, balloon)
};
let active = WindowMessage {
wparam: WPARAM(1), ..m
};
dispatch_window_message(&stray_world, stray_char, &active);
assert!(
!stray_world
.borrow()
.world()
.entity(stray_balloon)
.contains::<SinkObservationPending>(),
"活性化では目印を付けないべき"
);
}
#[test]
fn dispatch_returns_none_for_ncdestroy_excluded_from_table() {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let entity = world.borrow_mut().world_mut().spawn(()).id();
let ret = dispatch_window_message(&world, entity, &msg(WM_NCDESTROY));
assert!(
ret.is_none(),
"WM_NCDESTROY は配送表に無く None(DefWindowProcW 委譲)であるべき(破棄は registry drop 駆動)"
);
}
}