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_ash.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"
36 // Delay before showing.
37 const int kShowDelayMS
= 400;
39 // Delay before hiding.
40 const int kHideDelayMS
= 500;
42 // Padding from the bottom/right edge the resize widget is shown at.
43 const int kResizeWidgetPadding
= 15;
45 bool ContainsX(Window
* window
, int x
) {
46 return window
->bounds().x() <= x
&& window
->bounds().right() >= x
;
49 bool ContainsY(Window
* window
, int y
) {
50 return window
->bounds().y() <= y
&& window
->bounds().bottom() >= y
;
53 bool Intersects(int x1
, int max_1
, int x2
, int max_2
) {
54 return x2
<= max_1
&& max_2
> x1
;
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
{
63 explicit ResizeView(MultiWindowResizeController
* controller
,
65 : controller_(controller
),
66 direction_(direction
),
68 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
70 direction
== TOP_BOTTOM
? IDR_AURA_MULTI_WINDOW_RESIZE_H
:
71 IDR_AURA_MULTI_WINDOW_RESIZE_V
;
72 image_
= rb
.GetImageNamed(image_id
).ToImageSkia();
75 // views::View overrides:
76 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
77 return gfx::Size(image_
->width(), image_
->height());
79 virtual void OnPaint(gfx::Canvas
* canvas
) OVERRIDE
{
80 canvas
->DrawImageInt(*image_
, 0, 0);
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
);
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());
94 virtual void OnMouseReleased(const ui::MouseEvent
& event
) OVERRIDE
{
95 controller_
->CompleteResize(event
.flags());
97 virtual void OnMouseCaptureLost() OVERRIDE
{
98 controller_
->CancelResize();
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(
108 MultiWindowResizeController
* controller_
;
109 const Direction direction_
;
110 const gfx::ImageSkia
* image_
;
112 DISALLOW_COPY_AND_ASSIGN(ResizeView
);
115 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
116 // Contains() to MultiWindowResizeController.
117 class MultiWindowResizeController::ResizeMouseWatcherHost
:
118 public views::MouseWatcherHost
{
120 ResizeMouseWatcherHost(MultiWindowResizeController
* host
) : host_(host
) {}
122 // MouseWatcherHost overrides:
123 virtual bool Contains(const gfx::Point
& point_in_screen
,
124 MouseEventType type
) OVERRIDE
{
125 return host_
->IsOverWindows(point_in_screen
);
129 MultiWindowResizeController
* host_
;
131 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost
);
134 MultiWindowResizeController::ResizeWindows::ResizeWindows()
137 direction(TOP_BOTTOM
){
140 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
143 bool MultiWindowResizeController::ResizeWindows::Equals(
144 const ResizeWindows
& other
) const {
145 return window1
== other
.window1
&&
146 window2
== other
.window2
&&
147 direction
== other
.direction
;
150 MultiWindowResizeController::MultiWindowResizeController() {
153 MultiWindowResizeController::~MultiWindowResizeController() {
154 window_resizer_
.reset();
158 void MultiWindowResizeController::Show(Window
* window
,
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.
168 ResizeWindows
windows(DetermineWindows(window
, component
, point_in_window
));
170 if (windows_
.Equals(windows
))
171 return; // Over the same windows.
175 if (!windows
.is_valid())
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())
187 FROM_HERE
, base::TimeDelta::FromMilliseconds(kShowDelayMS
),
188 this, &MultiWindowResizeController::ShowIfValidMouseLocation
);
191 void MultiWindowResizeController::Hide() {
194 return; // Ignore hides while actively resizing.
196 if (windows_
.window1
) {
197 windows_
.window1
->RemoveObserver(this);
198 windows_
.window1
= NULL
;
200 if (windows_
.window2
) {
201 windows_
.window2
->RemoveObserver(this);
202 windows_
.window2
= NULL
;
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();
217 void MultiWindowResizeController::MouseMovedOutOfHost() {
221 void MultiWindowResizeController::OnWindowDestroying(
222 aura::Window
* window
) {
223 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
224 window_resizer_
.reset();
228 MultiWindowResizeController::ResizeWindows
229 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
);
239 MultiWindowResizeController::ResizeWindows
240 MultiWindowResizeController::DetermineWindows(
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
) {
249 result
.direction
= LEFT_RIGHT
;
250 result
.window1
= window
;
251 result
.window2
= FindWindowByEdge(
252 window
, HTLEFT
, window
->bounds().right(), point_in_parent
.y());
255 result
.direction
= LEFT_RIGHT
;
256 result
.window1
= FindWindowByEdge(
257 window
, HTRIGHT
, window
->bounds().x(), point_in_parent
.y());
258 result
.window2
= window
;
261 result
.direction
= TOP_BOTTOM
;
262 result
.window1
= FindWindowByEdge(
263 window
, HTBOTTOM
, point_in_parent
.x(), window
->bounds().y());
264 result
.window2
= window
;
267 result
.direction
= TOP_BOTTOM
;
268 result
.window1
= window
;
269 result
.window2
= FindWindowByEdge(
270 window
, HTTOP
, point_in_parent
.x(), window
->bounds().bottom());
278 Window
* MultiWindowResizeController::FindWindowByEdge(
279 Window
* window_to_ignore
,
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
) {
288 if (window
== window_to_ignore
|| !window
->IsVisible())
292 if (ContainsY(window
, y
) && window
->bounds().x() == x
)
296 if (ContainsY(window
, y
) && window
->bounds().right() == x
)
300 if (ContainsX(window
, x
) && window
->bounds().y() == y
)
304 if (ContainsX(window
, x
) && window
->bounds().bottom() == y
)
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
))
318 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
) {
328 if (other
== window
|| !other
->IsVisible())
332 if (other
->bounds().y() == bottom
&&
333 Intersects(other
->bounds().x(), other
->bounds().right(),
334 window
->bounds().x(), window
->bounds().right())) {
339 if (other
->bounds().x() == right
&&
340 Intersects(other
->bounds().y(), other
->bounds().bottom(),
341 window
->bounds().y(), window
->bounds().bottom())) {
352 void MultiWindowResizeController::FindWindowsTouching(
355 std::vector
<aura::Window
*>* others
) const {
357 start
= FindWindowTouching(start
, direction
);
359 others
->push_back(start
);
363 void MultiWindowResizeController::DelayedHide() {
364 if (hide_timer_
.IsRunning())
367 hide_timer_
.Start(FROM_HERE
,
368 base::TimeDelta::FromMilliseconds(kHideDelayMS
),
369 this, &MultiWindowResizeController::Hide
);
372 void MultiWindowResizeController::ShowIfValidMouseLocation() {
373 if (DetermineWindowsFromScreenPoint(windows_
.window1
).Equals(windows_
) ||
374 DetermineWindowsFromScreenPoint(windows_
.window2
).Equals(windows_
)) {
381 void MultiWindowResizeController::ShowNow() {
382 DCHECK(!resize_widget_
.get());
383 DCHECK(windows_
.is_valid());
385 resize_widget_
.reset(new views::Widget
);
386 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
387 params
.transparent
= true;
388 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
389 params
.parent
= Shell::GetContainer(
390 Shell::GetActiveRootWindow(),
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),
409 mouse_watcher_
->set_notify_on_exit_time(
410 base::TimeDelta::FromMilliseconds(kHideDelayMS
));
411 mouse_watcher_
->Start();
414 bool MultiWindowResizeController::IsShowing() const {
415 return resize_widget_
.get() || show_timer_
.IsRunning();
418 void MultiWindowResizeController::StartResize(
419 const gfx::Point
& location_in_screen
) {
420 DCHECK(!window_resizer_
.get());
421 DCHECK(windows_
.is_valid());
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
]);
435 int component
= windows_
.direction
== LEFT_RIGHT
? HTRIGHT
: HTBOTTOM
;
436 window_resizer_
.reset(WorkspaceWindowResizer::Create(
437 windows_
.window1
, location_in_parent
, component
, 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
= ScreenAsh::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(int event_flags
) {
458 window_resizer_
->CompleteDrag(event_flags
);
459 window_resizer_
.reset();
461 // Mouse may still be over resizer, if not hide.
462 gfx::Point screen_loc
= Shell::GetScreen()->GetCursorScreenPoint();
463 if (!resize_widget_
->GetWindowBoundsInScreen().Contains(screen_loc
)) {
466 // If the mouse is over the resizer we need to remove observers on any of
467 // the |other_windows|. If we start another resize we'll recalculate the
468 // |other_windows| and invoke AddObserver() as necessary.
469 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
)
470 windows_
.other_windows
[i
]->RemoveObserver(this);
471 windows_
.other_windows
.clear();
475 void MultiWindowResizeController::CancelResize() {
476 if (!window_resizer_
)
477 return; // Happens if window was destroyed and we nuked the WindowResizer.
478 window_resizer_
->RevertDrag();
479 window_resizer_
.reset();
483 gfx::Rect
MultiWindowResizeController::CalculateResizeWidgetBounds(
484 const gfx::Point
& location_in_parent
) const {
485 gfx::Size pref
= resize_widget_
->GetContentsView()->GetPreferredSize();
487 if (windows_
.direction
== LEFT_RIGHT
) {
488 x
= windows_
.window1
->bounds().right() - pref
.width() / 2;
489 y
= location_in_parent
.y() + kResizeWidgetPadding
;
490 if (y
+ pref
.height() / 2 > windows_
.window1
->bounds().bottom() &&
491 y
+ pref
.height() / 2 > windows_
.window2
->bounds().bottom()) {
492 y
= location_in_parent
.y() - kResizeWidgetPadding
- pref
.height();
495 x
= location_in_parent
.x() + kResizeWidgetPadding
;
496 if (x
+ pref
.height() / 2 > windows_
.window1
->bounds().right() &&
497 x
+ pref
.height() / 2 > windows_
.window2
->bounds().right()) {
498 x
= location_in_parent
.x() - kResizeWidgetPadding
- pref
.width();
500 y
= windows_
.window1
->bounds().bottom() - pref
.height() / 2;
502 return gfx::Rect(x
, y
, pref
.width(), pref
.height());
505 bool MultiWindowResizeController::IsOverWindows(
506 const gfx::Point
& location_in_screen
) const {
508 return true; // Ignore hides while actively resizing.
510 if (resize_widget_
->GetWindowBoundsInScreen().Contains(location_in_screen
))
514 if (windows_
.direction
== TOP_BOTTOM
) {
522 return IsOverWindow(windows_
.window1
, location_in_screen
, hit1
) ||
523 IsOverWindow(windows_
.window2
, location_in_screen
, hit2
);
526 bool MultiWindowResizeController::IsOverWindow(
527 aura::Window
* window
,
528 const gfx::Point
& location_in_screen
,
529 int component
) const {
530 if (!window
->delegate())
533 gfx::Point
window_loc(location_in_screen
);
534 aura::Window::ConvertPointToTarget(
535 window
->GetRootWindow(), window
, &window_loc
);
536 return window
->HitTest(window_loc
) &&
537 window
->delegate()->GetNonClientComponent(window_loc
) == component
;
540 } // namespace internal