use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use std::sync::Arc;
use bevy_ecs::entity::Entity;
use bevy_ecs::world::World;
use event_listener::Event;
use tracing::{debug, trace, warn};
use crate::ecs::PointF;
use crate::ecs::drag::{DragStateSnapshot, snapshot_drag_state};
use crate::ecs::hit_test_in_window;
use crate::ecs::world::EcsWorld;
use crate::ecs::{PhysicalPoint, WindowPos};
use crate::win_style::{apply_click_through, apply_layered_companion};
use windows::Win32::Foundation::{HWND, POINT};
use super::{ClickThroughRegistry, CursorMonitorBridge, DesiredState};
pub(crate) fn resolve_transition(
hit: Option<Entity>,
drag: &DragStateSnapshot,
last_applied: DesiredState,
) -> Option<DesiredState> {
let desired = match drag {
DragStateSnapshot::Dragging { .. } | DragStateSnapshot::JustStarted { .. } => {
DesiredState::Opaque
}
DragStateSnapshot::Idle
| DragStateSnapshot::Preparing { .. }
| DragStateSnapshot::JustEnded { .. } => match hit {
Some(_) => DesiredState::Opaque,
None => DesiredState::Transparent,
},
};
if desired == last_applied {
None
} else {
Some(desired)
}
}
pub(crate) fn prune_dead_targets(world: &World, registry: &mut ClickThroughRegistry) -> usize {
registry.retain(|t| world.get_entity(t.window).is_ok())
}
pub(crate) fn evaluate_targets(
world: &World,
registry: &mut ClickThroughRegistry,
mut screen_to_client: impl FnMut(HWND) -> Option<PointF>,
) {
prune_dead_targets(world, registry);
let drag = snapshot_drag_state();
let targets: Vec<(Entity, windows::Win32::Foundation::HWND, DesiredState, bool)> = registry
.iter()
.map(|t| (t.window, t.hwnd, t.last_applied, t.layered_applied))
.collect();
for (window, hwnd, last_applied, layered_applied) in targets {
if !layered_applied {
match apply_layered_companion(hwnd) {
Ok(()) => {
debug!(?window, "clickthrough: WS_EX_LAYERED 同伴フラグ適用");
registry.mark_layered_applied(window);
}
Err(e) => {
warn!(?window, error = %e, "clickthrough: apply_layered_companion 失敗 — skip");
continue;
}
}
}
if world
.get::<WindowPos>(window)
.and_then(|wp| wp.position)
.is_none()
{
trace!(?window, "clickthrough: WindowPos.position 未確定 — skip");
continue;
}
let Some(client) = screen_to_client(hwnd) else {
trace!(?window, "clickthrough: screen→client 変換失敗 — skip");
continue;
};
let hit = hit_test_in_window(world, window, client);
let Some(desired) = resolve_transition(hit, &drag, last_applied) else {
continue;
};
match apply_click_through(hwnd, desired == DesiredState::Transparent) {
Ok(()) => {
debug!(?window, ?desired, "clickthrough: ex-style トグル適用");
registry.set_last_applied(window, desired);
}
Err(e) => {
warn!(?window, error = %e, "clickthrough: apply_click_through 失敗 — skip");
}
}
}
}
fn screen_to_client_point(hwnd: HWND, screen: PhysicalPoint) -> Option<PointF> {
use windows::Win32::Graphics::Gdi::ScreenToClient;
let mut pt = POINT {
x: screen.x,
y: screen.y,
};
if unsafe { ScreenToClient(hwnd, &mut pt).as_bool() } {
Some(PointF::new(pt.x as f32, pt.y as f32))
} else {
None
}
}
pub(crate) struct ClickThroughController;
impl ClickThroughController {
pub(crate) fn start(
world: Weak<RefCell<EcsWorld>>,
registry: Rc<RefCell<ClickThroughRegistry>>,
wake_event: Arc<Event>,
running: Rc<Cell<bool>>,
) -> ClickThroughHandle {
let monitor = Rc::new(CursorMonitorBridge::spawn(Arc::clone(&wake_event)));
let join = crate::executor::spawn_local(run_click_through(
Arc::clone(&wake_event),
Weak::clone(&world),
Rc::clone(®istry),
Rc::downgrade(&monitor),
running,
));
debug!("ClickThroughController started (UI-thread eval loop + cursor worker)");
ClickThroughHandle {
monitor,
registry,
wake_event,
_join: join,
}
}
}
pub(crate) struct ClickThroughHandle {
monitor: Rc<CursorMonitorBridge>,
registry: Rc<RefCell<ClickThroughRegistry>>,
wake_event: Arc<Event>,
_join: crate::executor::JoinHandle<()>,
}
impl ClickThroughHandle {
pub(crate) fn register(&self, window: Entity, hwnd: windows::Win32::Foundation::HWND) {
self.registry.borrow_mut().register(window, hwnd);
}
pub(crate) fn remove(&self, window: Entity) -> bool {
self.registry.borrow_mut().remove(window)
}
pub(crate) fn registry(&self) -> &Rc<RefCell<ClickThroughRegistry>> {
&self.registry
}
pub(crate) fn wake_event(&self) -> &Arc<Event> {
&self.wake_event
}
pub(crate) fn wake(&self) {
self.wake_event.notify(usize::MAX);
}
pub(crate) fn latest_cursor(&self) -> PhysicalPoint {
self.monitor.latest_cursor()
}
}
pub struct ClickThroughRegistryHandle {
registry: Rc<RefCell<ClickThroughRegistry>>,
}
impl ClickThroughRegistryHandle {
pub(crate) fn new(registry: Rc<RefCell<ClickThroughRegistry>>) -> Self {
Self { registry }
}
pub fn register(&self, window: Entity, hwnd: windows::Win32::Foundation::HWND) {
self.registry.borrow_mut().register(window, hwnd);
}
pub fn remove(&self, window: Entity) -> bool {
self.registry.borrow_mut().remove(window)
}
pub fn len(&self) -> usize {
self.registry.borrow().len()
}
pub fn is_empty(&self) -> bool {
self.registry.borrow().is_empty()
}
}
async fn run_click_through(
wake_event: Arc<Event>,
world: Weak<RefCell<EcsWorld>>,
registry: Rc<RefCell<ClickThroughRegistry>>,
monitor: Weak<CursorMonitorBridge>,
running: Rc<Cell<bool>>,
) {
debug!("ClickThrough eval loop started (dual-wake: cursor notify + VSync tick)");
loop {
let listener = wake_event.listen();
listener.await;
if !running.get() {
debug!("ClickThrough eval loop stopping (message loop has returned)");
return;
}
let (Some(world_rc), Some(monitor_rc)) = (world.upgrade(), monitor.upgrade()) else {
debug!("ClickThrough eval loop stopping (world/handle dropped — shutdown)");
return;
};
let cursor = monitor_rc.latest_cursor();
match world_rc.try_borrow() {
Ok(ecs_world) => {
let mut reg = registry.borrow_mut();
evaluate_targets(ecs_world.world(), &mut reg, |hwnd| {
screen_to_client_point(hwnd, cursor)
});
}
Err(_) => {
trace!("clickthrough: World borrow 失敗 — このサイクルを安全スキップ");
}
}
}
}
#[cfg(test)]
#[path = "controller_tests.rs"]
mod tests;