#![allow(non_snake_case)]
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Instant;
use bevy_ecs::change_detection::DetectChangesMut;
use bevy_ecs::prelude::Entity;
use tracing::{debug, trace, warn};
use windows::Win32::Foundation::*;
use windows::Win32::UI::WindowsAndMessaging::*;
use crate::ecs::window::transition_diag::{
self, MSG_DPICHANGED, MSG_WINDOWPOSCHANGED, MsgRecord, ORIGIN_DPI_SUGGESTED, WindowDpiRecord,
WriteRecord, WriteStage, WriteTag,
};
use crate::ecs::world::EcsWorld;
type HandlerResult = Option<LRESULT>;
#[inline]
pub(super) fn WM_WINDOWPOSCHANGED(
world: &Rc<RefCell<EcsWorld>>,
entity: Entity,
hwnd: HWND,
_wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
let is_echo = crate::ecs::window::is_self_initiated();
if transition_diag::is_enabled() {
transition_diag::emit_line(&transition_diag::msg_line(&MsgRecord {
stamp: transition_diag::stamp(),
msg: MSG_WINDOWPOSCHANGED,
hwnd,
in_swp: is_echo,
since_flush_us: transition_diag::since_flush_us(),
}));
}
{
{
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 = 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,
has_dpi_ctx = dpi_context.is_some(),
entity = ?entity,
window_xy = format_args!("({},{})", wp.x, wp.y),
window_size = format_args!("{}x{}", wp.cx, wp.cy),
client_xy = format_args!("({},{})", client_pos.x, client_pos.y),
client_size = format_args!("{}x{}", client_size.width, client_size.height),
dpi = format_args!("{:.2}", dpi.scale_x()),
"[WM_WINDOWPOSCHANGED]"
);
let box_style_snapshot =
entity_ref.get::<crate::ecs::layout::BoxStyle>().cloned();
if let Some(mut window_pos) =
entity_ref.get_mut::<crate::ecs::window::WindowPos>()
{
let use_bypass = is_echo && dpi_context.is_none();
if use_bypass {
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, no DPI change)"
);
} else {
let corrected_pos = super::dpi_helpers::correct_position_for_dpi_center_preserve(
client_pos,
client_size,
&dpi_context,
box_style_snapshot.as_ref(),
&dpi,
);
let pos_changed = window_pos.position != Some(corrected_pos);
let size_changed = window_pos.size != Some(client_size);
if pos_changed || size_changed {
window_pos.position = Some(corrected_pos);
window_pos.size = Some(client_size);
if dpi_context.is_some() {
debug!(
entity = ?entity,
is_echo,
original_x = client_pos.x,
original_y = client_pos.y,
corrected_x = corrected_pos.x,
corrected_y = corrected_pos.y,
client_cx = client_size.width,
client_cy = client_size.height,
"[WM_WINDOWPOSCHANGED] WindowPos updated (DPI change, center-preserve)"
);
} else {
debug!(
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.width,
client_cy = client_size.height,
"WindowPos updated (external change, values differ)"
);
}
} else {
trace!(
entity = ?entity,
client_x = client_pos.x,
client_y = client_pos.y,
"WindowPos unchanged (external, same values — skipping DerefMut)"
);
}
}
}
let skip_box_style = is_echo || dpi_context.is_some();
if !skip_box_style {
use crate::ecs::layout::{BoxSize, Dimension};
let physical_width = client_size.width as f32;
let physical_height = client_size.height as f32;
let logical_width = physical_width / dpi.scale_x();
let logical_height = physical_height / dpi.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;
}
debug!(
entity = ?entity,
logical_width = logical_width,
logical_height = logical_height,
physical_width = physical_width,
physical_height = physical_height,
dpi_scale = dpi.scale_x(),
"[WM_WINDOWPOSCHANGED] BoxStyle.size updated (logical px, external resize)"
);
} else {
trace!(
entity = ?entity,
"[WM_WINDOWPOSCHANGED] BoxStyle.size unchanged, skipping update"
);
}
} else {
trace!(
entity = ?entity,
is_echo = is_echo,
has_dpi_ctx = dpi_context.is_some(),
"[WM_WINDOWPOSCHANGED] BoxStyle.size skipped (echo or DPI change)"
);
}
}
}
}
}
{
use crate::ecs::world::VsyncTick;
let _ = world.try_tick_on_vsync();
}
crate::ecs::window::flush_window_pos_commands();
}
}
None }
#[inline]
pub(super) fn WM_DPICHANGED(
world: &Rc<RefCell<EcsWorld>>,
entity: Entity,
hwnd: HWND,
wparam: WPARAM,
lparam: LPARAM,
) -> HandlerResult {
if transition_diag::is_enabled() {
transition_diag::emit_line(&transition_diag::msg_line(&MsgRecord {
stamp: transition_diag::stamp(),
msg: MSG_DPICHANGED,
hwnd,
in_swp: crate::ecs::window::is_self_initiated(),
since_flush_us: transition_diag::since_flush_us(),
}));
}
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"
);
let mut policy: Option<crate::ecs::window::DpiSuggestedRectPolicy> = None;
let mut entity_reached = false;
{
{
if let Ok(mut world_borrow) = world.try_borrow_mut() {
if let Ok(mut entity_ref) = world_borrow.world_mut().get_entity_mut(entity) {
entity_reached = true;
policy = entity_ref
.get::<crate::ecs::window::DpiSuggestedRectPolicy>()
.copied();
if let Some(mut dpi_comp) = entity_ref.get_mut::<crate::ecs::window::DPI>() {
let old_dpi = *dpi_comp;
*dpi_comp = new_dpi;
debug!(
entity = ?entity,
old_dpi_x = old_dpi.dpi_x,
old_dpi_y = old_dpi.dpi_y,
new_dpi_x = new_dpi.dpi_x,
new_dpi_y = new_dpi.dpi_y,
"[WM_DPICHANGED] DPI component directly updated (Changed<DPI>)"
);
if transition_diag::is_enabled() {
transition_diag::emit_line(&transition_diag::windpi_line(
&WindowDpiRecord {
stamp: transition_diag::stamp(),
entity,
old_dpi: u32::from(old_dpi.dpi_x),
new_dpi: u32::from(new_dpi.dpi_x),
},
));
}
}
}
}
}
}
let decision =
super::dpi_helpers::dpi_suggested_position_decision(policy.as_ref(), &suggested_rect);
let applied = decision.is_some();
let policy_label = match (entity_reached, policy) {
(false, _) => "unreachable",
(true, None) => "unset",
(true, Some(crate::ecs::window::DpiSuggestedRectPolicy::ApplyPosition)) => "ApplyPosition",
(true, Some(crate::ecs::window::DpiSuggestedRectPolicy::ExternalAuthority)) => {
"ExternalAuthority"
}
};
debug!(
entity = ?entity,
hwnd = ?hwnd,
policy = format_args!("{policy_label}"),
applied = applied,
suggested_left = suggested_rect.left,
suggested_top = suggested_rect.top,
"[WM_DPICHANGED] suggested position write decision"
);
if let Some((x, y)) = decision {
crate::ecs::window::DpiChangeContext::set(crate::ecs::window::DpiChangeContext::new(
new_dpi,
suggested_rect,
));
let flags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;
let observe = transition_diag::is_enabled();
let call_started = observe.then(Instant::now);
let result =
unsafe { crate::ecs::window::guarded_set_window_pos(hwnd, None, x, y, 0, 0, flags) };
if observe {
let call_us = call_started.map_or(0, transition_diag::elapsed_us);
transition_diag::emit_line(&transition_diag::write_line(&WriteRecord {
stamp: transition_diag::stamp(),
stage: WriteStage::Sync,
seq: 0,
hwnd,
tag: WriteTag {
origin: ORIGIN_DPI_SUGGESTED,
..WriteTag::UNTAGGED
},
x,
y,
cx: 0,
cy: 0,
flags: flags.0,
after: crate::ecs::window::read_back_window_rect(hwnd),
call_us,
ok: result.is_ok(),
in_batch: false,
}));
}
if let Err(e) = result {
warn!(hwnd = ?hwnd, error = ?e, "SetWindowPos failed in WM_DPICHANGED");
}
}
Some(LRESULT(0))
}
#[cfg(test)]
#[path = "window_pos_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "window_pos_transition_tests.rs"]
mod window_pos_transition_tests;