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.
This commit is contained in:
Matt Brubeck 2015-08-14 07:24:50 -07:00
parent 4ed15a8853
commit fe7460f34d
9 changed files with 276 additions and 15 deletions

View file

@ -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>) {