diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index ab959f17a3f..16d81c4c6f5 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -2,14 +2,13 @@ * 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 compositor_data::{CompositorData, DoesntWantScrollEvents, WantsScrollEvents}; +use compositor_layer::{CompositorData, CompositorLayer, DoesntWantScrollEvents}; +use compositor_layer::{ScrollPositionChanged, WantsScrollEvents}; use compositor_task::{Msg, CompositorTask, Exit, ChangeReadyState, SetIds, LayerProperties}; use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer}; use compositor_task::{SetLayerOrigin, Paint, ScrollFragmentPoint, LoadComplete}; use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded}; use constellation::SendableFrameTree; -use events; -use events::ScrollPositionChanged; use pipeline::CompositionPipeline; use windowing; use windowing::{FinishedWindowEvent, IdleWindowEvent, LoadUrlWindowEvent, MouseWindowClickEvent}; @@ -132,6 +131,11 @@ enum ShutdownState { FinishedShuttingDown, } +struct HitTestResult { + layer: Rc>, + point: TypedPoint2D, +} + impl IOCompositor { fn new(window: Rc, port: Receiver, @@ -233,7 +237,7 @@ impl IOCompositor { // Clear out the compositor layers so that painting tasks can destroy the buffers. match self.scene.root { None => {} - Some(ref layer) => CompositorData::forget_all_tiles(layer.clone()), + Some(ref layer) => layer.forget_all_tiles(), } // Drain compositor port, sometimes messages contain channels that are blocking @@ -403,7 +407,7 @@ impl IOCompositor { // If we have an old root layer, release all old tiles before replacing it. match self.scene.root { - Some(ref mut layer) => CompositorData::clear_all_tiles(layer.clone()), + Some(ref mut layer) => layer.clear_all_tiles(), None => { } } self.scene.root = Some(self.create_frame_tree_root_layers(frame_tree, None)); @@ -451,21 +455,6 @@ impl IOCompositor { return root_layer; } - fn find_layer_with_pipeline_and_layer_id(&self, - pipeline_id: PipelineId, - layer_id: LayerId) - -> Option>> { - match self.scene.root { - Some(ref root_layer) => { - CompositorData::find_layer_with_pipeline_and_layer_id(root_layer.clone(), - pipeline_id, - layer_id) - } - None => None, - } - - } - fn find_pipeline_root_layer(&self, pipeline_id: PipelineId) -> Rc> { match self.find_layer_with_pipeline_and_layer_id(pipeline_id, LayerId::null()) { Some(ref layer) => layer.clone(), @@ -476,7 +465,7 @@ impl IOCompositor { fn update_layer_if_exists(&mut self, properties: LayerProperties) -> bool { match self.find_layer_with_pipeline_and_layer_id(properties.pipeline_id, properties.id) { Some(existing_layer) => { - CompositorData::update_layer(existing_layer.clone(), properties); + existing_layer.update_layer(properties); true } None => false, @@ -487,7 +476,7 @@ impl IOCompositor { let need_new_root_layer = !self.update_layer_if_exists(layer_properties); if need_new_root_layer { let root_layer = self.find_pipeline_root_layer(layer_properties.pipeline_id); - CompositorData::update_layer_except_size(root_layer.clone(), layer_properties); + root_layer.update_layer_except_size(layer_properties); let root_layer_pipeline = root_layer.extra_data.borrow().pipeline.clone(); let first_child = CompositorData::new_layer(root_layer_pipeline.clone(), @@ -548,8 +537,7 @@ impl IOCompositor { match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) { Some(ref layer) => { if layer.extra_data.borrow().wants_scroll_events == WantsScrollEvents { - events::clamp_scroll_offset_and_scroll_layer(layer.clone(), - TypedPoint2D(0f32, 0f32) - origin); + layer.clamp_scroll_offset_and_scroll_layer(TypedPoint2D(0f32, 0f32) - origin); } true } @@ -599,7 +587,7 @@ impl IOCompositor { match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) { Some(ref layer) => { - assert!(CompositorData::add_buffers(layer.clone(), new_layer_buffer_set, epoch)); + assert!(layer.add_buffers(new_layer_buffer_set, epoch)); self.recomposite = true; } None => { @@ -720,14 +708,16 @@ impl IOCompositor { MouseWindowMouseDownEvent(_, p) => p, MouseWindowMouseUpEvent(_, p) => p, }; - for layer in self.scene.root.iter() { - events::send_mouse_event(layer.clone(), mouse_window_event, point / self.scene.scale); + match self.find_topmost_layer_at_point(point / self.scene.scale) { + Some(result) => result.layer.send_mouse_event(mouse_window_event, result.point), + None => {}, } } fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D) { - for layer in self.scene.root.iter() { - events::send_mouse_move_event(layer.clone(), cursor / self.scene.scale); + match self.find_topmost_layer_at_point(cursor / self.scene.scale) { + Some(result) => result.layer.send_mouse_move_event(result.point), + None => {}, } } @@ -740,9 +730,7 @@ impl IOCompositor { let mut scroll = false; match self.scene.root { Some(ref mut layer) => { - scroll = events::handle_scroll_event(layer.clone(), - delta, - cursor) == ScrollPositionChanged; + scroll = layer.handle_scroll_event(delta, cursor) == ScrollPositionChanged; } None => { } } @@ -798,9 +786,7 @@ impl IOCompositor { let cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer. match self.scene.root { Some(ref mut layer) => { - events::handle_scroll_event(layer.clone(), - page_delta, - cursor); + layer.handle_scroll_event(page_delta, cursor); } None => { } } @@ -1002,4 +988,68 @@ impl IOCompositor { fn recomposite_if(&mut self, result: bool) { self.recomposite = result || self.recomposite; } + + fn find_topmost_layer_at_point_for_layer(&self, + layer: Rc>, + point: TypedPoint2D) + -> Option { + let child_point = point - layer.bounds.borrow().origin; + for child in layer.children().iter().rev() { + let result = self.find_topmost_layer_at_point_for_layer(child.clone(), child_point); + if result.is_some() { + return result; + } + } + + let point = point - *layer.content_offset.borrow(); + if !layer.bounds.borrow().contains(&point) { + return None; + } + + return Some(HitTestResult { layer: layer, point: point }); + } + + fn find_topmost_layer_at_point(&self, + point: TypedPoint2D) + -> Option { + match self.scene.root { + Some(ref layer) => self.find_topmost_layer_at_point_for_layer(layer.clone(), point), + None => None, + } + } + + fn find_layer_with_pipeline_and_layer_id(&self, + pipeline_id: PipelineId, + layer_id: LayerId) + -> Option>> { + match self.scene.root { + Some(ref layer) => + find_layer_with_pipeline_and_layer_id_for_layer(layer.clone(), + pipeline_id, + layer_id), + + None => None, + } + } +} + +fn find_layer_with_pipeline_and_layer_id_for_layer(layer: Rc>, + pipeline_id: PipelineId, + layer_id: LayerId) + -> Option>> { + if layer.extra_data.borrow().pipeline.id == pipeline_id && + layer.extra_data.borrow().id == layer_id { + return Some(layer); + } + + for kid in layer.children().iter() { + let result = find_layer_with_pipeline_and_layer_id_for_layer(kid.clone(), + pipeline_id, + layer_id); + if result.is_some() { + return result; + } + } + + return None; } diff --git a/components/compositing/compositor_data.rs b/components/compositing/compositor_data.rs deleted file mode 100644 index 13e3c3a4e6b..00000000000 --- a/components/compositing/compositor_data.rs +++ /dev/null @@ -1,196 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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 compositor_task::LayerProperties; -use events; -use pipeline::CompositionPipeline; - -use azure::azure_hl; -use geom::point::TypedPoint2D; -use geom::size::Size2D; -use geom::rect::Rect; -use gfx::render_task::UnusedBufferMsg; -use layers::color::Color; -use layers::geometry::LayerPixel; -use layers::layers::{Layer, LayerBufferSet}; -use layers::platform::surface::NativeSurfaceMethods; -use servo_msg::compositor_msg::{Epoch, LayerId}; -use servo_msg::compositor_msg::ScrollPolicy; -use servo_msg::constellation_msg::PipelineId; -use std::rc::Rc; - -pub struct CompositorData { - /// This layer's pipeline. BufferRequests and mouse events will be sent through this. - pub pipeline: CompositionPipeline, - - /// The ID of this layer within the pipeline. - pub id: LayerId, - - /// The behavior of this layer when a scroll message is received. - pub wants_scroll_events: WantsScrollEventsFlag, - - /// Whether an ancestor layer that receives scroll events moves this layer. - pub scroll_policy: ScrollPolicy, - - /// A monotonically increasing counter that keeps track of the current epoch. - /// add_buffer() calls that don't match the current epoch will be ignored. - pub epoch: Epoch, - - /// The scroll offset originating from this scrolling root. This allows scrolling roots - /// to track their current scroll position even while their content_offset does not change. - pub scroll_offset: TypedPoint2D, -} - -#[deriving(PartialEq, Clone)] -pub enum WantsScrollEventsFlag { - WantsScrollEvents, - DoesntWantScrollEvents, -} - -fn to_layers_color(color: &azure_hl::Color) -> Color { - Color { r: color.r, g: color.g, b: color.b, a: color.a } -} - -impl CompositorData { - pub fn new_layer(pipeline: CompositionPipeline, - layer_properties: LayerProperties, - wants_scroll_events: WantsScrollEventsFlag, - tile_size: uint) - -> Rc> { - let new_compositor_data = CompositorData { - pipeline: pipeline, - id: layer_properties.id, - wants_scroll_events: wants_scroll_events, - scroll_policy: layer_properties.scroll_policy, - epoch: layer_properties.epoch, - scroll_offset: TypedPoint2D(0., 0.), - }; - - Rc::new(Layer::new(Rect::from_untyped(&layer_properties.rect), - tile_size, - to_layers_color(&layer_properties.background_color), - new_compositor_data)) - } - - pub fn update_layer_except_size(layer: Rc>, - layer_properties: LayerProperties) { - layer.extra_data.borrow_mut().epoch = layer_properties.epoch; - layer.extra_data.borrow_mut().scroll_policy = layer_properties.scroll_policy; - - *layer.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color); - - layer.contents_changed(); - } - - pub fn update_layer(layer: Rc>, layer_properties: LayerProperties) { - layer.resize(Size2D::from_untyped(&layer_properties.rect.size)); - - // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the - // cursor position to make sure the scroll isn't propagated downwards. - events::handle_scroll_event(layer.clone(), - TypedPoint2D(0f32, 0f32), - TypedPoint2D(-1f32, -1f32)); - CompositorData::update_layer_except_size(layer, layer_properties); - } - - pub fn find_layer_with_pipeline_and_layer_id(layer: Rc>, - pipeline_id: PipelineId, - layer_id: LayerId) - -> Option>> { - if layer.extra_data.borrow().pipeline.id == pipeline_id && - layer.extra_data.borrow().id == layer_id { - return Some(layer.clone()); - } - - for kid in layer.children().iter() { - match CompositorData::find_layer_with_pipeline_and_layer_id(kid.clone(), - pipeline_id, - layer_id) { - v @ Some(_) => { return v; } - None => { } - } - } - - return None; - } - - // Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that - // matches the given pipeline ID was not found; otherwise returns None and consumes the layer - // buffer set. - // - // If the epoch of the message does not match the layer's epoch, the message is ignored, the - // layer buffer set is consumed, and None is returned. - pub fn add_buffers(layer: Rc>, - new_buffers: Box, - epoch: Epoch) - -> bool { - if layer.extra_data.borrow().epoch != epoch { - debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}", - layer.extra_data.borrow().epoch, - epoch, - layer.extra_data.borrow().pipeline.id); - let msg = UnusedBufferMsg(new_buffers.buffers); - let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(msg); - return false; - } - - { - for buffer in new_buffers.buffers.into_iter().rev() { - layer.add_buffer(buffer); - } - - let unused_buffers = layer.collect_unused_buffers(); - if !unused_buffers.is_empty() { // send back unused buffers - let msg = UnusedBufferMsg(unused_buffers); - let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(msg); - } - } - - return true; - } - - /// Destroys all layer tiles, sending the buffers back to the renderer to be destroyed or - /// reused. - fn clear(layer: Rc>) { - let mut buffers = layer.collect_buffers(); - - if !buffers.is_empty() { - // We have no way of knowing without a race whether the render task is even up and - // running, but mark the buffers as not leaking. If the render task died, then the - // buffers are going to be cleaned up. - for buffer in buffers.iter_mut() { - buffer.mark_wont_leak() - } - - let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(buffers)); - } - } - - /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the - /// renderer to be destroyed or reused. - pub fn clear_all_tiles(layer: Rc>) { - CompositorData::clear(layer.clone()); - for kid in layer.children().iter() { - CompositorData::clear_all_tiles(kid.clone()); - } - } - - /// Destroys all tiles of all layers, including children, *without* sending them back to the - /// renderer. You must call this only when the render task is destined to be going down; - /// otherwise, you will leak tiles. - /// - /// This is used during shutdown, when we know the render task is going away. - pub fn forget_all_tiles(layer: Rc>) { - let tiles = layer.collect_buffers(); - for tile in tiles.into_iter() { - let mut tile = tile; - tile.mark_wont_leak() - } - - for kid in layer.children().iter() { - CompositorData::forget_all_tiles(kid.clone()); - } - } -} - diff --git a/components/compositing/compositor_layer.rs b/components/compositing/compositor_layer.rs new file mode 100644 index 00000000000..2404230e5b5 --- /dev/null +++ b/components/compositing/compositor_layer.rs @@ -0,0 +1,360 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 compositor_task::LayerProperties; +use pipeline::CompositionPipeline; +use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent}; +use windowing::MouseWindowMouseUpEvent; +use windowing::WindowMethods; + +use azure::azure_hl; +use geom::length::Length; +use geom::matrix::identity; +use geom::point::{Point2D, TypedPoint2D}; +use geom::size::{Size2D, TypedSize2D}; +use geom::rect::Rect; +use gfx::render_task::UnusedBufferMsg; +use layers::color::Color; +use layers::geometry::LayerPixel; +use layers::layers::{Layer, LayerBufferSet}; +use layers::platform::surface::NativeSurfaceMethods; +use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg}; +use script_traits::{ScriptControlChan}; +use servo_msg::compositor_msg::{Epoch, FixedPosition, LayerId, ScrollPolicy}; +use std::rc::Rc; + +pub struct CompositorData { + /// This layer's pipeline. BufferRequests and mouse events will be sent through this. + pub pipeline: CompositionPipeline, + + /// The ID of this layer within the pipeline. + pub id: LayerId, + + /// The behavior of this layer when a scroll message is received. + pub wants_scroll_events: WantsScrollEventsFlag, + + /// Whether an ancestor layer that receives scroll events moves this layer. + pub scroll_policy: ScrollPolicy, + + /// A monotonically increasing counter that keeps track of the current epoch. + /// add_buffer() calls that don't match the current epoch will be ignored. + pub epoch: Epoch, + + /// The scroll offset originating from this scrolling root. This allows scrolling roots + /// to track their current scroll position even while their content_offset does not change. + pub scroll_offset: TypedPoint2D, +} + +impl CompositorData { + pub fn new_layer(pipeline: CompositionPipeline, + layer_properties: LayerProperties, + wants_scroll_events: WantsScrollEventsFlag, + tile_size: uint) + -> Rc> { + let new_compositor_data = CompositorData { + pipeline: pipeline, + id: layer_properties.id, + wants_scroll_events: wants_scroll_events, + scroll_policy: layer_properties.scroll_policy, + epoch: layer_properties.epoch, + scroll_offset: TypedPoint2D(0., 0.), + }; + + Rc::new(Layer::new(Rect::from_untyped(&layer_properties.rect), + tile_size, + to_layers_color(&layer_properties.background_color), + new_compositor_data)) + } +} + +pub trait CompositorLayer { + fn update_layer_except_size(&self, layer_properties: LayerProperties); + + fn update_layer(&self, layer_properties: LayerProperties); + + fn add_buffers(&self, new_buffers: Box, epoch: Epoch) -> bool; + + /// Destroys all layer tiles, sending the buffers back to the renderer to be destroyed or + /// reused. + fn clear(&self); + + /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the + /// renderer to be destroyed or reused. + fn clear_all_tiles(&self); + + /// Destroys all tiles of all layers, including children, *without* sending them back to the + /// renderer. You must call this only when the render task is destined to be going down; + /// otherwise, you will leak tiles. + /// + /// This is used during shutdown, when we know the render task is going away. + fn forget_all_tiles(&self); + + /// Move the layer's descendants that don't want scroll events and scroll by a relative + /// specified amount in page coordinates. This also takes in a cursor position to see if the + /// mouse is over child layers first. If a layer successfully scrolled returns either + /// ScrollPositionUnchanged or ScrollPositionChanged. If no layer was targeted by the event + /// returns ScrollEventUnhandled. + fn handle_scroll_event(&self, + delta: TypedPoint2D, + cursor: TypedPoint2D) + -> ScrollEventResult; + + // Takes in a MouseWindowEvent, determines if it should be passed to children, and + // sends the event off to the appropriate pipeline. NB: the cursor position is in + // page coordinates. + fn send_mouse_event(&self, + event: MouseWindowEvent, + cursor: TypedPoint2D); + + fn send_mouse_move_event(&self, + cursor: TypedPoint2D); + + fn clamp_scroll_offset_and_scroll_layer(&self, + new_offset: TypedPoint2D) + -> ScrollEventResult; + + fn scroll_layer_and_all_child_layers(&self, + new_offset: TypedPoint2D) + -> bool; +} + +#[deriving(PartialEq, Clone)] +pub enum WantsScrollEventsFlag { + WantsScrollEvents, + DoesntWantScrollEvents, +} + +fn to_layers_color(color: &azure_hl::Color) -> Color { + Color { r: color.r, g: color.g, b: color.b, a: color.a } +} + +trait Clampable { + fn clamp(&self, mn: &Self, mx: &Self) -> Self; +} + +impl Clampable for f32 { + /// Returns the number constrained within the range `mn <= self <= mx`. + /// If any of the numbers are `NAN` then `NAN` is returned. + #[inline] + fn clamp(&self, mn: &f32, mx: &f32) -> f32 { + match () { + _ if self.is_nan() => *self, + _ if !(*self <= *mx) => *mx, + _ if !(*self >= *mn) => *mn, + _ => *self, + } + } +} + +fn calculate_content_size_for_layer(layer: &Layer) + -> TypedSize2D { + layer.children().iter().fold(Rect::zero(), + |unioned_rect, child_rect| { + unioned_rect.union(&*child_rect.bounds.borrow()) + }).size +} + +#[deriving(PartialEq)] +pub enum ScrollEventResult { + ScrollEventUnhandled, + ScrollPositionChanged, + ScrollPositionUnchanged, +} + +impl CompositorLayer for Layer { + fn update_layer_except_size(&self, layer_properties: LayerProperties) { + self.extra_data.borrow_mut().epoch = layer_properties.epoch; + self.extra_data.borrow_mut().scroll_policy = layer_properties.scroll_policy; + + *self.background_color.borrow_mut() = to_layers_color(&layer_properties.background_color); + + self.contents_changed(); + } + + fn update_layer(&self, layer_properties: LayerProperties) { + self.resize(Size2D::from_untyped(&layer_properties.rect.size)); + + // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the + // cursor position to make sure the scroll isn't propagated downwards. + self.handle_scroll_event(TypedPoint2D(0f32, 0f32), TypedPoint2D(-1f32, -1f32)); + self.update_layer_except_size(layer_properties); + } + + // Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that + // matches the given pipeline ID was not found; otherwise returns None and consumes the layer + // buffer set. + // + // If the epoch of the message does not match the layer's epoch, the message is ignored, the + // layer buffer set is consumed, and None is returned. + fn add_buffers(&self, new_buffers: Box, epoch: Epoch) -> bool { + if self.extra_data.borrow().epoch != epoch { + debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}", + self.extra_data.borrow().epoch, + epoch, + self.extra_data.borrow().pipeline.id); + let msg = UnusedBufferMsg(new_buffers.buffers); + let _ = self.extra_data.borrow().pipeline.render_chan.send_opt(msg); + return false; + } + + { + for buffer in new_buffers.buffers.into_iter().rev() { + self.add_buffer(buffer); + } + + let unused_buffers = self.collect_unused_buffers(); + if !unused_buffers.is_empty() { // send back unused buffers + let msg = UnusedBufferMsg(unused_buffers); + let _ = self.extra_data.borrow().pipeline.render_chan.send_opt(msg); + } + } + + return true; + } + + fn clear(&self) { + let mut buffers = self.collect_buffers(); + + if !buffers.is_empty() { + // We have no way of knowing without a race whether the render task is even up and + // running, but mark the buffers as not leaking. If the render task died, then the + // buffers are going to be cleaned up. + for buffer in buffers.iter_mut() { + buffer.mark_wont_leak() + } + + let _ = self.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(buffers)); + } + } + + /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the + /// renderer to be destroyed or reused. + fn clear_all_tiles(&self) { + self.clear(); + for kid in self.children().iter() { + kid.clear_all_tiles(); + } + } + + /// Destroys all tiles of all layers, including children, *without* sending them back to the + /// renderer. You must call this only when the render task is destined to be going down; + /// otherwise, you will leak tiles. + /// + /// This is used during shutdown, when we know the render task is going away. + fn forget_all_tiles(&self) { + let tiles = self.collect_buffers(); + for tile in tiles.into_iter() { + let mut tile = tile; + tile.mark_wont_leak() + } + + for kid in self.children().iter() { + kid.forget_all_tiles(); + } + } + + fn handle_scroll_event(&self, + delta: TypedPoint2D, + cursor: TypedPoint2D) + -> ScrollEventResult { + // If this layer doesn't want scroll events, neither it nor its children can handle scroll + // events. + if self.extra_data.borrow().wants_scroll_events != WantsScrollEvents { + return ScrollEventUnhandled; + } + + //// Allow children to scroll. + let scroll_offset = self.extra_data.borrow().scroll_offset; + let new_cursor = cursor - scroll_offset; + for child in self.children().iter() { + let child_bounds = child.bounds.borrow(); + if child_bounds.contains(&new_cursor) { + let result = child.handle_scroll_event(delta, new_cursor - child_bounds.origin); + if result != ScrollEventUnhandled { + return result; + } + } + } + + self.clamp_scroll_offset_and_scroll_layer(scroll_offset + delta) + } + + fn clamp_scroll_offset_and_scroll_layer(&self, + new_offset: TypedPoint2D) + -> ScrollEventResult { + let layer_size = self.bounds.borrow().size; + let content_size = calculate_content_size_for_layer(self); + let min_x = (layer_size.width - content_size.width).get().min(0.0); + let min_y = (layer_size.height - content_size.height).get().min(0.0); + let new_offset : TypedPoint2D = + Point2D(Length(new_offset.x.get().clamp(&min_x, &0.0)), + Length(new_offset.y.get().clamp(&min_y, &0.0))); + + if self.extra_data.borrow().scroll_offset == new_offset { + return ScrollPositionUnchanged; + } + + // The scroll offset is just a record of the scroll position of this scrolling root, + // but scroll_layer_and_all_child_layers actually moves the child layers. + self.extra_data.borrow_mut().scroll_offset = new_offset; + + let mut result = false; + for child in self.children().iter() { + result |= child.scroll_layer_and_all_child_layers(new_offset); + } + + if result { + return ScrollPositionChanged; + } else { + return ScrollPositionUnchanged; + } + } + + fn send_mouse_event(&self, + event: MouseWindowEvent, + cursor: TypedPoint2D) { + let event_point = cursor.to_untyped(); + let message = match event { + MouseWindowClickEvent(button, _) => ClickEvent(button, event_point), + MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, event_point), + MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, event_point), + }; + let pipeline = &self.extra_data.borrow().pipeline; + let ScriptControlChan(ref chan) = pipeline.script_chan; + let _ = chan.send_opt(SendEventMsg(pipeline.id.clone(), message)); + } + + fn send_mouse_move_event(&self, + cursor: TypedPoint2D) { + let message = MouseMoveEvent(cursor.to_untyped()); + let pipeline = &self.extra_data.borrow().pipeline; + let ScriptControlChan(ref chan) = pipeline.script_chan; + let _ = chan.send_opt(SendEventMsg(pipeline.id.clone(), message)); + } + + fn scroll_layer_and_all_child_layers(&self, + new_offset: TypedPoint2D) + -> bool { + let mut result = false; + + // Only scroll this layer if it's not fixed-positioned. + if self.extra_data.borrow().scroll_policy != FixedPosition { + let new_offset = new_offset.to_untyped(); + *self.transform.borrow_mut() = identity().translate(new_offset.x, + new_offset.y, + 0.0); + *self.content_offset.borrow_mut() = Point2D::from_untyped(&new_offset); + result = true + } + + let offset_for_children = new_offset + self.extra_data.borrow().scroll_offset; + for child in self.children().iter() { + result |= child.scroll_layer_and_all_child_layers(offset_for_children); + } + + return result; + } + +} + diff --git a/components/compositing/events.rs b/components/compositing/events.rs deleted file mode 100644 index 371f033b724..00000000000 --- a/components/compositing/events.rs +++ /dev/null @@ -1,201 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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 compositor_data::{CompositorData, WantsScrollEvents}; -use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent}; -use windowing::MouseWindowMouseUpEvent; - -use geom::length::Length; -use geom::point::{Point2D, TypedPoint2D}; -use geom::rect::Rect; -use geom::size::TypedSize2D; -use layers::geometry::LayerPixel; -use layers::layers::Layer; -use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg}; -use script_traits::{ScriptControlChan}; -use servo_msg::compositor_msg::FixedPosition; -use std::rc::Rc; - - -use geom::matrix::identity; - -trait Clampable { - fn clamp(&self, mn: &Self, mx: &Self) -> Self; -} - -impl Clampable for f32 { - /// Returns the number constrained within the range `mn <= self <= mx`. - /// If any of the numbers are `NAN` then `NAN` is returned. - #[inline] - fn clamp(&self, mn: &f32, mx: &f32) -> f32 { - match () { - _ if self.is_nan() => *self, - _ if !(*self <= *mx) => *mx, - _ if !(*self >= *mn) => *mn, - _ => *self, - } - } -} - -#[deriving(PartialEq)] -pub enum ScrollEventResult { - ScrollEventUnhandled, - ScrollPositionChanged, - ScrollPositionUnchanged, -} - -/// Move the layer's descendants that don't want scroll events and scroll by a relative -/// specified amount in page coordinates. This also takes in a cursor position to see if the -/// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise -/// returns false, so a parent layer can scroll instead. -pub fn handle_scroll_event(layer: Rc>, - delta: TypedPoint2D, - cursor: TypedPoint2D) - -> ScrollEventResult { - // If this layer doesn't want scroll events, neither it nor its children can handle scroll - // events. - if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents { - return ScrollEventUnhandled; - } - - // Allow children to scroll. - let scroll_offset = layer.extra_data.borrow().scroll_offset; - let new_cursor = cursor - scroll_offset; - for child in layer.children().iter() { - let child_bounds = child.bounds.borrow(); - if child_bounds.contains(&new_cursor) { - let result = handle_scroll_event(child.clone(), - delta, - new_cursor - child_bounds.origin); - if result != ScrollEventUnhandled { - return result; - } - } - } - - clamp_scroll_offset_and_scroll_layer(layer, scroll_offset + delta) -} - -pub fn calculate_content_size_for_layer(layer: Rc>) - -> TypedSize2D { - layer.children().iter().fold(Rect::zero(), - |unioned_rect, child_rect| { - unioned_rect.union(&*child_rect.bounds.borrow()) - }).size -} - -pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc>, - new_offset: TypedPoint2D) - -> ScrollEventResult { - let layer_size = layer.bounds.borrow().size; - let content_size = calculate_content_size_for_layer(layer.clone()); - let min_x = (layer_size.width - content_size.width).get().min(0.0); - let min_y = (layer_size.height - content_size.height).get().min(0.0); - let new_offset : TypedPoint2D = - Point2D(Length(new_offset.x.get().clamp(&min_x, &0.0)), - Length(new_offset.y.get().clamp(&min_y, &0.0))); - - if layer.extra_data.borrow().scroll_offset == new_offset { - return ScrollPositionUnchanged; - } - - // The scroll offset is just a record of the scroll position of this scrolling root, - // but scroll_layer_and_all_child_layers actually moves the child layers. - layer.extra_data.borrow_mut().scroll_offset = new_offset; - - let mut result = false; - for child in layer.children().iter() { - result |= scroll_layer_and_all_child_layers(child.clone(), new_offset); - } - - if result { - return ScrollPositionChanged; - } else { - return ScrollPositionUnchanged; - } -} - -fn scroll_layer_and_all_child_layers(layer: Rc>, - new_offset: TypedPoint2D) - -> bool { - let mut result = false; - - // Only scroll this layer if it's not fixed-positioned. - if layer.extra_data.borrow().scroll_policy != FixedPosition { - let new_offset = new_offset.to_untyped(); - *layer.transform.borrow_mut() = identity().translate(new_offset.x, - new_offset.y, - 0.0); - *layer.content_offset.borrow_mut() = Point2D::from_untyped(&new_offset); - result = true - } - - let offset_for_children = new_offset + layer.extra_data.borrow().scroll_offset; - for child in layer.children().iter() { - result |= scroll_layer_and_all_child_layers(child.clone(), offset_for_children); - } - - return result; -} - -struct HitTestResult { - layer: Rc>, - point: TypedPoint2D, -} - -pub fn find_topmost_layer_at_point(layer: Rc>, - point: TypedPoint2D) - -> Option { - let child_point = point - layer.bounds.borrow().origin; - for child in layer.children().iter().rev() { - let result = find_topmost_layer_at_point(child.clone(), child_point); - if result.is_some() { - return result; - } - } - - let point = point - *layer.content_offset.borrow(); - if !layer.bounds.borrow().contains(&point) { - return None; - } - - return Some(HitTestResult { layer: layer, point: point }); -} - -// Takes in a MouseWindowEvent, determines if it should be passed to children, and -// sends the event off to the appropriate pipeline. NB: the cursor position is in -// page coordinates. -pub fn send_mouse_event(layer: Rc>, - event: MouseWindowEvent, - cursor: TypedPoint2D) { - match find_topmost_layer_at_point(layer.clone(), cursor) { - Some(result) => { - let event_point = result.point.to_untyped(); - let message = match event { - MouseWindowClickEvent(button, _) => ClickEvent(button, event_point), - MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, event_point), - MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, event_point), - }; - let pipeline = &result.layer.extra_data.borrow().pipeline; - let ScriptControlChan(ref chan) = pipeline.script_chan; - let _ = chan.send_opt(SendEventMsg(pipeline.id.clone(), message)); - }, - None => {}, - } - -} - -pub fn send_mouse_move_event(layer: Rc>, - cursor: TypedPoint2D) { - match find_topmost_layer_at_point(layer.clone(), cursor) { - Some(result) => { - let message = MouseMoveEvent(result.point.to_untyped()); - let pipeline = &result.layer.extra_data.borrow().pipeline; - let ScriptControlChan(ref chan) = pipeline.script_chan; - let _ = chan.send_opt(SendEventMsg(pipeline.id.clone(), message)); - }, - None => {}, - } -} - diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs index 44592caa61c..0f3cf47696c 100644 --- a/components/compositing/lib.rs +++ b/components/compositing/lib.rs @@ -43,9 +43,7 @@ pub use constellation::Constellation; pub mod compositor_task; -mod compositor_data; -mod events; - +mod compositor_layer; mod compositor; mod headless;