aboutsummaryrefslogtreecommitdiffstats
path: root/components/compositing
diff options
context:
space:
mode:
authorMatt Brubeck <mbrubeck@limpet.net>2015-08-14 07:24:50 -0700
committerMatt Brubeck <mbrubeck@limpet.net>2015-10-22 10:37:03 -0700
commitfe7460f34d20d5f17d21d60e1053028b21c63ebc (patch)
tree6cc401775f503ce01b528021cc87bc7580fc7140 /components/compositing
parent4ed15a8853fc49d0940ed24d939fd0c407ee80a9 (diff)
downloadservo-fe7460f34d20d5f17d21d60e1053028b21c63ebc.tar.gz
servo-fe7460f34d20d5f17d21d60e1053028b21c63ebc.zip
Dispatch touch events and perform default touch actions.
This is currently limited to simple single-touch actions. It does not include momentum scrolling or pinch zooming.
Diffstat (limited to 'components/compositing')
-rw-r--r--components/compositing/compositor.rs152
-rw-r--r--components/compositing/compositor_layer.rs24
-rw-r--r--components/compositing/compositor_task.rs9
-rw-r--r--components/compositing/headless.rs1
4 files changed, 174 insertions, 12 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs
index 0097d70b9de..0b44a715e88 100644
--- a/components/compositing/compositor.rs
+++ b/components/compositing/compositor.rs
@@ -26,7 +26,7 @@ use layers::rendergl;
use layers::rendergl::RenderContext;
use layers::scene::Scene;
use layout_traits::LayoutControlChan;
-use msg::compositor_msg::{Epoch, FrameTreeId, LayerId, LayerKind};
+use msg::compositor_msg::{Epoch, EventResult, FrameTreeId, LayerId, LayerKind};
use msg::compositor_msg::{LayerProperties, ScrollPolicy};
use msg::constellation_msg::Msg as ConstellationMsg;
use msg::constellation_msg::{AnimationState, Image, PixelFormat};
@@ -35,7 +35,8 @@ use msg::constellation_msg::{NavigationDirection, PipelineId, WindowSizeData};
use pipeline::CompositionPipeline;
use profile_traits::mem::{self, ReportKind, Reporter, ReporterRequest};
use profile_traits::time::{self, ProfilerCategory, profile};
-use script_traits::{ConstellationControlMsg, LayoutControlMsg};
+use script_traits::CompositorEvent::{TouchDownEvent, TouchMoveEvent, TouchUpEvent};
+use script_traits::{ConstellationControlMsg, LayoutControlMsg, MouseButton};
use scrolling::ScrollingTimerProxy;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet};
@@ -59,6 +60,9 @@ const BUFFER_MAP_SIZE: usize = 10000000;
const MAX_ZOOM: f32 = 8.0;
const MIN_ZOOM: f32 = 0.1;
+/// Minimum number of ScreenPx to begin touch scrolling.
+const TOUCH_PAN_MIN_SCREEN_PX: f32 = 20.0;
+
/// Holds the state when running reftests that determines when it is
/// safe to save the output image.
#[derive(Copy, Clone, PartialEq)]
@@ -68,6 +72,23 @@ enum ReadyState {
ReadyToSaveImage,
}
+/// The states of the touch input state machine.
+///
+/// TODO: Currently Add support for "flinging" (scrolling inertia), pinch zooming, better
+/// support for multiple touch points.
+enum TouchState {
+ /// Not tracking any touch point
+ Nothing,
+ /// A touchstart event was dispatched to the page, but the response wasn't received yet.
+ WaitingForScript,
+ /// Script is consuming the current touch point; don't perform default actions.
+ DefaultPrevented,
+ /// A single touch point is active and may perform click or pan default actions.
+ Touching,
+ /// A single touch point is active and has started panning.
+ Panning,
+}
+
/// NB: Never block on the constellation, because sometimes the constellation blocks on us.
pub struct IOCompositor<Window: WindowMethods> {
/// The application window.
@@ -156,6 +177,15 @@ pub struct IOCompositor<Window: WindowMethods> {
/// Pending scroll to fragment event, if any
fragment_point: Option<Point2D<f32>>,
+ /// Touch input state machine
+ touch_gesture_state: TouchState,
+
+ /// When tracking a touch for gesture detection, the point where it started
+ first_touch_point: Option<TypedPoint2D<DevicePixel, f32>>,
+
+ /// When tracking a touch for gesture detection, its most recent point
+ last_touch_point: Option<TypedPoint2D<DevicePixel, f32>>,
+
/// Pending scroll events.
pending_scroll_events: Vec<ScrollEvent>,
@@ -295,6 +325,9 @@ impl<Window: WindowMethods> IOCompositor<Window> {
channel_to_self: state.sender.clone_compositor_proxy(),
scrolling_timer: ScrollingTimerProxy::new(state.sender),
composition_request: CompositionRequest::NoCompositingNecessary,
+ touch_gesture_state: TouchState::Nothing,
+ first_touch_point: None,
+ last_touch_point: None,
pending_scroll_events: Vec::new(),
composite_target: composite_target,
shutdown_state: ShutdownState::NotShuttingDown,
@@ -502,6 +535,18 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
}
+ (Msg::TouchEventProcessed(result), ShutdownState::NotShuttingDown) => {
+ match self.touch_gesture_state {
+ TouchState::WaitingForScript => {
+ self.touch_gesture_state = match result {
+ EventResult::DefaultAllowed => TouchState::Touching,
+ EventResult::DefaultPrevented => TouchState::DefaultPrevented,
+ };
+ }
+ _ => {}
+ }
+ }
+
(Msg::SetCursor(cursor), ShutdownState::NotShuttingDown) => {
self.window.set_cursor(cursor)
}
@@ -1097,6 +1142,109 @@ impl<Window: WindowMethods> IOCompositor<Window> {
}
}
+ fn on_touch_down(&mut self, identifier: i32, point: TypedPoint2D<DevicePixel, f32>) {
+ match self.touch_gesture_state {
+ TouchState::Nothing => {
+ // TODO: Don't wait for script if we know the page has no touch event listeners.
+ self.first_touch_point = Some(point);
+ self.last_touch_point = Some(point);
+ self.touch_gesture_state = TouchState::WaitingForScript;
+ }
+ TouchState::WaitingForScript => {
+ // TODO: Queue events while waiting for script?
+ }
+ TouchState::DefaultPrevented => {}
+ TouchState::Touching => {}
+ TouchState::Panning => {}
+ }
+ if let Some(result) = self.find_topmost_layer_at_point(point / self.scene.scale) {
+ result.layer.send_event(self, TouchDownEvent(identifier, result.point.to_untyped()));
+ }
+ }
+
+ fn on_touch_move(&mut self, identifier: i32, point: TypedPoint2D<DevicePixel, f32>) {
+ match self.touch_gesture_state {
+ TouchState::Nothing => warn!("Got unexpected touch move event"),
+
+ TouchState::WaitingForScript => {
+ // TODO: Queue events while waiting for script?
+ }
+ TouchState::Touching => {
+ match self.first_touch_point {
+ Some(p0) => {
+ let delta = point - p0;
+ let px: TypedPoint2D<ScreenPx, _> = delta / self.device_pixels_per_screen_px();
+ let px = px.to_untyped();
+
+ if px.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
+ px.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
+ {
+ self.touch_gesture_state = TouchState::Panning;
+ self.on_scroll_window_event(delta, point.cast().unwrap());
+ }
+ }
+ None => warn!("first_touch_point not set")
+ }
+ }
+ TouchState::Panning => {
+ match self.last_touch_point {
+ Some(p0) => {
+ let delta = point - p0;
+ self.on_scroll_window_event(delta, point.cast().unwrap());
+ }
+ None => warn!("last_touch_point not set")
+ }
+ }
+ TouchState::DefaultPrevented => {
+ // Send the event to script.
+ if let Some(result) = self.find_topmost_layer_at_point(point / self.scene.scale) {
+ result.layer.send_event(self,
+ TouchMoveEvent(identifier, result.point.to_untyped()));
+ }
+ }
+ }
+ self.last_touch_point = Some(point);
+ }
+
+ fn on_touch_up(&mut self, identifier: i32, point: TypedPoint2D<DevicePixel, f32>) {
+ // TODO: Track the number of active touch points, and don't reset stuff until it is zero.
+ self.first_touch_point = None;
+ self.last_touch_point = None;
+
+ // Send the event to script.
+ if let Some(result) = self.find_topmost_layer_at_point(point / self.scene.scale) {
+ result.layer.send_event(self, TouchUpEvent(identifier, result.point.to_untyped()));
+ }
+
+ match self.touch_gesture_state {
+ TouchState::Nothing => warn!("Got unexpected touch up event"),
+
+ TouchState::WaitingForScript => {}
+ TouchState::Touching => {
+ // TODO: If the duration exceeds some threshold, send a contextmenu event instead.
+ // TODO: Don't send a click if preventDefault is called on the touchend event.
+ self.simulate_mouse_click(point);
+ }
+ TouchState::Panning => {}
+ TouchState::DefaultPrevented => {}
+ }
+ self.touch_gesture_state = TouchState::Nothing;
+ }
+
+ /// http://w3c.github.io/touch-events/#mouse-events
+ fn simulate_mouse_click(&self, p: TypedPoint2D<DevicePixel, f32>) {
+ match self.find_topmost_layer_at_point(p / self.scene.scale) {
+ Some(HitTestResult { layer, point }) => {
+ let button = MouseButton::Left;
+ layer.send_mouse_move_event(self, point);
+ layer.send_mouse_event(self, MouseWindowEvent::MouseDown(button, p), point);
+ layer.send_mouse_event(self, MouseWindowEvent::MouseUp(button, p), point);
+ layer.send_mouse_event(self, MouseWindowEvent::Click(button, p), point);
+ }
+ None => {},
+ }
+ }
+
fn on_scroll_window_event(&mut self,
delta: TypedPoint2D<DevicePixel, f32>,
cursor: TypedPoint2D<DevicePixel, i32>) {
diff --git a/components/compositing/compositor_layer.rs b/components/compositing/compositor_layer.rs
index 811f704f52d..8e1330b9344 100644
--- a/components/compositing/compositor_layer.rs
+++ b/components/compositing/compositor_layer.rs
@@ -12,7 +12,8 @@ use layers::color::Color;
use layers::geometry::LayerPixel;
use layers::layers::{Layer, LayerBufferSet};
use msg::compositor_msg::{Epoch, LayerId, LayerProperties, ScrollPolicy};
-use msg::constellation_msg::{PipelineId};
+use msg::constellation_msg::PipelineId;
+use script_traits::CompositorEvent;
use script_traits::CompositorEvent::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent};
use script_traits::ConstellationControlMsg;
use std::rc::Rc;
@@ -132,6 +133,11 @@ pub trait CompositorLayer {
cursor: TypedPoint2D<LayerPixel, f32>)
where Window: WindowMethods;
+ fn send_event<Window>(&self,
+ compositor: &IOCompositor<Window>,
+ event: CompositorEvent)
+ where Window: WindowMethods;
+
fn clamp_scroll_offset_and_scroll_layer(&self,
new_offset: TypedPoint2D<LayerPixel, f32>)
-> ScrollEventResult;
@@ -372,22 +378,22 @@ impl CompositorLayer for Layer<CompositorData> {
MouseWindowEvent::MouseUp(button, _) =>
MouseUpEvent(button, event_point),
};
-
- if let Some(pipeline) = compositor.pipeline(self.pipeline_id()) {
- pipeline.script_chan
- .send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message))
- .unwrap();
- }
+ self.send_event(compositor, message);
}
fn send_mouse_move_event<Window>(&self,
compositor: &IOCompositor<Window>,
cursor: TypedPoint2D<LayerPixel, f32>)
where Window: WindowMethods {
- let message = MouseMoveEvent(cursor.to_untyped());
+ self.send_event(compositor, MouseMoveEvent(cursor.to_untyped()));
+ }
+
+ fn send_event<Window>(&self,
+ compositor: &IOCompositor<Window>,
+ event: CompositorEvent) where Window: WindowMethods {
if let Some(pipeline) = compositor.pipeline(self.pipeline_id()) {
let _ = pipeline.script_chan
- .send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), message));
+ .send(ConstellationControlMsg::SendEvent(pipeline.id.clone(), event));
}
}
diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs
index cff97842653..b9d73c6b25d 100644
--- a/components/compositing/compositor_task.rs
+++ b/components/compositing/compositor_task.rs
@@ -10,7 +10,7 @@ use headless;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use layers::layers::{BufferRequest, LayerBufferSet};
use layers::platform::surface::{NativeDisplay, NativeSurface};
-use msg::compositor_msg::{Epoch, FrameTreeId, LayerId, LayerProperties};
+use msg::compositor_msg::{Epoch, EventResult, FrameTreeId, LayerId, LayerProperties};
use msg::compositor_msg::{PaintListener, ScriptToCompositorMsg};
use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId};
use msg::constellation_msg::{Image, Key, KeyModifiers, KeyState};
@@ -94,6 +94,10 @@ pub fn run_script_listener_thread(compositor_proxy: Box<CompositorProxy + 'stati
ScriptToCompositorMsg::SendKeyEvent(key, key_state, key_modifiers) => {
compositor_proxy.send(Msg::KeyEvent(key, key_state, key_modifiers))
}
+
+ ScriptToCompositorMsg::TouchEventProcessed(result) => {
+ compositor_proxy.send(Msg::TouchEventProcessed(result))
+ }
}
}
}
@@ -189,6 +193,8 @@ pub enum Msg {
RecompositeAfterScroll,
/// Sends an unconsumed key event back to the compositor.
KeyEvent(Key, KeyState, KeyModifiers),
+ /// Script has handled a touch event, and either prevented or allowed default actions.
+ TouchEventProcessed(EventResult),
/// Changes the cursor.
SetCursor(Cursor),
/// Composite to a PNG file and return the Image over a passed channel.
@@ -238,6 +244,7 @@ impl Debug for Msg {
Msg::ScrollTimeout(..) => write!(f, "ScrollTimeout"),
Msg::RecompositeAfterScroll => write!(f, "RecompositeAfterScroll"),
Msg::KeyEvent(..) => write!(f, "KeyEvent"),
+ Msg::TouchEventProcessed(..) => write!(f, "TouchEventProcessed"),
Msg::SetCursor(..) => write!(f, "SetCursor"),
Msg::CreatePng(..) => write!(f, "CreatePng"),
Msg::PaintTaskExited(..) => write!(f, "PaintTaskExited"),
diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs
index d322ef77445..1db4d7b7ad5 100644
--- a/components/compositing/headless.rs
+++ b/components/compositing/headless.rs
@@ -120,6 +120,7 @@ impl CompositorEventListener for NullCompositor {
Msg::ChangePageTitle(..) |
Msg::ChangePageUrl(..) |
Msg::KeyEvent(..) |
+ Msg::TouchEventProcessed(..) |
Msg::SetCursor(..) |
Msg::ViewportConstrained(..) => {}
Msg::CreatePng(..) |