OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "ash/common/wm/wm_toplevel_window_event_handler.h" | |
6 | |
7 #include "ash/common/wm/window_resizer.h" | |
8 #include "ash/common/wm/window_state.h" | |
9 #include "ash/common/wm/window_state_observer.h" | |
10 #include "ash/common/wm/wm_event.h" | |
11 #include "ash/common/wm_shell.h" | |
12 #include "ash/common/wm_window.h" | |
13 #include "ui/aura/window.h" | |
14 #include "ui/aura/window_observer.h" | |
15 #include "ui/base/hit_test.h" | |
16 #include "ui/events/event.h" | |
17 | |
18 namespace { | |
19 const double kMinHorizVelocityForWindowSwipe = 1100; | |
20 const double kMinVertVelocityForWindowMinimize = 1000; | |
21 } | |
22 | |
23 namespace ash { | |
24 namespace wm { | |
25 | |
26 namespace { | |
27 | |
28 // Returns whether |window| can be moved via a two finger drag given | |
29 // the hittest results of the two fingers. | |
30 bool CanStartTwoFingerMove(WmWindow* window, | |
31 int window_component1, | |
32 int window_component2) { | |
33 // We allow moving a window via two fingers when the hittest components are | |
34 // HTCLIENT. This is done so that a window can be dragged via two fingers when | |
35 // the tab strip is full and hitting the caption area is difficult. We check | |
36 // the window type and the state type so that we do not steal touches from the | |
37 // web contents. | |
38 if (!window->GetWindowState()->IsNormalOrSnapped() || | |
39 window->GetType() != ui::wm::WINDOW_TYPE_NORMAL) { | |
40 return false; | |
41 } | |
42 int component1_behavior = | |
43 WindowResizer::GetBoundsChangeForWindowComponent(window_component1); | |
44 int component2_behavior = | |
45 WindowResizer::GetBoundsChangeForWindowComponent(window_component2); | |
46 return (component1_behavior & WindowResizer::kBoundsChange_Resizes) == 0 && | |
47 (component2_behavior & WindowResizer::kBoundsChange_Resizes) == 0; | |
48 } | |
49 | |
50 // Returns whether |window| can be moved or resized via one finger given | |
51 // |window_component|. | |
52 bool CanStartOneFingerDrag(int window_component) { | |
53 return WindowResizer::GetBoundsChangeForWindowComponent(window_component) != | |
54 0; | |
55 } | |
56 | |
57 // Returns the window component containing |event|'s location. | |
58 int GetWindowComponent(WmWindow* window, const ui::LocatedEvent& event) { | |
59 return window->GetNonClientComponent(event.location()); | |
60 } | |
61 | |
62 } // namespace | |
63 | |
64 // ScopedWindowResizer --------------------------------------------------------- | |
65 | |
66 // Wraps a WindowResizer and installs an observer on its target window. When | |
67 // the window is destroyed ResizerWindowDestroyed() is invoked back on the | |
68 // WmToplevelWindowEventHandler to clean up. | |
69 class WmToplevelWindowEventHandler::ScopedWindowResizer | |
70 : public aura::WindowObserver, | |
71 public wm::WindowStateObserver { | |
72 public: | |
73 ScopedWindowResizer(WmToplevelWindowEventHandler* handler, | |
74 std::unique_ptr<WindowResizer> resizer); | |
75 ~ScopedWindowResizer() override; | |
76 | |
77 // Returns true if the drag moves the window and does not resize. | |
78 bool IsMove() const; | |
79 | |
80 WindowResizer* resizer() { return resizer_.get(); } | |
81 | |
82 // WindowObserver overrides: | |
83 void OnWindowDestroying(aura::Window* window) override; | |
84 | |
85 // WindowStateObserver overrides: | |
86 void OnPreWindowStateTypeChange(wm::WindowState* window_state, | |
87 wm::WindowStateType type) override; | |
88 | |
89 private: | |
90 WmToplevelWindowEventHandler* handler_; | |
91 std::unique_ptr<WindowResizer> resizer_; | |
92 | |
93 // Whether ScopedWindowResizer grabbed capture. | |
94 bool grabbed_capture_; | |
95 | |
96 DISALLOW_COPY_AND_ASSIGN(ScopedWindowResizer); | |
97 }; | |
98 | |
99 WmToplevelWindowEventHandler::ScopedWindowResizer::ScopedWindowResizer( | |
100 WmToplevelWindowEventHandler* handler, | |
101 std::unique_ptr<WindowResizer> resizer) | |
102 : handler_(handler), resizer_(std::move(resizer)), grabbed_capture_(false) { | |
103 WmWindow* target = resizer_->GetTarget(); | |
104 target->aura_window()->AddObserver(this); | |
105 target->GetWindowState()->AddObserver(this); | |
106 | |
107 if (!target->HasCapture()) { | |
108 grabbed_capture_ = true; | |
109 target->SetCapture(); | |
110 } | |
111 } | |
112 | |
113 WmToplevelWindowEventHandler::ScopedWindowResizer::~ScopedWindowResizer() { | |
114 WmWindow* target = resizer_->GetTarget(); | |
115 target->aura_window()->RemoveObserver(this); | |
116 target->GetWindowState()->RemoveObserver(this); | |
117 if (grabbed_capture_) | |
118 target->ReleaseCapture(); | |
119 } | |
120 | |
121 bool WmToplevelWindowEventHandler::ScopedWindowResizer::IsMove() const { | |
122 return resizer_->details().bounds_change == | |
123 WindowResizer::kBoundsChange_Repositions; | |
124 } | |
125 | |
126 void WmToplevelWindowEventHandler::ScopedWindowResizer:: | |
127 OnPreWindowStateTypeChange(wm::WindowState* window_state, | |
128 wm::WindowStateType old) { | |
129 handler_->CompleteDrag(DragResult::SUCCESS); | |
130 } | |
131 | |
132 void WmToplevelWindowEventHandler::ScopedWindowResizer::OnWindowDestroying( | |
133 aura::Window* window) { | |
134 DCHECK_EQ(resizer_->GetTarget(), WmWindow::Get(window)); | |
135 handler_->ResizerWindowDestroyed(); | |
136 } | |
137 | |
138 // WmToplevelWindowEventHandler | |
139 // -------------------------------------------------- | |
140 | |
141 WmToplevelWindowEventHandler::WmToplevelWindowEventHandler(WmShell* shell) | |
142 : shell_(shell), first_finger_hittest_(HTNOWHERE) { | |
143 shell_->AddDisplayObserver(this); | |
144 } | |
145 | |
146 WmToplevelWindowEventHandler::~WmToplevelWindowEventHandler() { | |
147 shell_->RemoveDisplayObserver(this); | |
148 } | |
149 | |
150 void WmToplevelWindowEventHandler::OnKeyEvent(ui::KeyEvent* event) { | |
151 if (window_resizer_.get() && event->type() == ui::ET_KEY_PRESSED && | |
152 event->key_code() == ui::VKEY_ESCAPE) { | |
153 CompleteDrag(DragResult::REVERT); | |
154 } | |
155 } | |
156 | |
157 void WmToplevelWindowEventHandler::OnMouseEvent(ui::MouseEvent* event, | |
158 WmWindow* target) { | |
159 if (event->handled()) | |
160 return; | |
161 if ((event->flags() & | |
162 (ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON)) != 0) | |
163 return; | |
164 | |
165 if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED) { | |
166 // Capture is grabbed when both gesture and mouse drags start. Handle | |
167 // capture loss regardless of which type of drag is in progress. | |
168 HandleCaptureLost(event); | |
169 return; | |
170 } | |
171 | |
172 if (in_gesture_drag_) | |
173 return; | |
174 | |
175 switch (event->type()) { | |
176 case ui::ET_MOUSE_PRESSED: | |
177 HandleMousePressed(target, event); | |
178 break; | |
179 case ui::ET_MOUSE_DRAGGED: | |
180 HandleDrag(target, event); | |
181 break; | |
182 case ui::ET_MOUSE_RELEASED: | |
183 HandleMouseReleased(target, event); | |
184 break; | |
185 case ui::ET_MOUSE_MOVED: | |
186 HandleMouseMoved(target, event); | |
187 break; | |
188 case ui::ET_MOUSE_EXITED: | |
189 HandleMouseExited(target, event); | |
190 break; | |
191 default: | |
192 break; | |
193 } | |
194 } | |
195 | |
196 void WmToplevelWindowEventHandler::OnGestureEvent(ui::GestureEvent* event, | |
197 WmWindow* target) { | |
198 if (event->handled()) | |
199 return; | |
200 if (!target->HasNonClientArea()) | |
201 return; | |
202 | |
203 if (window_resizer_.get() && !in_gesture_drag_) | |
204 return; | |
205 | |
206 if (window_resizer_.get() && | |
207 window_resizer_->resizer()->GetTarget() != target) { | |
208 return; | |
209 } | |
210 | |
211 if (event->details().touch_points() > 2) { | |
212 if (CompleteDrag(DragResult::SUCCESS)) | |
213 event->StopPropagation(); | |
214 return; | |
215 } | |
216 | |
217 switch (event->type()) { | |
218 case ui::ET_GESTURE_TAP_DOWN: { | |
219 int component = GetWindowComponent(target, *event); | |
220 if (!(WindowResizer::GetBoundsChangeForWindowComponent(component) & | |
221 WindowResizer::kBoundsChange_Resizes)) | |
222 return; | |
223 target->ShowResizeShadow(component); | |
224 return; | |
225 } | |
226 case ui::ET_GESTURE_END: { | |
227 target->HideResizeShadow(); | |
228 | |
229 if (window_resizer_.get() && | |
230 (event->details().touch_points() == 1 || | |
231 !CanStartOneFingerDrag(first_finger_hittest_))) { | |
232 CompleteDrag(DragResult::SUCCESS); | |
233 event->StopPropagation(); | |
234 } | |
235 return; | |
236 } | |
237 case ui::ET_GESTURE_BEGIN: { | |
238 if (event->details().touch_points() == 1) { | |
239 first_finger_hittest_ = GetWindowComponent(target, *event); | |
240 } else if (window_resizer_.get()) { | |
241 if (!window_resizer_->IsMove()) { | |
242 // The transition from resizing with one finger to resizing with two | |
243 // fingers causes unintended resizing because the location of | |
244 // ET_GESTURE_SCROLL_UPDATE jumps from the position of the first | |
245 // finger to the position in the middle of the two fingers. For this | |
246 // reason two finger resizing is not supported. | |
247 CompleteDrag(DragResult::SUCCESS); | |
248 event->StopPropagation(); | |
249 } | |
250 } else { | |
251 int second_finger_hittest = GetWindowComponent(target, *event); | |
252 if (CanStartTwoFingerMove(target, first_finger_hittest_, | |
253 second_finger_hittest)) { | |
254 gfx::Point location_in_parent = | |
255 event->details().bounding_box().CenterPoint(); | |
256 AttemptToStartDrag(target, location_in_parent, HTCAPTION, | |
257 aura::client::WINDOW_MOVE_SOURCE_TOUCH, | |
258 EndClosure()); | |
259 event->StopPropagation(); | |
260 } | |
261 } | |
262 return; | |
263 } | |
264 case ui::ET_GESTURE_SCROLL_BEGIN: { | |
265 // The one finger drag is not started in ET_GESTURE_BEGIN to avoid the | |
266 // window jumping upon initiating a two finger drag. When a one finger | |
267 // drag is converted to a two finger drag, a jump occurs because the | |
268 // location of the ET_GESTURE_SCROLL_UPDATE event switches from the single | |
269 // finger's position to the position in the middle of the two fingers. | |
270 if (window_resizer_.get()) | |
271 return; | |
272 int component = GetWindowComponent(target, *event); | |
273 if (!CanStartOneFingerDrag(component)) | |
274 return; | |
275 gfx::Point location_in_parent( | |
276 target->ConvertPointToTarget(target->GetParent(), event->location())); | |
277 AttemptToStartDrag(target, location_in_parent, component, | |
278 aura::client::WINDOW_MOVE_SOURCE_TOUCH, EndClosure()); | |
279 event->StopPropagation(); | |
280 return; | |
281 } | |
282 default: | |
283 break; | |
284 } | |
285 | |
286 if (!window_resizer_.get()) | |
287 return; | |
288 | |
289 switch (event->type()) { | |
290 case ui::ET_GESTURE_SCROLL_UPDATE: | |
291 HandleDrag(target, event); | |
292 event->StopPropagation(); | |
293 return; | |
294 case ui::ET_GESTURE_SCROLL_END: | |
295 // We must complete the drag here instead of as a result of ET_GESTURE_END | |
296 // because otherwise the drag will be reverted when EndMoveLoop() is | |
297 // called. | |
298 // TODO(pkotwicz): Pass drag completion status to | |
299 // WindowMoveClient::EndMoveLoop(). | |
300 CompleteDrag(DragResult::SUCCESS); | |
301 event->StopPropagation(); | |
302 return; | |
303 case ui::ET_SCROLL_FLING_START: | |
304 CompleteDrag(DragResult::SUCCESS); | |
305 | |
306 // TODO(pkotwicz): Fix tests which inadvertantly start flings and check | |
307 // window_resizer_->IsMove() instead of the hittest component at |event|'s | |
308 // location. | |
309 if (GetWindowComponent(target, *event) != HTCAPTION || | |
310 !target->GetWindowState()->IsNormalOrSnapped()) { | |
311 return; | |
312 } | |
313 | |
314 if (event->details().velocity_y() > kMinVertVelocityForWindowMinimize) { | |
315 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MINIMIZED); | |
316 } else if (event->details().velocity_y() < | |
317 -kMinVertVelocityForWindowMinimize) { | |
318 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MAXIMIZED); | |
319 } else if (event->details().velocity_x() > | |
320 kMinHorizVelocityForWindowSwipe) { | |
321 SetWindowStateTypeFromGesture(target, | |
322 wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED); | |
323 } else if (event->details().velocity_x() < | |
324 -kMinHorizVelocityForWindowSwipe) { | |
325 SetWindowStateTypeFromGesture(target, | |
326 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED); | |
327 } | |
328 event->StopPropagation(); | |
329 return; | |
330 case ui::ET_GESTURE_SWIPE: | |
331 DCHECK_GT(event->details().touch_points(), 0); | |
332 if (event->details().touch_points() == 1) | |
333 return; | |
334 if (!target->GetWindowState()->IsNormalOrSnapped()) | |
335 return; | |
336 | |
337 CompleteDrag(DragResult::SUCCESS); | |
338 | |
339 if (event->details().swipe_down()) { | |
340 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MINIMIZED); | |
341 } else if (event->details().swipe_up()) { | |
342 SetWindowStateTypeFromGesture(target, wm::WINDOW_STATE_TYPE_MAXIMIZED); | |
343 } else if (event->details().swipe_right()) { | |
344 SetWindowStateTypeFromGesture(target, | |
345 wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED); | |
346 } else { | |
347 SetWindowStateTypeFromGesture(target, | |
348 wm::WINDOW_STATE_TYPE_LEFT_SNAPPED); | |
349 } | |
350 event->StopPropagation(); | |
351 return; | |
352 default: | |
353 return; | |
354 } | |
355 } | |
356 | |
357 bool WmToplevelWindowEventHandler::AttemptToStartDrag( | |
358 WmWindow* window, | |
359 const gfx::Point& point_in_parent, | |
360 int window_component, | |
361 aura::client::WindowMoveSource source, | |
362 const EndClosure& end_closure) { | |
363 if (window_resizer_.get()) | |
364 return false; | |
365 std::unique_ptr<WindowResizer> resizer( | |
366 CreateWindowResizer(window, point_in_parent, window_component, source)); | |
367 if (!resizer) | |
368 return false; | |
369 | |
370 end_closure_ = end_closure; | |
371 window_resizer_.reset(new ScopedWindowResizer(this, std::move(resizer))); | |
372 | |
373 pre_drag_window_bounds_ = window->GetBounds(); | |
374 in_gesture_drag_ = (source == aura::client::WINDOW_MOVE_SOURCE_TOUCH); | |
375 return true; | |
376 } | |
377 | |
378 void WmToplevelWindowEventHandler::RevertDrag() { | |
379 CompleteDrag(DragResult::REVERT); | |
380 } | |
381 | |
382 bool WmToplevelWindowEventHandler::CompleteDrag(DragResult result) { | |
383 if (!window_resizer_) | |
384 return false; | |
385 | |
386 std::unique_ptr<ScopedWindowResizer> resizer(std::move(window_resizer_)); | |
387 switch (result) { | |
388 case DragResult::SUCCESS: | |
389 resizer->resizer()->CompleteDrag(); | |
390 break; | |
391 case DragResult::REVERT: | |
392 resizer->resizer()->RevertDrag(); | |
393 break; | |
394 case DragResult::WINDOW_DESTROYED: | |
395 // We explicitly do not invoke RevertDrag() since that may do things to | |
396 // the window that was destroyed. | |
397 break; | |
398 } | |
399 | |
400 first_finger_hittest_ = HTNOWHERE; | |
401 in_gesture_drag_ = false; | |
402 if (!end_closure_.is_null()) { | |
403 // Clear local state in case running the closure deletes us. | |
404 EndClosure end_closure = end_closure_; | |
405 end_closure_.Reset(); | |
406 end_closure.Run(result); | |
407 } | |
408 return true; | |
409 } | |
410 | |
411 void WmToplevelWindowEventHandler::HandleMousePressed(WmWindow* target, | |
412 ui::MouseEvent* event) { | |
413 if (event->phase() != ui::EP_PRETARGET || !target->HasNonClientArea()) | |
414 return; | |
415 | |
416 // We also update the current window component here because for the | |
417 // mouse-drag-release-press case, where the mouse is released and | |
418 // pressed without mouse move event. | |
419 int component = GetWindowComponent(target, *event); | |
420 if ((event->flags() & (ui::EF_IS_DOUBLE_CLICK | ui::EF_IS_TRIPLE_CLICK)) == | |
421 0 && | |
422 WindowResizer::GetBoundsChangeForWindowComponent(component)) { | |
423 gfx::Point location_in_parent( | |
424 target->ConvertPointToTarget(target->GetParent(), event->location())); | |
425 AttemptToStartDrag(target, location_in_parent, component, | |
426 aura::client::WINDOW_MOVE_SOURCE_MOUSE, EndClosure()); | |
427 // Set as handled so that other event handlers do no act upon the event | |
428 // but still receive it so that they receive both parts of each pressed/ | |
429 // released pair. | |
430 event->SetHandled(); | |
431 } else { | |
432 CompleteDrag(DragResult::SUCCESS); | |
433 } | |
434 } | |
435 | |
436 void WmToplevelWindowEventHandler::HandleMouseReleased(WmWindow* target, | |
437 ui::MouseEvent* event) { | |
438 if (event->phase() == ui::EP_PRETARGET) | |
439 CompleteDrag(DragResult::SUCCESS); | |
440 } | |
441 | |
442 void WmToplevelWindowEventHandler::HandleDrag(WmWindow* target, | |
443 ui::LocatedEvent* event) { | |
444 // This function only be triggered to move window | |
445 // by mouse drag or touch move event. | |
446 DCHECK(event->type() == ui::ET_MOUSE_DRAGGED || | |
447 event->type() == ui::ET_TOUCH_MOVED || | |
448 event->type() == ui::ET_GESTURE_SCROLL_UPDATE); | |
449 | |
450 // Drag actions are performed pre-target handling to prevent spurious mouse | |
451 // moves from the move/size operation from being sent to the target. | |
452 if (event->phase() != ui::EP_PRETARGET) | |
453 return; | |
454 | |
455 if (!window_resizer_) | |
456 return; | |
457 window_resizer_->resizer()->Drag( | |
458 target->ConvertPointToTarget(target->GetParent(), event->location()), | |
459 event->flags()); | |
460 event->StopPropagation(); | |
461 } | |
462 | |
463 void WmToplevelWindowEventHandler::HandleMouseMoved(WmWindow* target, | |
464 ui::LocatedEvent* event) { | |
465 // Shadow effects are applied after target handling. Note that we don't | |
466 // respect ER_HANDLED here right now since we have not had a reason to allow | |
467 // the target to cancel shadow rendering. | |
468 if (event->phase() != ui::EP_POSTTARGET || !target->HasNonClientArea()) | |
469 return; | |
470 | |
471 // TODO(jamescook): Move the resize cursor update code into here from | |
472 // CompoundEventFilter? | |
473 if (event->flags() & ui::EF_IS_NON_CLIENT) { | |
474 int component = target->GetNonClientComponent(event->location()); | |
475 target->ShowResizeShadow(component); | |
476 } else { | |
477 target->HideResizeShadow(); | |
478 } | |
479 } | |
480 | |
481 void WmToplevelWindowEventHandler::HandleMouseExited(WmWindow* target, | |
482 ui::LocatedEvent* event) { | |
483 // Shadow effects are applied after target handling. Note that we don't | |
484 // respect ER_HANDLED here right now since we have not had a reason to allow | |
485 // the target to cancel shadow rendering. | |
486 if (event->phase() != ui::EP_POSTTARGET) | |
487 return; | |
488 | |
489 target->HideResizeShadow(); | |
490 } | |
491 | |
492 void WmToplevelWindowEventHandler::HandleCaptureLost(ui::LocatedEvent* event) { | |
493 if (event->phase() == ui::EP_PRETARGET) { | |
494 // We complete the drag instead of reverting it, as reverting it will result | |
495 // in a weird behavior when a dragged tab produces a modal dialog while the | |
496 // drag is in progress. crbug.com/558201. | |
497 CompleteDrag(DragResult::SUCCESS); | |
498 } | |
499 } | |
500 | |
501 void WmToplevelWindowEventHandler::SetWindowStateTypeFromGesture( | |
502 WmWindow* window, | |
503 wm::WindowStateType new_state_type) { | |
504 wm::WindowState* window_state = window->GetWindowState(); | |
505 // TODO(oshima): Move extra logic (set_unminimize_to_restore_bounds, | |
506 // SetRestoreBoundsInParent) that modifies the window state | |
507 // into WindowState. | |
508 switch (new_state_type) { | |
509 case wm::WINDOW_STATE_TYPE_MINIMIZED: | |
510 if (window_state->CanMinimize()) { | |
511 window_state->Minimize(); | |
512 window_state->set_unminimize_to_restore_bounds(true); | |
513 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); | |
514 } | |
515 break; | |
516 case wm::WINDOW_STATE_TYPE_MAXIMIZED: | |
517 if (window_state->CanMaximize()) { | |
518 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); | |
519 window_state->Maximize(); | |
520 } | |
521 break; | |
522 case wm::WINDOW_STATE_TYPE_LEFT_SNAPPED: | |
523 if (window_state->CanSnap()) { | |
524 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); | |
525 const wm::WMEvent event(wm::WM_EVENT_SNAP_LEFT); | |
526 window_state->OnWMEvent(&event); | |
527 } | |
528 break; | |
529 case wm::WINDOW_STATE_TYPE_RIGHT_SNAPPED: | |
530 if (window_state->CanSnap()) { | |
531 window_state->SetRestoreBoundsInParent(pre_drag_window_bounds_); | |
532 const wm::WMEvent event(wm::WM_EVENT_SNAP_RIGHT); | |
533 window_state->OnWMEvent(&event); | |
534 } | |
535 break; | |
536 default: | |
537 NOTREACHED(); | |
538 } | |
539 } | |
540 | |
541 void WmToplevelWindowEventHandler::ResizerWindowDestroyed() { | |
542 CompleteDrag(DragResult::WINDOW_DESTROYED); | |
543 } | |
544 | |
545 void WmToplevelWindowEventHandler::OnDisplayConfigurationChanging() { | |
546 CompleteDrag(DragResult::REVERT); | |
547 } | |
548 | |
549 } // namespace wm | |
550 } // namespace ash | |
OLD | NEW |