diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2013-05-30 17:28:08 -0700 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2013-05-30 17:28:08 -0700 |
commit | f77eef59883dcb96f99c5633b1cca7a8df545159 (patch) | |
tree | 0b607a954873888b641c7337f439986cb74c8dc2 | |
parent | ea1a406589b391fcf0dbcf523969e134decaf227 (diff) | |
download | servo-f77eef59883dcb96f99c5633b1cca7a8df545159.tar.gz servo-f77eef59883dcb96f99c5633b1cca7a8df545159.zip |
Basic hit testing functionality
-rw-r--r-- | src/components/gfx/display_list.rs | 2 | ||||
-rw-r--r-- | src/components/main/compositing/mod.rs | 17 | ||||
-rw-r--r-- | src/components/main/layout/layout_task.rs | 88 | ||||
-rw-r--r-- | src/components/main/platform/common/glut_windowing.rs | 20 | ||||
-rw-r--r-- | src/components/main/windowing.rs | 6 | ||||
-rw-r--r-- | src/components/script/dom/event.rs | 5 | ||||
-rw-r--r-- | src/components/script/layout_interface.rs | 7 | ||||
-rw-r--r-- | src/components/script/script_task.rs | 23 |
8 files changed, 148 insertions, 20 deletions
diff --git a/src/components/gfx/display_list.rs b/src/components/gfx/display_list.rs index 74c3bc0b45a..8a7d5135242 100644 --- a/src/components/gfx/display_list.rs +++ b/src/components/gfx/display_list.rs @@ -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> { diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 63e4e55564b..c0b08be2f15 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -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. diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index a45d46e6df8..7034593fbe2 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -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. // @@ -292,6 +311,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) + } } } diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 8df2985425b..613064f2110 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -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) diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 708ea4105ec..9a9cefaf03b 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -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); diff --git a/src/components/script/dom/event.rs b/src/components/script/dom/event.rs index 7b1fba2ac37..a0ef39d8fca 100644 --- a/src/components/script/dom/event.rs +++ b/src/components/script/dom/event.rs @@ -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_ { diff --git a/src/components/script/layout_interface.rs b/src/components/script/layout_interface.rs index b442dc440d3..86077244ecd 100644 --- a/src/components/script/layout_interface.rs +++ b/src/components/script/layout_interface.rs @@ -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. diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index cf99428c677..094336bf1ab 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -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")); + } + }; + } } } } |