OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "components/mus/ws/event_dispatcher.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/time/time.h" | |
10 #include "components/mus/ws/accelerator.h" | |
11 #include "components/mus/ws/display.h" | |
12 #include "components/mus/ws/event_dispatcher_delegate.h" | |
13 #include "components/mus/ws/server_window.h" | |
14 #include "components/mus/ws/server_window_delegate.h" | |
15 #include "components/mus/ws/window_coordinate_conversions.h" | |
16 #include "components/mus/ws/window_finder.h" | |
17 #include "ui/events/event_utils.h" | |
18 #include "ui/gfx/geometry/point.h" | |
19 #include "ui/gfx/geometry/point_conversions.h" | |
20 | |
21 namespace mus { | |
22 namespace ws { | |
23 | |
24 using Entry = std::pair<uint32_t, std::unique_ptr<Accelerator>>; | |
25 | |
26 namespace { | |
27 | |
28 bool IsOnlyOneMouseButtonDown(int flags) { | |
29 const uint32_t button_only_flags = | |
30 flags & (ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON | | |
31 ui::EF_RIGHT_MOUSE_BUTTON); | |
32 return button_only_flags == ui::EF_LEFT_MOUSE_BUTTON || | |
33 button_only_flags == ui::EF_MIDDLE_MOUSE_BUTTON || | |
34 button_only_flags == ui::EF_RIGHT_MOUSE_BUTTON; | |
35 } | |
36 | |
37 bool IsLocationInNonclientArea(const ServerWindow* target, | |
38 const gfx::Point& location) { | |
39 if (!target->parent()) | |
40 return false; | |
41 | |
42 gfx::Rect client_area(target->bounds().size()); | |
43 client_area.Inset(target->client_area()); | |
44 if (client_area.Contains(location)) | |
45 return false; | |
46 | |
47 for (const auto& rect : target->additional_client_areas()) { | |
48 if (rect.Contains(location)) | |
49 return false; | |
50 } | |
51 | |
52 return true; | |
53 } | |
54 | |
55 uint32_t PointerId(const ui::LocatedEvent& event) { | |
56 if (event.IsPointerEvent()) | |
57 return event.AsPointerEvent()->pointer_id(); | |
58 if (event.IsMouseWheelEvent()) | |
59 return ui::PointerEvent::kMousePointerId; | |
60 | |
61 NOTREACHED(); | |
62 return 0; | |
63 } | |
64 | |
65 } // namespace | |
66 | |
67 //////////////////////////////////////////////////////////////////////////////// | |
68 | |
69 EventDispatcher::EventDispatcher(EventDispatcherDelegate* delegate) | |
70 : delegate_(delegate), | |
71 capture_window_(nullptr), | |
72 capture_window_client_id_(kInvalidClientId), | |
73 modal_window_controller_(this), | |
74 mouse_button_down_(false), | |
75 mouse_cursor_source_window_(nullptr), | |
76 mouse_cursor_in_non_client_area_(false) {} | |
77 | |
78 EventDispatcher::~EventDispatcher() { | |
79 if (capture_window_) { | |
80 UnobserveWindow(capture_window_); | |
81 capture_window_ = nullptr; | |
82 capture_window_client_id_ = kInvalidClientId; | |
83 } | |
84 for (const auto& pair : pointer_targets_) { | |
85 if (pair.second.window) | |
86 UnobserveWindow(pair.second.window); | |
87 } | |
88 pointer_targets_.clear(); | |
89 } | |
90 | |
91 void EventDispatcher::Reset() { | |
92 if (capture_window_) { | |
93 CancelPointerEventsToTarget(capture_window_); | |
94 DCHECK(capture_window_ == nullptr); | |
95 } | |
96 | |
97 while (!pointer_targets_.empty()) | |
98 StopTrackingPointer(pointer_targets_.begin()->first); | |
99 | |
100 mouse_button_down_ = false; | |
101 } | |
102 | |
103 void EventDispatcher::SetMousePointerScreenLocation( | |
104 const gfx::Point& screen_location) { | |
105 DCHECK(pointer_targets_.empty()); | |
106 mouse_pointer_last_location_ = screen_location; | |
107 UpdateCursorProviderByLastKnownLocation(); | |
108 // Write our initial location back to our shared screen coordinate. This | |
109 // shouldn't cause problems because we already read the cursor before we | |
110 // process any events in views during window construction. | |
111 delegate_->OnMouseCursorLocationChanged(screen_location); | |
112 } | |
113 | |
114 bool EventDispatcher::GetCurrentMouseCursor(int32_t* cursor_out) { | |
115 if (!mouse_cursor_source_window_) | |
116 return false; | |
117 | |
118 *cursor_out = mouse_cursor_in_non_client_area_ | |
119 ? mouse_cursor_source_window_->non_client_cursor() | |
120 : mouse_cursor_source_window_->cursor(); | |
121 return true; | |
122 } | |
123 | |
124 bool EventDispatcher::SetCaptureWindow(ServerWindow* window, | |
125 ClientSpecificId client_id) { | |
126 if (!window) | |
127 client_id = kInvalidClientId; | |
128 | |
129 if (window == capture_window_ && client_id == capture_window_client_id_) | |
130 return true; | |
131 | |
132 // A window that is blocked by a modal window cannot gain capture. | |
133 if (window && modal_window_controller_.IsWindowBlocked(window)) | |
134 return false; | |
135 | |
136 if (capture_window_) { | |
137 // Stop observing old capture window. |pointer_targets_| are cleared on | |
138 // initial setting of a capture window. | |
139 delegate_->OnServerWindowCaptureLost(capture_window_); | |
140 UnobserveWindow(capture_window_); | |
141 } else { | |
142 // Cancel implicit capture to all other windows. | |
143 for (const auto& pair : pointer_targets_) { | |
144 ServerWindow* target = pair.second.window; | |
145 if (!target) | |
146 continue; | |
147 UnobserveWindow(target); | |
148 if (target == window) | |
149 continue; | |
150 | |
151 ui::EventType event_type = pair.second.is_mouse_event | |
152 ? ui::ET_POINTER_EXITED | |
153 : ui::ET_POINTER_CANCELLED; | |
154 ui::EventPointerType pointer_type = | |
155 pair.second.is_mouse_event ? ui::EventPointerType::POINTER_TYPE_MOUSE | |
156 : ui::EventPointerType::POINTER_TYPE_TOUCH; | |
157 // TODO(jonross): Track previous location in PointerTarget for sending | |
158 // cancels. | |
159 ui::PointerEvent event( | |
160 event_type, gfx::Point(), gfx::Point(), ui::EF_NONE, pair.first, | |
161 ui::PointerDetails(pointer_type), ui::EventTimeForNow()); | |
162 DispatchToPointerTarget(pair.second, event); | |
163 } | |
164 pointer_targets_.clear(); | |
165 } | |
166 | |
167 // Set the capture before changing native capture; otherwise, the callback | |
168 // from native platform might try to set the capture again. | |
169 bool had_capture_window = capture_window_ != nullptr; | |
170 capture_window_ = window; | |
171 capture_window_client_id_ = client_id; | |
172 | |
173 // Begin tracking the capture window if it is not yet being observed. | |
174 if (window) { | |
175 ObserveWindow(window); | |
176 // TODO(sky): this conditional is problematic for the case of capture moving | |
177 // to a different display. | |
178 if (!had_capture_window) | |
179 delegate_->SetNativeCapture(window); | |
180 } else { | |
181 delegate_->ReleaseNativeCapture(); | |
182 if (!mouse_button_down_) | |
183 UpdateCursorProviderByLastKnownLocation(); | |
184 } | |
185 | |
186 return true; | |
187 } | |
188 | |
189 void EventDispatcher::AddSystemModalWindow(ServerWindow* window) { | |
190 modal_window_controller_.AddSystemModalWindow(window); | |
191 } | |
192 | |
193 void EventDispatcher::ReleaseCaptureBlockedByModalWindow( | |
194 const ServerWindow* modal_window) { | |
195 if (!capture_window_) | |
196 return; | |
197 | |
198 if (modal_window_controller_.IsWindowBlockedBy(capture_window_, | |
199 modal_window)) { | |
200 SetCaptureWindow(nullptr, kInvalidClientId); | |
201 } | |
202 } | |
203 | |
204 void EventDispatcher::ReleaseCaptureBlockedByAnyModalWindow() { | |
205 if (!capture_window_) | |
206 return; | |
207 | |
208 if (modal_window_controller_.IsWindowBlocked(capture_window_)) | |
209 SetCaptureWindow(nullptr, kInvalidClientId); | |
210 } | |
211 | |
212 void EventDispatcher::UpdateNonClientAreaForCurrentWindow() { | |
213 if (mouse_cursor_source_window_) { | |
214 gfx::Point location = mouse_pointer_last_location_; | |
215 ServerWindow* target = FindDeepestVisibleWindowForEvents(&location); | |
216 if (target == mouse_cursor_source_window_) { | |
217 mouse_cursor_in_non_client_area_ = | |
218 mouse_cursor_source_window_ | |
219 ? IsLocationInNonclientArea(mouse_cursor_source_window_, location) | |
220 : false; | |
221 } | |
222 } | |
223 } | |
224 | |
225 void EventDispatcher::UpdateCursorProviderByLastKnownLocation() { | |
226 if (!mouse_button_down_) { | |
227 gfx::Point location = mouse_pointer_last_location_; | |
228 mouse_cursor_source_window_ = FindDeepestVisibleWindowForEvents(&location); | |
229 | |
230 mouse_cursor_in_non_client_area_ = | |
231 mouse_cursor_source_window_ | |
232 ? IsLocationInNonclientArea(mouse_cursor_source_window_, location) | |
233 : false; | |
234 } | |
235 } | |
236 | |
237 bool EventDispatcher::AddAccelerator(uint32_t id, | |
238 mojom::EventMatcherPtr event_matcher) { | |
239 std::unique_ptr<Accelerator> accelerator(new Accelerator(id, *event_matcher)); | |
240 // If an accelerator with the same id or matcher already exists, then abort. | |
241 for (const auto& pair : accelerators_) { | |
242 if (pair.first == id || accelerator->EqualEventMatcher(pair.second.get())) | |
243 return false; | |
244 } | |
245 accelerators_.insert(Entry(id, std::move(accelerator))); | |
246 return true; | |
247 } | |
248 | |
249 void EventDispatcher::RemoveAccelerator(uint32_t id) { | |
250 auto it = accelerators_.find(id); | |
251 // Clients may pass bogus ids. | |
252 if (it != accelerators_.end()) | |
253 accelerators_.erase(it); | |
254 } | |
255 | |
256 void EventDispatcher::ProcessEvent(const ui::Event& event) { | |
257 if (event.IsKeyEvent()) { | |
258 const ui::KeyEvent* key_event = event.AsKeyEvent(); | |
259 if (event.type() == ui::ET_KEY_PRESSED && !key_event->is_char()) { | |
260 Accelerator* pre_target = | |
261 FindAccelerator(*key_event, ui::mojom::AcceleratorPhase::PRE_TARGET); | |
262 if (pre_target) { | |
263 delegate_->OnAccelerator(pre_target->id(), event); | |
264 return; | |
265 } | |
266 } | |
267 ProcessKeyEvent(*key_event); | |
268 return; | |
269 } | |
270 | |
271 if (event.IsPointerEvent() || event.IsMouseWheelEvent()) { | |
272 ProcessLocatedEvent(*event.AsLocatedEvent()); | |
273 return; | |
274 } | |
275 | |
276 NOTREACHED(); | |
277 } | |
278 | |
279 void EventDispatcher::ProcessKeyEvent(const ui::KeyEvent& event) { | |
280 Accelerator* post_target = | |
281 FindAccelerator(event, ui::mojom::AcceleratorPhase::POST_TARGET); | |
282 ServerWindow* focused_window = | |
283 delegate_->GetFocusedWindowForEventDispatcher(); | |
284 if (focused_window) { | |
285 // Assume key events are for the client area. | |
286 const bool in_nonclient_area = false; | |
287 const ClientSpecificId client_id = | |
288 delegate_->GetEventTargetClientId(focused_window, in_nonclient_area); | |
289 delegate_->DispatchInputEventToWindow(focused_window, client_id, event, | |
290 post_target); | |
291 return; | |
292 } | |
293 delegate_->OnEventTargetNotFound(event); | |
294 if (post_target) | |
295 delegate_->OnAccelerator(post_target->id(), event); | |
296 } | |
297 | |
298 void EventDispatcher::ProcessLocatedEvent(const ui::LocatedEvent& event) { | |
299 DCHECK(event.IsPointerEvent() || event.IsMouseWheelEvent()); | |
300 const bool is_mouse_event = | |
301 event.IsMousePointerEvent() || event.IsMouseWheelEvent(); | |
302 | |
303 if (is_mouse_event) { | |
304 mouse_pointer_last_location_ = event.location(); | |
305 delegate_->OnMouseCursorLocationChanged(event.root_location()); | |
306 } | |
307 | |
308 // Release capture on pointer up. For mouse we only release if there are | |
309 // no buttons down. | |
310 const bool is_pointer_going_up = | |
311 (event.type() == ui::ET_POINTER_UP || | |
312 event.type() == ui::ET_POINTER_CANCELLED) && | |
313 (!is_mouse_event || IsOnlyOneMouseButtonDown(event.flags())); | |
314 | |
315 // Update mouse down state upon events which change it. | |
316 if (is_mouse_event) { | |
317 if (event.type() == ui::ET_POINTER_DOWN) | |
318 mouse_button_down_ = true; | |
319 else if (is_pointer_going_up) | |
320 mouse_button_down_ = false; | |
321 } | |
322 | |
323 if (capture_window_) { | |
324 mouse_cursor_source_window_ = capture_window_; | |
325 DispatchToClient(capture_window_, capture_window_client_id_, event); | |
326 return; | |
327 } | |
328 | |
329 const int32_t pointer_id = PointerId(event); | |
330 if (!IsTrackingPointer(pointer_id) || | |
331 !pointer_targets_[pointer_id].is_pointer_down) { | |
332 const bool any_pointers_down = AreAnyPointersDown(); | |
333 UpdateTargetForPointer(pointer_id, event); | |
334 if (is_mouse_event) | |
335 mouse_cursor_source_window_ = pointer_targets_[pointer_id].window; | |
336 | |
337 PointerTarget& pointer_target = pointer_targets_[pointer_id]; | |
338 if (pointer_target.is_pointer_down) { | |
339 if (is_mouse_event) | |
340 mouse_cursor_source_window_ = pointer_target.window; | |
341 if (!any_pointers_down) { | |
342 delegate_->SetFocusedWindowFromEventDispatcher(pointer_target.window); | |
343 delegate_->SetNativeCapture(pointer_target.window); | |
344 } | |
345 } | |
346 } | |
347 | |
348 // When we release the mouse button, we want the cursor to be sourced from | |
349 // the window under the mouse pointer, even though we're sending the button | |
350 // up event to the window that had implicit capture. We have to set this | |
351 // before we perform dispatch because the Delegate is going to read this | |
352 // information from us. | |
353 if (is_pointer_going_up && is_mouse_event) | |
354 UpdateCursorProviderByLastKnownLocation(); | |
355 | |
356 DispatchToPointerTarget(pointer_targets_[pointer_id], event); | |
357 | |
358 if (is_pointer_going_up) { | |
359 if (is_mouse_event) | |
360 pointer_targets_[pointer_id].is_pointer_down = false; | |
361 else | |
362 StopTrackingPointer(pointer_id); | |
363 if (!AreAnyPointersDown()) | |
364 delegate_->ReleaseNativeCapture(); | |
365 } | |
366 } | |
367 | |
368 void EventDispatcher::StartTrackingPointer( | |
369 int32_t pointer_id, | |
370 const PointerTarget& pointer_target) { | |
371 DCHECK(!IsTrackingPointer(pointer_id)); | |
372 ObserveWindow(pointer_target.window); | |
373 pointer_targets_[pointer_id] = pointer_target; | |
374 } | |
375 | |
376 void EventDispatcher::StopTrackingPointer(int32_t pointer_id) { | |
377 DCHECK(IsTrackingPointer(pointer_id)); | |
378 ServerWindow* window = pointer_targets_[pointer_id].window; | |
379 pointer_targets_.erase(pointer_id); | |
380 if (window) | |
381 UnobserveWindow(window); | |
382 } | |
383 | |
384 void EventDispatcher::UpdateTargetForPointer(int32_t pointer_id, | |
385 const ui::LocatedEvent& event) { | |
386 if (!IsTrackingPointer(pointer_id)) { | |
387 StartTrackingPointer(pointer_id, PointerTargetForEvent(event)); | |
388 return; | |
389 } | |
390 | |
391 const PointerTarget pointer_target = PointerTargetForEvent(event); | |
392 if (pointer_target.window == pointer_targets_[pointer_id].window && | |
393 pointer_target.in_nonclient_area == | |
394 pointer_targets_[pointer_id].in_nonclient_area) { | |
395 // The targets are the same, only set the down state to true if necessary. | |
396 // Down going to up is handled by ProcessLocatedEvent(). | |
397 if (pointer_target.is_pointer_down) | |
398 pointer_targets_[pointer_id].is_pointer_down = true; | |
399 return; | |
400 } | |
401 | |
402 // The targets are changing. Send an exit if appropriate. | |
403 if (event.IsMousePointerEvent()) { | |
404 ui::PointerEvent exit_event( | |
405 ui::ET_POINTER_EXITED, event.location(), event.root_location(), | |
406 event.flags(), ui::PointerEvent::kMousePointerId, | |
407 ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE), | |
408 event.time_stamp()); | |
409 DispatchToPointerTarget(pointer_targets_[pointer_id], exit_event); | |
410 } | |
411 | |
412 // Technically we're updating in place, but calling start then stop makes for | |
413 // simpler code. | |
414 StopTrackingPointer(pointer_id); | |
415 StartTrackingPointer(pointer_id, pointer_target); | |
416 } | |
417 | |
418 EventDispatcher::PointerTarget EventDispatcher::PointerTargetForEvent( | |
419 const ui::LocatedEvent& event) { | |
420 PointerTarget pointer_target; | |
421 gfx::Point location(event.location()); | |
422 ServerWindow* target_window = FindDeepestVisibleWindowForEvents(&location); | |
423 pointer_target.window = | |
424 modal_window_controller_.GetTargetForWindow(target_window); | |
425 pointer_target.is_mouse_event = event.IsMousePointerEvent(); | |
426 pointer_target.in_nonclient_area = | |
427 target_window != pointer_target.window || | |
428 IsLocationInNonclientArea(pointer_target.window, location); | |
429 pointer_target.is_pointer_down = event.type() == ui::ET_POINTER_DOWN; | |
430 return pointer_target; | |
431 } | |
432 | |
433 bool EventDispatcher::AreAnyPointersDown() const { | |
434 for (const auto& pair : pointer_targets_) { | |
435 if (pair.second.is_pointer_down) | |
436 return true; | |
437 } | |
438 return false; | |
439 } | |
440 | |
441 void EventDispatcher::DispatchToPointerTarget(const PointerTarget& target, | |
442 const ui::LocatedEvent& event) { | |
443 if (!target.window) { | |
444 delegate_->OnEventTargetNotFound(event); | |
445 return; | |
446 } | |
447 | |
448 if (target.is_mouse_event) | |
449 mouse_cursor_in_non_client_area_ = target.in_nonclient_area; | |
450 | |
451 DispatchToClient(target.window, delegate_->GetEventTargetClientId( | |
452 target.window, target.in_nonclient_area), | |
453 event); | |
454 } | |
455 | |
456 void EventDispatcher::DispatchToClient(ServerWindow* window, | |
457 ClientSpecificId client_id, | |
458 const ui::LocatedEvent& event) { | |
459 gfx::Point location(event.location()); | |
460 gfx::Transform transform(GetTransformToWindow(window)); | |
461 transform.TransformPoint(&location); | |
462 std::unique_ptr<ui::Event> clone = ui::Event::Clone(event); | |
463 clone->AsLocatedEvent()->set_location(location); | |
464 // TODO(jonross): add post-target accelerator support once accelerators | |
465 // support pointer events. | |
466 delegate_->DispatchInputEventToWindow(window, client_id, *clone, nullptr); | |
467 } | |
468 | |
469 void EventDispatcher::CancelPointerEventsToTarget(ServerWindow* window) { | |
470 if (capture_window_ == window) { | |
471 UnobserveWindow(window); | |
472 capture_window_ = nullptr; | |
473 capture_window_client_id_ = kInvalidClientId; | |
474 mouse_button_down_ = false; | |
475 // A window only cares to be informed that it lost capture if it explicitly | |
476 // requested capture. A window can lose capture if another window gains | |
477 // explicit capture. | |
478 delegate_->OnServerWindowCaptureLost(window); | |
479 delegate_->ReleaseNativeCapture(); | |
480 UpdateCursorProviderByLastKnownLocation(); | |
481 return; | |
482 } | |
483 | |
484 for (auto& pair : pointer_targets_) { | |
485 if (pair.second.window == window) { | |
486 UnobserveWindow(window); | |
487 pair.second.window = nullptr; | |
488 } | |
489 } | |
490 } | |
491 | |
492 void EventDispatcher::ObserveWindow(ServerWindow* window) { | |
493 auto res = observed_windows_.insert(std::make_pair(window, 0u)); | |
494 res.first->second++; | |
495 if (res.second) | |
496 window->AddObserver(this); | |
497 } | |
498 | |
499 void EventDispatcher::UnobserveWindow(ServerWindow* window) { | |
500 auto it = observed_windows_.find(window); | |
501 DCHECK(it != observed_windows_.end()); | |
502 DCHECK_LT(0u, it->second); | |
503 it->second--; | |
504 if (!it->second) { | |
505 window->RemoveObserver(this); | |
506 observed_windows_.erase(it); | |
507 } | |
508 } | |
509 | |
510 Accelerator* EventDispatcher::FindAccelerator( | |
511 const ui::KeyEvent& event, | |
512 const ui::mojom::AcceleratorPhase phase) { | |
513 for (const auto& pair : accelerators_) { | |
514 if (pair.second->MatchesEvent(event, phase)) { | |
515 return pair.second.get(); | |
516 } | |
517 } | |
518 return nullptr; | |
519 } | |
520 | |
521 ServerWindow* EventDispatcher::FindDeepestVisibleWindowForEvents( | |
522 gfx::Point* location) { | |
523 ServerWindow* root = delegate_->GetRootWindowContaining(*location); | |
524 if (!root) | |
525 return nullptr; | |
526 | |
527 return mus::ws::FindDeepestVisibleWindowForEvents(root, location); | |
528 } | |
529 | |
530 void EventDispatcher::OnWillChangeWindowHierarchy(ServerWindow* window, | |
531 ServerWindow* new_parent, | |
532 ServerWindow* old_parent) { | |
533 // TODO(sky): moving to a different root likely needs to transfer capture. | |
534 // TODO(sky): this isn't quite right, I think the logic should be (assuming | |
535 // moving in same root and still drawn): | |
536 // . if there is capture and window is still in the same root, continue | |
537 // sending to it. | |
538 // . if there isn't capture, then reevaluate each of the pointer targets | |
539 // sending exit as necessary. | |
540 // http://crbug.com/613646 . | |
541 if (!new_parent || !new_parent->IsDrawn() || | |
542 new_parent->GetRoot() != old_parent->GetRoot()) { | |
543 CancelPointerEventsToTarget(window); | |
544 } | |
545 } | |
546 | |
547 void EventDispatcher::OnWindowVisibilityChanged(ServerWindow* window) { | |
548 CancelPointerEventsToTarget(window); | |
549 } | |
550 | |
551 void EventDispatcher::OnWindowDestroyed(ServerWindow* window) { | |
552 CancelPointerEventsToTarget(window); | |
553 | |
554 if (mouse_cursor_source_window_ == window) | |
555 mouse_cursor_source_window_ = nullptr; | |
556 } | |
557 | |
558 } // namespace ws | |
559 } // namespace mus | |
OLD | NEW |