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