use crate::{
layout::layoutbox::{LayoutBox, Rect},
tdom::node::{NodeArena, NodeId},
};
use super::{ElementRef, Geometry};
#[derive(Debug, Clone)]
pub struct HitRegion {
pub node_id: NodeId,
pub rect: Rect,
pub path: Vec<ElementRef>,
}
impl HitRegion {
pub fn id(&self) -> Option<&str> {
self.path.first().and_then(|e| e.id.as_deref())
}
}
#[derive(Default)]
pub struct HitMap {
regions: Vec<HitRegion>,
}
impl HitMap {
pub fn new() -> HitMap {
HitMap::default()
}
pub fn len(&self) -> usize {
self.regions.len()
}
pub fn is_empty(&self) -> bool {
self.regions.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &HitRegion> {
self.regions.iter()
}
pub fn build(geometry: &Geometry) -> HitMap {
let mut regions = Vec::new();
collect_regions(&geometry.boxes, &geometry.arena, &mut regions);
for (node_id, rect) in crate::layout::label_regions(&geometry.arena, &geometry.boxes) {
regions.push(HitRegion {
node_id,
rect,
path: ancestor_path(&geometry.arena, node_id),
});
}
HitMap { regions }
}
pub fn hit_test(&self, x: u32, y: u32) -> Option<&HitRegion> {
let mut best: Option<&HitRegion> = None;
for region in &self.regions {
if !region.rect.contains(x, y) {
continue;
}
match best {
None => best = Some(region),
Some(current) => {
if region.rect.area() <= current.rect.area() {
best = Some(region);
}
}
}
}
best
}
pub fn resolve_by_id(&self, id: &str) -> Option<&HitRegion> {
self.regions.iter().find(|r| r.id() == Some(id))
}
}
fn collect_regions(boxes: &[LayoutBox], arena: &NodeArena, out: &mut Vec<HitRegion>) {
for b in boxes {
out.push(HitRegion {
node_id: b.node_id,
rect: b.rect,
path: ancestor_path(arena, b.node_id),
});
collect_regions(&b.children, arena, out);
}
}
fn ancestor_path(arena: &NodeArena, start: NodeId) -> Vec<ElementRef> {
let mut path = Vec::new();
let mut current = Some(start);
while let Some(id) = current {
let node = &arena[id];
path.push(ElementRef {
id: node.get_id().cloned(),
node_id: id,
});
current = node.parent;
}
path
}