use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use tracing::trace;
use windows::Win32::Foundation::{HWND, LRESULT, POINT};
use windows::Win32::Graphics::Gdi::ScreenToClient;
use crate::ecs::layout::hit_test::{PhysicalPoint, hit_test_in_window};
use crate::ecs::world::EcsWorld;
const HTCLIENT: i32 = 1;
const HTTRANSPARENT: i32 = -1;
struct NchittestCacheEntry {
screen_point: (i32, i32),
lresult: LRESULT,
}
thread_local! {
static NCHITTEST_CACHE: RefCell<HashMap<isize, NchittestCacheEntry>>
= RefCell::new(HashMap::new());
}
fn lookup(hwnd: HWND, screen_point: (i32, i32)) -> Option<LRESULT> {
NCHITTEST_CACHE.with(|cache| {
let cache = cache.borrow();
if let Some(entry) = cache.get(&(hwnd.0 as isize)) {
if entry.screen_point == screen_point {
return Some(entry.lresult);
}
}
None
})
}
fn insert(hwnd: HWND, screen_point: (i32, i32), lresult: LRESULT) {
NCHITTEST_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
cache.insert(
hwnd.0 as isize,
NchittestCacheEntry {
screen_point,
lresult,
},
);
});
}
pub fn cached_nchittest(
hwnd: HWND,
screen_point: (i32, i32),
entity: bevy_ecs::prelude::Entity,
ecs_world: &Rc<RefCell<EcsWorld>>,
) -> Option<LRESULT> {
if let Some(lresult) = lookup(hwnd, screen_point) {
trace!(
hwnd = ?hwnd,
x = screen_point.0,
y = screen_point.1,
lresult = lresult.0,
"NCHITTEST cache hit"
);
return Some(lresult);
}
let mut pt = POINT {
x: screen_point.0,
y: screen_point.1,
};
if unsafe { !ScreenToClient(hwnd, &mut pt).as_bool() } {
return None;
}
let hit_result = match ecs_world.try_borrow() {
Ok(world_ref) => hit_test_in_window(
world_ref.world(),
entity,
PhysicalPoint::new(pt.x as f32, pt.y as f32),
),
Err(_) => {
return None; }
};
let is_dragging = crate::ecs::drag::read_drag_state(|state| {
matches!(
state,
crate::ecs::drag::DragState::Preparing { .. }
| crate::ecs::drag::DragState::JustStarted { .. }
| crate::ecs::drag::DragState::Dragging { .. }
)
});
let lresult = if is_dragging || hit_result.is_some() {
LRESULT(HTCLIENT as isize)
} else {
LRESULT(HTTRANSPARENT as isize)
};
insert(hwnd, screen_point, lresult);
trace!(
hwnd = ?hwnd,
x = screen_point.0,
y = screen_point.1,
lresult = lresult.0,
hit_entity = ?hit_result,
"NCHITTEST cache miss"
);
Some(lresult)
}
pub fn clear_nchittest_cache() {
NCHITTEST_CACHE.with(|cache| {
cache.borrow_mut().clear();
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_lookup_insert() {
let hwnd = HWND(12345 as *mut std::ffi::c_void);
let screen_point = (100, 200);
let lresult = LRESULT(1);
assert!(lookup(hwnd, screen_point).is_none());
insert(hwnd, screen_point, lresult);
assert_eq!(lookup(hwnd, screen_point), Some(lresult));
assert!(lookup(hwnd, (101, 200)).is_none());
clear_nchittest_cache();
}
#[test]
fn test_cache_multiple_hwnds() {
let hwnd1 = HWND(111 as *mut std::ffi::c_void);
let hwnd2 = HWND(222 as *mut std::ffi::c_void);
let screen_point = (50, 50);
let lresult1 = LRESULT(1);
let lresult2 = LRESULT(-1);
insert(hwnd1, screen_point, lresult1);
insert(hwnd2, screen_point, lresult2);
assert_eq!(lookup(hwnd1, screen_point), Some(lresult1));
assert_eq!(lookup(hwnd2, screen_point), Some(lresult2));
clear_nchittest_cache();
}
#[test]
fn test_cache_clear() {
let hwnd = HWND(999 as *mut std::ffi::c_void);
let screen_point = (10, 20);
let lresult = LRESULT(1);
insert(hwnd, screen_point, lresult);
assert!(lookup(hwnd, screen_point).is_some());
clear_nchittest_cache();
assert!(lookup(hwnd, screen_point).is_none());
}
#[test]
fn test_cache_update() {
let hwnd = HWND(777 as *mut std::ffi::c_void);
let point1 = (100, 100);
let point2 = (200, 200);
let lresult1 = LRESULT(1);
let lresult2 = LRESULT(-1);
insert(hwnd, point1, lresult1);
assert_eq!(lookup(hwnd, point1), Some(lresult1));
insert(hwnd, point2, lresult2);
assert!(lookup(hwnd, point1).is_none());
assert_eq!(lookup(hwnd, point2), Some(lresult2));
clear_nchittest_cache();
}
#[test]
fn test_cache_httransparent_storage() {
let hwnd = HWND(5001 as *mut std::ffi::c_void);
let screen_point = (300, 400);
let httransparent = LRESULT(HTTRANSPARENT as isize);
insert(hwnd, screen_point, httransparent);
let result = lookup(hwnd, screen_point);
assert_eq!(result, Some(httransparent));
assert_eq!(result.unwrap().0, -1);
clear_nchittest_cache();
}
#[test]
fn test_cache_htclient_and_httransparent_coexist() {
let hwnd1 = HWND(6001 as *mut std::ffi::c_void);
let hwnd2 = HWND(6002 as *mut std::ffi::c_void);
let screen_point = (150, 250);
let htclient = LRESULT(HTCLIENT as isize);
let httransparent = LRESULT(HTTRANSPARENT as isize);
insert(hwnd1, screen_point, htclient);
insert(hwnd2, screen_point, httransparent);
assert_eq!(lookup(hwnd1, screen_point), Some(htclient));
assert_eq!(lookup(hwnd2, screen_point), Some(httransparent));
clear_nchittest_cache();
}
}