mirror of
https://github.com/servo/servo.git
synced 2025-06-21 07:38:59 +01:00
Basic hit testing functionality
This commit is contained in:
parent
ea1a406589
commit
f77eef5988
8 changed files with 148 additions and 20 deletions
|
@ -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> {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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_ {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue