use bevy_ecs::prelude::*;
use bevy_ecs::system::NonSendMarker;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::{GW_HWNDNEXT, GWLP_HWNDPARENT, GetWindow};
use crate::api::{clear_window_owner, get_window_long_ptr, set_window_owner};
use super::WindowHandle;
use super::command::{SetWindowPosCommand, guarded_set_window_pos};
use super::zorder_chain::{
ChainOp, CrossEdge, CrossOwnerLink, ZOrderChainPlan, log_chain_link_failed, log_chain_linked,
log_chain_settled, log_chain_skipped, log_chain_unlink_failed, log_chain_unlinked,
nudge_command, plan_chain_ops,
};
use super::zorder_chain_diag::{ChainSegment, ChainSkipReason, DetachReason};
use super::zorder_pair::measure_windows_in_front;
type CrossLinkQuery<'w, 's> = Query<'w, 's, (Entity, &'static CrossOwnerLink)>;
type HandleQuery<'w, 's> = Query<'w, 's, Option<&'static WindowHandle>>;
#[doc(alias = "carry_announced")]
pub fn apply_zorder_chain(
_ui_thread: NonSendMarker,
mut commands: Commands,
plan: Option<ResMut<ZOrderChainPlan>>,
links: CrossLinkQuery,
handles: HandleQuery,
mut carry_announced: Local<bool>,
) {
let departed = detach_cross_owner_links_for_departing(&mut commands, &links, &handles);
let Some(mut plan) = plan else {
return;
};
if !plan.dirty {
return;
}
plan.dirty = false;
let (members, desired) = match plan.chain.as_ref() {
Some(chain) => (chain.members.clone(), chain.cross_edges.clone()),
None => (Vec::new(), Vec::new()),
};
let current: Vec<(Entity, CrossOwnerLink)> = links
.iter()
.filter(|(owned, _)| !departed.contains(owned))
.map(|(owned, link)| (owned, *link))
.collect();
let ops = plan_chain_ops(&desired, ¤t);
refresh_ledger_segments(&mut commands, &desired, ¤t);
if ops.is_empty() {
log_chain_skipped(ChainSkipReason::NoChange);
*carry_announced = false;
return;
}
let outcome = execute_ops(
&mut commands,
&ops,
&desired,
¤t,
&members,
&handles,
!*carry_announced,
);
if !outcome.acted {
if outcome.handle_missing {
plan.dirty = true;
*carry_announced = true;
} else {
*carry_announced = false;
}
return;
}
*carry_announced = false;
nudge_and_measure(&members, &handles);
}
fn detach_cross_owner_links_for_departing(
commands: &mut Commands,
links: &CrossLinkQuery,
handles: &HandleQuery,
) -> Vec<Entity> {
let mut departed = Vec::new();
for (owned, link) in links.iter() {
let owner_gone = match handles.get(link.owner) {
Ok(Some(_)) => false,
Ok(None) | Err(_) => true,
};
if !owner_gone {
continue;
}
detach_one(commands, owned, link, DetachReason::Departing);
departed.push(owned);
}
departed
}
fn refresh_ledger_segments(
commands: &mut Commands,
desired: &[CrossEdge],
current: &[(Entity, CrossOwnerLink)],
) {
for edge in desired {
let Some((_, link)) = current.iter().find(|(owned, _)| *owned == edge.owned) else {
continue;
};
if link.owner != edge.owner || link.segment == edge.segment {
continue;
}
commands.entity(edge.owned).insert(CrossOwnerLink {
segment: edge.segment,
..*link
});
}
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
struct ExecOutcome {
acted: bool,
handle_missing: bool,
}
fn execute_ops(
commands: &mut Commands,
ops: &[ChainOp],
desired: &[CrossEdge],
current: &[(Entity, CrossOwnerLink)],
members: &[Entity],
handles: &HandleQuery,
record_handle_missing: bool,
) -> ExecOutcome {
let total = members.len();
let mut acted = false;
let mut handle_missing = false;
for op in ops {
match *op {
ChainOp::Detach { owned, reason } => {
let Some((_, link)) = current.iter().find(|(recorded, _)| *recorded == owned)
else {
log_chain_skipped(ChainSkipReason::HandleMissing);
continue;
};
acted |= detach_one(commands, owned, link, reason);
}
ChainOp::Attach { owned, owner } => {
let segment = desired
.iter()
.find(|edge| edge.owned == owned && edge.owner == owner)
.map(|edge| edge.segment);
let (Some(owned_hwnd), Some(owner_hwnd)) =
(hwnd_of(handles, owned), hwnd_of(handles, owner))
else {
handle_missing = true;
if record_handle_missing {
log_chain_skipped(ChainSkipReason::HandleMissing);
}
continue;
};
acted = true;
match os_set_owner(owned_hwnd, owner_hwnd) {
Ok(()) => {
commands.entity(owned).insert(CrossOwnerLink {
owner,
owned_hwnd,
owner_hwnd,
segment: segment.unwrap_or(ChainSegment::Tail),
});
let pos = members
.iter()
.position(|m| *m == owned)
.map_or(0, |i| i + 1);
log_chain_linked(
segment,
owned,
owner,
Some(owned_hwnd),
Some(owner_hwnd),
pos,
total,
);
}
Err(err) => {
log_chain_link_failed(segment, Some(owned_hwnd), Some(owner_hwnd), &err);
}
}
}
}
}
ExecOutcome {
acted,
handle_missing,
}
}
fn detach_one(
commands: &mut Commands,
owned: Entity,
link: &CrossOwnerLink,
reason: DetachReason,
) -> bool {
let hwnd = link.owned_hwnd;
let touched = match os_read_owner(hwnd) {
Ok(actual) if actual == link.owner_hwnd => match os_clear_owner(hwnd) {
Ok(()) => {
log_chain_unlinked(
Some(link.segment),
owned,
Some(hwnd),
Some(link.owner_hwnd),
reason,
);
true
}
Err(err) => {
log_chain_unlink_failed(Some(hwnd), &err);
true
}
},
Ok(_) => {
log_chain_unlinked(
Some(link.segment),
owned,
Some(hwnd),
Some(link.owner_hwnd),
DetachReason::Diverged,
);
false
}
Err(err) => {
log_chain_unlink_failed(Some(hwnd), &err);
false
}
};
commands.entity(owned).remove::<CrossOwnerLink>();
touched
}
fn nudge_and_measure(members: &[Entity], handles: &HandleQuery) {
let declared: Vec<HWND> = members
.iter()
.filter_map(|m| hwnd_of(handles, *m))
.collect();
let anchor_next = declared
.len()
.checked_sub(2)
.and_then(|at| os_raw_next(declared[at]));
let Some(cmd) = nudge_command(&declared, anchor_next) else {
log_chain_skipped(ChainSkipReason::TooFewPresent);
return;
};
let nudge_ok = os_nudge(&cmd).is_ok();
let measured = measure_chain_order(&declared);
log_chain_settled(
Some(cmd.hwnd),
cmd.hwnd_insert_after,
&declared,
&measured,
Some(nudge_ok),
);
}
fn measure_chain_order(declared: &[HWND]) -> Vec<HWND> {
let Some(root) = declared.last().copied() else {
return Vec::new();
};
let mut measured: Vec<HWND> = os_measure_front(root)
.into_iter()
.filter(|found| declared.contains(found))
.collect();
measured.reverse();
measured.push(root);
measured
}
fn hwnd_of(handles: &HandleQuery, entity: Entity) -> Option<HWND> {
match handles.get(entity) {
Ok(Some(handle)) => Some(handle.hwnd),
Ok(None) | Err(_) => None,
}
}
fn os_read_owner(hwnd: HWND) -> windows::core::Result<HWND> {
#[cfg(test)]
if let Some(scripted) = double::read_owner(hwnd) {
return scripted;
}
get_window_long_ptr(hwnd, GWLP_HWNDPARENT).map(|value| HWND(value as *mut _))
}
fn os_clear_owner(hwnd: HWND) -> windows::core::Result<()> {
#[cfg(test)]
if let Some(scripted) = double::clear_owner(hwnd) {
return scripted;
}
clear_window_owner(hwnd)
}
fn os_set_owner(owned: HWND, owner: HWND) -> windows::core::Result<()> {
#[cfg(test)]
if let Some(scripted) = double::set_owner(owned, owner) {
return scripted;
}
set_window_owner(owned, owner)
}
fn os_raw_next(hwnd: HWND) -> Option<HWND> {
#[cfg(test)]
if let Some(scripted) = double::raw_next(hwnd) {
return scripted;
}
unsafe { GetWindow(hwnd, GW_HWNDNEXT) }.ok()
}
fn os_nudge(cmd: &SetWindowPosCommand) -> windows::core::Result<()> {
#[cfg(test)]
if let Some(scripted) = double::nudge(cmd) {
return scripted;
}
unsafe {
guarded_set_window_pos(
cmd.hwnd,
cmd.hwnd_insert_after,
cmd.x,
cmd.y,
cmd.width,
cmd.height,
cmd.flags,
)
}
}
fn os_measure_front(hwnd: HWND) -> Vec<HWND> {
#[cfg(test)]
if let Some(scripted) = double::measure_front(hwnd) {
return scripted;
}
measure_windows_in_front(hwnd).windows
}
#[cfg(test)]
pub(crate) mod double {
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use windows::Win32::Foundation::HWND;
use windows::core::HRESULT;
use super::SetWindowPosCommand;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Call {
ReadOwner(usize),
ClearOwner(usize),
SetOwner(usize, usize),
RawNext(usize),
Nudge(usize, usize),
MeasureFront(usize),
}
#[derive(Default)]
pub(crate) struct Script {
pub calls: Vec<Call>,
pub owner_of: HashMap<usize, usize>,
pub read_fails: HashSet<usize>,
pub clear_fails: HashSet<usize>,
pub set_fails: HashSet<usize>,
pub next_of: HashMap<usize, usize>,
pub nudge_fails: bool,
pub front_of: HashMap<usize, Vec<usize>>,
}
thread_local! {
static SCRIPT: RefCell<Option<Script>> = const { RefCell::new(None) };
}
fn failure() -> windows::core::Error {
windows::core::Error::from(HRESULT(0x8007_0578u32 as i32))
}
pub(crate) fn with_script<R>(script: Script, body: impl FnOnce() -> R) -> (R, Script) {
struct Restore;
impl Drop for Restore {
fn drop(&mut self) {
SCRIPT.with(|cell| *cell.borrow_mut() = None);
}
}
SCRIPT.with(|cell| *cell.borrow_mut() = Some(script));
let _restore = Restore;
let out = body();
let used = SCRIPT.with(|cell| cell.borrow_mut().take());
(out, used.expect("台本は走行中に落ちない"))
}
fn with<R>(f: impl FnOnce(&mut Script) -> R) -> Option<R> {
SCRIPT.with(|cell| cell.borrow_mut().as_mut().map(f))
}
pub(super) fn read_owner(hwnd: HWND) -> Option<windows::core::Result<HWND>> {
with(|s| {
let key = hwnd.0 as usize;
s.calls.push(Call::ReadOwner(key));
if s.read_fails.contains(&key) {
return Err(failure());
}
let owner = s.owner_of.get(&key).copied().unwrap_or(0);
Ok(HWND(owner as *mut _))
})
}
pub(super) fn clear_owner(hwnd: HWND) -> Option<windows::core::Result<()>> {
with(|s| {
let key = hwnd.0 as usize;
s.calls.push(Call::ClearOwner(key));
if s.clear_fails.contains(&key) {
return Err(failure());
}
s.owner_of.remove(&key);
Ok(())
})
}
pub(super) fn set_owner(owned: HWND, owner: HWND) -> Option<windows::core::Result<()>> {
with(|s| {
let key = owned.0 as usize;
s.calls.push(Call::SetOwner(key, owner.0 as usize));
if s.set_fails.contains(&key) {
return Err(failure());
}
s.owner_of.insert(key, owner.0 as usize);
Ok(())
})
}
pub(super) fn raw_next(hwnd: HWND) -> Option<Option<HWND>> {
with(|s| {
let key = hwnd.0 as usize;
s.calls.push(Call::RawNext(key));
s.next_of.get(&key).map(|next| HWND(*next as *mut _))
})
}
pub(super) fn nudge(cmd: &SetWindowPosCommand) -> Option<windows::core::Result<()>> {
with(|s| {
s.calls.push(Call::Nudge(
cmd.hwnd.0 as usize,
cmd.hwnd_insert_after.map_or(0, |h| h.0 as usize),
));
if s.nudge_fails {
return Err(failure());
}
Ok(())
})
}
pub(super) fn measure_front(hwnd: HWND) -> Option<Vec<HWND>> {
with(|s| {
let key = hwnd.0 as usize;
s.calls.push(Call::MeasureFront(key));
s.front_of
.get(&key)
.map(|found| found.iter().map(|v| HWND(*v as *mut _)).collect())
.unwrap_or_default()
})
}
}
#[cfg(test)]
#[path = "zorder_chain_apply_tests.rs"]
mod zorder_chain_apply_tests;
#[cfg(test)]
#[path = "zorder_chain_order_tests.rs"]
mod zorder_chain_order_tests;
#[cfg(test)]
#[path = "zorder_chain_order_lifecycle_tests.rs"]
mod zorder_chain_order_lifecycle_tests;
#[cfg(test)]
#[path = "zorder_chain_order_outsider_tests.rs"]
mod zorder_chain_order_outsider_tests;