diff options
author | Matt Brubeck <mbrubeck@limpet.net> | 2015-08-14 07:24:50 -0700 |
---|---|---|
committer | Matt Brubeck <mbrubeck@limpet.net> | 2015-10-22 10:37:03 -0700 |
commit | fe7460f34d20d5f17d21d60e1053028b21c63ebc (patch) | |
tree | 6cc401775f503ce01b528021cc87bc7580fc7140 /components/compositing | |
parent | 4ed15a8853fc49d0940ed24d939fd0c407ee80a9 (diff) | |
download | servo-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.rs | 152 | ||||
-rw-r--r-- | components/compositing/compositor_layer.rs | 24 | ||||
-rw-r--r-- | components/compositing/compositor_task.rs | 9 | ||||
-rw-r--r-- | components/compositing/headless.rs | 1 |
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(..) | |