Put WeakPtrFactory member last in USBEventRouter
[chromium-blink-merge.git] / ash / wm / workspace / multi_window_resize_controller.cc
blob1b8b2cf711c12d0f69ece9334b54582041ccf989
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.
5 #include "ash/wm/workspace/multi_window_resize_controller.h"
7 #include "ash/screen_util.h"
8 #include "ash/shell.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/window_animations.h"
11 #include "ash/wm/workspace/workspace_event_handler.h"
12 #include "ash/wm/workspace/workspace_window_resizer.h"
13 #include "grit/ash_resources.h"
14 #include "ui/aura/client/screen_position_client.h"
15 #include "ui/aura/window.h"
16 #include "ui/aura/window_delegate.h"
17 #include "ui/aura/window_event_dispatcher.h"
18 #include "ui/base/hit_test.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/events/event_targeter.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/screen.h"
24 #include "ui/views/view.h"
25 #include "ui/views/widget/widget.h"
26 #include "ui/views/widget/widget_delegate.h"
27 #include "ui/wm/core/compound_event_filter.h"
28 #include "ui/wm/core/coordinate_conversion.h"
30 using aura::Window;
32 namespace ash {
33 namespace {
35 // Delay before showing.
36 const int kShowDelayMS = 400;
38 // Delay before hiding.
39 const int kHideDelayMS = 500;
41 // Padding from the bottom/right edge the resize widget is shown at.
42 const int kResizeWidgetPadding = 15;
44 bool ContainsX(Window* window, int x) {
45 return x >= 0 && x <= window->bounds().width();
48 bool ContainsScreenX(Window* window, int x_in_screen) {
49 gfx::Point window_loc(x_in_screen, 0);
50 ::wm::ConvertPointFromScreen(window, &window_loc);
51 return ContainsX(window, window_loc.x());
54 bool ContainsY(Window* window, int y) {
55 return y >= 0 && y <= window->bounds().height();
58 bool ContainsScreenY(Window* window, int y_in_screen) {
59 gfx::Point window_loc(0, y_in_screen);
60 ::wm::ConvertPointFromScreen(window, &window_loc);
61 return ContainsY(window, window_loc.y());
64 bool Intersects(int x1, int max_1, int x2, int max_2) {
65 return x2 <= max_1 && max_2 > x1;
68 } // namespace
70 // View contained in the widget. Passes along mouse events to the
71 // MultiWindowResizeController so that it can start/stop the resize loop.
72 class MultiWindowResizeController::ResizeView : public views::View {
73 public:
74 explicit ResizeView(MultiWindowResizeController* controller,
75 Direction direction)
76 : controller_(controller),
77 direction_(direction),
78 image_(NULL) {
79 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
80 int image_id =
81 direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
82 IDR_AURA_MULTI_WINDOW_RESIZE_V;
83 image_ = rb.GetImageNamed(image_id).ToImageSkia();
86 // views::View overrides:
87 gfx::Size GetPreferredSize() const override {
88 return gfx::Size(image_->width(), image_->height());
90 void OnPaint(gfx::Canvas* canvas) override {
91 canvas->DrawImageInt(*image_, 0, 0);
93 bool OnMousePressed(const ui::MouseEvent& event) override {
94 gfx::Point location(event.location());
95 views::View::ConvertPointToScreen(this, &location);
96 controller_->StartResize(location);
97 return true;
99 bool OnMouseDragged(const ui::MouseEvent& event) override {
100 gfx::Point location(event.location());
101 views::View::ConvertPointToScreen(this, &location);
102 controller_->Resize(location, event.flags());
103 return true;
105 void OnMouseReleased(const ui::MouseEvent& event) override {
106 controller_->CompleteResize();
108 void OnMouseCaptureLost() override { controller_->CancelResize(); }
109 gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
110 int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
111 return ::wm::CompoundEventFilter::CursorForWindowComponent(
112 component);
115 private:
116 MultiWindowResizeController* controller_;
117 const Direction direction_;
118 const gfx::ImageSkia* image_;
120 DISALLOW_COPY_AND_ASSIGN(ResizeView);
123 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
124 // Contains() to MultiWindowResizeController.
125 class MultiWindowResizeController::ResizeMouseWatcherHost :
126 public views::MouseWatcherHost {
127 public:
128 ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
130 // MouseWatcherHost overrides:
131 bool Contains(const gfx::Point& point_in_screen,
132 MouseEventType type) override {
133 return (type == MOUSE_PRESS)
134 ? host_->IsOverResizeWidget(point_in_screen)
135 : host_->IsOverWindows(point_in_screen);
138 private:
139 MultiWindowResizeController* host_;
141 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
144 MultiWindowResizeController::ResizeWindows::ResizeWindows()
145 : window1(NULL),
146 window2(NULL),
147 direction(TOP_BOTTOM){
150 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
153 bool MultiWindowResizeController::ResizeWindows::Equals(
154 const ResizeWindows& other) const {
155 return window1 == other.window1 &&
156 window2 == other.window2 &&
157 direction == other.direction;
160 MultiWindowResizeController::MultiWindowResizeController() {
163 MultiWindowResizeController::~MultiWindowResizeController() {
164 window_resizer_.reset();
165 Hide();
168 void MultiWindowResizeController::Show(Window* window,
169 int component,
170 const gfx::Point& point_in_window) {
171 // When the resize widget is showing we ignore Show() requests. Instead we
172 // only care about mouse movements from MouseWatcher. This is necessary as
173 // WorkspaceEventHandler only sees mouse movements over the windows, not all
174 // windows or over the desktop.
175 if (resize_widget_)
176 return;
178 ResizeWindows windows(DetermineWindows(window, component, point_in_window));
179 if (IsShowing() && windows_.Equals(windows))
180 return;
182 Hide();
183 if (!windows.is_valid()) {
184 windows_ = ResizeWindows();
185 return;
188 windows_ = windows;
189 windows_.window1->AddObserver(this);
190 windows_.window2->AddObserver(this);
191 show_location_in_parent_ = point_in_window;
192 Window::ConvertPointToTarget(
193 window, window->parent(), &show_location_in_parent_);
194 show_timer_.Start(
195 FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
196 this, &MultiWindowResizeController::ShowIfValidMouseLocation);
199 void MultiWindowResizeController::Hide() {
200 if (window_resizer_)
201 return; // Ignore hides while actively resizing.
203 if (windows_.window1) {
204 windows_.window1->RemoveObserver(this);
205 windows_.window1 = NULL;
207 if (windows_.window2) {
208 windows_.window2->RemoveObserver(this);
209 windows_.window2 = NULL;
212 show_timer_.Stop();
214 if (!resize_widget_)
215 return;
217 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
218 windows_.other_windows[i]->RemoveObserver(this);
219 mouse_watcher_.reset();
220 resize_widget_.reset();
221 windows_ = ResizeWindows();
224 void MultiWindowResizeController::MouseMovedOutOfHost() {
225 Hide();
228 void MultiWindowResizeController::OnWindowDestroying(
229 aura::Window* window) {
230 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
231 window_resizer_.reset();
232 Hide();
235 MultiWindowResizeController::ResizeWindows
236 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
237 aura::Window* window) const {
238 gfx::Point mouse_location(
239 gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
240 ::wm::ConvertPointFromScreen(window, &mouse_location);
241 const int component =
242 window->delegate()->GetNonClientComponent(mouse_location);
243 return DetermineWindows(window, component, mouse_location);
246 void MultiWindowResizeController::CreateMouseWatcher() {
247 mouse_watcher_.reset(new views::MouseWatcher(
248 new ResizeMouseWatcherHost(this), this));
249 mouse_watcher_->set_notify_on_exit_time(
250 base::TimeDelta::FromMilliseconds(kHideDelayMS));
251 mouse_watcher_->Start();
254 MultiWindowResizeController::ResizeWindows
255 MultiWindowResizeController::DetermineWindows(
256 Window* window,
257 int window_component,
258 const gfx::Point& point) const {
259 ResizeWindows result;
260 gfx::Point point_in_parent(point);
261 Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
262 switch (window_component) {
263 case HTRIGHT:
264 result.direction = LEFT_RIGHT;
265 result.window1 = window;
266 result.window2 = FindWindowByEdge(
267 window, HTLEFT, window->bounds().right(), point_in_parent.y());
268 break;
269 case HTLEFT:
270 result.direction = LEFT_RIGHT;
271 result.window1 = FindWindowByEdge(
272 window, HTRIGHT, window->bounds().x(), point_in_parent.y());
273 result.window2 = window;
274 break;
275 case HTTOP:
276 result.direction = TOP_BOTTOM;
277 result.window1 = FindWindowByEdge(
278 window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
279 result.window2 = window;
280 break;
281 case HTBOTTOM:
282 result.direction = TOP_BOTTOM;
283 result.window1 = window;
284 result.window2 = FindWindowByEdge(
285 window, HTTOP, point_in_parent.x(), window->bounds().bottom());
286 break;
287 default:
288 break;
290 return result;
293 Window* MultiWindowResizeController::FindWindowByEdge(
294 Window* window_to_ignore,
295 int edge_want,
296 int x_in_parent,
297 int y_in_parent) const {
298 Window* parent = window_to_ignore->parent();
299 const Window::Windows& windows(parent->children());
300 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
301 i != windows.rend(); ++i) {
302 Window* window = *i;
303 if (window == window_to_ignore || !window->IsVisible())
304 continue;
306 // Ignore windows without a delegate. A delegate is necessary to query the
307 // non-client component.
308 if (!window->delegate())
309 continue;
311 gfx::Point p(x_in_parent, y_in_parent);
312 aura::Window::ConvertPointToTarget(parent, window, &p);
313 switch (edge_want) {
314 case HTLEFT:
315 if (ContainsY(window, p.y()) && p.x() == 0)
316 return window;
317 break;
318 case HTRIGHT:
319 if (ContainsY(window, p.y()) && p.x() == window->bounds().width())
320 return window;
321 break;
322 case HTTOP:
323 if (ContainsX(window, p.x()) && p.y() == 0)
324 return window;
325 break;
326 case HTBOTTOM:
327 if (ContainsX(window, p.x()) && p.y() == window->bounds().height())
328 return window;
329 break;
330 default:
331 NOTREACHED();
333 // Window doesn't contain the edge, but if window contains |point|
334 // it's obscuring any other window that could be at the location.
335 if (window->bounds().Contains(x_in_parent, y_in_parent))
336 return NULL;
338 return NULL;
341 aura::Window* MultiWindowResizeController::FindWindowTouching(
342 aura::Window* window,
343 Direction direction) const {
344 int right = window->bounds().right();
345 int bottom = window->bounds().bottom();
346 Window* parent = window->parent();
347 const Window::Windows& windows(parent->children());
348 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
349 i != windows.rend(); ++i) {
350 Window* other = *i;
351 if (other == window || !other->IsVisible())
352 continue;
353 switch (direction) {
354 case TOP_BOTTOM:
355 if (other->bounds().y() == bottom &&
356 Intersects(other->bounds().x(), other->bounds().right(),
357 window->bounds().x(), window->bounds().right())) {
358 return other;
360 break;
361 case LEFT_RIGHT:
362 if (other->bounds().x() == right &&
363 Intersects(other->bounds().y(), other->bounds().bottom(),
364 window->bounds().y(), window->bounds().bottom())) {
365 return other;
367 break;
368 default:
369 NOTREACHED();
372 return NULL;
375 void MultiWindowResizeController::FindWindowsTouching(
376 aura::Window* start,
377 Direction direction,
378 std::vector<aura::Window*>* others) const {
379 while (start) {
380 start = FindWindowTouching(start, direction);
381 if (start)
382 others->push_back(start);
386 void MultiWindowResizeController::ShowIfValidMouseLocation() {
387 if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
388 DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
389 ShowNow();
390 } else {
391 Hide();
395 void MultiWindowResizeController::ShowNow() {
396 DCHECK(!resize_widget_.get());
397 DCHECK(windows_.is_valid());
398 show_timer_.Stop();
399 resize_widget_.reset(new views::Widget);
400 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
401 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
402 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
403 params.parent = Shell::GetContainer(Shell::GetTargetRootWindow(),
404 kShellWindowId_AlwaysOnTopContainer);
405 ResizeView* view = new ResizeView(this, windows_.direction);
406 resize_widget_->set_focus_on_creation(false);
407 resize_widget_->Init(params);
408 ::wm::SetWindowVisibilityAnimationType(
409 resize_widget_->GetNativeWindow(),
410 ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
411 resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
412 resize_widget_->SetContentsView(view);
413 show_bounds_in_screen_ = ScreenUtil::ConvertRectToScreen(
414 windows_.window1->parent(),
415 CalculateResizeWidgetBounds(show_location_in_parent_));
416 resize_widget_->SetBounds(show_bounds_in_screen_);
417 resize_widget_->Show();
418 CreateMouseWatcher();
421 bool MultiWindowResizeController::IsShowing() const {
422 return resize_widget_.get() || show_timer_.IsRunning();
425 void MultiWindowResizeController::StartResize(
426 const gfx::Point& location_in_screen) {
427 DCHECK(!window_resizer_.get());
428 DCHECK(windows_.is_valid());
429 gfx::Point location_in_parent(location_in_screen);
430 aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
431 ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
432 std::vector<aura::Window*> windows;
433 windows.push_back(windows_.window2);
434 DCHECK(windows_.other_windows.empty());
435 FindWindowsTouching(windows_.window2, windows_.direction,
436 &windows_.other_windows);
437 for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
438 windows_.other_windows[i]->AddObserver(this);
439 windows.push_back(windows_.other_windows[i]);
441 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
442 wm::WindowState* window_state = wm::GetWindowState(windows_.window1);
443 window_state->CreateDragDetails(windows_.window1,
444 location_in_parent,
445 component,
446 aura::client::WINDOW_MOVE_SOURCE_MOUSE);
447 window_resizer_.reset(WorkspaceWindowResizer::Create(window_state, windows));
449 // Do not hide the resize widget while a drag is active.
450 mouse_watcher_.reset();
453 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
454 int event_flags) {
455 gfx::Point location_in_parent(location_in_screen);
456 aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
457 ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
458 window_resizer_->Drag(location_in_parent, event_flags);
459 gfx::Rect bounds = ScreenUtil::ConvertRectToScreen(
460 windows_.window1->parent(),
461 CalculateResizeWidgetBounds(location_in_parent));
463 if (windows_.direction == LEFT_RIGHT)
464 bounds.set_y(show_bounds_in_screen_.y());
465 else
466 bounds.set_x(show_bounds_in_screen_.x());
467 resize_widget_->SetBounds(bounds);
470 void MultiWindowResizeController::CompleteResize() {
471 window_resizer_->CompleteDrag();
472 wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
473 window_resizer_.reset();
475 // Mouse may still be over resizer, if not hide.
476 gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
477 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
478 Hide();
479 } else {
480 // If the mouse is over the resizer we need to remove observers on any of
481 // the |other_windows|. If we start another resize we'll recalculate the
482 // |other_windows| and invoke AddObserver() as necessary.
483 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
484 windows_.other_windows[i]->RemoveObserver(this);
485 windows_.other_windows.clear();
487 CreateMouseWatcher();
491 void MultiWindowResizeController::CancelResize() {
492 if (!window_resizer_)
493 return; // Happens if window was destroyed and we nuked the WindowResizer.
494 window_resizer_->RevertDrag();
495 wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
496 window_resizer_.reset();
497 Hide();
500 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
501 const gfx::Point& location_in_parent) const {
502 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
503 int x = 0, y = 0;
504 if (windows_.direction == LEFT_RIGHT) {
505 x = windows_.window1->bounds().right() - pref.width() / 2;
506 y = location_in_parent.y() + kResizeWidgetPadding;
507 if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
508 y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
509 y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
511 } else {
512 x = location_in_parent.x() + kResizeWidgetPadding;
513 if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
514 x + pref.height() / 2 > windows_.window2->bounds().right()) {
515 x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
517 y = windows_.window1->bounds().bottom() - pref.height() / 2;
519 return gfx::Rect(x, y, pref.width(), pref.height());
522 bool MultiWindowResizeController::IsOverResizeWidget(
523 const gfx::Point& location_in_screen) const {
524 return resize_widget_->GetWindowBoundsInScreen().Contains(
525 location_in_screen);
528 bool MultiWindowResizeController::IsOverWindows(
529 const gfx::Point& location_in_screen) const {
530 if (IsOverResizeWidget(location_in_screen))
531 return true;
533 if (windows_.direction == TOP_BOTTOM) {
534 if (!ContainsScreenX(windows_.window1, location_in_screen.x()) ||
535 !ContainsScreenX(windows_.window2, location_in_screen.x())) {
536 return false;
538 } else {
539 if (!ContainsScreenY(windows_.window1, location_in_screen.y()) ||
540 !ContainsScreenY(windows_.window2, location_in_screen.y())) {
541 return false;
545 // Check whether |location_in_screen| is in the event target's resize region.
546 // This is tricky because a window's resize region can extend outside a
547 // window's bounds.
548 gfx::Point location_in_root(location_in_screen);
549 aura::Window* root = windows_.window1->GetRootWindow();
550 ::wm::ConvertPointFromScreen(root, &location_in_root);
551 ui::MouseEvent test_event(ui::ET_MOUSE_MOVED, location_in_root,
552 location_in_root, ui::EF_NONE, ui::EF_NONE);
553 ui::EventTarget* event_handler = static_cast<ui::EventTarget*>(root)
554 ->GetEventTargeter()
555 ->FindTargetForEvent(root, &test_event);
556 if (event_handler == windows_.window1) {
557 return IsOverComponent(
558 windows_.window1,
559 location_in_screen,
560 windows_.direction == TOP_BOTTOM ? HTBOTTOM : HTRIGHT);
561 } else if (event_handler == windows_.window2) {
562 return IsOverComponent(
563 windows_.window2,
564 location_in_screen,
565 windows_.direction == TOP_BOTTOM ? HTTOP : HTLEFT);
567 return false;
570 bool MultiWindowResizeController::IsOverComponent(
571 aura::Window* window,
572 const gfx::Point& location_in_screen,
573 int component) const {
574 gfx::Point window_loc(location_in_screen);
575 ::wm::ConvertPointFromScreen(window, &window_loc);
576 return window->delegate()->GetNonClientComponent(window_loc) == component;
579 } // namespace ash