use std::cell::RefCell;
use std::pin::Pin;
use std::rc::{Rc, Weak};
use bevy_ecs::message::{Message, Messages};
use bevy_ecs::prelude::Entity;
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, POINT, WPARAM};
use windows::Win32::Graphics::Gdi::ClientToScreen;
use windows::Win32::UI::Input::KeyboardAndMouse::VK_ESCAPE;
use windows::Win32::UI::WindowsAndMessaging::{
SWP_NOACTIVATE, SWP_NOZORDER, SetWindowPos, WINDOW_EX_STYLE, WM_ACTIVATE, WM_CANCELMODE,
WM_CAPTURECHANGED, WM_KEYDOWN, WM_MOUSEMOVE,
};
use super::{WndState, make_wndproc};
use crate::ecs::Point;
use crate::ecs::drag::{
DragAccumulatorResource, DragConfig, DragEndEvent, DragStartEvent, DragStateSnapshot,
cancel_dragging, dispatch_drag_events, snapshot_drag_state, start_preparing,
};
use crate::ecs::pointer::PhysicalPoint;
use crate::ecs::window::{Window, WindowHandle, WindowPos};
use crate::ecs::world::EcsWorld;
use crate::executor::util::{Window as LibWindow, WindowMessage, WindowType, get_instance_handle};
pub(super) const PRESS: PhysicalPoint = PhysicalPoint { x: 100, y: 100 };
pub(super) const MOVED: PhysicalPoint = PhysicalPoint { x: 130, y: 140 };
type Entrance = Box<dyn Fn(Pin<&WndState>, WindowMessage) -> Option<LRESULT>>;
pub(super) struct Rig {
window: Option<RealWindow>,
pub world: Rc<RefCell<EcsWorld>>,
pub target: Entity,
pub hwnd: HWND,
pub origin: PhysicalPoint,
state: Pin<Box<WndState>>,
entrance: Entrance,
}
impl Rig {
pub fn new() -> Self {
cancel_dragging();
let world = Rc::new(RefCell::new(EcsWorld::new()));
let target = spawn_drag_window(&world, PhysicalPoint::new(0, 0));
let state = Box::pin(WndState {
world: Rc::downgrade(&world),
entity: target,
});
Self {
window: None,
world,
target,
hwnd: HWND(std::ptr::null_mut()),
origin: PhysicalPoint::new(0, 0),
state,
entrance: Box::new(make_wndproc()),
}
}
pub fn with_real_window(at: PhysicalPoint) -> Self {
let mut rig = Self::new();
let window = RealWindow::new(&rig.world, rig.target, at);
rig.hwnd = window.hwnd();
rig.origin = window.client_origin();
rig.world
.borrow_mut()
.world_mut()
.entity_mut(rig.target)
.insert((
WindowPos {
position: Some(Point {
x: rig.origin.x,
y: rig.origin.y,
}),
..Default::default()
},
WindowHandle {
hwnd: rig.hwnd,
instance: get_instance_handle(),
},
));
rig.window = Some(window);
rig
}
pub fn screen(&self, client: PhysicalPoint) -> PhysicalPoint {
PhysicalPoint::new(client.x + self.origin.x, client.y + self.origin.y)
}
pub fn send(&self, msg: u32, wparam: usize, lparam: isize) -> Option<LRESULT> {
self.send_as(self.state.as_ref(), self.hwnd, msg, wparam, lparam)
}
pub fn send_as(
&self,
state: Pin<&WndState>,
hwnd: HWND,
msg: u32,
wparam: usize,
lparam: isize,
) -> Option<LRESULT> {
(self.entrance)(
state,
WindowMessage {
hwnd,
msg,
wparam: WPARAM(wparam),
lparam: LPARAM(lparam),
},
)
}
pub fn send_while_borrowed(&self, msg: u32, wparam: usize, lparam: isize) -> Option<LRESULT> {
let _held = self.world.borrow_mut();
self.send(msg, wparam, lparam)
}
pub fn press(&self) {
start_preparing(self.target, self.screen(PRESS), self.hwnd);
assert!(
matches!(snapshot_drag_state(), DragStateSnapshot::Preparing { .. }),
"押しの後は準備中: {:?}",
snapshot_drag_state()
);
}
pub fn cross_threshold(&self) {
self.send(WM_MOUSEMOVE, 0, client_lparam(MOVED));
assert!(
matches!(snapshot_drag_state(), DragStateSnapshot::JustStarted { .. }),
"閾値を越えた後は開始直後: {:?}",
snapshot_drag_state()
);
}
pub fn start_drag(&self) {
self.press();
self.cross_threshold();
let starts = self.dispatch::<DragStartEvent>();
assert_eq!(starts.len(), 1, "開始の知らせは 1 件: {starts:?}");
assert!(
matches!(snapshot_drag_state(), DragStateSnapshot::Dragging { .. }),
"開始を配った後はドラッグ中: {:?}",
snapshot_drag_state()
);
}
pub fn dispatch<E: Message>(&self) -> Vec<E> {
let mut w = self.world.borrow_mut();
let w = w.world_mut();
dispatch_drag_events(w);
w.resource_mut::<Messages<E>>().drain().collect()
}
pub fn dispatch_ends(&self) -> Vec<DragEndEvent> {
self.dispatch::<DragEndEvent>()
}
pub fn accumulator(&self) -> DragAccumulatorResource {
self.world
.borrow()
.world()
.resource::<DragAccumulatorResource>()
.clone()
}
pub fn target_has<C: bevy_ecs::component::Component>(&self) -> bool {
self.world
.borrow()
.world()
.entity(self.target)
.contains::<C>()
}
}
pub(super) fn spawn_drag_window(world: &Rc<RefCell<EcsWorld>>, origin: PhysicalPoint) -> Entity {
world
.borrow_mut()
.world_mut()
.spawn((
Window::default(),
WindowPos {
position: Some(Point {
x: origin.x,
y: origin.y,
}),
..Default::default()
},
DragConfig {
move_window: false,
..Default::default()
},
))
.id()
}
pub(super) fn client_lparam(p: PhysicalPoint) -> isize {
((p.y as u16 as isize) << 16) | (p.x as u16 as isize)
}
#[derive(Clone, Copy)]
pub(super) struct Cancel {
pub what: &'static str,
pub msg: u32,
pub wparam: usize,
}
pub(super) const ESC: Cancel = Cancel {
what: "ESC の押下(WM_KEYDOWN)",
msg: WM_KEYDOWN,
wparam: VK_ESCAPE.0 as usize,
};
pub(super) const CANCELMODE: Cancel = Cancel {
what: "メニューやダイアログの割り込み(WM_CANCELMODE)",
msg: WM_CANCELMODE,
wparam: 0,
};
pub(super) const DEACTIVATE: Cancel = Cancel {
what: "非活性化(WM_ACTIVATE・WA_INACTIVE)",
msg: WM_ACTIVATE,
wparam: 0, };
pub(super) const CAPTURE_LOST: Cancel = Cancel {
what: "捕捉の喪失(WM_CAPTURECHANGED)",
msg: WM_CAPTURECHANGED,
wparam: 0,
};
pub(super) const CANCELS: [Cancel; 4] = [ESC, CANCELMODE, DEACTIVATE, CAPTURE_LOST];
pub(super) fn rests_cancelled() -> bool {
matches!(
snapshot_drag_state(),
DragStateSnapshot::JustEnded {
cancelled: true,
..
}
)
}
pub(super) struct RealWindow {
window: Option<LibWindow<WndState>>,
world: Weak<RefCell<EcsWorld>>,
}
const REAL_WINDOW_SIZE: i32 = 240;
impl RealWindow {
pub fn new(world: &Rc<RefCell<EcsWorld>>, entity: Entity, at: PhysicalPoint) -> Self {
Self::with_wndproc(world, entity, at, make_wndproc())
}
pub fn with_wndproc(
world: &Rc<RefCell<EcsWorld>>,
entity: Entity,
at: PhysicalPoint,
wndproc: impl Fn(Pin<&WndState>, WindowMessage) -> Option<LRESULT> + 'static,
) -> Self {
let _held = world.borrow_mut();
let state = WndState {
world: Rc::downgrade(world),
entity,
};
let window = LibWindow::new_ex(WindowType::TopLevel, WINDOW_EX_STYLE(0), state, wndproc)
.expect("隠れた実物の窓を作れる");
unsafe {
SetWindowPos(
window.hwnd(),
None,
at.x,
at.y,
REAL_WINDOW_SIZE,
REAL_WINDOW_SIZE,
SWP_NOZORDER | SWP_NOACTIVATE,
)
.expect("窓を決まった位置へ置ける");
}
Self {
window: Some(window),
world: Rc::downgrade(world),
}
}
pub fn hwnd(&self) -> HWND {
self.window.as_ref().expect("窓は生きている").hwnd()
}
pub fn client_origin(&self) -> PhysicalPoint {
let mut p = POINT { x: 0, y: 0 };
let ok = unsafe { ClientToScreen(self.hwnd(), &mut p) };
assert!(ok.as_bool(), "ClientToScreen が実物の窓で失敗した");
PhysicalPoint::new(p.x, p.y)
}
}
impl Drop for RealWindow {
fn drop(&mut self) {
let world = self.world.upgrade();
let _held = world.as_ref().and_then(|w| w.try_borrow_mut().ok());
drop(self.window.take());
}
}