Chromium Code Reviews| 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..5cbe80636a1936d8bd801bab158d3d098bfb88f5 100644 |
| --- a/ui/chromeos/touch_exploration_controller.cc |
| +++ b/ui/chromeos/touch_exploration_controller.cc |
| @@ -7,14 +7,19 @@ |
| #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 { |
| TouchExplorationController::TouchExplorationController( |
| aura::Window* root_window) |
| - : root_window_(root_window) { |
| + : root_window_(root_window), |
| + initial_touch_id_passthrough_mapping_(0), |
| + state_(NO_FINGERS_DOWN), |
| + event_handler_for_testing_(NULL) { |
| CHECK(root_window); |
| root_window->GetHost()->GetEventSource()->AddEventRewriter(this); |
| } |
| @@ -23,6 +28,17 @@ TouchExplorationController::~TouchExplorationController() { |
| root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); |
| } |
| +void TouchExplorationController::CallTapTimerNowForTesting() { |
| + CHECK(tap_timer_.IsRunning()); |
| + tap_timer_.Stop(); |
| + OnTapTimerFired(); |
| +} |
| + |
| +void TouchExplorationController::SetEventHandlerForTesting( |
| + ui::EventHandler* event_handler_for_testing) { |
| + event_handler_for_testing_ = event_handler_for_testing; |
| +} |
| + |
| ui::EventRewriteStatus TouchExplorationController::RewriteEvent( |
| const ui::Event& event, |
| scoped_ptr<ui::Event>* rewritten_event) { |
| @@ -34,113 +50,269 @@ ui::EventRewriteStatus TouchExplorationController::RewriteEvent( |
| 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); |
| 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. |
| + |
| + // Can happen if touch exploration is enabled while fingers were down. |
| if (it == 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(); |
| + touch_locations_.erase(touch_id); |
| + } else if (type == ui::ET_TOUCH_MOVED) { |
| + std::vector<int>::iterator it = |
| + std::find(touch_ids_.begin(), touch_ids_.end(), touch_id); |
| - if (num_fingers_remaining == 0) { |
| - *rewritten_event = CreateMouseMoveEvent(location, flags); |
| - return ui::EVENT_REWRITE_REWRITTEN; |
| - } |
| + // Can happen if touch exploration is enabled while fingers were down. |
| + if (it == touch_ids_.end()) |
| + return ui::EVENT_REWRITE_CONTINUE; |
| - // If we are left with one finger - enter the mouse move mode. |
| - const bool enter_mouse_move_mode = num_fingers_remaining == 1; |
| + touch_locations_[*it] = location; |
| + } |
| - if (!enter_mouse_move_mode && !first_finger_released) { |
| - // No special handling needed. |
| - return ui::EVENT_REWRITE_CONTINUE; |
| - } |
| + // The rest of the processing depends on what state we're in. |
| + switch(state_) { |
| + case NO_FINGERS_DOWN: |
| + return OnNoFingersDown(touch_event, rewritten_event); |
| + case GRACE_PERIOD: |
| + return OnGracePeriod(touch_event, rewritten_event); |
| + case TOUCH_EXPLORATION: |
| + return OnTouchExploration(touch_event, rewritten_event); |
| + case SINGLE_TAP_PENDING: |
| + return OnSingleTapPending(touch_event, rewritten_event); |
| + case DOUBLE_TAP_PRESSED: |
| + return OnDoubleTapPressed(touch_event, rewritten_event); |
| + case PASSTHROUGH_MINUS_ONE: |
| + return OnPassthroughMinusOne(touch_event, rewritten_event); |
| + } |
| - // 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)); |
| - } |
| + NOTREACHED(); |
| + return ui::EVENT_REWRITE_CONTINUE; |
| +} |
| - 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; |
| +ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent( |
| + const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) { |
| + NOTREACHED(); |
| + return ui::EVENT_REWRITE_CONTINUE; |
| +} |
| + |
| +ui::EventRewriteStatus TouchExplorationController::OnNoFingersDown( |
| + 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_ = GRACE_PERIOD; |
| + return ui::EVENT_REWRITE_DISCARD; |
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
|
aboxhall
2014/06/02 16:33:14
What's the point of these last two if blocks? They
dmazzoni
2014/06/03 07:46:51
You're right. Simplified.
|
| + NOTREACHED(); |
| + return ui::EVENT_REWRITE_CONTINUE; |
| + } else if (type == ui::ET_TOUCH_MOVED) { |
| + NOTREACHED(); |
| + return ui::EVENT_REWRITE_CONTINUE; |
| + } |
| + NOTREACHED() << "Unexpected event type received."; |
| + return ui::EVENT_REWRITE_CONTINUE; |
| +} |
| + |
| +ui::EventRewriteStatus TouchExplorationController::OnGracePeriod( |
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { |
| + const ui::EventType type = event.type(); |
| + if (event.time_stamp() - initial_press_->time_stamp() > |
|
aboxhall
2014/06/02 16:33:14
Even though it's reasonably self-explanatory, it m
dmazzoni
2014/06/03 07:46:51
Done.
|
| + gesture_detector_config_.double_tap_timeout) { |
| + EnterTouchToMouseMode(); |
| + state_ = TOUCH_EXPLORATION; |
| + return OnTouchExploration(event, rewritten_event); |
|
mfomitchev
2014/06/02 23:12:18
I think there still is a problem:
Suppose event i
dmazzoni
2014/06/03 07:46:51
Good catch.
I realized that we can handle this ca
|
| + } |
| + |
| + if (type == ui::ET_TOUCH_PRESSED) { |
| + // Adding a second finger within the timeout period switches to |
| + // passthrough. |
| + state_ = PASSTHROUGH_MINUS_ONE; |
| + return OnPassthroughMinusOne(event, rewritten_event); |
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
| + DCHECK_EQ(0U, touch_ids_.size()); |
| + state_ = SINGLE_TAP_PENDING; |
| + 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. Note that |
|
aboxhall
2014/06/02 16:33:14
Rephrase this note as an explicit TODO?
dmazzoni
2014/06/03 07:46:51
Done.
|
| + // when we add gesture recognition, 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 OnTouchExploration(event, rewritten_event); |
| } |
| + |
| + return EVENT_REWRITE_DISCARD; |
| + } |
| + NOTREACHED() << "Unexpected event type received."; |
| + return ui::EVENT_REWRITE_CONTINUE; |
| +} |
| + |
| +ui::EventRewriteStatus TouchExplorationController::OnTouchExploration( |
| + 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. Later, we should support "split-tap". |
|
aboxhall
2014/06/02 16:33:14
TODO?
dmazzoni
2014/06/03 07:46:51
Done.
|
| + return ui::EVENT_REWRITE_DISCARD; |
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
| + if (touch_ids_.size() == 0) |
| + state_ = NO_FINGERS_DOWN; |
| + } 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::OnSingleTapPending( |
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { |
| + const ui::EventType type = event.type(); |
|
mfomitchev
2014/06/02 23:12:18
Should we have a case here for
(event.time_stamp(
dmazzoni
2014/06/03 07:46:51
Yes, but I think it's cleaner to have it up above.
|
| + if (type == ui::ET_TOUCH_PRESSED) { |
|
aboxhall
2014/06/02 16:33:14
Comment stating explicitly that this represents a
dmazzoni
2014/06/03 07:46:51
Done.
|
| + // 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; |
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
|
aboxhall
2014/06/02 16:33:14
Similarly, why are these else blocks here? Do they
dmazzoni
2014/06/03 07:46:51
Done.
|
| + NOTREACHED(); |
| + return ui::EVENT_REWRITE_CONTINUE; |
| } 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(); |
| + NOTREACHED(); |
| + return ui::EVENT_REWRITE_CONTINUE; |
| + } |
| + NOTREACHED() << "Unexpected event type received."; |
| + return ui::EVENT_REWRITE_CONTINUE; |
| +} |
| + |
| +ui::EventRewriteStatus TouchExplorationController::OnDoubleTapPressed( |
| + const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { |
| + const ui::EventType type = event.type(); |
| + if (type == ui::ET_TOUCH_PRESSED) { |
|
aboxhall
2014/06/02 16:33:14
How could we get a TOUCH_PRESSED directly after a
dmazzoni
2014/06/03 07:46:51
You have lots of fingers. :)
|
| + return ui::EVENT_REWRITE_DISCARD; |
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
| + if (touch_ids_.size() != 0) |
|
aboxhall
2014/06/02 16:33:14
It took me quite a while to work out what was goin
dmazzoni
2014/06/03 07:46:51
Sure, how about current_touch_ids_
|
| + 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); |
| + state_ = NO_FINGERS_DOWN; |
| + return ui::EVENT_REWRITE_REWRITTEN; |
| + } else if (type == ui::ET_TOUCH_MOVED) { |
| + return ui::EVENT_REWRITE_DISCARD; |
| + } |
| + NOTREACHED() << "Unexpected event type received."; |
| + return ui::EVENT_REWRITE_CONTINUE; |
| +} |
| + |
| +ui::EventRewriteStatus TouchExplorationController::OnPassthroughMinusOne( |
| + 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_PRESSED) { |
| + return ui::EVENT_REWRITE_CONTINUE; |
|
mfomitchev
2014/06/02 23:12:18
Nit: It might make sense to get rid of this if. Th
dmazzoni
2014/06/03 07:46:51
Done.
|
| + } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
| + if (touch_ids_.size() == 0) |
| + state_ = NO_FINGERS_DOWN; |
|
mfomitchev
2014/06/02 23:12:18
We also need to set initial_touch_id_passthrough_m
dmazzoni
2014/06/03 07:46:51
Done.
|
| + |
| + bool first_finger_still_down = |
|
aboxhall
2014/06/02 16:33:14
Does the rest of this need to go inside this if bl
dmazzoni
2014/06/03 07:46:51
No, the ET_TOUCH_MOVED case doesn't early-return.
|
| + touch_ids_.size() >= 1 && |
| + touch_ids_[0] == initial_press_->touch_id(); |
| + |
| + if (first_finger_still_down && |
| + touch_ids_.size() == 1 && |
|
mfomitchev
2014/06/02 23:12:18
Hmm.. why do we only do this when there's only one
dmazzoni
2014/06/03 07:46:51
Sure, this is a bit simpler.
|
| + event.touch_id() != initial_press_->touch_id() && |
| + initial_touch_id_passthrough_mapping_ == 0) { |
| + // 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. |
| - return ui::EVENT_REWRITE_DISCARD; |
| - } |
| + } else if (type != ui::ET_TOUCH_MOVED) { |
| + NOTREACHED(); |
| return ui::EVENT_REWRITE_CONTINUE; |
| } |
| - NOTREACHED() << "Unexpected event type received."; |
| + |
| + if (event.touch_id() == initial_press_->touch_id()) { |
| + if (!initial_touch_id_passthrough_mapping_) |
|
mfomitchev
2014/06/02 23:12:18
This would need to be changed to <= 0 if we use -1
dmazzoni
2014/06/03 07:46:51
Done.
|
| + return ui::EVENT_REWRITE_DISCARD; |
| + |
| + ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent( |
| + type, |
| + location, |
| + initial_touch_id_passthrough_mapping_, |
|
aboxhall
2014/06/02 16:33:14
I don't follow this either. It looks like we only
dmazzoni
2014/06/03 07:46:51
When you press two fingers, the first finger is ig
|
| + event.time_stamp()); |
| + rewritten_passthrough_event->set_flags(event.flags()); |
| + rewritten_event->reset(rewritten_passthrough_event); |
| + return ui::EVENT_REWRITE_REWRITTEN; |
| + } |
| + |
| 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_PENDING && state_ != GRACE_PERIOD) |
| + return; |
| + |
| + if (state_ == SINGLE_TAP_PENDING) { |
| + state_ = NO_FINGERS_DOWN; |
| + } 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( |