#![allow(non_snake_case)]
use bevy_ecs::change_detection::DetectChangesMut;
use tracing::{debug, info, trace, warn};
use windows::Win32::Foundation::*;
use windows::Win32::UI::WindowsAndMessaging::*;
type HandlerResult = Option<LRESULT>;
#[inline]
pub(super) fn WM_NCCREATE(
hwnd: HWND,
_message: u32,
_wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
let cs = lparam.0 as *const CREATESTRUCTW;
if !cs.is_null() {
let entity_bits = unsafe { (*cs).lpCreateParams as isize };
unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, entity_bits) };
}
None }
#[inline]
pub(super) fn WM_NCDESTROY(
hwnd: HWND,
_message: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
if let Some(entity) = super::get_entity_from_hwnd(hwnd) {
if let Some(world) = super::try_get_ecs_world() {
let mut world = world.borrow_mut();
world.world_mut().despawn(entity);
}
}
unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0) };
None }
#[inline]
pub(super) fn WM_ERASEBKGND(
_hwnd: HWND,
_message: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
Some(LRESULT(1)) }
#[inline]
pub(super) fn WM_PAINT(
hwnd: HWND,
_message: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
use windows::Win32::Graphics::Gdi::ValidateRect;
let _ = unsafe { ValidateRect(Some(hwnd), None) };
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_CLOSE(
hwnd: HWND,
_message: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
let _ = unsafe { DestroyWindow(hwnd) };
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_WINDOWPOSCHANGED(
hwnd: HWND,
_message: u32,
_wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
let is_echo = crate::ecs::window::is_self_initiated();
if let Some(entity) = super::get_entity_from_hwnd(hwnd) {
if let Some(world) = super::try_get_ecs_world() {
let dpi_context = crate::ecs::window::DpiChangeContext::take();
if let Ok(mut world_borrow) = world.try_borrow_mut() {
let windowpos = lparam.0 as *const WINDOWPOS;
if !windowpos.is_null() {
let wp = unsafe { &*windowpos };
if let Ok(mut entity_ref) = world_borrow.world_mut().get_entity_mut(entity) {
let dpi = if let Some(ref ctx) = dpi_context {
if let Some(mut dpi_comp) =
entity_ref.get_mut::<crate::ecs::window::DPI>()
{
let old_dpi = *dpi_comp;
*dpi_comp = ctx.new_dpi;
trace!(
entity = ?entity,
old_dpi_x = old_dpi.dpi_x,
old_dpi_y = old_dpi.dpi_y,
new_dpi_x = ctx.new_dpi.dpi_x,
new_dpi_y = ctx.new_dpi.dpi_y,
"DPI updated via DpiChangeContext"
);
}
ctx.new_dpi
} else {
entity_ref
.get::<crate::ecs::window::DPI>()
.copied()
.unwrap_or_default()
};
let client_coords = entity_ref
.get::<crate::ecs::window::WindowHandle>()
.and_then(|handle| {
handle
.window_to_client_coords(wp.x, wp.y, wp.cx, wp.cy)
.ok()
});
if let Some((client_pos, client_size)) = client_coords {
debug!(
is_echo = is_echo,
entity = ?entity,
x = client_pos.x,
y = client_pos.y,
cx = client_size.cx,
cy = client_size.cy,
"[WM_WINDOWPOSCHANGED] Processing"
);
if let Some(mut window_pos) =
entity_ref.get_mut::<crate::ecs::window::WindowPos>()
{
if is_echo {
let bypass = window_pos.bypass_change_detection();
bypass.position = Some(client_pos);
bypass.size = Some(client_size);
trace!(
entity = ?entity,
client_x = client_pos.x,
client_y = client_pos.y,
"WindowPos updated via bypass (echo)"
);
} else {
window_pos.position = Some(client_pos);
window_pos.size = Some(client_size);
trace!(
entity = ?entity,
window_x = wp.x,
window_y = wp.y,
window_cx = wp.cx,
window_cy = wp.cy,
client_x = client_pos.x,
client_y = client_pos.y,
client_cx = client_size.cx,
client_cy = client_size.cy,
"WindowPos updated (external change)"
);
}
}
{
use crate::ecs::layout::{BoxSize, Dimension};
let physical_width = client_size.cx as f32;
let physical_height = client_size.cy as f32;
let scale_x = dpi.scale_x();
let scale_y = dpi.scale_y();
let logical_width = physical_width / scale_x;
let logical_height = physical_height / scale_y;
let new_size = Some(BoxSize {
width: Some(Dimension::Px(logical_width)),
height: Some(Dimension::Px(logical_height)),
});
let current_size = entity_ref
.get::<crate::ecs::layout::BoxStyle>()
.map(|bs| bs.size);
let size_changed =
current_size.map(|cs| cs != new_size).unwrap_or(false);
if size_changed {
if let Some(mut box_style) =
entity_ref.get_mut::<crate::ecs::layout::BoxStyle>()
{
box_style.size = new_size;
}
trace!(
entity = ?entity,
physical_width = physical_width,
physical_height = physical_height,
logical_width = logical_width,
logical_height = logical_height,
scale_x = scale_x,
scale_y = scale_y,
"[WM_WINDOWPOSCHANGED] BoxStyle.size updated (size changed)"
);
} else {
trace!(
entity = ?entity,
"[WM_WINDOWPOSCHANGED] BoxStyle.size unchanged, skipping update"
);
}
}
}
}
}
}
{
use crate::ecs::world::VsyncTick;
let _ = world.try_tick_on_vsync();
}
crate::ecs::window::flush_window_pos_commands();
}
}
None }
#[inline]
pub(super) fn WM_DISPLAYCHANGE(
_hwnd: HWND,
_message: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
if let Some(world) = super::try_get_ecs_world() {
if let Ok(mut world_borrow) = world.try_borrow_mut() {
if let Some(mut app) = world_borrow
.world_mut()
.get_resource_mut::<crate::ecs::App>()
{
app.mark_display_change();
}
}
}
None }
#[inline]
pub(super) fn WM_DPICHANGED(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
let new_dpi = crate::ecs::window::DPI::from_WM_DPICHANGED(wparam, lparam);
let suggested_rect_ptr = lparam.0 as *const RECT;
let suggested_rect = if !suggested_rect_ptr.is_null() {
unsafe { *suggested_rect_ptr }
} else {
RECT::default()
};
debug!(
hwnd = ?hwnd,
dpi_x = new_dpi.dpi_x,
dpi_y = new_dpi.dpi_y,
scale_x = format_args!("{:.2}", new_dpi.scale_x()),
scale_y = format_args!("{:.2}", new_dpi.scale_y()),
suggested_left = suggested_rect.left,
suggested_top = suggested_rect.top,
suggested_right = suggested_rect.right,
suggested_bottom = suggested_rect.bottom,
"WM_DPICHANGED"
);
crate::ecs::window::DpiChangeContext::set(crate::ecs::window::DpiChangeContext::new(
new_dpi,
suggested_rect,
));
let width = suggested_rect.right - suggested_rect.left;
let height = suggested_rect.bottom - suggested_rect.top;
trace!(
hwnd = ?hwnd,
x = suggested_rect.left,
y = suggested_rect.top,
width,
height,
"Calling guarded_set_window_pos with suggested_rect"
);
let result = unsafe {
crate::ecs::window::guarded_set_window_pos(
hwnd,
None,
suggested_rect.left,
suggested_rect.top,
width,
height,
SWP_NOZORDER | SWP_NOACTIVATE,
)
};
if let Err(e) = result {
warn!(hwnd = ?hwnd, error = ?e, "SetWindowPos failed in WM_DPICHANGED");
}
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_NCHITTEST(
hwnd: HWND,
_message: u32,
_wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
use windows::Win32::UI::WindowsAndMessaging::GetClientRect;
let x = (lparam.0 & 0xFFFF) as i16 as i32;
let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as i32;
unsafe {
use windows::Win32::Graphics::Gdi::ScreenToClient;
let mut pt = POINT { x, y };
if !ScreenToClient(hwnd, &mut pt).as_bool() {
return None; }
let mut rect = RECT::default();
if GetClientRect(hwnd, &mut rect).is_err() {
return None;
}
if pt.x < rect.left || pt.x >= rect.right || pt.y < rect.top || pt.y >= rect.bottom {
return None; }
}
let Some(entity) = super::get_entity_from_hwnd(hwnd) else {
return None;
};
let Some(world) = super::try_get_ecs_world() else {
return None;
};
crate::ecs::nchittest_cache::cached_nchittest(hwnd, (x, y), entity, &world)
}
fn collect_entities_to_leave(
world: &mut bevy_ecs::world::World,
window_entity: bevy_ecs::prelude::Entity,
exclude: bevy_ecs::prelude::Entity,
) -> Vec<bevy_ecs::prelude::Entity> {
use crate::ecs::pointer::PointerState;
use crate::ecs::window::find_owner_window;
let mut result = Vec::new();
let mut query = world.query::<(bevy_ecs::prelude::Entity, &PointerState)>();
for (e, _) in query.iter(world) {
if e != exclude && find_owner_window(world, e) == Some(window_entity) {
result.push(e);
}
}
result
}
#[inline]
pub(super) fn WM_MOUSEMOVE(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
use crate::ecs::layout::hit_test::{PhysicalPoint as HitTestPoint, hit_test_in_window};
use crate::ecs::pointer::{
PointerLeave, PointerState, WindowPointerTracking, push_pointer_sample, set_modifier_state,
};
use std::time::Instant;
use windows::Win32::UI::Input::KeyboardAndMouse::{
TME_LEAVE, TRACKMOUSEEVENT, TrackMouseEvent,
};
let Some(window_entity) = super::get_entity_from_hwnd(hwnd) else {
return None;
};
let x = (lparam.0 & 0xFFFF) as i16 as i32;
let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as i32;
tracing::trace!("[WM_MOUSEMOVE] Received: x={}, y={}", x, y);
let wparam_val = wparam.0 as u32;
let shift = (wparam_val & 0x04) != 0; let ctrl = (wparam_val & 0x08) != 0;
let mut deferred_set_window_pos: Option<(HWND, i32, i32)> = None;
if let Some(world) = super::try_get_ecs_world() {
if let Ok(mut world_borrow) = world.try_borrow_mut() {
if let Ok(mut entity_ref) = world_borrow.world_mut().get_entity_mut(window_entity) {
let needs_tracking = entity_ref
.get::<WindowPointerTracking>()
.is_none_or(|t| !t.0);
if needs_tracking {
let mut tme = TRACKMOUSEEVENT {
cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
dwFlags: TME_LEAVE,
hwndTrack: hwnd,
dwHoverTime: 0,
};
let _ = unsafe { TrackMouseEvent(&mut tme) };
if entity_ref.get::<WindowPointerTracking>().is_some() {
if let Some(mut tracking) = entity_ref.get_mut::<WindowPointerTracking>() {
tracking.0 = true;
}
} else {
drop(entity_ref);
world_borrow
.world_mut()
.entity_mut(window_entity)
.insert(WindowPointerTracking(true));
}
trace!(
entity = ?window_entity,
hwnd = ?hwnd,
"TrackMouseEvent(TME_LEAVE) set"
);
}
}
let hit_entity = hit_test_in_window(
world_borrow.world(),
window_entity,
HitTestPoint::new(x as f32, y as f32),
);
let window_pos = world_borrow
.world()
.get::<crate::ecs::window::WindowPos>(window_entity);
let (screen_x, screen_y) = if let Some(wp) = window_pos {
if let Some(pos) = wp.position {
(x + pos.x, y + pos.y)
} else {
(x, y)
}
} else {
(x, y)
};
let state_snapshot = crate::ecs::drag::read_drag_state(|state| state.clone());
match state_snapshot {
crate::ecs::drag::DragState::Preparing {
entity,
start_pos,
start_time,
} => {
if let Some(drag_config) = world_borrow
.world()
.get::<crate::ecs::drag::DragConfig>(entity)
{
if drag_config.enabled {
let current_pos =
crate::ecs::pointer::PhysicalPoint::new(screen_x, screen_y);
let dx = current_pos.x - start_pos.x;
let dy = current_pos.y - start_pos.y;
let distance_sq = dx * dx + dy * dy;
let threshold_sq = drag_config.threshold * drag_config.threshold;
if distance_sq >= threshold_sq {
crate::ecs::drag::start_dragging(current_pos);
if let Some(accumulator) = world_borrow
.world()
.get_resource::<crate::ecs::drag::DragAccumulatorResource>(
) {
accumulator.set_transition(
crate::ecs::drag::DragTransition::Started {
entity,
start_pos,
timestamp: start_time,
},
);
accumulator.update_position(current_pos);
}
}
}
}
}
crate::ecs::drag::DragState::Dragging {
prev_pos,
start_pos,
hwnd: drag_hwnd,
initial_window_pos,
move_window,
constraint,
..
} => {
let current_pos = crate::ecs::pointer::PhysicalPoint::new(screen_x, screen_y);
let delta = crate::ecs::pointer::PhysicalPoint::new(
current_pos.x - prev_pos.x,
current_pos.y - prev_pos.y,
);
tracing::trace!(
"[WM_MOUSEMOVE] Dragging: delta=({}, {}), current=({}, {})",
delta.x,
delta.y,
current_pos.x,
current_pos.y
);
if let Some(accumulator) = world_borrow
.world()
.get_resource::<crate::ecs::drag::DragAccumulatorResource>(
) {
accumulator.accumulate_delta(delta);
accumulator.update_position(current_pos);
}
let ctx_res = world_borrow
.world()
.get_resource::<crate::ecs::drag::WindowDragContextResource>();
crate::ecs::drag::update_dragging(current_pos, ctx_res);
if move_window {
let mut new_x = initial_window_pos.x + (current_pos.x - start_pos.x);
let mut new_y = initial_window_pos.y + (current_pos.y - start_pos.y);
if let Some(ref c) = constraint {
let (cx, cy) = c.apply(new_x, new_y);
new_x = cx;
new_y = cy;
}
deferred_set_window_pos = Some((drag_hwnd, new_x, new_y));
}
}
_ => {}
}
if let Some(target_entity) = hit_entity {
let entities_to_leave = collect_entities_to_leave(
world_borrow.world_mut(),
window_entity,
target_entity,
);
for old_entity in entities_to_leave {
if let Ok(mut entity_ref) = world_borrow.world_mut().get_entity_mut(old_entity)
{
entity_ref.remove::<PointerState>();
entity_ref.insert(PointerLeave);
debug!(
old_entity = ?old_entity,
new_entity = ?target_entity,
"PointerState moved, Leave marker inserted"
);
}
}
if let Ok(entity_ref) = world_borrow.world_mut().get_entity_mut(target_entity) {
let needs_insert = entity_ref.get::<PointerState>().is_none();
tracing::trace!(
entity = ?target_entity,
needs_insert,
"[WM_MOUSEMOVE] PointerState check"
);
drop(entity_ref);
if needs_insert {
world_borrow
.world_mut()
.entity_mut(target_entity)
.insert(PointerState {
client_point: crate::ecs::pointer::PhysicalPoint::new(x, y),
local_point: crate::ecs::pointer::PhysicalPoint::new(x, y),
shift_down: shift,
ctrl_down: ctrl,
..Default::default()
});
debug!(
entity = ?target_entity,
x, y,
"PointerState inserted (Enter)"
);
}
} else {
tracing::warn!(
entity = ?target_entity,
"[WM_MOUSEMOVE] Failed to get entity_mut"
);
}
push_pointer_sample(target_entity, x as f32, y as f32, Instant::now());
set_modifier_state(target_entity, shift, ctrl);
} else {
let target_entity = window_entity;
let entities_to_leave = collect_entities_to_leave(
world_borrow.world_mut(),
window_entity,
target_entity,
);
for old_entity in entities_to_leave {
if let Ok(mut entity_ref) = world_borrow.world_mut().get_entity_mut(old_entity)
{
entity_ref.remove::<PointerState>();
entity_ref.insert(PointerLeave);
debug!(
old_entity = ?old_entity,
new_entity = ?target_entity,
"PointerState moved to Window, Leave marker inserted"
);
}
}
if let Ok(entity_ref) = world_borrow.world_mut().get_entity_mut(target_entity) {
let needs_insert = entity_ref.get::<PointerState>().is_none();
drop(entity_ref);
if needs_insert {
world_borrow
.world_mut()
.entity_mut(target_entity)
.insert(PointerState {
client_point: crate::ecs::pointer::PhysicalPoint::new(x, y),
local_point: crate::ecs::pointer::PhysicalPoint::new(x, y),
shift_down: shift,
ctrl_down: ctrl,
..Default::default()
});
debug!(
entity = ?target_entity,
x, y,
"PointerState inserted on Window (no hit)"
);
}
}
push_pointer_sample(target_entity, x as f32, y as f32, Instant::now());
set_modifier_state(target_entity, shift, ctrl);
}
}
}
if let Some((drag_hwnd, new_x, new_y)) = deferred_set_window_pos {
let _ = unsafe {
crate::ecs::window::guarded_set_window_pos(
drag_hwnd,
None,
new_x,
new_y,
0,
0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE,
)
};
}
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_MOUSELEAVE(
hwnd: HWND,
_message: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
use crate::ecs::pointer::{PointerLeave, PointerState, WindowPointerTracking};
use crate::ecs::window::find_owner_window;
let Some(window_entity) = super::get_entity_from_hwnd(hwnd) else {
return None;
};
if let Some(world) = super::try_get_ecs_world() {
if let Ok(mut world_borrow) = world.try_borrow_mut() {
let mut entities_with_pointer_state = Vec::new();
{
let world_mut = world_borrow.world_mut();
let mut query = world_mut.query::<(bevy_ecs::prelude::Entity, &PointerState)>();
for (e, _) in query.iter(world_mut) {
if find_owner_window(world_mut, e) == Some(window_entity) {
entities_with_pointer_state.push(e);
}
}
}
for entity in entities_with_pointer_state {
if let Ok(mut entity_ref) = world_borrow.world_mut().get_entity_mut(entity) {
entity_ref.remove::<PointerState>();
entity_ref.insert(PointerLeave);
debug!(
entity = ?entity,
hwnd = ?hwnd,
"PointerLeave marker inserted (scoped to window)"
);
}
}
if let Ok(mut entity_ref) = world_borrow.world_mut().get_entity_mut(window_entity) {
if let Some(mut tracking) = entity_ref.get_mut::<WindowPointerTracking>() {
tracking.0 = false;
}
}
}
}
Some(LRESULT(0))
}
#[inline]
fn handle_button_message(
hwnd: HWND,
wparam: WPARAM,
lparam: LPARAM,
button: crate::ecs::pointer::PointerButton,
is_down: bool,
) -> HandlerResult {
use crate::ecs::layout::hit_test::{PhysicalPoint as HitTestPoint, hit_test_in_window};
use crate::ecs::pointer::{PhysicalPoint, PointerState};
let Some(window_entity) = super::get_entity_from_hwnd(hwnd) else {
return None;
};
let x = (lparam.0 & 0xFFFF) as i16 as i32;
let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as i32;
tracing::debug!(
"[WM_BUTTON] button={:?}, is_down={}, x={}, y={}",
button,
is_down,
x,
y
);
let wparam_val = wparam.0 as u32;
let shift = (wparam_val & 0x04) != 0;
let ctrl = (wparam_val & 0x08) != 0;
let (screen_x, screen_y) = if let Some(world) = super::try_get_ecs_world() {
if let Ok(world_borrow) = world.try_borrow() {
let window_pos = world_borrow
.world()
.get::<crate::ecs::window::WindowPos>(window_entity);
if let Some(wp) = window_pos {
if let Some(pos) = wp.position {
(x + pos.x, y + pos.y)
} else {
(x, y)
}
} else {
(x, y)
}
} else {
(x, y)
}
} else {
(x, y)
};
if let Some(world) = super::try_get_ecs_world() {
if let Ok(mut world_borrow) = world.try_borrow_mut() {
if let Some(target_entity) = hit_test_in_window(
world_borrow.world(),
window_entity,
HitTestPoint::new(x as f32, y as f32),
) {
let entity_name = world_borrow
.world()
.get::<bevy_ecs::name::Name>(target_entity)
.map(|n| n.as_str().to_string())
.unwrap_or_else(|| format!("{:?}", target_entity));
let bounds_info = world_borrow
.world()
.get::<crate::ecs::layout::GlobalArrangement>(target_entity)
.map(|g| {
format!(
"({:.0},{:.0})-({:.0},{:.0})",
g.bounds.left, g.bounds.top, g.bounds.right, g.bounds.bottom
)
})
.unwrap_or_else(|| "no bounds".to_string());
info!(
target_entity = ?target_entity,
entity_name = %entity_name,
client_x = x,
client_y = y,
screen_x,
screen_y,
bounds = %bounds_info,
button = ?button,
is_down,
"[handle_button_message] hit_test result"
);
if world_borrow
.world()
.get::<PointerState>(target_entity)
.is_none()
{
world_borrow
.world_mut()
.entity_mut(target_entity)
.insert(PointerState {
client_point: PhysicalPoint::new(x, y),
local_point: PhysicalPoint::new(x, y),
left_down: button == crate::ecs::pointer::PointerButton::Left
&& is_down,
right_down: button == crate::ecs::pointer::PointerButton::Right
&& is_down,
middle_down: button == crate::ecs::pointer::PointerButton::Middle
&& is_down,
shift_down: shift,
ctrl_down: ctrl,
..Default::default()
});
debug!(
entity = ?target_entity,
button = ?button,
is_down,
"PointerState inserted on button event"
);
}
crate::ecs::pointer::set_modifier_state(target_entity, shift, ctrl);
if is_down {
crate::ecs::pointer::record_button_down(target_entity, button);
if button == crate::ecs::pointer::PointerButton::Left {
let drag_entity = {
let w = world_borrow.world();
find_ancestor_with_drag_config(w, target_entity)
};
if let Some((entity_with_config, drag_config)) = drag_entity {
if drag_config.enabled && drag_config.left_button {
tracing::info!(
hit_entity = ?target_entity,
drag_entity = ?entity_with_config,
x = screen_x,
y = screen_y,
"[handle_button_message] Calling start_preparing (ancestor search)"
);
crate::ecs::drag::start_preparing(
entity_with_config,
PhysicalPoint::new(screen_x, screen_y),
);
}
}
}
} else {
crate::ecs::pointer::record_button_up(target_entity, button);
if button == crate::ecs::pointer::PointerButton::Left {
let state_snapshot =
crate::ecs::drag::read_drag_state(|state| state.clone());
let should_end = match &state_snapshot {
crate::ecs::drag::DragState::Dragging {
hwnd: drag_hwnd, ..
} => *drag_hwnd == hwnd,
crate::ecs::drag::DragState::Preparing { entity, .. }
| crate::ecs::drag::DragState::JustStarted { entity, .. } => {
crate::ecs::window::find_owner_window(world_borrow.world(), *entity)
== Some(window_entity)
}
_ => false,
};
if should_end {
if let crate::ecs::drag::DragState::Dragging { entity, .. }
| crate::ecs::drag::DragState::Preparing { entity, .. }
| crate::ecs::drag::DragState::JustStarted { entity, .. } =
&state_snapshot
{
if let Some(accumulator) = world_borrow
.world()
.get_resource::<crate::ecs::drag::DragAccumulatorResource>(
) {
accumulator.set_transition(
crate::ecs::drag::DragTransition::Ended {
entity: *entity,
end_pos: PhysicalPoint::new(screen_x, screen_y),
cancelled: false,
},
);
}
}
crate::ecs::drag::end_dragging(
PhysicalPoint::new(screen_x, screen_y),
false,
);
} else {
trace!(
hwnd = format!("0x{:X}", hwnd.0 as usize),
"[handle_button_message] Drag end skipped: HWND mismatch"
);
}
}
}
return Some(LRESULT(0));
}
}
}
if is_down {
crate::ecs::pointer::record_button_down(window_entity, button);
} else {
crate::ecs::pointer::record_button_up(window_entity, button);
if button == crate::ecs::pointer::PointerButton::Left {
let state_snapshot = crate::ecs::drag::read_drag_state(|state| state.clone());
let should_end = match &state_snapshot {
crate::ecs::drag::DragState::Dragging {
hwnd: drag_hwnd, ..
} => *drag_hwnd == hwnd,
crate::ecs::drag::DragState::Preparing { entity, .. }
| crate::ecs::drag::DragState::JustStarted { entity, .. } => {
if let Some(world) = super::try_get_ecs_world() {
if let Ok(world_borrow) = world.try_borrow() {
crate::ecs::window::find_owner_window(world_borrow.world(), *entity)
== Some(window_entity)
} else {
false
}
} else {
false
}
}
_ => false,
};
if should_end {
if let Some(world) = super::try_get_ecs_world() {
if let Ok(world_borrow) = world.try_borrow() {
if let crate::ecs::drag::DragState::Dragging { entity, .. }
| crate::ecs::drag::DragState::Preparing { entity, .. }
| crate::ecs::drag::DragState::JustStarted { entity, .. } =
&state_snapshot
{
if let Some(accumulator) = world_borrow
.world()
.get_resource::<crate::ecs::drag::DragAccumulatorResource>(
) {
accumulator.set_transition(
crate::ecs::drag::DragTransition::Ended {
entity: *entity,
end_pos: PhysicalPoint::new(screen_x, screen_y),
cancelled: false,
},
);
}
}
}
}
crate::ecs::drag::end_dragging(PhysicalPoint::new(screen_x, screen_y), false);
} else {
trace!(
hwnd = format!("0x{:X}", hwnd.0 as usize),
"[handle_button_message] Fallback drag end skipped: HWND mismatch"
);
}
}
}
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_LBUTTONDOWN(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_button_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::PointerButton::Left,
true,
)
}
#[inline]
pub(super) fn WM_LBUTTONUP(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_button_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::PointerButton::Left,
false,
)
}
#[inline]
pub(super) fn WM_RBUTTONDOWN(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_button_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::PointerButton::Right,
true,
)
}
#[inline]
pub(super) fn WM_RBUTTONUP(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_button_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::PointerButton::Right,
false,
)
}
#[inline]
pub(super) fn WM_MBUTTONDOWN(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_button_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::PointerButton::Middle,
true,
)
}
#[inline]
pub(super) fn WM_MBUTTONUP(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_button_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::PointerButton::Middle,
false,
)
}
#[inline]
pub(super) fn WM_XBUTTONDOWN(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
let xbutton = ((wparam.0 >> 16) & 0xFFFF) as u16;
let button = if xbutton == 1 {
crate::ecs::pointer::PointerButton::XButton1
} else {
crate::ecs::pointer::PointerButton::XButton2
};
handle_button_message(hwnd, wparam, lparam, button, true)
}
#[inline]
pub(super) fn WM_XBUTTONUP(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
let xbutton = ((wparam.0 >> 16) & 0xFFFF) as u16;
let button = if xbutton == 1 {
crate::ecs::pointer::PointerButton::XButton1
} else {
crate::ecs::pointer::PointerButton::XButton2
};
handle_button_message(hwnd, wparam, lparam, button, false)
}
#[inline]
fn handle_double_click_message(
hwnd: HWND,
wparam: WPARAM,
lparam: LPARAM,
double_click: crate::ecs::pointer::DoubleClick,
) -> HandlerResult {
use crate::ecs::layout::hit_test::{PhysicalPoint as HitTestPoint, hit_test_in_window};
use crate::ecs::pointer::{PhysicalPoint, PointerState};
let Some(window_entity) = super::get_entity_from_hwnd(hwnd) else {
return None;
};
let x = (lparam.0 & 0xFFFF) as i16 as i32;
let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as i32;
let wparam_val = wparam.0 as u32;
let shift = (wparam_val & 0x04) != 0;
let ctrl = (wparam_val & 0x08) != 0;
let button = match double_click {
crate::ecs::pointer::DoubleClick::Left => crate::ecs::pointer::PointerButton::Left,
crate::ecs::pointer::DoubleClick::Right => crate::ecs::pointer::PointerButton::Right,
crate::ecs::pointer::DoubleClick::Middle => crate::ecs::pointer::PointerButton::Middle,
crate::ecs::pointer::DoubleClick::XButton1 => crate::ecs::pointer::PointerButton::XButton1,
crate::ecs::pointer::DoubleClick::XButton2 => crate::ecs::pointer::PointerButton::XButton2,
crate::ecs::pointer::DoubleClick::None => return Some(LRESULT(0)),
};
if let Some(world) = super::try_get_ecs_world() {
if let Ok(mut world_borrow) = world.try_borrow_mut() {
if let Some(target_entity) = hit_test_in_window(
world_borrow.world(),
window_entity,
HitTestPoint::new(x as f32, y as f32),
) {
tracing::info!(
window_entity = ?window_entity,
target_entity = ?target_entity,
double_click = ?double_click,
x, y,
"[handle_double_click_message] Double-click detected"
);
if world_borrow
.world()
.get::<PointerState>(target_entity)
.is_none()
{
world_borrow
.world_mut()
.entity_mut(target_entity)
.insert(PointerState {
client_point: PhysicalPoint::new(x, y),
local_point: PhysicalPoint::new(x, y),
left_down: button == crate::ecs::pointer::PointerButton::Left,
right_down: button == crate::ecs::pointer::PointerButton::Right,
middle_down: button == crate::ecs::pointer::PointerButton::Middle,
xbutton1_down: button == crate::ecs::pointer::PointerButton::XButton1,
xbutton2_down: button == crate::ecs::pointer::PointerButton::XButton2,
shift_down: shift,
ctrl_down: ctrl,
double_click,
..Default::default()
});
debug!(
entity = ?target_entity,
button = ?button,
double_click = ?double_click,
"PointerState inserted on double-click event"
);
} else {
if let Some(mut ps) = world_borrow
.world_mut()
.get_mut::<PointerState>(target_entity)
{
ps.double_click = double_click;
ps.shift_down = shift;
ps.ctrl_down = ctrl;
}
}
crate::ecs::pointer::set_modifier_state(target_entity, shift, ctrl);
crate::ecs::pointer::record_button_down(target_entity, button);
}
}
}
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_LBUTTONDBLCLK(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_double_click_message(hwnd, wparam, lparam, crate::ecs::pointer::DoubleClick::Left)
}
#[inline]
pub(super) fn WM_RBUTTONDBLCLK(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_double_click_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::DoubleClick::Right,
)
}
#[inline]
pub(super) fn WM_MBUTTONDBLCLK(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
handle_double_click_message(
hwnd,
wparam,
lparam,
crate::ecs::pointer::DoubleClick::Middle,
)
}
#[inline]
pub(super) fn WM_XBUTTONDBLCLK(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
let xbutton = ((wparam.0 >> 16) & 0xFFFF) as u16;
let double_click = if xbutton == 1 {
crate::ecs::pointer::DoubleClick::XButton1
} else {
crate::ecs::pointer::DoubleClick::XButton2
};
handle_double_click_message(hwnd, wparam, lparam, double_click)
}
#[inline]
pub(super) fn WM_MOUSEWHEEL(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
let Some(entity) = super::get_entity_from_hwnd(hwnd) else {
return None;
};
let delta = ((wparam.0 >> 16) & 0xFFFF) as i16;
crate::ecs::pointer::add_wheel_vertical(entity, delta);
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_MOUSEHWHEEL(
hwnd: HWND,
_message: u32,
wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
let Some(entity) = super::get_entity_from_hwnd(hwnd) else {
return None;
};
let delta = ((wparam.0 >> 16) & 0xFFFF) as i16;
crate::ecs::pointer::add_wheel_horizontal(entity, delta);
Some(LRESULT(0))
}
#[inline]
pub(super) fn WM_KEYDOWN(
_hwnd: HWND,
_message: u32,
wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
use windows::Win32::UI::Input::KeyboardAndMouse::VK_ESCAPE;
if wparam.0 == VK_ESCAPE.0 as usize {
let state_snapshot = crate::ecs::drag::read_drag_state(|state| state.clone());
if let crate::ecs::drag::DragState::Dragging {
entity, start_pos, ..
}
| crate::ecs::drag::DragState::Preparing {
entity, start_pos, ..
}
| crate::ecs::drag::DragState::JustStarted {
entity, start_pos, ..
} = state_snapshot
{
if let Some(world) = super::try_get_ecs_world() {
if let Ok(world_borrow) = world.try_borrow() {
if let Some(accumulator) = world_borrow
.world()
.get_resource::<crate::ecs::drag::DragAccumulatorResource>(
) {
accumulator.set_transition(crate::ecs::drag::DragTransition::Ended {
entity,
end_pos: start_pos,
cancelled: true,
});
}
}
}
}
crate::ecs::drag::cancel_dragging();
tracing::debug!("[WM_KEYDOWN] ESC key pressed, drag cancelled");
}
None }
#[inline]
pub(super) fn WM_CANCELMODE(
_hwnd: HWND,
_message: u32,
_wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
let state_snapshot = crate::ecs::drag::read_drag_state(|state| state.clone());
if let crate::ecs::drag::DragState::Dragging {
entity, start_pos, ..
}
| crate::ecs::drag::DragState::Preparing {
entity, start_pos, ..
}
| crate::ecs::drag::DragState::JustStarted {
entity, start_pos, ..
} = state_snapshot
{
if let Some(world) = super::try_get_ecs_world() {
if let Ok(world_borrow) = world.try_borrow() {
if let Some(accumulator) = world_borrow
.world()
.get_resource::<crate::ecs::drag::DragAccumulatorResource>(
) {
accumulator.set_transition(crate::ecs::drag::DragTransition::Ended {
entity,
end_pos: start_pos,
cancelled: true,
});
}
}
}
}
crate::ecs::drag::cancel_dragging();
tracing::debug!("[WM_CANCELMODE] System cancel, drag cancelled");
None }
pub(super) fn WM_ACTIVATE(
_hwnd: HWND,
_message: u32,
wparam: WPARAM,
_lparam: LPARAM,
) -> HandlerResult {
let activation_state = (wparam.0 & 0xFFFF) as u32;
if activation_state != 0 {
return None;
}
let state_snapshot = crate::ecs::drag::read_drag_state(|state| state.clone());
match state_snapshot {
crate::ecs::drag::DragState::Dragging {
entity, start_pos, ..
} => {
tracing::info!(
entity = ?entity,
"[WM_ACTIVATE] Window deactivated during drag, cancelling"
);
if let Some(world) = super::try_get_ecs_world() {
if let Ok(world_borrow) = world.try_borrow() {
if let Some(accumulator) = world_borrow
.world()
.get_resource::<crate::ecs::drag::DragAccumulatorResource>(
) {
accumulator.set_transition(crate::ecs::drag::DragTransition::Ended {
entity,
end_pos: start_pos,
cancelled: true,
});
}
}
}
crate::ecs::drag::cancel_dragging();
}
crate::ecs::drag::DragState::Preparing { .. } => {
tracing::debug!("[WM_ACTIVATE] Window deactivated during drag prepare, resetting");
crate::ecs::drag::reset_to_idle();
}
_ => {}
}
None }
fn find_ancestor_with_drag_config(
world: &bevy_ecs::world::World,
start: bevy_ecs::entity::Entity,
) -> Option<(bevy_ecs::entity::Entity, crate::ecs::drag::DragConfig)> {
let mut current = start;
loop {
if let Some(config) = world.get::<crate::ecs::drag::DragConfig>(current) {
return Some((current, config.clone()));
}
if let Some(child_of) = world.get::<bevy_ecs::prelude::ChildOf>(current) {
current = child_of.parent();
} else {
return None;
}
}
}