aboutsummaryrefslogtreecommitdiffstats
path: root/components/compositing/events.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/compositing/events.rs')
-rw-r--r--components/compositing/events.rs178
1 files changed, 178 insertions, 0 deletions
diff --git a/components/compositing/events.rs b/components/compositing/events.rs
new file mode 100644
index 00000000000..25779a417af
--- /dev/null
+++ b/components/compositing/events.rs
@@ -0,0 +1,178 @@
+/* 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::TypedPoint2D;
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use layers::geometry::DevicePixel;
+use layers::layers::Layer;
+use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg};
+use script_traits::{ScriptControlChan};
+use servo_msg::compositor_msg::{FixedPosition, LayerId};
+use servo_msg::constellation_msg::PipelineId;
+use servo_util::geometry::PagePx;
+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,
+ }
+ }
+}
+
+/// 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<Layer<CompositorData>>,
+ delta: TypedPoint2D<DevicePixel, f32>,
+ cursor: TypedPoint2D<DevicePixel, f32>,
+ window_size: TypedSize2D<DevicePixel, f32>)
+ -> bool {
+ // 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 false
+ }
+
+ // Allow children to scroll.
+ let content_offset = layer.content_offset.borrow().clone();
+ let cursor = cursor - content_offset;
+ for child in layer.children().iter() {
+ let child_bounds = child.bounds.borrow();
+ if child_bounds.contains(&cursor) &&
+ handle_scroll_event(child.clone(),
+ delta,
+ cursor - child_bounds.origin,
+ child_bounds.size) {
+ return true
+ }
+ }
+
+ clamp_scroll_offset_and_scroll_layer(layer, content_offset + delta, window_size)
+
+}
+
+pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc<Layer<CompositorData>>,
+ mut new_offset: TypedPoint2D<DevicePixel, f32>,
+ window_size: TypedSize2D<DevicePixel, f32>)
+ -> bool {
+ let layer_size = layer.bounds.borrow().size;
+ let min_x = (window_size.width - layer_size.width).get().min(0.0);
+ new_offset.x = Length(new_offset.x.get().clamp(&min_x, &0.0));
+
+ let min_y = (window_size.height - layer_size.height).get().min(0.0);
+ new_offset.y = Length(new_offset.y.get().clamp(&min_y, &0.0));
+
+ if *layer.content_offset.borrow() == new_offset {
+ return false
+ }
+
+ // FIXME: This allows the base layer to record the current content offset without
+ // updating its transform. This should be replaced with something less strange.
+ *layer.content_offset.borrow_mut() = new_offset;
+ scroll_layer_and_all_child_layers(layer.clone(), new_offset)
+}
+
+fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
+ new_offset: TypedPoint2D<DevicePixel, f32>)
+ -> bool {
+ let mut result = false;
+
+ // Only scroll this layer if it's not fixed-positioned.
+ if layer.extra_data.borrow().scroll_policy != FixedPosition {
+ *layer.transform.borrow_mut() = identity().translate(new_offset.x.get(),
+ new_offset.y.get(),
+ 0.0);
+ *layer.content_offset.borrow_mut() = new_offset;
+ result = true
+ }
+
+ for child in layer.children().iter() {
+ result |= scroll_layer_and_all_child_layers(child.clone(), new_offset);
+ }
+
+ return result;
+}
+
+// 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<Layer<CompositorData>>,
+ event: MouseWindowEvent,
+ cursor: TypedPoint2D<DevicePixel, f32>,
+ device_pixels_per_page_px: ScaleFactor<PagePx, DevicePixel, f32>) {
+ let cursor = cursor - *layer.content_offset.borrow();
+ for child in layer.children().iter() {
+ let child_bounds = child.bounds.borrow();
+ if child_bounds.contains(&cursor) {
+ send_mouse_event(child.clone(),
+ event,
+ cursor - child_bounds.origin,
+ device_pixels_per_page_px);
+ return;
+ }
+ }
+
+ // This mouse event is mine!
+ let cursor = cursor / device_pixels_per_page_px;
+ let message = match event {
+ MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
+ MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
+ MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor.to_untyped()),
+ };
+ let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
+ let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
+}
+
+pub fn send_mouse_move_event(layer: Rc<Layer<CompositorData>>,
+ cursor: TypedPoint2D<PagePx, f32>) {
+ let message = MouseMoveEvent(cursor.to_untyped());
+ let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
+ let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
+}
+
+pub fn move(layer: Rc<Layer<CompositorData>>,
+ pipeline_id: PipelineId,
+ layer_id: LayerId,
+ origin: TypedPoint2D<DevicePixel, f32>,
+ window_size: TypedSize2D<DevicePixel, f32>)
+ -> bool {
+ // Search children for the right layer to move.
+ if layer.extra_data.borrow().pipeline.id != pipeline_id ||
+ layer.extra_data.borrow().id != layer_id {
+ return layer.children().iter().any(|kid| {
+ move(kid.clone(),
+ pipeline_id,
+ layer_id,
+ origin,
+ window_size)
+ });
+ }
+
+ if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
+ return false
+ }
+
+ clamp_scroll_offset_and_scroll_layer(layer, TypedPoint2D(0f32, 0f32) - origin, window_size)
+}