Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: ui/chromeos/touch_exploration_controller.cc

Issue 296403011: Support double-tap to click in touch accessibility controller. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address feedback Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/chromeos/touch_exploration_controller.h" 5 #include "ui/chromeos/touch_exploration_controller.h"
6 6
7 #include "base/logging.h" 7 #include "base/logging.h"
8 #include "ui/aura/client/cursor_client.h" 8 #include "ui/aura/client/cursor_client.h"
9 #include "ui/aura/window.h" 9 #include "ui/aura/window.h"
10 #include "ui/aura/window_event_dispatcher.h"
10 #include "ui/aura/window_tree_host.h" 11 #include "ui/aura/window_tree_host.h"
11 #include "ui/events/event.h" 12 #include "ui/events/event.h"
13 #include "ui/events/event_processor.h"
12 14
13 namespace ui { 15 namespace ui {
14 16
17 namespace {
18 // The default value for initial_touch_id_passthrough_mapping_ used
19 // when the user has not yet released any fingers yet, so there's no
20 // touch id remapping yet.
21 const int kTouchIdUnassigned = 0;
22
23 // The value for initial_touch_id_passthrough_mapping_ if the user has
24 // released the first finger but some other fingers are held down. In this
25 // state we don't do any touch id remapping, but we distinguish it from the
26 // kTouchIdUnassigned state because we don't want to assign
27 // initial_touch_id_passthrough_mapping_ a touch id anymore,
28 // until all fingers are released.
29 const int kTouchIdNone = -1;
30 };
James Cook 2014/06/06 00:09:45 nit: "} // namespace" and no ;
dmazzoni 2014/06/06 15:27:22 Done.
31
15 TouchExplorationController::TouchExplorationController( 32 TouchExplorationController::TouchExplorationController(
16 aura::Window* root_window) 33 aura::Window* root_window)
17 : root_window_(root_window) { 34 : root_window_(root_window),
35 initial_touch_id_passthrough_mapping_(kTouchIdUnassigned),
36 state_(NO_FINGERS_DOWN),
37 event_handler_for_testing_(NULL) {
18 CHECK(root_window); 38 CHECK(root_window);
19 root_window->GetHost()->GetEventSource()->AddEventRewriter(this); 39 root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
20 } 40 }
21 41
22 TouchExplorationController::~TouchExplorationController() { 42 TouchExplorationController::~TouchExplorationController() {
23 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); 43 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
24 } 44 }
25 45
46 void TouchExplorationController::CallTapTimerNowForTesting() {
47 DCHECK(tap_timer_.IsRunning());
48 tap_timer_.Stop();
49 OnTapTimerFired();
50 }
51
52 void TouchExplorationController::SetEventHandlerForTesting(
53 ui::EventHandler* event_handler_for_testing) {
54 event_handler_for_testing_ = event_handler_for_testing;
55 }
56
57 bool TouchExplorationController::IsInNoFingersDownStateForTesting() const {
58 return state_ == NO_FINGERS_DOWN;
59 }
60
26 ui::EventRewriteStatus TouchExplorationController::RewriteEvent( 61 ui::EventRewriteStatus TouchExplorationController::RewriteEvent(
27 const ui::Event& event, 62 const ui::Event& event,
28 scoped_ptr<ui::Event>* rewritten_event) { 63 scoped_ptr<ui::Event>* rewritten_event) {
29 if (!event.IsTouchEvent()) 64 if (!event.IsTouchEvent())
30 return ui::EVENT_REWRITE_CONTINUE; 65 return ui::EVENT_REWRITE_CONTINUE;
31
32 const ui::TouchEvent& touch_event = 66 const ui::TouchEvent& touch_event =
33 static_cast<const ui::TouchEvent&>(event); 67 static_cast<const ui::TouchEvent&>(event);
68
69 // If the tap timer should have fired by now but hasn't, run it now and
70 // stop the timer. This is important so that behavior is consistent with
71 // the timestamps of the events, and not dependent on the granularity of
72 // the timer.
73 if (tap_timer_.IsRunning() &&
74 touch_event.time_stamp() - initial_press_->time_stamp() >
75 gesture_detector_config_.double_tap_timeout) {
76 tap_timer_.Stop();
77 OnTapTimerFired();
78 // Note: this may change the state. We should now continue and process
79 // this event under this new state.
80 }
81
34 const ui::EventType type = touch_event.type(); 82 const ui::EventType type = touch_event.type();
35 const gfx::PointF& location = touch_event.location_f(); 83 const gfx::PointF& location = touch_event.location_f();
36 const int touch_id = touch_event.touch_id(); 84 const int touch_id = touch_event.touch_id();
37 const int flags = touch_event.flags(); 85
38 86 // Always update touch ids and touch locations, so we can use those
39 if (type == ui::ET_TOUCH_PRESSED) { 87 // no matter what state we're in.
40 touch_ids_.push_back(touch_id); 88 if (type == ui::ET_TOUCH_PRESSED) {
89 current_touch_ids_.push_back(touch_id);
41 touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location)); 90 touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location));
42 // If this is the first and only finger touching - rewrite the touch as a 91 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
43 // mouse move. Otherwise let the it go through as is. 92 std::vector<int>::iterator it = std::find(
44 if (touch_ids_.size() == 1) { 93 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
45 *rewritten_event = CreateMouseMoveEvent(location, flags); 94
95 // Can happen if touch exploration is enabled while fingers were down.
96 if (it == current_touch_ids_.end())
97 return ui::EVENT_REWRITE_CONTINUE;
98
99 current_touch_ids_.erase(it);
100 touch_locations_.erase(touch_id);
101 } else if (type == ui::ET_TOUCH_MOVED) {
102 std::vector<int>::iterator it = std::find(
103 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id);
104
105 // Can happen if touch exploration is enabled while fingers were down.
106 if (it == current_touch_ids_.end())
107 return ui::EVENT_REWRITE_CONTINUE;
108
109 touch_locations_[*it] = location;
110 }
111
112 // The rest of the processing depends on what state we're in.
113 switch(state_) {
114 case NO_FINGERS_DOWN:
115 return InNoFingersDown(touch_event, rewritten_event);
116 case SINGLE_TAP_PRESSED:
117 return InSingleTapPressed(touch_event, rewritten_event);
118 case SINGLE_TAP_RELEASED:
119 return InSingleTapReleased(touch_event, rewritten_event);
120 case DOUBLE_TAP_PRESSED:
121 return InDoubleTapPressed(touch_event, rewritten_event);
122 case TOUCH_EXPLORATION:
123 return InTouchExploration(touch_event, rewritten_event);
124 case PASSTHROUGH_MINUS_ONE:
125 return InPassthroughMinusOne(touch_event, rewritten_event);
126 }
127
128 NOTREACHED();
129 return ui::EVENT_REWRITE_CONTINUE;
130 }
131
132 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent(
133 const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) {
134 NOTREACHED();
135 return ui::EVENT_REWRITE_CONTINUE;
136 }
137
138 ui::EventRewriteStatus TouchExplorationController::InNoFingersDown(
139 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
140 const ui::EventType type = event.type();
141 if (type == ui::ET_TOUCH_PRESSED) {
142 initial_press_.reset(new TouchEvent(event));
143 tap_timer_.Start(FROM_HERE,
144 gesture_detector_config_.double_tap_timeout,
145 this,
146 &TouchExplorationController::OnTapTimerFired);
147 state_ = SINGLE_TAP_PRESSED;
148 return ui::EVENT_REWRITE_DISCARD;
149 }
150
151 NOTREACHED();
152 return ui::EVENT_REWRITE_CONTINUE;
153 }
154
155 ui::EventRewriteStatus TouchExplorationController::InSingleTapPressed(
156 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
157 const ui::EventType type = event.type();
158
159 if (type == ui::ET_TOUCH_PRESSED) {
160 // Adding a second finger within the timeout period switches to
161 // passthrough.
162 state_ = PASSTHROUGH_MINUS_ONE;
163 return InPassthroughMinusOne(event, rewritten_event);
164 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
165 DCHECK_EQ(0U, current_touch_ids_.size());
166 state_ = SINGLE_TAP_RELEASED;
167 return EVENT_REWRITE_DISCARD;
168 } else if (type == ui::ET_TOUCH_MOVED) {
169 // If the user moves far enough from the initial touch location (outside
170 // the "slop" region, jump to the touch exploration mode early.
171 // TODO(evy, lisayin): Add gesture recognition here instead -
172 // we should probably jump to gesture mode here if the velocity is
173 // high enough, and touch exploration if the velocity is lower.
174 float delta = (event.location() - initial_press_->location()).Length();
175 if (delta > gesture_detector_config_.touch_slop) {
46 EnterTouchToMouseMode(); 176 EnterTouchToMouseMode();
47 return ui::EVENT_REWRITE_REWRITTEN; 177 state_ = TOUCH_EXPLORATION;
178 return InTouchExploration(event, rewritten_event);
48 } 179 }
49 return ui::EVENT_REWRITE_CONTINUE; 180
50 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { 181 return EVENT_REWRITE_DISCARD;
51 std::vector<int>::iterator it = 182 }
52 std::find(touch_ids_.begin(), touch_ids_.end(), touch_id); 183 NOTREACHED() << "Unexpected event type received.";
53 // We may fail to find the finger if the exploration mode was turned on 184 return ui::EVENT_REWRITE_CONTINUE;
54 // while the user had some fingers touching the screen. We simply ignore 185 }
55 // those fingers for the purposes of event transformation. 186
56 if (it == touch_ids_.end()) 187 ui::EventRewriteStatus TouchExplorationController::InSingleTapReleased(
57 return ui::EVENT_REWRITE_CONTINUE; 188 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
58 const bool first_finger_released = it == touch_ids_.begin(); 189 const ui::EventType type = event.type();
59 touch_ids_.erase(it); 190 if (type == ui::ET_TOUCH_PRESSED) {
60 int num_erased = touch_locations_.erase(touch_id); 191 // This is the second tap in a double-tap (or double tap-hold).
61 DCHECK_EQ(num_erased, 1); 192 // Rewrite at location of last touch exploration.
62 const int num_fingers_remaining = touch_ids_.size(); 193 ui::TouchEvent* rewritten_press_event = new ui::TouchEvent(
63 194 ui::ET_TOUCH_PRESSED,
64 if (num_fingers_remaining == 0) { 195 last_touch_exploration_location_,
65 *rewritten_event = CreateMouseMoveEvent(location, flags); 196 event.touch_id(),
66 return ui::EVENT_REWRITE_REWRITTEN; 197 event.time_stamp());
67 } 198 rewritten_press_event->set_flags(event.flags());
68 199 rewritten_event->reset(rewritten_press_event);
69 // If we are left with one finger - enter the mouse move mode. 200 state_ = DOUBLE_TAP_PRESSED;
70 const bool enter_mouse_move_mode = num_fingers_remaining == 1; 201 return ui::EVENT_REWRITE_REWRITTEN;
71 202 }
72 if (!enter_mouse_move_mode && !first_finger_released) { 203
73 // No special handling needed. 204 NOTREACHED();
74 return ui::EVENT_REWRITE_CONTINUE; 205 return ui::EVENT_REWRITE_CONTINUE;
75 } 206 }
76 207
77 // If the finger which was released was the first one, - we need to rewrite 208 ui::EventRewriteStatus TouchExplorationController::InDoubleTapPressed(
78 // the release event as a release of the was second / now first finger. 209 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
79 // This is the finger which will now be getting "substracted". 210 const ui::EventType type = event.type();
80 if (first_finger_released) { 211 if (type == ui::ET_TOUCH_PRESSED) {
81 int rewritten_release_id = touch_ids_[0]; 212 return ui::EVENT_REWRITE_DISCARD;
82 const gfx::PointF& rewritten_release_location = 213 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
83 touch_locations_[rewritten_release_id]; 214 if (current_touch_ids_.size() != 0)
84 ui::TouchEvent* rewritten_release_event = new ui::TouchEvent( 215 return EVENT_REWRITE_DISCARD;
85 ui::ET_TOUCH_RELEASED, 216
86 rewritten_release_location, 217 // Rewrite at location of last touch exploration.
87 rewritten_release_id, 218 ui::TouchEvent* rewritten_release_event = new ui::TouchEvent(
88 event.time_stamp()); 219 ui::ET_TOUCH_RELEASED,
89 rewritten_release_event->set_flags(touch_event.flags()); 220 last_touch_exploration_location_,
90 rewritten_event->reset(rewritten_release_event); 221 event.touch_id(),
91 } else if (enter_mouse_move_mode) { 222 event.time_stamp());
92 // Dispatch the release event as is. 223 rewritten_release_event->set_flags(event.flags());
93 // TODO(mfomitchev): We can get rid of this clause once we have 224 rewritten_event->reset(rewritten_release_event);
94 // EVENT_REWRITE_DISPATCH_ANOTHER working without having to set 225 ResetToNoFingersDown();
95 // rewritten_event.
96 rewritten_event->reset(new ui::TouchEvent(touch_event));
97 }
98
99 if (enter_mouse_move_mode) {
100 // Since we are entering the mouse move mode - also dispatch a mouse move
101 // event at the location of the one remaining finger. (num_fingers == 1)
102 const gfx::PointF& mouse_move_location = touch_locations_[touch_ids_[0]];
103 next_dispatch_event_ =
104 CreateMouseMoveEvent(mouse_move_location, flags).Pass();
105 return ui::EVENT_REWRITE_DISPATCH_ANOTHER;
106 }
107 return ui::EVENT_REWRITE_REWRITTEN; 226 return ui::EVENT_REWRITE_REWRITTEN;
108 } else if (type == ui::ET_TOUCH_MOVED) { 227 } else if (type == ui::ET_TOUCH_MOVED) {
109 std::vector<int>::iterator it = 228 return ui::EVENT_REWRITE_DISCARD;
110 std::find(touch_ids_.begin(), touch_ids_.end(), touch_id); 229 }
111 // We may fail to find the finger if the exploration mode was turned on 230 NOTREACHED() << "Unexpected event type received.";
112 // while the user had some fingers touching the screen. We simply ignore 231 return ui::EVENT_REWRITE_CONTINUE;
113 // those fingers for the purposes of event transformation. 232 }
114 if (it == touch_ids_.end()) 233
115 return ui::EVENT_REWRITE_CONTINUE; 234 ui::EventRewriteStatus TouchExplorationController::InTouchExploration(
116 touch_locations_[*it] = location; 235 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
117 if (touch_ids_.size() == 1) { 236 const ui::EventType type = event.type();
118 // Touch moves are rewritten as mouse moves when there's only one finger 237 if (type == ui::ET_TOUCH_PRESSED) {
119 // touching the screen. 238 // Ignore any additional fingers when we're already in touch exploration
120 *rewritten_event = CreateMouseMoveEvent(location, flags).Pass(); 239 // mode. TODO(evy, lisayin): Support "split-tap" here instead.
121 return ui::EVENT_REWRITE_REWRITTEN; 240 return ui::EVENT_REWRITE_DISCARD;
241 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
242 if (current_touch_ids_.size() == 0)
243 ResetToNoFingersDown();
244 } else if (type != ui::ET_TOUCH_MOVED) {
245 NOTREACHED() << "Unexpected event type received.";
246 return ui::EVENT_REWRITE_CONTINUE;
247 }
248
249 // Rewrite as a mouse-move event.
250 *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags());
251 last_touch_exploration_location_ = event.location();
252 return ui::EVENT_REWRITE_REWRITTEN;
253 }
254
255 ui::EventRewriteStatus TouchExplorationController::InPassthroughMinusOne(
256 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) {
257 ui::EventType type = event.type();
258 gfx::PointF location = event.location_f();
259
260 if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) {
261 if (current_touch_ids_.size() == 0)
262 ResetToNoFingersDown();
263
264 if (initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
265 if (event.touch_id() == initial_press_->touch_id()) {
266 initial_touch_id_passthrough_mapping_ = kTouchIdNone;
267 } else {
268 // If the only finger now remaining is the first finger,
269 // rewrite as a move to the location of the first finger.
270 initial_touch_id_passthrough_mapping_ = event.touch_id();
271 ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent(
272 ui::ET_TOUCH_MOVED,
273 touch_locations_[initial_press_->touch_id()],
274 initial_touch_id_passthrough_mapping_,
275 event.time_stamp());
276 rewritten_passthrough_event->set_flags(event.flags());
277 rewritten_event->reset(rewritten_passthrough_event);
278 return ui::EVENT_REWRITE_REWRITTEN;
279 }
122 } 280 }
123 if (touch_id == touch_ids_.front()) { 281 }
124 // Touch moves of the first finger are discarded when there's more than 282
125 // one finger touching. 283 if (event.touch_id() == initial_press_->touch_id()) {
284 if (initial_touch_id_passthrough_mapping_ == kTouchIdNone ||
285 initial_touch_id_passthrough_mapping_ == kTouchIdUnassigned) {
126 return ui::EVENT_REWRITE_DISCARD; 286 return ui::EVENT_REWRITE_DISCARD;
127 } 287 }
128 return ui::EVENT_REWRITE_CONTINUE; 288
129 } 289 ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent(
130 NOTREACHED() << "Unexpected event type received."; 290 type,
131 return ui::EVENT_REWRITE_CONTINUE; 291 location,
132 } 292 initial_touch_id_passthrough_mapping_,
133 293 event.time_stamp());
134 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent( 294 rewritten_passthrough_event->set_flags(event.flags());
135 const ui::Event& last_event, 295 rewritten_event->reset(rewritten_passthrough_event);
136 scoped_ptr<ui::Event>* new_event) { 296 return ui::EVENT_REWRITE_REWRITTEN;
137 CHECK(next_dispatch_event_); 297 }
138 DCHECK(last_event.IsTouchEvent()); 298
139 *new_event = next_dispatch_event_.Pass(); 299 return ui::EVENT_REWRITE_CONTINUE;
140 // Enter the mouse move mode if needed 300 }
141 if ((*new_event)->IsMouseEvent()) 301
302 void TouchExplorationController::OnTapTimerFired() {
303 if (state_ != SINGLE_TAP_RELEASED && state_ != SINGLE_TAP_PRESSED)
304 return;
305
306 if (state_ == SINGLE_TAP_RELEASED) {
307 ResetToNoFingersDown();
308 } else {
142 EnterTouchToMouseMode(); 309 EnterTouchToMouseMode();
143 return ui::EVENT_REWRITE_REWRITTEN; 310 state_ = TOUCH_EXPLORATION;
311 }
312
313 scoped_ptr<ui::Event> mouse_move = CreateMouseMoveEvent(
314 initial_press_->location(), initial_press_->flags());
315 DispatchEvent(mouse_move.get());
316 last_touch_exploration_location_ = initial_press_->location();
317 }
318
319 void TouchExplorationController::DispatchEvent(ui::Event* event) {
320 if (event_handler_for_testing_) {
321 event_handler_for_testing_->OnEvent(event);
322 return;
323 }
324
325 ui::EventDispatchDetails result ALLOW_UNUSED =
326 root_window_->GetHost()->dispatcher()->OnEventFromSource(event);
144 } 327 }
145 328
146 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent( 329 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent(
147 const gfx::PointF& location, 330 const gfx::PointF& location,
148 int flags) { 331 int flags) {
149 return scoped_ptr<ui::Event>( 332 return scoped_ptr<ui::Event>(
150 new ui::MouseEvent( 333 new ui::MouseEvent(
151 ui::ET_MOUSE_MOVED, 334 ui::ET_MOUSE_MOVED,
152 location, 335 location,
153 location, 336 location,
154 flags | ui::EF_IS_SYNTHESIZED | ui::EF_TOUCH_ACCESSIBILITY, 337 flags | ui::EF_IS_SYNTHESIZED | ui::EF_TOUCH_ACCESSIBILITY,
155 0)); 338 0));
156 } 339 }
157 340
158 void TouchExplorationController::EnterTouchToMouseMode() { 341 void TouchExplorationController::EnterTouchToMouseMode() {
159 aura::client::CursorClient* cursor_client = 342 aura::client::CursorClient* cursor_client =
160 aura::client::GetCursorClient(root_window_); 343 aura::client::GetCursorClient(root_window_);
161 if (cursor_client && !cursor_client->IsMouseEventsEnabled()) 344 if (cursor_client && !cursor_client->IsMouseEventsEnabled())
162 cursor_client->EnableMouseEvents(); 345 cursor_client->EnableMouseEvents();
163 if (cursor_client && cursor_client->IsCursorVisible()) 346 if (cursor_client && cursor_client->IsCursorVisible())
164 cursor_client->HideCursor(); 347 cursor_client->HideCursor();
165 } 348 }
166 349
350 void TouchExplorationController::ResetToNoFingersDown() {
351 state_ = NO_FINGERS_DOWN;
352 initial_touch_id_passthrough_mapping_ = kTouchIdUnassigned;
353 if (tap_timer_.IsRunning())
354 tap_timer_.Stop();
355 }
356
167 } // namespace ui 357 } // namespace ui
OLDNEW
« no previous file with comments | « ui/chromeos/touch_exploration_controller.h ('k') | ui/chromeos/touch_exploration_controller_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698