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/wm/workspace/multi_window_resize_controller.h"
6
7 #include "ash/screen_ash.h"
8 #include "ash/shell.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/coordinate_conversion.h"
11 #include "ash/wm/window_animations.h"
12 #include "ash/wm/workspace/workspace_event_handler.h"
13 #include "ash/wm/workspace/workspace_window_resizer.h"
14 #include "grit/ash_resources.h"
15 #include "ui/aura/client/screen_position_client.h"
16 #include "ui/aura/root_window.h"
17 #include "ui/aura/window.h"
18 #include "ui/aura/window_delegate.h"
19 #include "ui/base/hit_test.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/gfx/canvas.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/screen.h"
24 #include "ui/views/corewm/compound_event_filter.h"
25 #include "ui/views/view.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/views/widget/widget_delegate.h"
28
29 using aura::Window;
30
31 namespace ash {
32 namespace internal {
33
34 namespace {
35
36 // Delay before showing.
37 const int kShowDelayMS = 400;
38
39 // Delay before hiding.
40 const int kHideDelayMS = 500;
41
42 // Padding from the bottom/right edge the resize widget is shown at.
43 const int kResizeWidgetPadding = 15;
44
ContainsX(Window * window,int x)45 bool ContainsX(Window* window, int x) {
46 return window->bounds().x() <= x && window->bounds().right() >= x;
47 }
48
ContainsY(Window * window,int y)49 bool ContainsY(Window* window, int y) {
50 return window->bounds().y() <= y && window->bounds().bottom() >= y;
51 }
52
Intersects(int x1,int max_1,int x2,int max_2)53 bool Intersects(int x1, int max_1, int x2, int max_2) {
54 return x2 <= max_1 && max_2 > x1;
55 }
56
57 } // namespace
58
59 // View contained in the widget. Passes along mouse events to the
60 // MultiWindowResizeController so that it can start/stop the resize loop.
61 class MultiWindowResizeController::ResizeView : public views::View {
62 public:
ResizeView(MultiWindowResizeController * controller,Direction direction)63 explicit ResizeView(MultiWindowResizeController* controller,
64 Direction direction)
65 : controller_(controller),
66 direction_(direction),
67 image_(NULL) {
68 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
69 int image_id =
70 direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
71 IDR_AURA_MULTI_WINDOW_RESIZE_V;
72 image_ = rb.GetImageNamed(image_id).ToImageSkia();
73 }
74
75 // views::View overrides:
GetPreferredSize()76 virtual gfx::Size GetPreferredSize() OVERRIDE {
77 return gfx::Size(image_->width(), image_->height());
78 }
OnPaint(gfx::Canvas * canvas)79 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
80 canvas->DrawImageInt(*image_, 0, 0);
81 }
OnMousePressed(const ui::MouseEvent & event)82 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
83 gfx::Point location(event.location());
84 views::View::ConvertPointToScreen(this, &location);
85 controller_->StartResize(location);
86 return true;
87 }
OnMouseDragged(const ui::MouseEvent & event)88 virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE {
89 gfx::Point location(event.location());
90 views::View::ConvertPointToScreen(this, &location);
91 controller_->Resize(location, event.flags());
92 return true;
93 }
OnMouseReleased(const ui::MouseEvent & event)94 virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
95 controller_->CompleteResize(event.flags());
96 }
OnMouseCaptureLost()97 virtual void OnMouseCaptureLost() OVERRIDE {
98 controller_->CancelResize();
99 }
GetCursor(const ui::MouseEvent & event)100 virtual gfx::NativeCursor GetCursor(
101 const ui::MouseEvent& event) OVERRIDE {
102 int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
103 return views::corewm::CompoundEventFilter::CursorForWindowComponent(
104 component);
105 }
106
107 private:
108 MultiWindowResizeController* controller_;
109 const Direction direction_;
110 const gfx::ImageSkia* image_;
111
112 DISALLOW_COPY_AND_ASSIGN(ResizeView);
113 };
114
115 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
116 // Contains() to MultiWindowResizeController.
117 class MultiWindowResizeController::ResizeMouseWatcherHost :
118 public views::MouseWatcherHost {
119 public:
ResizeMouseWatcherHost(MultiWindowResizeController * host)120 ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
121
122 // MouseWatcherHost overrides:
Contains(const gfx::Point & point_in_screen,MouseEventType type)123 virtual bool Contains(const gfx::Point& point_in_screen,
124 MouseEventType type) OVERRIDE {
125 return host_->IsOverWindows(point_in_screen);
126 }
127
128 private:
129 MultiWindowResizeController* host_;
130
131 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
132 };
133
ResizeWindows()134 MultiWindowResizeController::ResizeWindows::ResizeWindows()
135 : window1(NULL),
136 window2(NULL),
137 direction(TOP_BOTTOM){
138 }
139
~ResizeWindows()140 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
141 }
142
Equals(const ResizeWindows & other) const143 bool MultiWindowResizeController::ResizeWindows::Equals(
144 const ResizeWindows& other) const {
145 return window1 == other.window1 &&
146 window2 == other.window2 &&
147 direction == other.direction;
148 }
149
MultiWindowResizeController()150 MultiWindowResizeController::MultiWindowResizeController() {
151 }
152
~MultiWindowResizeController()153 MultiWindowResizeController::~MultiWindowResizeController() {
154 window_resizer_.reset();
155 Hide();
156 }
157
Show(Window * window,int component,const gfx::Point & point_in_window)158 void MultiWindowResizeController::Show(Window* window,
159 int component,
160 const gfx::Point& point_in_window) {
161 // When the resize widget is showing we ignore Show() requests. Instead we
162 // only care about mouse movements from MouseWatcher. This is necessary as
163 // WorkspaceEventHandler only sees mouse movements over the windows, not all
164 // windows or over the desktop.
165 if (resize_widget_)
166 return;
167
168 ResizeWindows windows(DetermineWindows(window, component, point_in_window));
169 if (IsShowing()) {
170 if (windows_.Equals(windows))
171 return; // Over the same windows.
172 DelayedHide();
173 }
174
175 if (!windows.is_valid())
176 return;
177 Hide();
178 windows_ = windows;
179 windows_.window1->AddObserver(this);
180 windows_.window2->AddObserver(this);
181 show_location_in_parent_ = point_in_window;
182 Window::ConvertPointToTarget(
183 window, window->parent(), &show_location_in_parent_);
184 if (show_timer_.IsRunning())
185 return;
186 show_timer_.Start(
187 FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
188 this, &MultiWindowResizeController::ShowIfValidMouseLocation);
189 }
190
Hide()191 void MultiWindowResizeController::Hide() {
192 hide_timer_.Stop();
193 if (window_resizer_)
194 return; // Ignore hides while actively resizing.
195
196 if (windows_.window1) {
197 windows_.window1->RemoveObserver(this);
198 windows_.window1 = NULL;
199 }
200 if (windows_.window2) {
201 windows_.window2->RemoveObserver(this);
202 windows_.window2 = NULL;
203 }
204
205 show_timer_.Stop();
206
207 if (!resize_widget_)
208 return;
209
210 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
211 windows_.other_windows[i]->RemoveObserver(this);
212 mouse_watcher_.reset();
213 resize_widget_.reset();
214 windows_ = ResizeWindows();
215 }
216
MouseMovedOutOfHost()217 void MultiWindowResizeController::MouseMovedOutOfHost() {
218 Hide();
219 }
220
OnWindowDestroying(aura::Window * window)221 void MultiWindowResizeController::OnWindowDestroying(
222 aura::Window* window) {
223 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
224 window_resizer_.reset();
225 Hide();
226 }
227
228 MultiWindowResizeController::ResizeWindows
DetermineWindowsFromScreenPoint(aura::Window * window) const229 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
230 aura::Window* window) const {
231 gfx::Point mouse_location(
232 gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
233 wm::ConvertPointFromScreen(window, &mouse_location);
234 const int component =
235 window->delegate()->GetNonClientComponent(mouse_location);
236 return DetermineWindows(window, component, mouse_location);
237 }
238
239 MultiWindowResizeController::ResizeWindows
DetermineWindows(Window * window,int window_component,const gfx::Point & point) const240 MultiWindowResizeController::DetermineWindows(
241 Window* window,
242 int window_component,
243 const gfx::Point& point) const {
244 ResizeWindows result;
245 gfx::Point point_in_parent(point);
246 Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
247 switch (window_component) {
248 case HTRIGHT:
249 result.direction = LEFT_RIGHT;
250 result.window1 = window;
251 result.window2 = FindWindowByEdge(
252 window, HTLEFT, window->bounds().right(), point_in_parent.y());
253 break;
254 case HTLEFT:
255 result.direction = LEFT_RIGHT;
256 result.window1 = FindWindowByEdge(
257 window, HTRIGHT, window->bounds().x(), point_in_parent.y());
258 result.window2 = window;
259 break;
260 case HTTOP:
261 result.direction = TOP_BOTTOM;
262 result.window1 = FindWindowByEdge(
263 window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
264 result.window2 = window;
265 break;
266 case HTBOTTOM:
267 result.direction = TOP_BOTTOM;
268 result.window1 = window;
269 result.window2 = FindWindowByEdge(
270 window, HTTOP, point_in_parent.x(), window->bounds().bottom());
271 break;
272 default:
273 break;
274 }
275 return result;
276 }
277
FindWindowByEdge(Window * window_to_ignore,int edge_want,int x,int y) const278 Window* MultiWindowResizeController::FindWindowByEdge(
279 Window* window_to_ignore,
280 int edge_want,
281 int x,
282 int y) const {
283 Window* parent = window_to_ignore->parent();
284 const Window::Windows& windows(parent->children());
285 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
286 i != windows.rend(); ++i) {
287 Window* window = *i;
288 if (window == window_to_ignore || !window->IsVisible())
289 continue;
290 switch (edge_want) {
291 case HTLEFT:
292 if (ContainsY(window, y) && window->bounds().x() == x)
293 return window;
294 break;
295 case HTRIGHT:
296 if (ContainsY(window, y) && window->bounds().right() == x)
297 return window;
298 break;
299 case HTTOP:
300 if (ContainsX(window, x) && window->bounds().y() == y)
301 return window;
302 break;
303 case HTBOTTOM:
304 if (ContainsX(window, x) && window->bounds().bottom() == y)
305 return window;
306 break;
307 default:
308 NOTREACHED();
309 }
310 // Window doesn't contain the edge, but if window contains |point|
311 // it's obscuring any other window that could be at the location.
312 if (window->bounds().Contains(x, y))
313 return NULL;
314 }
315 return NULL;
316 }
317
FindWindowTouching(aura::Window * window,Direction direction) const318 aura::Window* MultiWindowResizeController::FindWindowTouching(
319 aura::Window* window,
320 Direction direction) const {
321 int right = window->bounds().right();
322 int bottom = window->bounds().bottom();
323 Window* parent = window->parent();
324 const Window::Windows& windows(parent->children());
325 for (Window::Windows::const_reverse_iterator i = windows.rbegin();
326 i != windows.rend(); ++i) {
327 Window* other = *i;
328 if (other == window || !other->IsVisible())
329 continue;
330 switch (direction) {
331 case TOP_BOTTOM:
332 if (other->bounds().y() == bottom &&
333 Intersects(other->bounds().x(), other->bounds().right(),
334 window->bounds().x(), window->bounds().right())) {
335 return other;
336 }
337 break;
338 case LEFT_RIGHT:
339 if (other->bounds().x() == right &&
340 Intersects(other->bounds().y(), other->bounds().bottom(),
341 window->bounds().y(), window->bounds().bottom())) {
342 return other;
343 }
344 break;
345 default:
346 NOTREACHED();
347 }
348 }
349 return NULL;
350 }
351
FindWindowsTouching(aura::Window * start,Direction direction,std::vector<aura::Window * > * others) const352 void MultiWindowResizeController::FindWindowsTouching(
353 aura::Window* start,
354 Direction direction,
355 std::vector<aura::Window*>* others) const {
356 while (start) {
357 start = FindWindowTouching(start, direction);
358 if (start)
359 others->push_back(start);
360 }
361 }
362
DelayedHide()363 void MultiWindowResizeController::DelayedHide() {
364 if (hide_timer_.IsRunning())
365 return;
366
367 hide_timer_.Start(FROM_HERE,
368 base::TimeDelta::FromMilliseconds(kHideDelayMS),
369 this, &MultiWindowResizeController::Hide);
370 }
371
ShowIfValidMouseLocation()372 void MultiWindowResizeController::ShowIfValidMouseLocation() {
373 if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
374 DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
375 ShowNow();
376 } else {
377 Hide();
378 }
379 }
380
ShowNow()381 void MultiWindowResizeController::ShowNow() {
382 DCHECK(!resize_widget_.get());
383 DCHECK(windows_.is_valid());
384 show_timer_.Stop();
385 resize_widget_.reset(new views::Widget);
386 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
387 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
388 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
389 params.parent = Shell::GetContainer(
390 Shell::GetTargetRootWindow(),
391 internal::kShellWindowId_AlwaysOnTopContainer);
392 params.can_activate = false;
393 ResizeView* view = new ResizeView(this, windows_.direction);
394 resize_widget_->set_focus_on_creation(false);
395 resize_widget_->Init(params);
396 views::corewm::SetWindowVisibilityAnimationType(
397 resize_widget_->GetNativeWindow(),
398 views::corewm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
399 resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
400 resize_widget_->SetContentsView(view);
401 show_bounds_in_screen_ = ScreenAsh::ConvertRectToScreen(
402 windows_.window1->parent(),
403 CalculateResizeWidgetBounds(show_location_in_parent_));
404 resize_widget_->SetBounds(show_bounds_in_screen_);
405 resize_widget_->Show();
406 mouse_watcher_.reset(new views::MouseWatcher(
407 new ResizeMouseWatcherHost(this),
408 this));
409 mouse_watcher_->set_notify_on_exit_time(
410 base::TimeDelta::FromMilliseconds(kHideDelayMS));
411 mouse_watcher_->Start();
412 }
413
IsShowing() const414 bool MultiWindowResizeController::IsShowing() const {
415 return resize_widget_.get() || show_timer_.IsRunning();
416 }
417
StartResize(const gfx::Point & location_in_screen)418 void MultiWindowResizeController::StartResize(
419 const gfx::Point& location_in_screen) {
420 DCHECK(!window_resizer_.get());
421 DCHECK(windows_.is_valid());
422 hide_timer_.Stop();
423 gfx::Point location_in_parent(location_in_screen);
424 aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
425 ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
426 std::vector<aura::Window*> windows;
427 windows.push_back(windows_.window2);
428 DCHECK(windows_.other_windows.empty());
429 FindWindowsTouching(windows_.window2, windows_.direction,
430 &windows_.other_windows);
431 for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
432 windows_.other_windows[i]->AddObserver(this);
433 windows.push_back(windows_.other_windows[i]);
434 }
435 int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
436 window_resizer_.reset(WorkspaceWindowResizer::Create(
437 windows_.window1,
438 location_in_parent,
439 component,
440 aura::client::WINDOW_MOVE_SOURCE_MOUSE,
441 windows));
442 }
443
Resize(const gfx::Point & location_in_screen,int event_flags)444 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
445 int event_flags) {
446 gfx::Point location_in_parent(location_in_screen);
447 aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
448 ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
449 window_resizer_->Drag(location_in_parent, event_flags);
450 gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(
451 windows_.window1->parent(),
452 CalculateResizeWidgetBounds(location_in_parent));
453
454 if (windows_.direction == LEFT_RIGHT)
455 bounds.set_y(show_bounds_in_screen_.y());
456 else
457 bounds.set_x(show_bounds_in_screen_.x());
458 resize_widget_->SetBounds(bounds);
459 }
460
CompleteResize(int event_flags)461 void MultiWindowResizeController::CompleteResize(int event_flags) {
462 window_resizer_->CompleteDrag(event_flags);
463 window_resizer_.reset();
464
465 // Mouse may still be over resizer, if not hide.
466 gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
467 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
468 Hide();
469 } else {
470 // If the mouse is over the resizer we need to remove observers on any of
471 // the |other_windows|. If we start another resize we'll recalculate the
472 // |other_windows| and invoke AddObserver() as necessary.
473 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
474 windows_.other_windows[i]->RemoveObserver(this);
475 windows_.other_windows.clear();
476 }
477 }
478
CancelResize()479 void MultiWindowResizeController::CancelResize() {
480 if (!window_resizer_)
481 return; // Happens if window was destroyed and we nuked the WindowResizer.
482 window_resizer_->RevertDrag();
483 window_resizer_.reset();
484 Hide();
485 }
486
CalculateResizeWidgetBounds(const gfx::Point & location_in_parent) const487 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
488 const gfx::Point& location_in_parent) const {
489 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
490 int x = 0, y = 0;
491 if (windows_.direction == LEFT_RIGHT) {
492 x = windows_.window1->bounds().right() - pref.width() / 2;
493 y = location_in_parent.y() + kResizeWidgetPadding;
494 if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
495 y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
496 y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
497 }
498 } else {
499 x = location_in_parent.x() + kResizeWidgetPadding;
500 if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
501 x + pref.height() / 2 > windows_.window2->bounds().right()) {
502 x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
503 }
504 y = windows_.window1->bounds().bottom() - pref.height() / 2;
505 }
506 return gfx::Rect(x, y, pref.width(), pref.height());
507 }
508
IsOverWindows(const gfx::Point & location_in_screen) const509 bool MultiWindowResizeController::IsOverWindows(
510 const gfx::Point& location_in_screen) const {
511 if (window_resizer_)
512 return true; // Ignore hides while actively resizing.
513
514 if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
515 return true;
516
517 int hit1, hit2;
518 if (windows_.direction == TOP_BOTTOM) {
519 hit1 = HTBOTTOM;
520 hit2 = HTTOP;
521 } else {
522 hit1 = HTRIGHT;
523 hit2 = HTLEFT;
524 }
525
526 return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
527 IsOverWindow(windows_.window2, location_in_screen, hit2);
528 }
529
IsOverWindow(aura::Window * window,const gfx::Point & location_in_screen,int component) const530 bool MultiWindowResizeController::IsOverWindow(
531 aura::Window* window,
532 const gfx::Point& location_in_screen,
533 int component) const {
534 if (!window->delegate())
535 return false;
536
537 gfx::Point window_loc(location_in_screen);
538 aura::Window::ConvertPointToTarget(
539 window->GetRootWindow(), window, &window_loc);
540 return window->HitTest(window_loc) &&
541 window->delegate()->GetNonClientComponent(window_loc) == component;
542 }
543
544 } // namespace internal
545 } // namespace ash
546