From 4cdea78df191ef6c87dda22f35fead42feed041e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=BClker?= Date: Sun, 17 Sep 2023 14:05:36 +0200 Subject: [PATCH] web/core: Implement basic hit testing We cannot track where within a node (text node for example) we clicked, but we can find *the* node that was clicked. --- web/core/src/css/fragment_tree/fragment.rs | 55 +++++++++++++++++++--- web/core/src/css/fragment_tree/mod.rs | 20 +++++++- web/core/src/css/layout/flow/block.rs | 6 ++- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/web/core/src/css/fragment_tree/fragment.rs b/web/core/src/css/fragment_tree/fragment.rs index fd70b5a0..f6fe0853 100644 --- a/web/core/src/css/fragment_tree/fragment.rs +++ b/web/core/src/css/fragment_tree/fragment.rs @@ -2,17 +2,21 @@ use std::rc::Rc; use math::Rectangle; -use crate::css::{ - display_list::Painter, - layout::{CSSPixels, Sides}, - properties::BackgroundColorValue, - stylecomputer::ComputedStyle, - values::color::Color, - FontMetrics, +use crate::{ + css::{ + display_list::Painter, + layout::{CSSPixels, Sides}, + properties::BackgroundColorValue, + stylecomputer::ComputedStyle, + values::color::Color, + FontMetrics, + }, + dom::{self, dom_objects, DOMPtr}, }; #[derive(Clone, Debug)] pub struct BoxFragment { + dom_node: Option>, style: Rc, margin: Sides, content_area: Rectangle, @@ -48,6 +52,35 @@ impl Fragment { Self::Text(text_fragment) => text_fragment.area, } } + + pub fn hit_test(&self, point: math::Vec2D) -> Option { + match self { + Self::Box(box_fragment) => { + log::info!( + "hit testing a {:?}", + box_fragment.dom_node.as_ref().map(DOMPtr::underlying_type) + ); + + let first_hit_child = box_fragment.children().iter().find(|child| { + child + .content_area_including_overflow() + .contains_point(point) + }); + + if let Some(hit_child) = first_hit_child { + hit_child.hit_test(point) + } else { + // None of our children were hit (or we have no children) + // In this case, this fragment is the target of the hit-test + Some(dom::BoundaryPoint::new(box_fragment.dom_node.clone()?, 0)) + } + }, + Self::Text(text_fragment) => { + log::info!("Clicked a text fragment! {:?}", text_fragment.text()); + None + }, + } + } } impl TextFragment { @@ -85,6 +118,7 @@ impl TextFragment { impl BoxFragment { #[must_use] pub fn new( + dom_node: Option>, style: Rc, margin: Sides, content_area: Rectangle, @@ -92,6 +126,7 @@ impl BoxFragment { children: Vec, ) -> Self { Self { + dom_node, style, margin, content_area, @@ -123,6 +158,12 @@ impl BoxFragment { self.margin.surround(self.content_area) } + #[inline] + #[must_use] + pub fn content_area_including_overflow(&self) -> Rectangle { + self.content_area_including_overflow + } + pub fn fill_display_list(&self, painter: &mut Painter) { match self.style.background_color() { BackgroundColorValue::Transparent => { diff --git a/web/core/src/css/fragment_tree/mod.rs b/web/core/src/css/fragment_tree/mod.rs index 3b9fb046..8d0b36ff 100644 --- a/web/core/src/css/fragment_tree/mod.rs +++ b/web/core/src/css/fragment_tree/mod.rs @@ -7,9 +7,11 @@ mod fragment; pub use fragment::{BoxFragment, Fragment, TextFragment}; -use super::display_list::Painter; +use crate::dom; -#[derive(Clone, Debug)] +use super::{display_list::Painter, layout::CSSPixels}; + +#[derive(Clone, Debug, Default)] pub struct FragmentTree { root_fragments: Vec, } @@ -24,4 +26,18 @@ impl FragmentTree { fragment.fill_display_list(painter); } } + + pub fn hit_test(&self, point: math::Vec2D) -> Option { + for fragment in &self.root_fragments { + if fragment + .content_area_including_overflow() + .contains_point(point) + { + if let Some(hit) = fragment.hit_test(point) { + return Some(hit); + } + } + } + None + } } diff --git a/web/core/src/css/layout/flow/block.rs b/web/core/src/css/layout/flow/block.rs index d7a1b4ee..78de5988 100644 --- a/web/core/src/css/layout/flow/block.rs +++ b/web/core/src/css/layout/flow/block.rs @@ -275,7 +275,8 @@ impl BlockLevelBox { let mut cursor = top_left; for block_box in block_level_boxes { let box_fragment = block_box.fragment(cursor, containing_block); - content_area_including_overflow.grow_to_contain(box_fragment.inner_area()); + content_area_including_overflow + .grow_to_contain(box_fragment.content_area_including_overflow()); cursor.y += box_fragment.outer_area().height(); children.push(Fragment::Box(box_fragment)); } @@ -287,7 +288,7 @@ impl BlockLevelBox { for fragment in &fragments { content_area_including_overflow - .grow_to_contain(fragment.content_area_including_overflow()) + .grow_to_contain(fragment.content_area_including_overflow()); } children.extend_from_slice(&fragments); @@ -318,6 +319,7 @@ impl BlockLevelBox { }; BoxFragment::new( + self.node.clone(), self.style(), margin, content_area,