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..ad27f5e0a191e8b7e756b149a872151298c228a9 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; |
+}; |
James Cook
2014/06/06 00:09:45
nit: "} // namespace" and no ;
dmazzoni
2014/06/06 15:27:22
Done.
|
+ |
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 |