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"
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"
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
;
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
{
61 explicit ResizeView(MultiWindowResizeController
* controller
,
63 : controller_(controller
),
64 direction_(direction
),
66 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
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 virtual gfx::Size
GetPreferredSize() const OVERRIDE
{
75 return gfx::Size(image_
->width(), image_
->height());
77 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
78 canvas
->DrawImageInt(*image_
, 0, 0);
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
);
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());
92 virtual void OnMouseReleased(const ui::MouseEvent
& event
) OVERRIDE
{
93 controller_
->CompleteResize();
95 virtual void OnMouseCaptureLost() OVERRIDE
{
96 controller_
->CancelResize();
98 virtual gfx::NativeCursor
GetCursor(
99 const ui::MouseEvent
& event
) OVERRIDE
{
100 int component
= (direction_
== LEFT_RIGHT
) ? HTRIGHT
: HTBOTTOM
;
101 return ::wm::CompoundEventFilter::CursorForWindowComponent(
106 MultiWindowResizeController
* controller_
;
107 const Direction direction_
;
108 const gfx::ImageSkia
* image_
;
110 DISALLOW_COPY_AND_ASSIGN(ResizeView
);
113 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
114 // Contains() to MultiWindowResizeController.
115 class MultiWindowResizeController::ResizeMouseWatcherHost
:
116 public views::MouseWatcherHost
{
118 ResizeMouseWatcherHost(MultiWindowResizeController
* host
) : host_(host
) {}
120 // MouseWatcherHost overrides:
121 virtual bool Contains(const gfx::Point
& point_in_screen
,
122 MouseEventType type
) OVERRIDE
{
123 return host_
->IsOverWindows(point_in_screen
);
127 MultiWindowResizeController
* host_
;
129 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost
);
132 MultiWindowResizeController::ResizeWindows::ResizeWindows()
135 direction(TOP_BOTTOM
){
138 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
141 bool MultiWindowResizeController::ResizeWindows::Equals(
142 const ResizeWindows
& other
) const {
143 return window1
== other
.window1
&&
144 window2
== other
.window2
&&
145 direction
== other
.direction
;
148 MultiWindowResizeController::MultiWindowResizeController() {
151 MultiWindowResizeController::~MultiWindowResizeController() {
152 window_resizer_
.reset();
156 void MultiWindowResizeController::Show(Window
* window
,
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.
166 ResizeWindows
windows(DetermineWindows(window
, component
, point_in_window
));
168 if (windows_
.Equals(windows
))
169 return; // Over the same windows.
173 if (!windows
.is_valid())
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())
185 FROM_HERE
, base::TimeDelta::FromMilliseconds(kShowDelayMS
),
186 this, &MultiWindowResizeController::ShowIfValidMouseLocation
);
189 void MultiWindowResizeController::Hide() {
192 return; // Ignore hides while actively resizing.
194 if (windows_
.window1
) {
195 windows_
.window1
->RemoveObserver(this);
196 windows_
.window1
= NULL
;
198 if (windows_
.window2
) {
199 windows_
.window2
->RemoveObserver(this);
200 windows_
.window2
= NULL
;
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();
215 void MultiWindowResizeController::MouseMovedOutOfHost() {
219 void MultiWindowResizeController::OnWindowDestroying(
220 aura::Window
* window
) {
221 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
222 window_resizer_
.reset();
226 MultiWindowResizeController::ResizeWindows
227 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
);
237 MultiWindowResizeController::ResizeWindows
238 MultiWindowResizeController::DetermineWindows(
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
) {
247 result
.direction
= LEFT_RIGHT
;
248 result
.window1
= window
;
249 result
.window2
= FindWindowByEdge(
250 window
, HTLEFT
, window
->bounds().right(), point_in_parent
.y());
253 result
.direction
= LEFT_RIGHT
;
254 result
.window1
= FindWindowByEdge(
255 window
, HTRIGHT
, window
->bounds().x(), point_in_parent
.y());
256 result
.window2
= window
;
259 result
.direction
= TOP_BOTTOM
;
260 result
.window1
= FindWindowByEdge(
261 window
, HTBOTTOM
, point_in_parent
.x(), window
->bounds().y());
262 result
.window2
= window
;
265 result
.direction
= TOP_BOTTOM
;
266 result
.window1
= window
;
267 result
.window2
= FindWindowByEdge(
268 window
, HTTOP
, point_in_parent
.x(), window
->bounds().bottom());
276 Window
* MultiWindowResizeController::FindWindowByEdge(
277 Window
* window_to_ignore
,
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
) {
286 if (window
== window_to_ignore
|| !window
->IsVisible())
290 if (ContainsY(window
, y
) && window
->bounds().x() == x
)
294 if (ContainsY(window
, y
) && window
->bounds().right() == x
)
298 if (ContainsX(window
, x
) && window
->bounds().y() == y
)
302 if (ContainsX(window
, x
) && window
->bounds().bottom() == y
)
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
))
316 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
) {
326 if (other
== window
|| !other
->IsVisible())
330 if (other
->bounds().y() == bottom
&&
331 Intersects(other
->bounds().x(), other
->bounds().right(),
332 window
->bounds().x(), window
->bounds().right())) {
337 if (other
->bounds().x() == right
&&
338 Intersects(other
->bounds().y(), other
->bounds().bottom(),
339 window
->bounds().y(), window
->bounds().bottom())) {
350 void MultiWindowResizeController::FindWindowsTouching(
353 std::vector
<aura::Window
*>* others
) const {
355 start
= FindWindowTouching(start
, direction
);
357 others
->push_back(start
);
361 void MultiWindowResizeController::DelayedHide() {
362 if (hide_timer_
.IsRunning())
365 hide_timer_
.Start(FROM_HERE
,
366 base::TimeDelta::FromMilliseconds(kHideDelayMS
),
367 this, &MultiWindowResizeController::Hide
);
370 void MultiWindowResizeController::ShowIfValidMouseLocation() {
371 if (DetermineWindowsFromScreenPoint(windows_
.window1
).Equals(windows_
) ||
372 DetermineWindowsFromScreenPoint(windows_
.window2
).Equals(windows_
)) {
379 void MultiWindowResizeController::ShowNow() {
380 DCHECK(!resize_widget_
.get());
381 DCHECK(windows_
.is_valid());
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),
405 mouse_watcher_
->set_notify_on_exit_time(
406 base::TimeDelta::FromMilliseconds(kHideDelayMS
));
407 mouse_watcher_
->Start();
410 bool MultiWindowResizeController::IsShowing() const {
411 return resize_widget_
.get() || show_timer_
.IsRunning();
414 void MultiWindowResizeController::StartResize(
415 const gfx::Point
& location_in_screen
) {
416 DCHECK(!window_resizer_
.get());
417 DCHECK(windows_
.is_valid());
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
]);
431 int component
= windows_
.direction
== LEFT_RIGHT
? HTRIGHT
: HTBOTTOM
;
432 wm::WindowState
* window_state
= wm::GetWindowState(windows_
.window1
);
433 window_state
->CreateDragDetails(windows_
.window1
,
436 aura::client::WINDOW_MOVE_SOURCE_MOUSE
);
437 window_resizer_
.reset(WorkspaceWindowResizer::Create(window_state
, windows
));
440 void MultiWindowResizeController::Resize(const gfx::Point
& location_in_screen
,
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
));
450 if (windows_
.direction
== LEFT_RIGHT
)
451 bounds
.set_y(show_bounds_in_screen_
.y());
453 bounds
.set_x(show_bounds_in_screen_
.x());
454 resize_widget_
->SetBounds(bounds
);
457 void MultiWindowResizeController::CompleteResize() {
458 window_resizer_
->CompleteDrag();
459 wm::GetWindowState(window_resizer_
->GetTarget())->DeleteDragDetails();
460 window_resizer_
.reset();
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
)) {
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();
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();
485 gfx::Rect
MultiWindowResizeController::CalculateResizeWidgetBounds(
486 const gfx::Point
& location_in_parent
) const {
487 gfx::Size pref
= resize_widget_
->GetContentsView()->GetPreferredSize();
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();
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();
502 y
= windows_
.window1
->bounds().bottom() - pref
.height() / 2;
504 return gfx::Rect(x
, y
, pref
.width(), pref
.height());
507 bool MultiWindowResizeController::IsOverWindows(
508 const gfx::Point
& location_in_screen
) const {
510 return true; // Ignore hides while actively resizing.
512 if (resize_widget_
->GetWindowBoundsInScreen().Contains(location_in_screen
))
516 if (windows_
.direction
== TOP_BOTTOM
) {
524 return IsOverWindow(windows_
.window1
, location_in_screen
, hit1
) ||
525 IsOverWindow(windows_
.window2
, location_in_screen
, hit2
);
528 bool MultiWindowResizeController::IsOverWindow(
529 aura::Window
* window
,
530 const gfx::Point
& location_in_screen
,
531 int component
) const {
532 if (!window
->delegate())
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
;