use std::cell::RefCell;
use std::rc::Weak;
use crate::executor::util::{Window as LibWindow, WindowType, get_instance_handle};
use bevy_ecs::prelude::Entity;
use bevy_ecs::world::World;
use tracing::{debug, error};
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::HiDpi::GetDpiForSystem;
use windows::Win32::UI::WindowsAndMessaging::{
CW_USEDEFAULT, GWL_STYLE, SW_SHOW, SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
SWP_NOZORDER, SetParent, SetWindowLongPtrW, SetWindowPos, SetWindowTextW, ShowWindow,
WS_EX_LAYERED,
};
use windows::core::HSTRING;
use crate::ecs::HasGraphicsResources;
use crate::ecs::window::{Window, WindowHandle, WindowPos, WindowStyle};
use crate::ecs::world::EcsWorld;
use crate::runtime::window_registry::WindowRegistry;
use crate::runtime::wndproc_bridge::{WndState, make_wndproc};
use windows::Win32::UI::WindowsAndMessaging::WINDOW_EX_STYLE;
fn compute_ex_style(style: &WindowStyle) -> WINDOW_EX_STYLE {
use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOREDIRECTIONBITMAP;
(style.ex_style & !WS_EX_LAYERED) | WS_EX_NOREDIRECTIONBITMAP
}
pub(crate) struct EcsWindowFactory;
impl EcsWindowFactory {
pub(crate) fn create_window(
world: &mut World,
entity: Entity,
ecs_world: Weak<RefCell<EcsWorld>>,
) {
let entity_ref = match world.get_entity(entity) {
Ok(e) => e,
Err(_) => return,
};
let Some(window) = entity_ref.get::<Window>() else {
return;
};
let title = window.title.clone();
let parent = window.parent;
let style = entity_ref.get::<WindowStyle>().copied().unwrap_or_default();
let pos = entity_ref.get::<WindowPos>().copied().unwrap_or_default();
let ex_style = compute_ex_style(&style);
let state = WndState {
world: ecs_world,
entity,
};
let wndproc = make_wndproc();
let lib_window = match LibWindow::new_ex(WindowType::TopLevel, ex_style, state, wndproc) {
Ok(w) => w,
Err(e) => {
error!(entity = ?entity, error = ?e, "ライブラリウィンドウ生成に失敗");
return;
}
};
let hwnd = lib_window.hwnd();
Self::apply_initial_state(hwnd, &style, &pos, ex_style, &title);
if let Some(parent_hwnd) = parent {
unsafe {
let _ = SetParent(hwnd, Some(parent_hwnd));
}
}
unsafe {
let _ = ShowWindow(hwnd, SW_SHOW);
}
if let Some(mut registry) = world.get_non_send_mut::<WindowRegistry<LibWindow<WndState>>>()
{
registry.insert(entity, lib_window);
} else {
let mut registry = WindowRegistry::<LibWindow<WndState>>::new();
registry.insert(entity, lib_window);
world.insert_non_send(registry);
debug!(entity = ?entity, "WindowRegistry を遅延生成して格納");
}
let instance = get_instance_handle();
world.entity_mut(entity).insert((
WindowHandle { hwnd, instance },
HasGraphicsResources::default(),
));
debug!(entity = ?entity, hwnd = ?hwnd, "EcsWindowFactory: ウィンドウ生成・登録完了");
}
fn apply_initial_state(
hwnd: HWND,
style: &WindowStyle,
pos: &WindowPos,
ex_style: WINDOW_EX_STYLE,
title: &str,
) {
unsafe {
SetWindowLongPtrW(hwnd, GWL_STYLE, style.style.0 as isize);
}
unsafe {
let _ = SetWindowPos(
hwnd,
None,
0,
0,
0,
0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE,
);
}
let system_dpi = unsafe { GetDpiForSystem() };
let (x, y, width, height) =
pos.to_window_coords_for_creation(style.style, ex_style, system_dpi);
if x != CW_USEDEFAULT && width != CW_USEDEFAULT {
unsafe {
let _ = SetWindowPos(
hwnd,
None,
x,
y,
width,
height,
SWP_NOZORDER | SWP_NOACTIVATE,
);
}
}
let title_h = HSTRING::from(title);
unsafe {
let _ = SetWindowTextW(hwnd, &title_h);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::rc::Rc;
use windows::Win32::UI::WindowsAndMessaging::{
GetWindowLongPtrW, GetWindowTextW, WS_EX_NOREDIRECTIONBITMAP, WS_OVERLAPPEDWINDOW,
WS_POPUP, WS_VISIBLE,
};
#[test]
fn ex_style_dcomp_sets_noredirection_clears_layered() {
let style = WindowStyle::default(); let ex = compute_ex_style(&style);
assert!(
(ex & WS_EX_NOREDIRECTIONBITMAP) == WS_EX_NOREDIRECTIONBITMAP,
"DComp は WS_EX_NOREDIRECTIONBITMAP を立てるべき"
);
assert!(
(ex & WS_EX_LAYERED).0 == 0,
"DComp は WS_EX_LAYERED を除去すべき"
);
}
#[test]
fn ex_style_dcomp_preserves_other_bits() {
use windows::Win32::UI::WindowsAndMessaging::{WS_EX_LAYERED, WS_EX_TOPMOST};
let style = WindowStyle {
style: WS_POPUP,
ex_style: WS_EX_LAYERED | WS_EX_TOPMOST,
};
let ex = compute_ex_style(&style);
assert!(
(ex & WS_EX_TOPMOST) == WS_EX_TOPMOST,
"LAYERED 以外の既存ビット(TOPMOST)は保持されるべき"
);
assert!((ex & WS_EX_LAYERED).0 == 0, "LAYERED は除去されるべき");
}
fn make_shared_world() -> Rc<RefCell<EcsWorld>> {
Rc::new(RefCell::new(EcsWorld::new()))
}
#[test]
fn factory_creates_registers_and_applies_state() {
let shared = make_shared_world();
let weak = Rc::downgrade(&shared);
let mut bevy_world = World::new();
bevy_world.insert_non_send(WindowRegistry::<LibWindow<WndState>>::new());
let entity = bevy_world
.spawn((
Window {
title: "FactoryTest".to_string(),
parent: None,
},
WindowStyle {
style: WS_POPUP,
ex_style: WS_EX_LAYERED,
},
WindowPos::default(), ))
.id();
EcsWindowFactory::create_window(&mut bevy_world, entity, weak);
let registry = bevy_world
.get_non_send::<WindowRegistry<LibWindow<WndState>>>()
.expect("registry が存在するべき");
let handle = bevy_world
.get::<WindowHandle>(entity)
.copied()
.expect("WindowHandle が insert されるべき");
assert!(!handle.hwnd.is_invalid(), "生成 HWND は有効であるべき");
assert!(
bevy_world.get::<HasGraphicsResources>(entity).is_some(),
"HasGraphicsResources が insert されるべき"
);
let _ = registry;
let hwnd = handle.hwnd;
let applied_style = unsafe { GetWindowLongPtrW(hwnd, GWL_STYLE) } as u32;
assert!(
(applied_style & WS_POPUP.0) == WS_POPUP.0,
"GWL_STYLE に WindowStyle.style(WS_POPUP)が反映されるべき"
);
let mut buf = [0u16; 64];
let len = unsafe { GetWindowTextW(hwnd, &mut buf) };
let text = String::from_utf16_lossy(&buf[..len as usize]);
assert_eq!(
text, "FactoryTest",
"SetWindowTextW でタイトルが反映されるべき"
);
drop(bevy_world);
drop(shared);
let _ = (WS_OVERLAPPEDWINDOW, WS_VISIBLE);
}
#[test]
fn create_windows_new_path_spawns_registers_and_handles_without_class_error() {
use crate::ecs::window::window_system::create_windows;
use crate::ecs::world::EcsWorldSelfRef;
let shared = make_shared_world();
let weak = Rc::downgrade(&shared);
let mut bevy_world = World::new();
bevy_world.insert_non_send(EcsWorldSelfRef(weak));
bevy_world.insert_non_send(WindowRegistry::<LibWindow<WndState>>::new());
let entity = bevy_world
.spawn((
Window {
title: "DeclFlow".to_string(),
parent: None,
},
WindowStyle {
style: WS_POPUP,
ex_style: WS_EX_LAYERED,
},
WindowPos::default(), ))
.id();
assert!(
bevy_world.get::<WindowHandle>(entity).is_none(),
"create_windows 実行前は WindowHandle 不在であるべき"
);
create_windows(&mut bevy_world);
let handle = bevy_world
.get::<WindowHandle>(entity)
.copied()
.expect("create_windows 後に WindowHandle が付与されるべき(生成成功)");
assert!(
!handle.hwnd.is_invalid(),
"生成 HWND は有効であるべき(クラス未登録エラーなく生成成立・要件 2.5)"
);
assert!(
bevy_world.get::<HasGraphicsResources>(entity).is_some(),
"create_windows 後に HasGraphicsResources が付与されるべき"
);
let registry = bevy_world
.get_non_send::<WindowRegistry<LibWindow<WndState>>>()
.expect("WindowRegistry が存在するべき");
assert!(
!registry.is_empty(),
"生成済みウィンドウが WindowRegistry に保持されるべき(spawn→生成→保持)"
);
let _ = registry;
create_windows(&mut bevy_world);
let registry = bevy_world
.get_non_send::<WindowRegistry<LibWindow<WndState>>>()
.expect("WindowRegistry が存在するべき");
assert!(
!registry.is_empty(),
"再 create_windows でも registry は維持される(二重生成・取りこぼしなし)"
);
let _ = registry;
drop(bevy_world);
drop(shared);
}
}