use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::prelude::*;
use tracing::{debug, warn};
use windows::Win32::Foundation::*;
use windows::Win32::UI::HiDpi::AdjustWindowRectExForDpi;
use windows::Win32::UI::WindowsAndMessaging::*;
use crate::ecs::layout::LayoutRoot;
use crate::ecs::types::{Point, SizeI};
use super::window_handle::WindowHandle;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ZOrder {
#[default]
NoChange,
TopMost,
NoTopMost,
Top,
Bottom,
InsertAfter(HWND),
}
unsafe impl Send for ZOrder {}
unsafe impl Sync for ZOrder {}
#[derive(Component, Debug, Clone, Copy, PartialEq)]
pub struct WindowPos {
pub zorder: ZOrder,
pub position: Option<Point>,
pub size: Option<SizeI>,
pub no_redraw: bool, pub no_activate: bool, pub frame_changed: bool, pub show_window: bool, pub hide_window: bool, pub no_copy_bits: bool, pub no_owner_zorder: bool, pub no_send_changing: bool, pub defer_erase: bool, pub async_window_pos: bool, }
impl Default for WindowPos {
fn default() -> Self {
Self {
zorder: ZOrder::NoChange,
position: Some(Point {
x: CW_USEDEFAULT,
y: CW_USEDEFAULT,
}),
size: Some(SizeI {
width: CW_USEDEFAULT,
height: CW_USEDEFAULT,
}),
no_redraw: false,
no_activate: false,
frame_changed: false,
show_window: false,
hide_window: false,
no_copy_bits: false,
no_owner_zorder: false,
no_send_changing: false,
defer_erase: false,
async_window_pos: false,
}
}
}
unsafe impl Send for WindowPos {}
unsafe impl Sync for WindowPos {}
impl WindowPos {
pub fn new() -> Self {
Self::default()
}
pub fn with_zorder(mut self, zorder: ZOrder) -> Self {
self.zorder = zorder;
self
}
pub fn zorder_no_change(mut self) -> Self {
self.zorder = ZOrder::NoChange;
self
}
pub fn zorder_topmost(mut self) -> Self {
self.zorder = ZOrder::TopMost;
self
}
pub fn zorder_notopmost(mut self) -> Self {
self.zorder = ZOrder::NoTopMost;
self
}
pub fn zorder_top(mut self) -> Self {
self.zorder = ZOrder::Top;
self
}
pub fn zorder_bottom(mut self) -> Self {
self.zorder = ZOrder::Bottom;
self
}
pub fn zorder_insert_after(mut self, hwnd: HWND) -> Self {
self.zorder = ZOrder::InsertAfter(hwnd);
self
}
pub fn with_position(mut self, position: Point) -> Self {
self.position = Some(position);
self
}
pub fn with_size(mut self, size: SizeI) -> Self {
self.size = Some(size);
self
}
pub fn no_redraw(mut self, value: bool) -> Self {
self.no_redraw = value;
self
}
pub fn no_activate(mut self, value: bool) -> Self {
self.no_activate = value;
self
}
pub fn frame_changed(mut self, value: bool) -> Self {
self.frame_changed = value;
self
}
pub fn show_window(mut self, value: bool) -> Self {
self.show_window = value;
self
}
pub fn hide_window(mut self, value: bool) -> Self {
self.hide_window = value;
self
}
pub fn no_copy_bits(mut self, value: bool) -> Self {
self.no_copy_bits = value;
self
}
pub fn no_owner_zorder(mut self, value: bool) -> Self {
self.no_owner_zorder = value;
self
}
pub fn no_send_changing(mut self, value: bool) -> Self {
self.no_send_changing = value;
self
}
pub fn defer_erase(mut self, value: bool) -> Self {
self.defer_erase = value;
self
}
pub fn async_window_pos(mut self, value: bool) -> Self {
self.async_window_pos = value;
self
}
fn build_flags(&self) -> SET_WINDOW_POS_FLAGS {
let mut flags = SET_WINDOW_POS_FLAGS(0);
if self.position.is_none() {
flags |= SWP_NOMOVE;
}
if self.size.is_none() {
flags |= SWP_NOSIZE;
}
if self.zorder == ZOrder::NoChange {
flags |= SWP_NOZORDER;
}
if self.no_redraw {
flags |= SWP_NOREDRAW;
}
if self.no_activate {
flags |= SWP_NOACTIVATE;
}
if self.frame_changed {
flags |= SWP_FRAMECHANGED;
}
if self.show_window {
flags |= SWP_SHOWWINDOW;
}
if self.hide_window {
flags |= SWP_HIDEWINDOW;
}
if self.no_copy_bits {
flags |= SWP_NOCOPYBITS;
}
if self.no_owner_zorder {
flags |= SWP_NOOWNERZORDER;
}
if self.no_send_changing {
flags |= SWP_NOSENDCHANGING;
}
if self.defer_erase {
flags |= SWP_DEFERERASE;
}
if self.async_window_pos {
flags |= SWP_ASYNCWINDOWPOS;
}
flags
}
pub fn build_flags_for_system(&self) -> SET_WINDOW_POS_FLAGS {
self.build_flags()
}
pub fn get_hwnd_insert_after(&self) -> Option<HWND> {
match self.zorder {
ZOrder::NoChange => None,
ZOrder::TopMost => Some(HWND_TOPMOST),
ZOrder::NoTopMost => Some(HWND_NOTOPMOST),
ZOrder::Top => Some(HWND_TOP),
ZOrder::Bottom => Some(HWND_BOTTOM),
ZOrder::InsertAfter(hwnd) => Some(hwnd),
}
}
pub fn set_window_pos(&self, hwnd: HWND) -> windows::core::Result<()> {
let (x, y) = if let Some(pos) = self.position {
(pos.x, pos.y)
} else {
(0, 0)
};
let (width, height) = if let Some(size) = self.size {
(size.width, size.height)
} else {
(0, 0)
};
let flags = self.build_flags();
let hwnd_insert_after = self.get_hwnd_insert_after();
unsafe { SetWindowPos(hwnd, hwnd_insert_after, x, y, width, height, flags) }
}
pub fn to_window_coords(
&self,
window_handle: &WindowHandle,
) -> Result<(i32, i32, i32, i32), String> {
let position = self.position.unwrap_or(Point { x: 0, y: 0 });
let size = self.size.unwrap_or(SizeI {
width: 0,
height: 0,
});
window_handle.client_to_window_coords(position, size)
}
pub fn to_window_coords_for_creation(
&self,
style: WINDOW_STYLE,
ex_style: WINDOW_EX_STYLE,
dpi: u32,
) -> (i32, i32, i32, i32) {
let position = self.position.unwrap_or(Point {
x: CW_USEDEFAULT,
y: CW_USEDEFAULT,
});
let size = self.size.unwrap_or(SizeI {
width: CW_USEDEFAULT,
height: CW_USEDEFAULT,
});
if position.x == CW_USEDEFAULT || size.width == CW_USEDEFAULT {
return (position.x, position.y, size.width, size.height);
}
let dpi = if dpi == 0 { 96 } else { dpi };
let mut rect = RECT {
left: position.x,
top: position.y,
right: position.x + size.width,
bottom: position.y + size.height,
};
let result = unsafe { AdjustWindowRectExForDpi(&mut rect, style, false, ex_style, dpi) };
if result.is_err() {
warn!(
error = ?result,
"AdjustWindowRectExForDpi failed during window creation, using original values"
);
return (position.x, position.y, size.width, size.height);
}
(
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
)
}
}
pub(super) struct SetWindowParentToLayoutRoot {
pub(super) entity: Entity,
}
impl Command for SetWindowParentToLayoutRoot {
type Out = ();
fn apply(self, world: &mut World) {
let mut query = world.query_filtered::<Entity, With<LayoutRoot>>();
let layout_root = query.iter(world).next();
if let Some(root) = layout_root {
if let Ok(mut entity_mut) = world.get_entity_mut(self.entity) {
if !entity_mut.contains::<ChildOf>() {
debug!(
root = ?root,
entity = ?self.entity,
"Setting ChildOf for Window entity"
);
entity_mut.insert(ChildOf(root));
}
}
}
}
}
#[cfg(test)]
mod tests;