Skip to content

Commit

Permalink
Restore some hit testing APIs
Browse files Browse the repository at this point in the history
These APIs are still used by Servo, so keep them around until Servo
can migrate off of them.
  • Loading branch information
mrobinson committed Mar 12, 2024
1 parent 618aad0 commit 974e744
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 19 deletions.
2 changes: 2 additions & 0 deletions examples/scrolling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,9 @@ impl Example for App {
winit::event::WindowEvent::MouseInput { .. } => {
let results = api.hit_test(
document_id,
None,
self.cursor_position,
HitTestFlags::empty(),
);

println!("Hit test results:");
Expand Down
93 changes: 82 additions & 11 deletions webrender/src/hit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::{BorderRadius, ClipMode, HitTestResultItem, HitTestResult, ItemTag, PrimitiveFlags};
use api::{PipelineId, ApiHitTester};
use api::{BorderRadius, ClipMode, HitTestFlags, HitTestResultItem, HitTestResult, ItemTag, PrimitiveFlags};
use api::{ApiHitTester, PipelineId};
use api::units::*;
use crate::clip::{rounded_rectangle_contains_point, ClipNodeId, ClipTreeBuilder};
use crate::clip::{polygon_contains_point, ClipItemKey, ClipItemKeyKind};
Expand Down Expand Up @@ -41,10 +41,13 @@ impl SharedHitTester {
}

impl ApiHitTester for SharedHitTester {
fn hit_test(&self,
fn hit_test(
&self,
pipeline_id: Option<PipelineId>,
point: WorldPoint,
flags: HitTestFlags,
) -> HitTestResult {
self.get_ref().hit_test(HitTest::new(point))
self.get_ref().hit_test(HitTest::new(pipeline_id, point, flags))
}
}

Expand Down Expand Up @@ -280,13 +283,15 @@ pub struct HitTester {
#[ignore_malloc_size_of = "Arc"]
scene: Arc<HitTestingScene>,
spatial_nodes: FastHashMap<SpatialNodeIndex, HitTestSpatialNode>,
pipeline_root_nodes: FastHashMap<PipelineId, SpatialNodeIndex>,
}

impl HitTester {
pub fn empty() -> Self {
HitTester {
scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())),
spatial_nodes: FastHashMap::default(),
pipeline_root_nodes: FastHashMap::default(),
}
}

Expand All @@ -297,6 +302,7 @@ impl HitTester {
let mut hit_tester = HitTester {
scene,
spatial_nodes: FastHashMap::default(),
pipeline_root_nodes: FastHashMap::default(),
};
hit_tester.read_spatial_tree(spatial_tree);
hit_tester
Expand All @@ -309,7 +315,13 @@ impl HitTester {
self.spatial_nodes.clear();
self.spatial_nodes.reserve(spatial_tree.spatial_node_count());

self.pipeline_root_nodes.clear();

spatial_tree.visit_nodes(|index, node| {
// If we haven't already seen a node for this pipeline, record this one as the root
// node.
self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);

//TODO: avoid inverting more than necessary:
// - if the coordinate system is non-invertible, no need to try any of these concrete transforms
// - if there are other places where inversion is needed, let's not repeat the step
Expand All @@ -328,10 +340,14 @@ impl HitTester {
}

pub fn hit_test(&self, test: HitTest) -> HitTestResult {
let point = test.get_absolute_point(self);

let mut result = HitTestResult::default();

let mut current_spatial_node_index = SpatialNodeIndex::INVALID;
let mut point_in_layer = None;
let mut current_root_spatial_node_index = SpatialNodeIndex::INVALID;
let mut point_in_viewport = None;

// For each hit test primitive
for item in self.scene.items.iter().rev() {
Expand All @@ -344,7 +360,7 @@ impl HitTester {
point_in_layer = scroll_node
.world_content_transform
.inverse()
.and_then(|inverted| inverted.project_point2d(test.point));
.and_then(|inverted| inverted.project_point2d(point));
current_spatial_node_index = item.spatial_node_index;
}

Expand Down Expand Up @@ -372,7 +388,7 @@ impl HitTester {
.world_content_transform;
if let Some(transformed_point) = transform
.inverse()
.and_then(|inverted| inverted.project_point2d(test.point))
.and_then(|inverted| inverted.project_point2d(point))
{
if !clip_node.region.contains(&transformed_point) {
is_valid = false;
Expand All @@ -392,29 +408,84 @@ impl HitTester {
continue;
}

result.items.push(HitTestResultItem {
pipeline: pipeline_id,
tag: item.tag,
animation_id: item.animation_id,
});
// We need to calculate the position of the test point relative to the origin of
// the pipeline of the hit item. If we cannot get a transformed point, we are
// in a situation with an uninvertible transformation so we should just skip this
// result.
let root_spatial_node_index = self.pipeline_root_nodes[&pipeline_id];
if root_spatial_node_index != current_root_spatial_node_index {
let root_node = &self.spatial_nodes[&root_spatial_node_index];
point_in_viewport = root_node
.world_viewport_transform
.inverse()
.and_then(|inverted| inverted.transform_point2d(test.point))
.map(|pt| pt - scroll_node.external_scroll_offset);

current_root_spatial_node_index = root_spatial_node_index;
}

if let Some(point_in_viewport) = point_in_viewport {
result.items.push(HitTestResultItem {
pipeline: pipeline_id,
tag: item.tag,
animation_id: item.animation_id,
point_in_viewport,
point_relative_to_item: point_in_layer - item.rect.min.to_vector(),
});
}

if !test.flags.contains(HitTestFlags::FIND_ALL) {
return result;
}
}

result.items.dedup();
result
}

fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestSpatialNode {
&self.spatial_nodes[&self.pipeline_root_nodes[&pipeline_id]]
}

}

#[derive(MallocSizeOf)]
pub struct HitTest {
pipeline_id: Option<PipelineId>,
point: WorldPoint,
#[ignore_malloc_size_of = "bitflags"]
flags: HitTestFlags,
}

impl HitTest {
pub fn new(
pipeline_id: Option<PipelineId>,
point: WorldPoint,
flags: HitTestFlags,
) -> HitTest {
HitTest {
pipeline_id,
point,
flags
}
}

fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
return self.point;
}

let point = LayoutPoint::new(self.point.x, self.point.y);
self.pipeline_id
.and_then(|id|
hit_tester
.get_pipeline_root(id)
.world_viewport_transform
.transform_point2d(point)
)
.unwrap_or_else(|| {
WorldPoint::new(self.point.x, self.point.y)
})
}

}
8 changes: 5 additions & 3 deletions webrender/src/render_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::marker::PhantomData;
use std::path::PathBuf;
use std::sync::Arc;
use std::u32;
use api::MinimapData;
use api::{HitTestFlags, MinimapData};
use time::precise_time_ns;
use crate::api::channel::{Sender, single_msg_channel, unbounded_channel};
use crate::api::{BuiltDisplayList, IdNamespace, ExternalScrollId, Parameter, BoolParameter};
Expand Down Expand Up @@ -792,7 +792,7 @@ pub enum FrameMsg {
///
UpdateEpoch(PipelineId, Epoch),
///
HitTest(WorldPoint, Sender<HitTestResult>),
HitTest(Option<PipelineId>, WorldPoint, HitTestFlags, Sender<HitTestResult>),
///
RequestHitTester(Sender<Arc<dyn ApiHitTester>>),
///
Expand Down Expand Up @@ -1276,13 +1276,15 @@ impl RenderApi {
/// front to back.
pub fn hit_test(&self,
document_id: DocumentId,
pipeline_id: Option<PipelineId>,
point: WorldPoint,
flags: HitTestFlags,
) -> HitTestResult {
let (tx, rx) = single_msg_channel();

self.send_frame_msg(
document_id,
FrameMsg::HitTest(point, tx)
FrameMsg::HitTest(pipeline_id, point, flags, tx)
);
rx.recv().unwrap()
}
Expand Down
4 changes: 2 additions & 2 deletions webrender/src/render_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,14 +443,14 @@ impl Document {
FrameMsg::UpdateEpoch(pipeline_id, epoch) => {
self.scene.pipeline_epochs.insert(pipeline_id, epoch);
}
FrameMsg::HitTest(point, tx) => {
FrameMsg::HitTest(pipeline_id, point, flags, tx) => {
if !self.hit_tester_is_valid {
self.rebuild_hit_tester();
}

let result = match self.hit_tester {
Some(ref hit_tester) => {
hit_tester.hit_test(HitTest::new(point))
hit_tester.hit_test(HitTest::new(pipeline_id, point, flags))
}
None => HitTestResult { items: Vec::new() },
};
Expand Down
28 changes: 25 additions & 3 deletions webrender_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,11 @@ impl NotificationRequest {
/// the RenderBackendThread.
pub trait ApiHitTester: Send + Sync {
/// Does a hit test on display items in the specified document, at the given
/// point. The vector of hit results will contain all display items that match,
/// ordered from front to back.
fn hit_test(&self, point: WorldPoint) -> HitTestResult;
/// point. If a pipeline_id is specified, it is used to further restrict the
/// hit results so that only items inside that pipeline are matched. The vector
/// of hit results will contain all display items that match, ordered from
/// front to back.
fn hit_test(&self, pipeline_id: Option<PipelineId>, point: WorldPoint, flags: HitTestFlags) -> HitTestResult;
}

/// A hit tester requested to the render backend thread but not necessarily ready yet.
Expand Down Expand Up @@ -367,6 +369,15 @@ pub struct HitTestResultItem {

/// The animation id from the stacking context.
pub animation_id: u64,

/// The hit point in the coordinate space of the "viewport" of the display item. The
/// viewport is the scroll node formed by the root reference frame of the display item's
/// pipeline.
pub point_in_viewport: LayoutPoint,

/// The coordinates of the original hit test point relative to the origin of this item.
/// This is useful for calculating things like text offsets in the client.
pub point_relative_to_item: LayoutPoint,
}

/// Returned by `RenderApi::hit_test`.
Expand All @@ -376,6 +387,17 @@ pub struct HitTestResult {
pub items: Vec<HitTestResultItem>,
}

bitflags! {
#[derive(Deserialize, Serialize)]
///
pub struct HitTestFlags: u8 {
///
const FIND_ALL = 0b00000001;
///
const POINT_RELATIVE_TO_PIPELINE_VIEWPORT = 0b00000010;
}
}

impl Drop for NotificationRequest {
fn drop(&mut self) {
if let Some(ref mut handler) = self.handler {
Expand Down
2 changes: 2 additions & 0 deletions wrench/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,9 @@ fn render<'a>(
VirtualKeyCode::X => {
let results = wrench.api.hit_test(
wrench.document_id,
None,
cursor_position,
HitTestFlags::empty(),
);

println!("Hit test results:");
Expand Down
2 changes: 2 additions & 0 deletions wrench/src/rawtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,9 @@ impl<'a> RawtestHarness<'a> {
let hit_test = |point: WorldPoint| -> HitTestResult {
self.wrench.api.hit_test(
self.wrench.document_id,
None,
point,
HitTestFlags::empty(),
)
};

Expand Down

0 comments on commit 974e744

Please sign in to comment.