OLD | NEW |
---|---|
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 |
15 TouchExplorationController::TouchExplorationController( | 17 TouchExplorationController::TouchExplorationController( |
16 aura::Window* root_window) | 18 aura::Window* root_window) |
17 : root_window_(root_window) { | 19 : root_window_(root_window), |
20 initial_touch_id_passthrough_mapping_(0), | |
21 state_(NO_FINGERS_DOWN), | |
22 event_handler_for_testing_(NULL) { | |
18 CHECK(root_window); | 23 CHECK(root_window); |
19 root_window->GetHost()->GetEventSource()->AddEventRewriter(this); | 24 root_window->GetHost()->GetEventSource()->AddEventRewriter(this); |
20 } | 25 } |
21 | 26 |
22 TouchExplorationController::~TouchExplorationController() { | 27 TouchExplorationController::~TouchExplorationController() { |
23 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); | 28 root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this); |
24 } | 29 } |
25 | 30 |
31 void TouchExplorationController::CallTapTimerNowForTesting() { | |
32 CHECK(tap_timer_.IsRunning()); | |
33 tap_timer_.Stop(); | |
34 OnTapTimerFired(); | |
35 } | |
36 | |
37 void TouchExplorationController::SetEventHandlerForTesting( | |
38 ui::EventHandler* event_handler_for_testing) { | |
39 event_handler_for_testing_ = event_handler_for_testing; | |
40 } | |
41 | |
26 ui::EventRewriteStatus TouchExplorationController::RewriteEvent( | 42 ui::EventRewriteStatus TouchExplorationController::RewriteEvent( |
27 const ui::Event& event, | 43 const ui::Event& event, |
28 scoped_ptr<ui::Event>* rewritten_event) { | 44 scoped_ptr<ui::Event>* rewritten_event) { |
29 if (!event.IsTouchEvent()) | 45 if (!event.IsTouchEvent()) |
30 return ui::EVENT_REWRITE_CONTINUE; | 46 return ui::EVENT_REWRITE_CONTINUE; |
31 | 47 |
32 const ui::TouchEvent& touch_event = | 48 const ui::TouchEvent& touch_event = |
33 static_cast<const ui::TouchEvent&>(event); | 49 static_cast<const ui::TouchEvent&>(event); |
34 const ui::EventType type = touch_event.type(); | 50 const ui::EventType type = touch_event.type(); |
35 const gfx::PointF& location = touch_event.location_f(); | 51 const gfx::PointF& location = touch_event.location_f(); |
36 const int touch_id = touch_event.touch_id(); | 52 const int touch_id = touch_event.touch_id(); |
37 const int flags = touch_event.flags(); | 53 |
38 | 54 // Always update touch ids and touch locations, so we can use those |
39 if (type == ui::ET_TOUCH_PRESSED) { | 55 // no matter what state we're in. |
40 touch_ids_.push_back(touch_id); | 56 if (type == ui::ET_TOUCH_PRESSED) { |
57 current_touch_ids_.push_back(touch_id); | |
41 touch_locations_.insert(std::pair<int, gfx::PointF>(touch_id, location)); | 58 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 | 59 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
43 // mouse move. Otherwise let the it go through as is. | 60 std::vector<int>::iterator it = std::find( |
44 if (touch_ids_.size() == 1) { | 61 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id); |
45 *rewritten_event = CreateMouseMoveEvent(location, flags); | 62 |
63 // Can happen if touch exploration is enabled while fingers were down. | |
64 if (it == current_touch_ids_.end()) | |
65 return ui::EVENT_REWRITE_CONTINUE; | |
66 | |
67 current_touch_ids_.erase(it); | |
68 touch_locations_.erase(touch_id); | |
69 } else if (type == ui::ET_TOUCH_MOVED) { | |
70 std::vector<int>::iterator it = std::find( | |
71 current_touch_ids_.begin(), current_touch_ids_.end(), touch_id); | |
72 | |
73 // Can happen if touch exploration is enabled while fingers were down. | |
74 if (it == current_touch_ids_.end()) | |
75 return ui::EVENT_REWRITE_CONTINUE; | |
76 | |
77 touch_locations_[*it] = location; | |
78 } | |
79 | |
80 // If the tap timer should have fired by now but hasn't, run it now and | |
mfomitchev
2014/06/04 20:39:30
Cool!
Nit: It might be worth moving this up to the
dmazzoni
2014/06/04 22:46:28
Done.
| |
81 // stop the timer. This is important so that behavior is consistent with | |
82 // the timestamps of the events, and not dependent on the granularity of | |
83 // the timer. | |
84 if (tap_timer_.IsRunning() && | |
85 touch_event.time_stamp() - initial_press_->time_stamp() > | |
86 gesture_detector_config_.double_tap_timeout) { | |
87 tap_timer_.Stop(); | |
88 OnTapTimerFired(); | |
89 // Note: this may change the state. We should now continue and process | |
90 // this event under this new state. | |
91 } | |
92 | |
93 // The rest of the processing depends on what state we're in. | |
94 switch(state_) { | |
95 case NO_FINGERS_DOWN: | |
96 return OnNoFingersDown(touch_event, rewritten_event); | |
97 case GRACE_PERIOD: | |
98 return OnGracePeriod(touch_event, rewritten_event); | |
99 case TOUCH_EXPLORATION: | |
100 return OnTouchExploration(touch_event, rewritten_event); | |
101 case SINGLE_TAP_PENDING: | |
102 return OnSingleTapPending(touch_event, rewritten_event); | |
103 case DOUBLE_TAP_PRESSED: | |
104 return OnDoubleTapPressed(touch_event, rewritten_event); | |
105 case PASSTHROUGH_MINUS_ONE: | |
106 return OnPassthroughMinusOne(touch_event, rewritten_event); | |
107 } | |
108 | |
109 NOTREACHED(); | |
110 return ui::EVENT_REWRITE_CONTINUE; | |
111 } | |
112 | |
113 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent( | |
114 const ui::Event& last_event, scoped_ptr<ui::Event>* new_event) { | |
115 NOTREACHED(); | |
116 return ui::EVENT_REWRITE_CONTINUE; | |
117 } | |
118 | |
119 ui::EventRewriteStatus TouchExplorationController::OnNoFingersDown( | |
120 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { | |
121 const ui::EventType type = event.type(); | |
122 if (type == ui::ET_TOUCH_PRESSED) { | |
123 initial_press_.reset(new TouchEvent(event)); | |
124 tap_timer_.Start(FROM_HERE, | |
125 gesture_detector_config_.double_tap_timeout, | |
126 this, | |
127 &TouchExplorationController::OnTapTimerFired); | |
128 state_ = GRACE_PERIOD; | |
129 return ui::EVENT_REWRITE_DISCARD; | |
130 } | |
131 | |
132 NOTREACHED(); | |
133 return ui::EVENT_REWRITE_CONTINUE; | |
134 } | |
135 | |
136 ui::EventRewriteStatus TouchExplorationController::OnGracePeriod( | |
137 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { | |
138 const ui::EventType type = event.type(); | |
139 | |
140 if (type == ui::ET_TOUCH_PRESSED) { | |
141 // Adding a second finger within the timeout period switches to | |
142 // passthrough. | |
143 state_ = PASSTHROUGH_MINUS_ONE; | |
144 return OnPassthroughMinusOne(event, rewritten_event); | |
145 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { | |
146 DCHECK_EQ(0U, current_touch_ids_.size()); | |
147 state_ = SINGLE_TAP_PENDING; | |
148 return EVENT_REWRITE_DISCARD; | |
149 } else if (type == ui::ET_TOUCH_MOVED) { | |
150 // If the user moves far enough from the initial touch location (outside | |
151 // the "slop" region, jump to the touch exploration mode early. | |
152 // TODO(evy, lisayin): Add gesture recognition here instead - | |
153 // we should probably jump to gesture mode here if the velocity is | |
154 // high enough, and touch exploration if the velocity is lower. | |
155 float delta = (event.location() - initial_press_->location()).Length(); | |
156 if (delta > gesture_detector_config_.touch_slop) { | |
46 EnterTouchToMouseMode(); | 157 EnterTouchToMouseMode(); |
47 return ui::EVENT_REWRITE_REWRITTEN; | 158 state_ = TOUCH_EXPLORATION; |
159 return OnTouchExploration(event, rewritten_event); | |
48 } | 160 } |
161 | |
162 return EVENT_REWRITE_DISCARD; | |
163 } | |
164 NOTREACHED() << "Unexpected event type received."; | |
165 return ui::EVENT_REWRITE_CONTINUE; | |
166 } | |
167 | |
168 ui::EventRewriteStatus TouchExplorationController::OnTouchExploration( | |
169 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { | |
170 const ui::EventType type = event.type(); | |
171 if (type == ui::ET_TOUCH_PRESSED) { | |
172 // Ignore any additional fingers when we're already in touch exploration | |
173 // mode. TODO(evy, lisayin): Support "split-tap" here instead. | |
174 return ui::EVENT_REWRITE_DISCARD; | |
175 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { | |
176 if (current_touch_ids_.size() == 0) | |
177 ResetToNoFingersDown(); | |
178 } else if (type != ui::ET_TOUCH_MOVED) { | |
179 NOTREACHED() << "Unexpected event type received."; | |
49 return ui::EVENT_REWRITE_CONTINUE; | 180 return ui::EVENT_REWRITE_CONTINUE; |
50 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { | 181 } |
51 std::vector<int>::iterator it = | 182 |
52 std::find(touch_ids_.begin(), touch_ids_.end(), touch_id); | 183 // Rewrite as a mouse-move event. |
53 // We may fail to find the finger if the exploration mode was turned on | 184 *rewritten_event = CreateMouseMoveEvent(event.location(), event.flags()); |
54 // while the user had some fingers touching the screen. We simply ignore | 185 last_touch_exploration_location_ = event.location(); |
55 // those fingers for the purposes of event transformation. | 186 return ui::EVENT_REWRITE_REWRITTEN; |
56 if (it == touch_ids_.end()) | 187 } |
57 return ui::EVENT_REWRITE_CONTINUE; | 188 |
58 const bool first_finger_released = it == touch_ids_.begin(); | 189 ui::EventRewriteStatus TouchExplorationController::OnSingleTapPending( |
59 touch_ids_.erase(it); | 190 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { |
60 int num_erased = touch_locations_.erase(touch_id); | 191 const ui::EventType type = event.type(); |
61 DCHECK_EQ(num_erased, 1); | 192 if (type == ui::ET_TOUCH_PRESSED) { |
62 const int num_fingers_remaining = touch_ids_.size(); | 193 // This is the second tap in a double-tap (or double tap-hold). |
63 | 194 // Rewrite at location of last touch exploration. |
64 if (num_fingers_remaining == 0) { | 195 ui::TouchEvent* rewritten_press_event = new ui::TouchEvent( |
65 *rewritten_event = CreateMouseMoveEvent(location, flags); | 196 ui::ET_TOUCH_PRESSED, |
66 return ui::EVENT_REWRITE_REWRITTEN; | 197 last_touch_exploration_location_, |
67 } | 198 event.touch_id(), |
68 | 199 event.time_stamp()); |
69 // If we are left with one finger - enter the mouse move mode. | 200 rewritten_press_event->set_flags(event.flags()); |
70 const bool enter_mouse_move_mode = num_fingers_remaining == 1; | 201 rewritten_event->reset(rewritten_press_event); |
71 | 202 state_ = DOUBLE_TAP_PRESSED; |
72 if (!enter_mouse_move_mode && !first_finger_released) { | 203 return ui::EVENT_REWRITE_REWRITTEN; |
73 // No special handling needed. | 204 } |
74 return ui::EVENT_REWRITE_CONTINUE; | 205 |
75 } | 206 NOTREACHED(); |
76 | 207 return ui::EVENT_REWRITE_CONTINUE; |
77 // If the finger which was released was the first one, - we need to rewrite | 208 } |
78 // the release event as a release of the was second / now first finger. | 209 |
79 // This is the finger which will now be getting "substracted". | 210 ui::EventRewriteStatus TouchExplorationController::OnDoubleTapPressed( |
80 if (first_finger_released) { | 211 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { |
81 int rewritten_release_id = touch_ids_[0]; | 212 const ui::EventType type = event.type(); |
82 const gfx::PointF& rewritten_release_location = | 213 if (type == ui::ET_TOUCH_PRESSED) { |
83 touch_locations_[rewritten_release_id]; | 214 return ui::EVENT_REWRITE_DISCARD; |
84 ui::TouchEvent* rewritten_release_event = new ui::TouchEvent( | 215 } else if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
85 ui::ET_TOUCH_RELEASED, | 216 if (current_touch_ids_.size() != 0) |
86 rewritten_release_location, | 217 return EVENT_REWRITE_DISCARD; |
87 rewritten_release_id, | 218 |
88 event.time_stamp()); | 219 // Rewrite at location of last touch exploration. |
89 rewritten_release_event->set_flags(touch_event.flags()); | 220 ui::TouchEvent* rewritten_release_event = new ui::TouchEvent( |
90 rewritten_event->reset(rewritten_release_event); | 221 ui::ET_TOUCH_RELEASED, |
91 } else if (enter_mouse_move_mode) { | 222 last_touch_exploration_location_, |
92 // Dispatch the release event as is. | 223 event.touch_id(), |
93 // TODO(mfomitchev): We can get rid of this clause once we have | 224 event.time_stamp()); |
94 // EVENT_REWRITE_DISPATCH_ANOTHER working without having to set | 225 rewritten_release_event->set_flags(event.flags()); |
95 // rewritten_event. | 226 rewritten_event->reset(rewritten_release_event); |
96 rewritten_event->reset(new ui::TouchEvent(touch_event)); | 227 ResetToNoFingersDown(); |
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; | 228 return ui::EVENT_REWRITE_REWRITTEN; |
108 } else if (type == ui::ET_TOUCH_MOVED) { | 229 } else if (type == ui::ET_TOUCH_MOVED) { |
109 std::vector<int>::iterator it = | 230 return ui::EVENT_REWRITE_DISCARD; |
110 std::find(touch_ids_.begin(), touch_ids_.end(), touch_id); | 231 } |
111 // We may fail to find the finger if the exploration mode was turned on | 232 NOTREACHED() << "Unexpected event type received."; |
112 // while the user had some fingers touching the screen. We simply ignore | 233 return ui::EVENT_REWRITE_CONTINUE; |
113 // those fingers for the purposes of event transformation. | 234 } |
114 if (it == touch_ids_.end()) | 235 |
115 return ui::EVENT_REWRITE_CONTINUE; | 236 ui::EventRewriteStatus TouchExplorationController::OnPassthroughMinusOne( |
116 touch_locations_[*it] = location; | 237 const ui::TouchEvent& event, scoped_ptr<ui::Event>* rewritten_event) { |
117 if (touch_ids_.size() == 1) { | 238 ui::EventType type = event.type(); |
118 // Touch moves are rewritten as mouse moves when there's only one finger | 239 gfx::PointF location = event.location_f(); |
119 // touching the screen. | 240 |
120 *rewritten_event = CreateMouseMoveEvent(location, flags).Pass(); | 241 if (type == ui::ET_TOUCH_RELEASED || type == ui::ET_TOUCH_CANCELLED) { |
121 return ui::EVENT_REWRITE_REWRITTEN; | 242 if (current_touch_ids_.size() == 0) |
243 ResetToNoFingersDown(); | |
244 | |
245 if (initial_touch_id_passthrough_mapping_ == 0) { | |
246 if (event.touch_id() == initial_press_->touch_id()) { | |
247 initial_touch_id_passthrough_mapping_ = -1; | |
248 } else { | |
249 // If the only finger now remaining is the first finger, | |
250 // rewrite as a move to the location of the first finger. | |
251 initial_touch_id_passthrough_mapping_ = event.touch_id(); | |
252 ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent( | |
253 ui::ET_TOUCH_MOVED, | |
254 touch_locations_[initial_press_->touch_id()], | |
255 initial_touch_id_passthrough_mapping_, | |
256 event.time_stamp()); | |
257 rewritten_passthrough_event->set_flags(event.flags()); | |
258 rewritten_event->reset(rewritten_passthrough_event); | |
259 return ui::EVENT_REWRITE_REWRITTEN; | |
260 } | |
122 } | 261 } |
123 if (touch_id == touch_ids_.front()) { | 262 } |
124 // Touch moves of the first finger are discarded when there's more than | 263 |
125 // one finger touching. | 264 if (event.touch_id() == initial_press_->touch_id()) { |
265 if (initial_touch_id_passthrough_mapping_ <= 0) | |
126 return ui::EVENT_REWRITE_DISCARD; | 266 return ui::EVENT_REWRITE_DISCARD; |
127 } | 267 |
128 return ui::EVENT_REWRITE_CONTINUE; | 268 ui::TouchEvent* rewritten_passthrough_event = new ui::TouchEvent( |
129 } | 269 type, |
130 NOTREACHED() << "Unexpected event type received."; | 270 location, |
131 return ui::EVENT_REWRITE_CONTINUE; | 271 initial_touch_id_passthrough_mapping_, |
132 } | 272 event.time_stamp()); |
133 | 273 rewritten_passthrough_event->set_flags(event.flags()); |
134 ui::EventRewriteStatus TouchExplorationController::NextDispatchEvent( | 274 rewritten_event->reset(rewritten_passthrough_event); |
135 const ui::Event& last_event, | 275 return ui::EVENT_REWRITE_REWRITTEN; |
136 scoped_ptr<ui::Event>* new_event) { | 276 } |
137 CHECK(next_dispatch_event_); | 277 |
138 DCHECK(last_event.IsTouchEvent()); | 278 return ui::EVENT_REWRITE_CONTINUE; |
139 *new_event = next_dispatch_event_.Pass(); | 279 } |
140 // Enter the mouse move mode if needed | 280 |
141 if ((*new_event)->IsMouseEvent()) | 281 void TouchExplorationController::OnTapTimerFired() { |
282 if (state_ != SINGLE_TAP_PENDING && state_ != GRACE_PERIOD) | |
283 return; | |
284 | |
285 if (state_ == SINGLE_TAP_PENDING) { | |
286 ResetToNoFingersDown(); | |
287 } else { | |
142 EnterTouchToMouseMode(); | 288 EnterTouchToMouseMode(); |
143 return ui::EVENT_REWRITE_REWRITTEN; | 289 state_ = TOUCH_EXPLORATION; |
290 } | |
291 | |
292 scoped_ptr<ui::Event> mouse_move = CreateMouseMoveEvent( | |
293 initial_press_->location(), initial_press_->flags()); | |
294 DispatchEvent(mouse_move.get()); | |
295 last_touch_exploration_location_ = initial_press_->location(); | |
296 } | |
297 | |
298 void TouchExplorationController::DispatchEvent(ui::Event* event) { | |
299 if (event_handler_for_testing_) { | |
300 event_handler_for_testing_->OnEvent(event); | |
301 return; | |
302 } | |
303 | |
304 ui::EventDispatchDetails result ALLOW_UNUSED = | |
305 root_window_->GetHost()->dispatcher()->OnEventFromSource(event); | |
144 } | 306 } |
145 | 307 |
146 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent( | 308 scoped_ptr<ui::Event> TouchExplorationController::CreateMouseMoveEvent( |
147 const gfx::PointF& location, | 309 const gfx::PointF& location, |
148 int flags) { | 310 int flags) { |
149 return scoped_ptr<ui::Event>( | 311 return scoped_ptr<ui::Event>( |
150 new ui::MouseEvent( | 312 new ui::MouseEvent( |
151 ui::ET_MOUSE_MOVED, | 313 ui::ET_MOUSE_MOVED, |
152 location, | 314 location, |
153 location, | 315 location, |
154 flags | ui::EF_IS_SYNTHESIZED | ui::EF_TOUCH_ACCESSIBILITY, | 316 flags | ui::EF_IS_SYNTHESIZED | ui::EF_TOUCH_ACCESSIBILITY, |
155 0)); | 317 0)); |
156 } | 318 } |
157 | 319 |
158 void TouchExplorationController::EnterTouchToMouseMode() { | 320 void TouchExplorationController::EnterTouchToMouseMode() { |
159 aura::client::CursorClient* cursor_client = | 321 aura::client::CursorClient* cursor_client = |
160 aura::client::GetCursorClient(root_window_); | 322 aura::client::GetCursorClient(root_window_); |
161 if (cursor_client && !cursor_client->IsMouseEventsEnabled()) | 323 if (cursor_client && !cursor_client->IsMouseEventsEnabled()) |
162 cursor_client->EnableMouseEvents(); | 324 cursor_client->EnableMouseEvents(); |
163 if (cursor_client && cursor_client->IsCursorVisible()) | 325 if (cursor_client && cursor_client->IsCursorVisible()) |
164 cursor_client->HideCursor(); | 326 cursor_client->HideCursor(); |
165 } | 327 } |
166 | 328 |
329 void TouchExplorationController::ResetToNoFingersDown() { | |
330 state_ = NO_FINGERS_DOWN; | |
331 initial_touch_id_passthrough_mapping_ = 0; | |
332 if (tap_timer_.IsRunning()) | |
333 tap_timer_.Stop(); | |
334 } | |
335 | |
167 } // namespace ui | 336 } // namespace ui |
OLD | NEW |