Mac: Fix performance issues with remote CoreAnimation
[chromium-blink-merge.git] / ash / wm / workspace / multi_window_resize_controller.cc
blob09e3a1678956f14af8d8b61b4f91e2adcddc8c69
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/gfx/canvas.h"
21 #include "ui/gfx/image/image.h"
22 #include "ui/gfx/screen.h"
23 #include "ui/views/view.h"
24 #include "ui/views/widget/widget.h"
25 #include "ui/views/widget/widget_delegate.h"
26 #include "ui/wm/core/compound_event_filter.h"
27 #include "ui/wm/core/coordinate_conversion.h"
29 using aura::Window;
31 namespace ash {
32 namespace {
34 // Delay before showing.
35 const int kShowDelayMS = 400;
37 // Delay before hiding.
38 const int kHideDelayMS = 500;
40 // Padding from the bottom/right edge the resize widget is shown at.
41 const int kResizeWidgetPadding = 15;
43 bool ContainsX(Window* window, int x) {
44 return window->bounds().x() <= x && window->bounds().right() >= x;
47 bool ContainsY(Window* window, int y) {
48 return window->bounds().y() <= y && window->bounds().bottom() >= y;
51 bool Intersects(int x1, int max_1, int x2, int max_2) {
52 return x2 <= max_1 && max_2 > x1;
55 } // namespace
57 // View contained in the widget. Passes along mouse events to the
58 // MultiWindowResizeController so that it can start/stop the resize loop.
59 class MultiWindowResizeController::ResizeView : public views::View {
60 public:
61 explicit ResizeView(MultiWindowResizeController* controller,
62 Direction direction)
63 : controller_(controller),
64 direction_(direction),
65 image_(NULL) {
66 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
67 int image_id =
68 direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
69 IDR_AURA_MULTI_WINDOW_RESIZE_V;
70 image_ = rb.GetImageNamed(image_id).ToImageSkia();
73 // views::View overrides:
74 gfx::Size GetPreferredSize() const override {
75 return gfx::Size(image_->width(), image_->height());
77 void OnPaint(gfx::Canvas* canvas) override {
78 canvas->DrawImageInt(*image_, 0, 0);
80 bool OnMousePressed(const ui::MouseEvent& event) override {
81 gfx::Point location(event.location());
82 views::View::ConvertPointToScreen(this, &location);
83 controller_->StartResize(location);
84 return true;
86 bool OnMouseDragged(const ui::MouseEvent& event) override {
87 gfx::Point location(event.location());
88 views::View::ConvertPointToScreen(this, &location);
89 controller_->Resize(location, event.flags());
90 return true;
92 void OnMouseReleased(const ui::MouseEvent& event) override {
93 controller_->CompleteResize();
95 void OnMouseCaptureLost() override { controller_->CancelResize(); }
96 gfx::NativeCursor GetCursor(const ui::MouseEvent& event) override {
97 int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
98 return ::wm::CompoundEventFilter::CursorForWindowComponent(
99 component);
102 private:
103 MultiWindowResizeController* controller_;
104 const Direction direction_;
105 const gfx::ImageSkia* image_;
107 DISALLOW_COPY_AND_ASSIGN(ResizeView);
110 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
111 // Contains() to MultiWindowResizeController.
112 class MultiWindowResizeController::ResizeMouseWatcherHost :
113 public views::MouseWatcherHost {
114 public:
115 ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
117 // MouseWatcherHost overrides:
118 bool Contains(const gfx::Point& point_in_screen,
119 MouseEventType type) override {
120 return host_->IsOverWindows(point_in_screen);
123 private:
124 MultiWindowResizeController* host_;
126 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
129 MultiWindowResizeController::ResizeWindows::ResizeWindows()
130 : window1(NULL),
131 window2(NULL),
132 direction(TOP_BOTTOM){
135 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
138 bool MultiWindowResizeController::ResizeWindows::Equals(
139 const ResizeWindows& other) const {
140 return window1 == other.window1 &&
141 window2 == other.window2 &&
142 direction == other.direction;
145 MultiWindowResizeController::MultiWindowResizeController() {
148 MultiWindowResizeController::~MultiWindowResizeController() {
149 window_resizer_.reset();
150 Hide();
153 void MultiWindowResizeController::Show(Window* window,
154 int component,
155 const gfx::Point& point_in_window) {
156 // When the resize widget is showing we ignore Show() requests. Instead we
157 // only care about mouse movements from MouseWatcher. This is necessary as
158 // WorkspaceEventHandler only sees mouse movements over the windows, not all
159 // windows or over the desktop.
160 if (resize_widget_)
161 return;
163 ResizeWindows windows(DetermineWindows(window, component, point_in_window));
164 if (IsShowing()) {
165 if (windows_.Equals(windows))
166 return; // Over the same windows.
167 DelayedHide();
170 if (!windows.is_valid())
171 return;
172 Hide();
173 windows_ = windows;
174 windows_.window1->AddObserver(this);
175 windows_.window2->AddObserver(this);
176 show_location_in_parent_ = point_in_window;
177 Window::ConvertPointToTarget(
178 window, window->parent(), &show_location_in_parent_);
179 if (show_timer_.IsRunning())
180 return;
181 show_timer_.Start(
182 FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
183 this, &MultiWindowResizeController::ShowIfValidMouseLocation);
186 void MultiWindowResizeController::Hide() {
187 hide_timer_.Stop();
188 if (window_resizer_)
189 return; // Ignore hides while actively resizing.
191 if (windows_.window1) {
192 windows_.window1->RemoveObserver(this);
193 windows_.window1 = NULL;
195 if (windows_.window2) {
196 windows_.window2->RemoveObserver(this);
197 windows_.window2 = NULL;
200 show_timer_.Stop();
202 if (!resize_widget_)
203 return;
205 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
206 windows_.other_windows[i]->RemoveObserver(this);
207 mouse_watcher_.reset();
208 resize_widget_.reset();
209 windows_ = ResizeWindows();
212 void MultiWindowResizeController::MouseMovedOutOfHost() {
213 Hide();
216 void MultiWindowResizeController::OnWindowDestroying(
217 aura::Window* window) {
218 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
219 window_resizer_.reset();
220 Hide();
223 MultiWindowResizeController::ResizeWindows
224 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
225 aura::Window* window) const {
226 gfx::Point mouse_location(
227 gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
228 ::wm::ConvertPointFromScreen(window, &mouse_location);
229 const int component =
230 window->delegate()->GetNonClientComponent(mouse_location);
231 return DetermineWindows(window, component, mouse_location);
234 MultiWindowResizeController::ResizeWindows
235 MultiWindowResizeController::DetermineWindows(
236 Window* window,
237 int window_component,
238 const gfx::Point& point) const {
239 ResizeWindows result;
240 gfx::Point point_in_parent(point);
241 Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
242 switch (window_component) {
243 case HTRIGHT:
244 result.direction = LEFT_RIGHT;
245 result.window1 = window;
246 result.window2 = FindWindowByEdge(
247 window, HTLEFT, window->bounds().right(), point_in_parent.y());
248 break;
249 case HTLEFT:
250 result.direction = LEFT_RIGHT;
251 result.window1 = FindWindowByEdge(
252 window, HTRIGHT, window->bounds().x(), point_in_parent.y());
253 result.window2 = window;
254 break;
255 case HTTOP:
256 result.direction = TOP_BOTTOM;
257 result.window1 = FindWindowByEdge(
258 window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
259 result.window2 = window;
260 break;
261 case HTBOTTOM:
262 result.direction = TOP_BOTTOM;
263 result.window1 = window;
264 result.window2 = FindWindowByEdge(
265 window, HTTOP, point_in_parent.x(), window->bounds().bottom());
266 break;
267 default:
268 break;
270 return result;
273 Window* MultiWindowResizeController::FindWindowByEdge(
274 Window* window_to_ignore,
275 int edge_want,
276 int x,
277 int y) const {
278 Window* parent = window_to_ignore->parent();
279 const Window::Windows& windows(parent->children());
280 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
281 i != windows.rend(); ++i) {
282 Window* window = *i;
283 if (window == window_to_ignore || !window->IsVisible())
284 continue;
285 switch (edge_want) {
286 case HTLEFT:
287 if (ContainsY(window, y) && window->bounds().x() == x)
288 return window;
289 break;
290 case HTRIGHT:
291 if (ContainsY(window, y) && window->bounds().right() == x)
292 return window;
293 break;
294 case HTTOP:
295 if (ContainsX(window, x) && window->bounds().y() == y)
296 return window;
297 break;
298 case HTBOTTOM:
299 if (ContainsX(window, x) && window->bounds().bottom() == y)
300 return window;
301 break;
302 default:
303 NOTREACHED();
305 // Window doesn't contain the edge, but if window contains |point|
306 // it's obscuring any other window that could be at the location.
307 if (window->bounds().Contains(x, y))
308 return NULL;
310 return NULL;
313 aura::Window* MultiWindowResizeController::FindWindowTouching(
314 aura::Window* window,
315 Direction direction) const {
316 int right = window->bounds().right();
317 int bottom = window->bounds().bottom();
318 Window* parent = window->parent();
319 const Window::Windows& windows(parent->children());
320 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
321 i != windows.rend(); ++i) {
322 Window* other = *i;
323 if (other == window || !other->IsVisible())
324 continue;
325 switch (direction) {
326 case TOP_BOTTOM:
327 if (other->bounds().y() == bottom &&
328 Intersects(other->bounds().x(), other->bounds().right(),
329 window->bounds().x(), window->bounds().right())) {
330 return other;
332 break;
333 case LEFT_RIGHT:
334 if (other->bounds().x() == right &&
335 Intersects(other->bounds().y(), other->bounds().bottom(),
336 window->bounds().y(), window->bounds().bottom())) {
337 return other;
339 break;
340 default:
341 NOTREACHED();
344 return NULL;
347 void MultiWindowResizeController::FindWindowsTouching(
348 aura::Window* start,
349 Direction direction,
350 std::vector<aura::Window*>* others) const {
351 while (start) {
352 start = FindWindowTouching(start, direction);
353 if (start)
354 others->push_back(start);
358 void MultiWindowResizeController::DelayedHide() {
359 if (hide_timer_.IsRunning())
360 return;
362 hide_timer_.Start(FROM_HERE,
363 base::TimeDelta::FromMilliseconds(kHideDelayMS),
364 this, &MultiWindowResizeController::Hide);
367 void MultiWindowResizeController::ShowIfValidMouseLocation() {
368 if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
369 DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
370 ShowNow();
371 } else {
372 Hide();
376 void MultiWindowResizeController::ShowNow() {
377 DCHECK(!resize_widget_.get());
378 DCHECK(windows_.is_valid());
379 show_timer_.Stop();
380 resize_widget_.reset(new views::Widget);
381 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
382 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
383 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
384 params.parent = Shell::GetContainer(Shell::GetTargetRootWindow(),
385 kShellWindowId_AlwaysOnTopContainer);
386 ResizeView* view = new ResizeView(this, windows_.direction);
387 resize_widget_->set_focus_on_creation(false);
388 resize_widget_->Init(params);
389 ::wm::SetWindowVisibilityAnimationType(
390 resize_widget_->GetNativeWindow(),
391 ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
392 resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
393 resize_widget_->SetContentsView(view);
394 show_bounds_in_screen_ = ScreenUtil::ConvertRectToScreen(
395 windows_.window1->parent(),
396 CalculateResizeWidgetBounds(show_location_in_parent_));
397 resize_widget_->SetBounds(show_bounds_in_screen_);
398 resize_widget_->Show();
399 mouse_watcher_.reset(new views::MouseWatcher(
400 new ResizeMouseWatcherHost(this),
401 this));
402 mouse_watcher_->set_notify_on_exit_time(
403 base::TimeDelta::FromMilliseconds(kHideDelayMS));
404 mouse_watcher_->Start();
407 bool MultiWindowResizeController::IsShowing() const {
408 return resize_widget_.get() || show_timer_.IsRunning();
411 void MultiWindowResizeController::StartResize(
412 const gfx::Point& location_in_screen) {
413 DCHECK(!window_resizer_.get());
414 DCHECK(windows_.is_valid());
415 hide_timer_.Stop();
416 gfx::Point location_in_parent(location_in_screen);
417 aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
418 ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
419 std::vector<aura::Window*> windows;
420 windows.push_back(windows_.window2);
421 DCHECK(windows_.other_windows.empty());
422 FindWindowsTouching(windows_.window2, windows_.direction,
423 &windows_.other_windows);
424 for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
425 windows_.other_windows[i]->AddObserver(this);
426 windows.push_back(windows_.other_windows[i]);
428 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
429 wm::WindowState* window_state = wm::GetWindowState(windows_.window1);
430 window_state->CreateDragDetails(windows_.window1,
431 location_in_parent,
432 component,
433 aura::client::WINDOW_MOVE_SOURCE_MOUSE);
434 window_resizer_.reset(WorkspaceWindowResizer::Create(window_state, windows));
437 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
438 int event_flags) {
439 gfx::Point location_in_parent(location_in_screen);
440 aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
441 ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
442 window_resizer_->Drag(location_in_parent, event_flags);
443 gfx::Rect bounds = ScreenUtil::ConvertRectToScreen(
444 windows_.window1->parent(),
445 CalculateResizeWidgetBounds(location_in_parent));
447 if (windows_.direction == LEFT_RIGHT)
448 bounds.set_y(show_bounds_in_screen_.y());
449 else
450 bounds.set_x(show_bounds_in_screen_.x());
451 resize_widget_->SetBounds(bounds);
454 void MultiWindowResizeController::CompleteResize() {
455 window_resizer_->CompleteDrag();
456 wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
457 window_resizer_.reset();
459 // Mouse may still be over resizer, if not hide.
460 gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
461 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
462 Hide();
463 } else {
464 // If the mouse is over the resizer we need to remove observers on any of
465 // the |other_windows|. If we start another resize we'll recalculate the
466 // |other_windows| and invoke AddObserver() as necessary.
467 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
468 windows_.other_windows[i]->RemoveObserver(this);
469 windows_.other_windows.clear();
473 void MultiWindowResizeController::CancelResize() {
474 if (!window_resizer_)
475 return; // Happens if window was destroyed and we nuked the WindowResizer.
476 window_resizer_->RevertDrag();
477 wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
478 window_resizer_.reset();
479 Hide();
482 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
483 const gfx::Point& location_in_parent) const {
484 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
485 int x = 0, y = 0;
486 if (windows_.direction == LEFT_RIGHT) {
487 x = windows_.window1->bounds().right() - pref.width() / 2;
488 y = location_in_parent.y() + kResizeWidgetPadding;
489 if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
490 y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
491 y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
493 } else {
494 x = location_in_parent.x() + kResizeWidgetPadding;
495 if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
496 x + pref.height() / 2 > windows_.window2->bounds().right()) {
497 x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
499 y = windows_.window1->bounds().bottom() - pref.height() / 2;
501 return gfx::Rect(x, y, pref.width(), pref.height());
504 bool MultiWindowResizeController::IsOverWindows(
505 const gfx::Point& location_in_screen) const {
506 if (window_resizer_)
507 return true; // Ignore hides while actively resizing.
509 if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
510 return true;
512 int hit1, hit2;
513 if (windows_.direction == TOP_BOTTOM) {
514 hit1 = HTBOTTOM;
515 hit2 = HTTOP;
516 } else {
517 hit1 = HTRIGHT;
518 hit2 = HTLEFT;
521 return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
522 IsOverWindow(windows_.window2, location_in_screen, hit2);
525 bool MultiWindowResizeController::IsOverWindow(
526 aura::Window* window,
527 const gfx::Point& location_in_screen,
528 int component) const {
529 if (!window->delegate())
530 return false;
532 gfx::Point window_loc(location_in_screen);
533 aura::Window::ConvertPointToTarget(
534 window->GetRootWindow(), window, &window_loc);
535 return window->ContainsPoint(window_loc) &&
536 window->delegate()->GetNonClientComponent(window_loc) == component;
539 } // namespace ash