| Index: ui/chromeos/touch_exploration_controller.cc
|
| diff --git a/ui/chromeos/touch_exploration_controller.cc b/ui/chromeos/touch_exploration_controller.cc
|
| index 57457697f3c294a28dd161b1668c580c3fd7d358..57ff1022de3e51a48a17e4dd73da305a05d96a31 100644
|
| --- a/ui/chromeos/touch_exploration_controller.cc
|
| +++ b/ui/chromeos/touch_exploration_controller.cc
|
| @@ -7,14 +7,34 @@
|
| #include "base/logging.h"
|
| #include "ui/aura/client/cursor_client.h"
|
| #include "ui/aura/window.h"
|
| +#include "ui/aura/window_event_dispatcher.h"
|
| #include "ui/aura/window_tree_host.h"
|
| #include "ui/events/event.h"
|
| +#include "ui/events/event_processor.h"
|
|
|
| namespace ui {
|
|
|
| +namespace {
|
| +// The default value for initial_touch_id_passthrough_mapping_ used
|
| +// when the user has not yet released any fingers yet, so there's no
|
| +// touch id remapping yet.
|
| +const int kTouchIdUnassigned = 0;
|
| +
|
| +// The value for initial_touch_id_passthrough_mapping_ if the user has
|
| +// released the first finger but some other fingers are held down. In this
|
| +// state we don't do any touch id remapping, but we distinguish it from the
|
| +// kTouchIdUnassigned state because we don't want to assign
|
| +// initial_touch_id_passthrough_mapping_ a touch id anymore,
|
| +// until all fingers are released.
|
| +const int kTouchIdNone = -1;
|
| +} // namespace
|
| +
|
| TouchExplorationController::TouchExplorationController(
|
| aura::Window* root_window)
|
| - : root_window_(root_window) {
|
| + : root_window_(root_window),
|
| + initial_touch_id_passthrough_mapping_(kTouchIdUnassigned),
|
| + state_(NO_FINGERS_DOWN),
|
| + event_handler_for_testing_(NULL) {
|
| CHECK(root_window);
|
| root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
|
| }
|
| @@ -23,124 +43,287 @@ TouchExplorationController::~TouchExplorationController() {
|
| root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
|
| }
|
|
|
| +void TouchExplorationController::CallTapTimerNowForTesting() {
|
| + DCHECK(tap_timer_.IsRunning());
|
| + tap_timer_.Stop();
|
| + OnTapTimerFired();
|
| +}
|
| +
|
| +void TouchExplorationController::SetEventHandlerForTesting(
|
| + ui::EventHandler* event_handler_for_testing) {
|
| + event_handler_for_testing_ = event_handler_for_testing;
|
| +}
|
| +
|
| +bool TouchExplorationController::IsInNoFingersDownStateForTesting() const {
|
| + return state_ == NO_FINGERS_DOWN;
|
| +}
|
| +
|
| ui::EventRewriteStatus TouchExplorationController::RewriteEvent(
|
| const ui::Event& event,
|
| scoped_ptr<ui::Event>* rewritten_event) {
|
| if (!event.IsTouchEvent())
|
| return ui::EVENT_REWRITE_CONTINUE;
|
| -
|
| const ui::TouchEvent& touch_event =
|
| static_cast<const ui::TouchEvent&>(event);
|
| +
|
| + // If the tap timer should have fired by now but hasn't, run it now and
|
| + // stop the timer. This is important so that behavior is consistent with
|
| + // the timestamps of the events, and not dependent on the granularity of
|
| + // the timer.
|
| + if (tap_timer_.IsRunning() &&
|
| + touch_event.time_stamp() - initial_press_->time_stamp() >
|
| + gesture_detector_config_.double_tap_timeout) {
|
| + tap_timer_.Stop();
|
| + OnTapTimerFired();
|
| + // Note: this may change the state. We should now continue and process
|
| + // this event under this new state.
|
| + }
|
| +
|
| const ui::EventType type = touch_event.type();
|
| const gfx::PointF& location = touch_event.location_f();
|
| const int touch_id = touch_event.touch_id();
|
| - const int flags = touch_event.flags();
|
|
|
| + // Always update touch ids and touch locations, so we can use those
|
| + // no matter what state we're in.
|
| if (type == ui::ET_TOUCH_PRESSED) {
|
| - touch_ids_.push_back(touch_id);
|
| + current_touch_ids_.push_back(touch_id);
|
| touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
|
| - // If this is the first and only finger touching - rewrite the touch as a
|
| - // mouse move. Otherwise let the it go through as is.
|
| - if (touch_ids_.size() == 1) {
|
| - *rewritten_event = CreateMouseMoveEvent(location, flags);
|
| - EnterTouchToMouseMode();
|
| - return ui::EVENT_REWRITE_REWRITTEN;
|
| - }
|
| - return ui::EVENT_REWRITE_CONTINUE;
|
| } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
|
| - std::vector<int>::iterator it =
|
| - std::find(touch_ids_.begin(), touch_ids_.end(), touch_id);
|
| - // We may fail to find the finger if the exploration mode was turned on
|
| - // while the user had some fingers touching the screen. We simply ignore
|
| - // those fingers for the purposes of event transformation.
|
| - if (it == touch_ids_.end())
|
| + std::vector<int>::iterator it = std::find(
|
| + current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
|
| +
|
| + // Can happen if touch exploration is enabled while fingers were down.
|
| + if (it == current_touch_ids_.end())
|
| return ui::EVENT_REWRITE_CONTINUE;
|
| - const bool first_finger_released = it == touch_ids_.begin();
|
| - touch_ids_.erase(it);
|
| - int num_erased = touch_locations_.erase(touch_id);
|
| - DCHECK_EQ(num_erased, 1);
|
| - const int num_fingers_remaining = touch_ids_.size();
|
| -
|
| - if (num_fingers_remaining == 0) {
|
| - *rewritten_event = CreateMouseMoveEvent(location, flags);
|
| - return ui::EVENT_REWRITE_REWRITTEN;
|
| - }
|
|
|
| - // If we are left with one finger - enter the mouse move mode.
|
| - const bool enter_mouse_move_mode = num_fingers_remaining == 1;
|
| + current_touch_ids_.erase(it);
|
| + touch_locations_.erase(touch_id);
|
| + } else if (type == ui::ET_TOUCH_MOVED) {
|
| + std::vector<int>::iterator it = std::find(
|
| + current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
|
|
|
| - if (!enter_mouse_move_mode && !first_finger_released) {
|
| - // No special handling needed.
|
| + // Can happen if touch exploration is enabled while fingers were down.
|
| + if (it == current_touch_ids_.end())
|
| return ui::EVENT_REWRITE_CONTINUE;
|
| - }
|
|
|
| - // If the finger which was released was the first one, - we need to rewrite
|
| - // the release event as a release of the was second / now first finger.
|
| - // This is the finger which will now be getting "substracted".
|
| - if (first_finger_released) {
|
| - int rewritten_release_id = touch_ids_[0];
|
| - const gfx::PointF& rewritten_release_location =
|
| - touch_locations_[rewritten_release_id];
|
| - ui::TouchEvent* rewritten_release_event = new ui::TouchEvent(
|
| - ui::ET_TOUCH_RELEASED,
|
| - rewritten_release_location,
|
| - rewritten_release_id,
|
| - event.time_stamp());
|
| - rewritten_release_event->set_flags(touch_event.flags());
|
| - rewritten_event->reset(rewritten_release_event);
|
| - } else if (enter_mouse_move_mode) {
|
| - // Dispatch the release event as is.
|
| - // TODO(mfomitchev): We can get rid of this clause once we have
|
| - // EVENT_REWRITE_DISPATCH_ANOTHER working without having to set
|
| - // rewritten_event.
|
| - rewritten_event->reset(new ui::TouchEvent(touch_event));
|
| - }
|
| + touch_locations_[*it] = location;
|
| + }
|
| +
|
| + // The rest of the processing depends on what state we're in.
|
| + switch(state_) {
|
| + case NO_FINGERS_DOWN:
|
| + return InNoFingersDown(touch_event, rewritten_event);
|
| + case SINGLE_TAP_PRESSED:
|
| + return InSingleTapPressed(touch_event, rewritten_event);
|
| + case SINGLE_TAP_RELEASED:
|
| + return InSingleTapReleased(touch_event, rewritten_event);
|
| + case DOUBLE_TAP_PRESSED:
|
| + return InDoubleTapPressed(touch_event, rewritten_event);
|
| + case TOUCH_EXPLORATION:
|
| + return InTouchExploration(touch_event, rewritten_event);
|
| + case PASSTHROUGH_MINUS_ONE:
|
| + return InPassthroughMinusOne(touch_event, rewritten_event);
|
| + }
|
|
|
| - if (enter_mouse_move_mode) {
|
| - // Since we are entering the mouse move mode - also dispatch a mouse move
|
| - // event at the location of the one remaining finger. (num_fingers == 1)
|
| - const gfx::PointF& mouse_move_location = touch_locations_[touch_ids_[0]];
|
| - next_dispatch_event_ =
|
| - CreateMouseMoveEvent(mouse_move_location, flags).Pass();
|
| - return ui::EVENT_REWRITE_DISPATCH_ANOTHER;
|
| + NOTREACHED();
|
| + return ui::EVENT_REWRITE_CONTINUE;
|
| +}
|
| +
|
| +ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
|
| + const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) {
|
| + NOTREACHED();
|
| + return ui::EVENT_REWRITE_CONTINUE;
|
| +}
|
| +
|
| +ui::EventRewriteStatus TouchExplorationController::InNoFingersDown(
|
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
|
| + const ui::EventType type = event.type();
|
| + if (type == ui::ET_TOUCH_PRESSED) {
|
| + initial_press_.reset(new TouchEvent(event));
|
| + tap_timer_.Start(FROM_HERE,
|
| + gesture_detector_config_.double_tap_timeout,
|
| + this,
|
| + &TouchExplorationController::OnTapTimerFired);
|
| + state_ = SINGLE_TAP_PRESSED;
|
| + return ui::EVENT_REWRITE_DISCARD;
|
| + }
|
| +
|
| + NOTREACHED();
|
| + return ui::EVENT_REWRITE_CONTINUE;
|
| +}
|
| +
|
| +ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed(
|
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
|
| + const ui::EventType type = event.type();
|
| +
|
| + if (type == ui::ET_TOUCH_PRESSED) {
|
| + // Adding a second finger within the timeout period switches to
|
| + // passthrough.
|
| + state_ = PASSTHROUGH_MINUS_ONE;
|
| + return InPassthroughMinusOne(event, rewritten_event);
|
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
|
| + DCHECK_EQ(0U, current_touch_ids_.size());
|
| + state_ = SINGLE_TAP_RELEASED;
|
| + return EVENT_REWRITE_DISCARD;
|
| + } else if (type == ui::ET_TOUCH_MOVED) {
|
| + // If the user moves far enough from the initial touch location (outside
|
| + // the "slop" region, jump to the touch exploration mode early.
|
| + // TODO(evy, lisayin): Add gesture recognition here instead -
|
| + // we should probably jump to gesture mode here if the velocity is
|
| + // high enough, and touch exploration if the velocity is lower.
|
| + float delta = (event.location() - initial_press_->location()).Length();
|
| + if (delta > gesture_detector_config_.touch_slop) {
|
| + EnterTouchToMouseMode();
|
| + state_ = TOUCH_EXPLORATION;
|
| + return InTouchExploration(event, rewritten_event);
|
| }
|
| +
|
| + return EVENT_REWRITE_DISCARD;
|
| + }
|
| + NOTREACHED() << "Unexpected event type received.";
|
| + return ui::EVENT_REWRITE_CONTINUE;
|
| +}
|
| +
|
| +ui::EventRewriteStatus TouchExplorationController::InSingleTapReleased(
|
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
|
| + const ui::EventType type = event.type();
|
| + if (type == ui::ET_TOUCH_PRESSED) {
|
| + // This is the second tap in a double-tap (or double tap-hold).
|
| + // Rewrite at location of last touch exploration.
|
| + ui::TouchEvent* rewritten_press_event = new ui::TouchEvent(
|
| + ui::ET_TOUCH_PRESSED,
|
| + last_touch_exploration_location_,
|
| + event.touch_id(),
|
| + event.time_stamp());
|
| + rewritten_press_event->set_flags(event.flags());
|
| + rewritten_event->reset(rewritten_press_event);
|
| + state_ = DOUBLE_TAP_PRESSED;
|
| + return ui::EVENT_REWRITE_REWRITTEN;
|
| + }
|
| +
|
| + NOTREACHED();
|
| + return ui::EVENT_REWRITE_CONTINUE;
|
| +}
|
| +
|
| +ui::EventRewriteStatus TouchExplorationController::InDoubleTapPressed(
|
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
|
| + const ui::EventType type = event.type();
|
| + if (type == ui::ET_TOUCH_PRESSED) {
|
| + return ui::EVENT_REWRITE_DISCARD;
|
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
|
| + if (current_touch_ids_.size() != 0)
|
| + return EVENT_REWRITE_DISCARD;
|
| +
|
| + // Rewrite at location of last touch exploration.
|
| + ui::TouchEvent* rewritten_release_event = new ui::TouchEvent(
|
| + ui::ET_TOUCH_RELEASED,
|
| + last_touch_exploration_location_,
|
| + event.touch_id(),
|
| + event.time_stamp());
|
| + rewritten_release_event->set_flags(event.flags());
|
| + rewritten_event->reset(rewritten_release_event);
|
| + ResetToNoFingersDown();
|
| return ui::EVENT_REWRITE_REWRITTEN;
|
| } else if (type == ui::ET_TOUCH_MOVED) {
|
| - std::vector<int>::iterator it =
|
| - std::find(touch_ids_.begin(), touch_ids_.end(), touch_id);
|
| - // We may fail to find the finger if the exploration mode was turned on
|
| - // while the user had some fingers touching the screen. We simply ignore
|
| - // those fingers for the purposes of event transformation.
|
| - if (it == touch_ids_.end())
|
| - return ui::EVENT_REWRITE_CONTINUE;
|
| - touch_locations_[*it] = location;
|
| - if (touch_ids_.size() == 1) {
|
| - // Touch moves are rewritten as mouse moves when there's only one finger
|
| - // touching the screen.
|
| - *rewritten_event = CreateMouseMoveEvent(location, flags).Pass();
|
| - return ui::EVENT_REWRITE_REWRITTEN;
|
| + return ui::EVENT_REWRITE_DISCARD;
|
| + }
|
| + NOTREACHED() << "Unexpected event type received.";
|
| + return ui::EVENT_REWRITE_CONTINUE;
|
| +}
|
| +
|
| +ui::EventRewriteStatus TouchExplorationController::InTouchExploration(
|
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
|
| + const ui::EventType type = event.type();
|
| + if (type == ui::ET_TOUCH_PRESSED) {
|
| + // Ignore any additional fingers when we're already in touch exploration
|
| + // mode. TODO(evy, lisayin): Support "split-tap" here instead.
|
| + return ui::EVENT_REWRITE_DISCARD;
|
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
|
| + if (current_touch_ids_.size() == 0)
|
| + ResetToNoFingersDown();
|
| + } else if (type != ui::ET_TOUCH_MOVED) {
|
| + NOTREACHED() << "Unexpected event type received.";
|
| + return ui::EVENT_REWRITE_CONTINUE;
|
| + }
|
| +
|
| + // Rewrite as a mouse-move event.
|
| + *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags());
|
| + last_touch_exploration_location_ = event.location();
|
| + return ui::EVENT_REWRITE_REWRITTEN;
|
| +}
|
| +
|
| +ui::EventRewriteStatus TouchExplorationController::InPassthroughMinusOne(
|
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
|
| + ui::EventType type = event.type();
|
| + gfx::PointF location = event.location_f();
|
| +
|
| + if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
|
| + if (current_touch_ids_.size() == 0)
|
| + ResetToNoFingersDown();
|
| +
|
| + if (initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
|
| + if (event.touch_id() == initial_press_->touch_id()) {
|
| + initial_touch_id_passthrough_mapping_ = kTouchIdNone;
|
| + } else {
|
| + // If the only finger now remaining is the first finger,
|
| + // rewrite as a move to the location of the first finger.
|
| + initial_touch_id_passthrough_mapping_ = event.touch_id();
|
| + ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent(
|
| + ui::ET_TOUCH_MOVED,
|
| + touch_locations_[initial_press_->touch_id()],
|
| + initial_touch_id_passthrough_mapping_,
|
| + event.time_stamp());
|
| + rewritten_passthrough_event->set_flags(event.flags());
|
| + rewritten_event->reset(rewritten_passthrough_event);
|
| + return ui::EVENT_REWRITE_REWRITTEN;
|
| + }
|
| }
|
| - if (touch_id == touch_ids_.front()) {
|
| - // Touch moves of the first finger are discarded when there's more than
|
| - // one finger touching.
|
| + }
|
| +
|
| + if (event.touch_id() == initial_press_->touch_id()) {
|
| + if (initial_touch_id_passthrough_mapping_ == kTouchIdNone ||
|
| + initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
|
| return ui::EVENT_REWRITE_DISCARD;
|
| }
|
| - return ui::EVENT_REWRITE_CONTINUE;
|
| +
|
| + ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent(
|
| + type,
|
| + location,
|
| + initial_touch_id_passthrough_mapping_,
|
| + event.time_stamp());
|
| + rewritten_passthrough_event->set_flags(event.flags());
|
| + rewritten_event->reset(rewritten_passthrough_event);
|
| + return ui::EVENT_REWRITE_REWRITTEN;
|
| }
|
| - NOTREACHED() << "Unexpected event type received.";
|
| +
|
| return ui::EVENT_REWRITE_CONTINUE;
|
| }
|
|
|
| -ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
|
| - const ui::Event& last_event,
|
| - scoped_ptr<ui::Event>* new_event) {
|
| - CHECK(next_dispatch_event_);
|
| - DCHECK(last_event.IsTouchEvent());
|
| - *new_event = next_dispatch_event_.Pass();
|
| - // Enter the mouse move mode if needed
|
| - if ((*new_event)->IsMouseEvent())
|
| +void TouchExplorationController::OnTapTimerFired() {
|
| + if (state_ != SINGLE_TAP_RELEASED && state_ != SINGLE_TAP_PRESSED)
|
| + return;
|
| +
|
| + if (state_ == SINGLE_TAP_RELEASED) {
|
| + ResetToNoFingersDown();
|
| + } else {
|
| EnterTouchToMouseMode();
|
| - return ui::EVENT_REWRITE_REWRITTEN;
|
| + state_ = TOUCH_EXPLORATION;
|
| + }
|
| +
|
| + scoped_ptr<ui::Event> mouse_move = CreateMouseMoveEvent(
|
| + initial_press_->location(), initial_press_->flags());
|
| + DispatchEvent(mouse_move.get());
|
| + last_touch_exploration_location_ = initial_press_->location();
|
| +}
|
| +
|
| +void TouchExplorationController::DispatchEvent(ui::Event* event) {
|
| + if (event_handler_for_testing_) {
|
| + event_handler_for_testing_->OnEvent(event);
|
| + return;
|
| + }
|
| +
|
| + ui::EventDispatchDetails result ALLOW_UNUSED =
|
| + root_window_->GetHost()->dispatcher()->OnEventFromSource(event);
|
| }
|
|
|
| scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent(
|
| @@ -164,4 +347,11 @@ void TouchExplorationController::EnterTouchToMouseMode() {
|
| cursor_client->HideCursor();
|
| }
|
|
|
| +void TouchExplorationController::ResetToNoFingersDown() {
|
| + state_ = NO_FINGERS_DOWN;
|
| + initial_touch_id_passthrough_mapping_ = kTouchIdUnassigned;
|
| + if (tap_timer_.IsRunning())
|
| + tap_timer_.Stop();
|
| +}
|
| +
|
| } // namespace ui
|
|
|