Basic hit testing functionality

This commit is contained in:
Patrick Walton 2013-05-30 17:28:08 -07:00
parent ea1a406589
commit f77eef5988
8 changed files with 148 additions and 20 deletions

View file

@ -28,7 +28,7 @@ use std::arc;
/// A list of rendering operations to be performed.
pub struct DisplayList<E> {
priv list: ~[DisplayItem<E>]
list: ~[DisplayItem<E>]
}
impl<E> DisplayList<E> {

View file

@ -4,8 +4,9 @@
use compositing::resize_rate_limiter::ResizeRateLimiter;
use platform::{Application, Window};
use script::script_task::{LoadMsg, ScriptMsg};
use script::script_task::{LoadMsg, ScriptMsg, SendEventMsg};
use windowing::{ApplicationMethods, WindowMethods};
use script::dom::event::ClickEvent;
use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods};
use core::cell::Cell;
@ -222,10 +223,22 @@ fn run_main_loop(port: Port<Msg>,
resize_rate_limiter.window_resized(width, height)
}
let script_chan_clone = script_chan.clone();
// When the user enters a new URL, load it.
do window.set_load_url_callback |url_string| {
debug!("osmain: loading URL `%s`", url_string);
script_chan.send(LoadMsg(url::make_url(url_string.to_str(), None)))
script_chan_clone.send(LoadMsg(url::make_url(url_string.to_str(), None)))
}
let script_chan_clone = script_chan.clone();
// When the user clicks, perform hit testing
do window.set_click_callback |layer_click_point| {
let world_click_point = layer_click_point + *world_offset;
debug!("osmain: clicked at %?", world_click_point);
script_chan_clone.send(SendEventMsg(ClickEvent(world_click_point)));
}
// When the user scrolls, move the layer around.

View file

@ -8,6 +8,7 @@
use css::matching::MatchMethods;
use css::select::new_css_select_ctx;
use layout::aux::{LayoutData, LayoutAuxMethods};
use layout::box::RenderBox;
use layout::box_builder::LayoutTreeBuilder;
use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, FlowDisplayListBuilderMethods};
@ -32,15 +33,17 @@ use newcss::types::OriginAuthor;
use script::dom::event::ReflowEvent;
use script::dom::node::{AbstractNode, LayoutView};
use script::layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, ContentBoxQuery};
use script::layout_interface::{ContentBoxResponse, ContentBoxesQuery, ContentBoxesResponse};
use script::layout_interface::{ExitMsg, LayoutQuery, LayoutResponse, LayoutTask};
use script::layout_interface::{MatchSelectorsDamage, Msg, NoDamage, QueryMsg, ReflowDamage};
use script::layout_interface::{HitTestQuery, ContentBoxResponse, HitTestResponse};
use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg, LayoutQuery};
use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDamage, Msg, NoDamage};
use script::layout_interface::{QueryMsg, ReflowDamage};
use script::script_task::{ScriptMsg, SendEventMsg};
use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
use servo_net::local_image_cache::LocalImageCache;
use servo_util::tree::{TreeNodeRef, TreeUtils};
use servo_util::time::{ProfilerChan, profile, time};
use servo_util::time;
use std::net::url::Url;
pub fn create_layout_task(render_task: RenderTask,
img_cache_task: ImageCacheTask,
@ -67,6 +70,8 @@ struct Layout {
local_image_cache: @mut LocalImageCache,
from_script: Port<Msg>,
font_ctx: @mut FontContext,
doc_url: Option<Url>,
screen_size: Option<Size2D<Au>>,
/// This is used to root reader data.
layout_refs: ~[@mut LayoutData],
@ -90,6 +95,9 @@ impl Layout {
local_image_cache: @mut LocalImageCache(image_cache_task),
from_script: from_script,
font_ctx: fctx,
doc_url: None,
screen_size: None,
layout_refs: ~[],
css_select_ctx: @mut new_css_select_ctx(),
profiler_chan: profiler_chan,
@ -102,6 +110,21 @@ impl Layout {
}
}
// Create a layout context for use in building display lists, hit testing, &c.
fn build_layout_context(&self) -> LayoutContext {
let image_cache = self.local_image_cache;
let font_ctx = self.font_ctx;
let screen_size = self.screen_size.unwrap();
let doc_url = self.doc_url.clone();
LayoutContext {
image_cache: image_cache,
font_ctx: font_ctx,
doc_url: doc_url.unwrap(),
screen_size: Rect(Point2D(Au(0), Au(0)), screen_size),
}
}
fn handle_request(&mut self) -> bool {
match self.from_script.recv() {
AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet),
@ -147,20 +170,16 @@ impl Layout {
debug!("layout: damage is %?", data.damage);
debug!("layout: parsed Node tree");
debug!("%?", node.dump());
// Reset the image cache.
self.local_image_cache.next_round(self.make_on_image_available_cb(script_chan));
self.doc_url = Some(doc_url);
let screen_size = Size2D(Au::from_px(data.window_size.width as int),
Au::from_px(data.window_size.height as int));
self.screen_size = Some(screen_size);
// Create a layout context for use throughout the following passes.
let mut layout_ctx = LayoutContext {
image_cache: self.local_image_cache,
font_ctx: self.font_ctx,
doc_url: doc_url,
screen_size: Rect(Point2D(Au(0), Au(0)), screen_size)
};
let mut layout_ctx = self.build_layout_context();
// Initialize layout data for each node.
//
@ -290,6 +309,55 @@ impl Layout {
}
};
reply_chan.send(response)
}
HitTestQuery(node, point) => {
// FIXME: Isolate this transmutation into a single "bridge" module.
let node: AbstractNode<LayoutView> = unsafe {
transmute(node)
};
let mut flow_node: AbstractNode<LayoutView> = node;
for node.traverse_preorder |node| {
if node.layout_data().flow.is_some() {
flow_node = node;
break;
}
};
let response = match flow_node.layout_data().flow {
None => {
debug!("HitTestQuery: flow is None");
Err(())
}
Some(flow) => {
let layout_ctx = self.build_layout_context();
let builder = DisplayListBuilder {
ctx: &layout_ctx,
};
let display_list: @Cell<DisplayList<RenderBox>> =
@Cell(DisplayList::new());
flow.build_display_list(&builder,
&flow.position(),
display_list);
// iterate in reverse to ensure we have the most recently painted render box
let (x, y) = (Au::from_frac_px(point.x as float),
Au::from_frac_px(point.y as float));
let mut resp = Err(());
let display_list = &display_list.take().list;
for display_list.each_reverse |display_item| {
let bounds = display_item.bounds();
if x <= bounds.origin.x + bounds.size.width &&
bounds.origin.x <= x &&
y < bounds.origin.y + bounds.size.height &&
bounds.origin.y < y {
resp = Ok(HitTestResponse(display_item.base().extra.node()));
break;
}
}
resp
}
};
reply_chan.send(response)
}
}

View file

@ -7,8 +7,8 @@
/// GLUT is a very old and bare-bones toolkit. However, it has good cross-platform support, at
/// least on desktops. It is designed for testing Servo without the need of a UI.
use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, ResizeCallback};
use windowing::{ScrollCallback, WindowMethods};
use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, ClickCallback};
use windowing::{ResizeCallback, ScrollCallback, WindowMethods};
use alert::{Alert, AlertMethods};
use core::libc::c_int;
@ -35,6 +35,7 @@ pub struct Window {
composite_callback: Option<CompositeCallback>,
resize_callback: Option<ResizeCallback>,
load_url_callback: Option<LoadUrlCallback>,
click_callback: Option<ClickCallback>,
scroll_callback: Option<ScrollCallback>,
drag_origin: Point2D<c_int>,
@ -54,6 +55,7 @@ impl WindowMethods<Application> for Window {
composite_callback: None,
resize_callback: None,
load_url_callback: None,
click_callback: None,
scroll_callback: None,
drag_origin: Point2D(0, 0),
@ -77,6 +79,7 @@ impl WindowMethods<Application> for Window {
window.handle_key(key)
}
do glut::mouse_func |_, _, x, y| {
window.handle_click(x, y);
window.start_drag(x, y)
}
do glut::motion_func |x, y| {
@ -111,6 +114,11 @@ impl WindowMethods<Application> for Window {
self.load_url_callback = Some(new_load_url_callback)
}
/// Registers a callback to be run when a click event occurs.
pub fn set_click_callback(&mut self, new_click_callback: ClickCallback) {
self.click_callback = Some(new_click_callback)
}
/// Registers a callback to be run when the user scrolls.
pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback) {
self.scroll_callback = Some(new_scroll_callback)
@ -136,6 +144,14 @@ impl Window {
}
}
/// Helper function to handle a click
fn handle_click(&self, x: c_int, y: c_int) {
match self.click_callback {
None => {}
Some(callback) => callback(Point2D(x as f32, y as f32)),
}
}
/// Helper function to start a drag.
fn start_drag(&mut self, x: c_int, y: c_int) {
self.drag_origin = Point2D(x, y)

View file

@ -16,6 +16,10 @@ pub type ResizeCallback = @fn(uint, uint);
/// Type of the function that is called when a new URL is to be loaded.
pub type LoadUrlCallback = @fn(&str);
/// Type of the function that is called when hit testing is to be performed.
/// FIXME this currently does not discriminate between left and right clicks or any modifiers
pub type ClickCallback = @fn(Point2D<f32>);
/// Type of the function that is called when the user scrolls.
pub type ScrollCallback = @fn(Point2D<f32>);
@ -38,6 +42,8 @@ pub trait WindowMethods<A> {
pub fn set_resize_callback(&mut self, new_resize_callback: ResizeCallback);
/// Registers a callback to run when a new URL is to be loaded.
pub fn set_load_url_callback(&mut self, new_load_url_callback: LoadUrlCallback);
/// Registers a callback to run when the user clicks.
pub fn set_click_callback(&mut self, new_click_callback: ClickCallback);
/// Registers a callback to run when the user scrolls.
pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback);

View file

@ -7,9 +7,12 @@ use dom::window::Window;
use dom::bindings::codegen::EventBinding;
use dom::bindings::utils::{DOMString, ErrorResult, WrapperCache};
use geom::point::Point2D;
pub enum Event {
ResizeEvent(uint, uint, comm::Chan<()>),
ReflowEvent
ReflowEvent,
ClickEvent(Point2D<f32>),
}
pub struct Event_ {

View file

@ -6,12 +6,13 @@
/// coupling between these two components, and enables the DOM to be placed in a separate crate
/// from layout.
use dom::node::{AbstractNode, ScriptView};
use dom::node::{AbstractNode, ScriptView, LayoutView};
use script_task::ScriptMsg;
use core::comm::{Chan, SharedChan};
use geom::rect::Rect;
use geom::size::Size2D;
use geom::point::Point2D;
use gfx::geometry::Au;
use newcss::stylesheet::Stylesheet;
use std::net::url::Url;
@ -43,6 +44,8 @@ pub enum LayoutQuery {
ContentBoxQuery(AbstractNode<ScriptView>),
/// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
ContentBoxesQuery(AbstractNode<ScriptView>),
/// Requests the node containing the point of interest
HitTestQuery(AbstractNode<ScriptView>, Point2D<f32>),
}
/// The reply of a synchronous message from script to layout.
@ -54,6 +57,8 @@ pub enum LayoutResponse {
ContentBoxResponse(Rect<Au>),
/// A response to the `ContentBoxesQuery` message.
ContentBoxesResponse(~[Rect<Au>]),
/// A response to the `HitTestQuery` message.
HitTestResponse(AbstractNode<LayoutView>),
}
/// Dirty bits for layout.

View file

@ -7,11 +7,11 @@
use dom::bindings::utils::GlobalStaticData;
use dom::document::Document;
use dom::event::{Event, ResizeEvent, ReflowEvent};
use dom::event::{Event, ResizeEvent, ReflowEvent, ClickEvent};
use dom::node::define_bindings;
use dom::window::Window;
use layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, Damage, LayoutQuery};
use layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDamage, NoDamage};
use layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, Damage, LayoutQuery, HitTestQuery};
use layout_interface::{LayoutResponse, HitTestResponse, LayoutTask, MatchSelectorsDamage, NoDamage};
use layout_interface::{QueryMsg, ReflowDamage};
use layout_interface;
@ -460,6 +460,23 @@ impl ScriptContext {
self.relayout()
}
}
ClickEvent(point) => {
debug!("ClickEvent: clicked at %?", point);
let root = match self.root_frame {
Some(ref frame) => frame.document.root,
None => fail!("root frame is None")
};
match self.query_layout(HitTestQuery(root, point)) {
Ok(node) => match node {
HitTestResponse(node) => debug!("clicked on %?", node.debug_str()),
_ => fail!(~"unexpected layout reply")
},
Err(()) => {
println(fmt!("layout query error"));
}
};
}
}
}
}