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/events/event_targeter.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 #include "ui/wm/core/coordinate_conversion.h"
35 // Delay before showing.
36 const int kShowDelayMS
= 400;
38 // Delay before hiding.
39 const int kHideDelayMS
= 500;
41 // Padding from the bottom/right edge the resize widget is shown at.
42 const int kResizeWidgetPadding
= 15;
44 bool ContainsX(Window
* window
, int x
) {
45 return x
>= 0 && x
<= window
->bounds().width();
48 bool ContainsScreenX(Window
* window
, int x_in_screen
) {
49 gfx::Point
window_loc(x_in_screen
, 0);
50 ::wm::ConvertPointFromScreen(window
, &window_loc
);
51 return ContainsX(window
, window_loc
.x());
54 bool ContainsY(Window
* window
, int y
) {
55 return y
>= 0 && y
<= window
->bounds().height();
58 bool ContainsScreenY(Window
* window
, int y_in_screen
) {
59 gfx::Point
window_loc(0, y_in_screen
);
60 ::wm::ConvertPointFromScreen(window
, &window_loc
);
61 return ContainsY(window
, window_loc
.y());
64 bool Intersects(int x1
, int max_1
, int x2
, int max_2
) {
65 return x2
<= max_1
&& max_2
> x1
;
70 // View contained in the widget. Passes along mouse events to the
71 // MultiWindowResizeController so that it can start/stop the resize loop.
72 class MultiWindowResizeController::ResizeView
: public views::View
{
74 explicit ResizeView(MultiWindowResizeController
* controller
,
76 : controller_(controller
),
77 direction_(direction
),
79 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
81 direction
== TOP_BOTTOM
? IDR_AURA_MULTI_WINDOW_RESIZE_H
:
82 IDR_AURA_MULTI_WINDOW_RESIZE_V
;
83 image_
= rb
.GetImageNamed(image_id
).ToImageSkia();
86 // views::View overrides:
87 gfx::Size
GetPreferredSize() const override
{
88 return gfx::Size(image_
->width(), image_
->height());
90 void OnPaint(gfx::Canvas
* canvas
) override
{
91 canvas
->DrawImageInt(*image_
, 0, 0);
93 bool OnMousePressed(const ui::MouseEvent
& event
) override
{
94 gfx::Point
location(event
.location());
95 views::View::ConvertPointToScreen(this, &location
);
96 controller_
->StartResize(location
);
99 bool OnMouseDragged(const ui::MouseEvent
& event
) override
{
100 gfx::Point
location(event
.location());
101 views::View::ConvertPointToScreen(this, &location
);
102 controller_
->Resize(location
, event
.flags());
105 void OnMouseReleased(const ui::MouseEvent
& event
) override
{
106 controller_
->CompleteResize();
108 void OnMouseCaptureLost() override
{ controller_
->CancelResize(); }
109 gfx::NativeCursor
GetCursor(const ui::MouseEvent
& event
) override
{
110 int component
= (direction_
== LEFT_RIGHT
) ? HTRIGHT
: HTBOTTOM
;
111 return ::wm::CompoundEventFilter::CursorForWindowComponent(
116 MultiWindowResizeController
* controller_
;
117 const Direction direction_
;
118 const gfx::ImageSkia
* image_
;
120 DISALLOW_COPY_AND_ASSIGN(ResizeView
);
123 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
124 // Contains() to MultiWindowResizeController.
125 class MultiWindowResizeController::ResizeMouseWatcherHost
:
126 public views::MouseWatcherHost
{
128 ResizeMouseWatcherHost(MultiWindowResizeController
* host
) : host_(host
) {}
130 // MouseWatcherHost overrides:
131 bool Contains(const gfx::Point
& point_in_screen
,
132 MouseEventType type
) override
{
133 return (type
== MOUSE_PRESS
)
134 ? host_
->IsOverResizeWidget(point_in_screen
)
135 : host_
->IsOverWindows(point_in_screen
);
139 MultiWindowResizeController
* host_
;
141 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost
);
144 MultiWindowResizeController::ResizeWindows::ResizeWindows()
147 direction(TOP_BOTTOM
){
150 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
153 bool MultiWindowResizeController::ResizeWindows::Equals(
154 const ResizeWindows
& other
) const {
155 return window1
== other
.window1
&&
156 window2
== other
.window2
&&
157 direction
== other
.direction
;
160 MultiWindowResizeController::MultiWindowResizeController() {
163 MultiWindowResizeController::~MultiWindowResizeController() {
164 window_resizer_
.reset();
168 void MultiWindowResizeController::Show(Window
* window
,
170 const gfx::Point
& point_in_window
) {
171 // When the resize widget is showing we ignore Show() requests. Instead we
172 // only care about mouse movements from MouseWatcher. This is necessary as
173 // WorkspaceEventHandler only sees mouse movements over the windows, not all
174 // windows or over the desktop.
178 ResizeWindows
windows(DetermineWindows(window
, component
, point_in_window
));
179 if (IsShowing() && windows_
.Equals(windows
))
183 if (!windows
.is_valid()) {
184 windows_
= ResizeWindows();
189 windows_
.window1
->AddObserver(this);
190 windows_
.window2
->AddObserver(this);
191 show_location_in_parent_
= point_in_window
;
192 Window::ConvertPointToTarget(
193 window
, window
->parent(), &show_location_in_parent_
);
195 FROM_HERE
, base::TimeDelta::FromMilliseconds(kShowDelayMS
),
196 this, &MultiWindowResizeController::ShowIfValidMouseLocation
);
199 void MultiWindowResizeController::Hide() {
201 return; // Ignore hides while actively resizing.
203 if (windows_
.window1
) {
204 windows_
.window1
->RemoveObserver(this);
205 windows_
.window1
= NULL
;
207 if (windows_
.window2
) {
208 windows_
.window2
->RemoveObserver(this);
209 windows_
.window2
= NULL
;
217 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
)
218 windows_
.other_windows
[i
]->RemoveObserver(this);
219 mouse_watcher_
.reset();
220 resize_widget_
.reset();
221 windows_
= ResizeWindows();
224 void MultiWindowResizeController::MouseMovedOutOfHost() {
228 void MultiWindowResizeController::OnWindowDestroying(
229 aura::Window
* window
) {
230 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
231 window_resizer_
.reset();
235 MultiWindowResizeController::ResizeWindows
236 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
237 aura::Window
* window
) const {
238 gfx::Point
mouse_location(
239 gfx::Screen::GetScreenFor(window
)->GetCursorScreenPoint());
240 ::wm::ConvertPointFromScreen(window
, &mouse_location
);
241 const int component
=
242 window
->delegate()->GetNonClientComponent(mouse_location
);
243 return DetermineWindows(window
, component
, mouse_location
);
246 void MultiWindowResizeController::CreateMouseWatcher() {
247 mouse_watcher_
.reset(new views::MouseWatcher(
248 new ResizeMouseWatcherHost(this), this));
249 mouse_watcher_
->set_notify_on_exit_time(
250 base::TimeDelta::FromMilliseconds(kHideDelayMS
));
251 mouse_watcher_
->Start();
254 MultiWindowResizeController::ResizeWindows
255 MultiWindowResizeController::DetermineWindows(
257 int window_component
,
258 const gfx::Point
& point
) const {
259 ResizeWindows result
;
260 gfx::Point
point_in_parent(point
);
261 Window::ConvertPointToTarget(window
, window
->parent(), &point_in_parent
);
262 switch (window_component
) {
264 result
.direction
= LEFT_RIGHT
;
265 result
.window1
= window
;
266 result
.window2
= FindWindowByEdge(
267 window
, HTLEFT
, window
->bounds().right(), point_in_parent
.y());
270 result
.direction
= LEFT_RIGHT
;
271 result
.window1
= FindWindowByEdge(
272 window
, HTRIGHT
, window
->bounds().x(), point_in_parent
.y());
273 result
.window2
= window
;
276 result
.direction
= TOP_BOTTOM
;
277 result
.window1
= FindWindowByEdge(
278 window
, HTBOTTOM
, point_in_parent
.x(), window
->bounds().y());
279 result
.window2
= window
;
282 result
.direction
= TOP_BOTTOM
;
283 result
.window1
= window
;
284 result
.window2
= FindWindowByEdge(
285 window
, HTTOP
, point_in_parent
.x(), window
->bounds().bottom());
293 Window
* MultiWindowResizeController::FindWindowByEdge(
294 Window
* window_to_ignore
,
297 int y_in_parent
) const {
298 Window
* parent
= window_to_ignore
->parent();
299 const Window::Windows
& windows(parent
->children());
300 for (Window::Windows::const_reverse_iterator i
= windows
.rbegin();
301 i
!= windows
.rend(); ++i
) {
303 if (window
== window_to_ignore
|| !window
->IsVisible())
306 // Ignore windows without a delegate. A delegate is necessary to query the
307 // non-client component.
308 if (!window
->delegate())
311 gfx::Point
p(x_in_parent
, y_in_parent
);
312 aura::Window::ConvertPointToTarget(parent
, window
, &p
);
315 if (ContainsY(window
, p
.y()) && p
.x() == 0)
319 if (ContainsY(window
, p
.y()) && p
.x() == window
->bounds().width())
323 if (ContainsX(window
, p
.x()) && p
.y() == 0)
327 if (ContainsX(window
, p
.x()) && p
.y() == window
->bounds().height())
333 // Window doesn't contain the edge, but if window contains |point|
334 // it's obscuring any other window that could be at the location.
335 if (window
->bounds().Contains(x_in_parent
, y_in_parent
))
341 aura::Window
* MultiWindowResizeController::FindWindowTouching(
342 aura::Window
* window
,
343 Direction direction
) const {
344 int right
= window
->bounds().right();
345 int bottom
= window
->bounds().bottom();
346 Window
* parent
= window
->parent();
347 const Window::Windows
& windows(parent
->children());
348 for (Window::Windows::const_reverse_iterator i
= windows
.rbegin();
349 i
!= windows
.rend(); ++i
) {
351 if (other
== window
|| !other
->IsVisible())
355 if (other
->bounds().y() == bottom
&&
356 Intersects(other
->bounds().x(), other
->bounds().right(),
357 window
->bounds().x(), window
->bounds().right())) {
362 if (other
->bounds().x() == right
&&
363 Intersects(other
->bounds().y(), other
->bounds().bottom(),
364 window
->bounds().y(), window
->bounds().bottom())) {
375 void MultiWindowResizeController::FindWindowsTouching(
378 std::vector
<aura::Window
*>* others
) const {
380 start
= FindWindowTouching(start
, direction
);
382 others
->push_back(start
);
386 void MultiWindowResizeController::ShowIfValidMouseLocation() {
387 if (DetermineWindowsFromScreenPoint(windows_
.window1
).Equals(windows_
) ||
388 DetermineWindowsFromScreenPoint(windows_
.window2
).Equals(windows_
)) {
395 void MultiWindowResizeController::ShowNow() {
396 DCHECK(!resize_widget_
.get());
397 DCHECK(windows_
.is_valid());
399 resize_widget_
.reset(new views::Widget
);
400 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
401 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
402 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
403 params
.parent
= Shell::GetContainer(Shell::GetTargetRootWindow(),
404 kShellWindowId_AlwaysOnTopContainer
);
405 ResizeView
* view
= new ResizeView(this, windows_
.direction
);
406 resize_widget_
->set_focus_on_creation(false);
407 resize_widget_
->Init(params
);
408 ::wm::SetWindowVisibilityAnimationType(
409 resize_widget_
->GetNativeWindow(),
410 ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE
);
411 resize_widget_
->GetNativeWindow()->SetName("MultiWindowResizeController");
412 resize_widget_
->SetContentsView(view
);
413 show_bounds_in_screen_
= ScreenUtil::ConvertRectToScreen(
414 windows_
.window1
->parent(),
415 CalculateResizeWidgetBounds(show_location_in_parent_
));
416 resize_widget_
->SetBounds(show_bounds_in_screen_
);
417 resize_widget_
->Show();
418 CreateMouseWatcher();
421 bool MultiWindowResizeController::IsShowing() const {
422 return resize_widget_
.get() || show_timer_
.IsRunning();
425 void MultiWindowResizeController::StartResize(
426 const gfx::Point
& location_in_screen
) {
427 DCHECK(!window_resizer_
.get());
428 DCHECK(windows_
.is_valid());
429 gfx::Point
location_in_parent(location_in_screen
);
430 aura::client::GetScreenPositionClient(windows_
.window2
->GetRootWindow())->
431 ConvertPointFromScreen(windows_
.window2
->parent(), &location_in_parent
);
432 std::vector
<aura::Window
*> windows
;
433 windows
.push_back(windows_
.window2
);
434 DCHECK(windows_
.other_windows
.empty());
435 FindWindowsTouching(windows_
.window2
, windows_
.direction
,
436 &windows_
.other_windows
);
437 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
) {
438 windows_
.other_windows
[i
]->AddObserver(this);
439 windows
.push_back(windows_
.other_windows
[i
]);
441 int component
= windows_
.direction
== LEFT_RIGHT
? HTRIGHT
: HTBOTTOM
;
442 wm::WindowState
* window_state
= wm::GetWindowState(windows_
.window1
);
443 window_state
->CreateDragDetails(windows_
.window1
,
446 aura::client::WINDOW_MOVE_SOURCE_MOUSE
);
447 window_resizer_
.reset(WorkspaceWindowResizer::Create(window_state
, windows
));
449 // Do not hide the resize widget while a drag is active.
450 mouse_watcher_
.reset();
453 void MultiWindowResizeController::Resize(const gfx::Point
& location_in_screen
,
455 gfx::Point
location_in_parent(location_in_screen
);
456 aura::client::GetScreenPositionClient(windows_
.window1
->GetRootWindow())->
457 ConvertPointFromScreen(windows_
.window1
->parent(), &location_in_parent
);
458 window_resizer_
->Drag(location_in_parent
, event_flags
);
459 gfx::Rect bounds
= ScreenUtil::ConvertRectToScreen(
460 windows_
.window1
->parent(),
461 CalculateResizeWidgetBounds(location_in_parent
));
463 if (windows_
.direction
== LEFT_RIGHT
)
464 bounds
.set_y(show_bounds_in_screen_
.y());
466 bounds
.set_x(show_bounds_in_screen_
.x());
467 resize_widget_
->SetBounds(bounds
);
470 void MultiWindowResizeController::CompleteResize() {
471 window_resizer_
->CompleteDrag();
472 wm::GetWindowState(window_resizer_
->GetTarget())->DeleteDragDetails();
473 window_resizer_
.reset();
475 // Mouse may still be over resizer, if not hide.
476 gfx::Point screen_loc
= Shell::GetScreen()->GetCursorScreenPoint();
477 if (!resize_widget_
->GetWindowBoundsInScreen().Contains(screen_loc
)) {
480 // If the mouse is over the resizer we need to remove observers on any of
481 // the |other_windows|. If we start another resize we'll recalculate the
482 // |other_windows| and invoke AddObserver() as necessary.
483 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
)
484 windows_
.other_windows
[i
]->RemoveObserver(this);
485 windows_
.other_windows
.clear();
487 CreateMouseWatcher();
491 void MultiWindowResizeController::CancelResize() {
492 if (!window_resizer_
)
493 return; // Happens if window was destroyed and we nuked the WindowResizer.
494 window_resizer_
->RevertDrag();
495 wm::GetWindowState(window_resizer_
->GetTarget())->DeleteDragDetails();
496 window_resizer_
.reset();
500 gfx::Rect
MultiWindowResizeController::CalculateResizeWidgetBounds(
501 const gfx::Point
& location_in_parent
) const {
502 gfx::Size pref
= resize_widget_
->GetContentsView()->GetPreferredSize();
504 if (windows_
.direction
== LEFT_RIGHT
) {
505 x
= windows_
.window1
->bounds().right() - pref
.width() / 2;
506 y
= location_in_parent
.y() + kResizeWidgetPadding
;
507 if (y
+ pref
.height() / 2 > windows_
.window1
->bounds().bottom() &&
508 y
+ pref
.height() / 2 > windows_
.window2
->bounds().bottom()) {
509 y
= location_in_parent
.y() - kResizeWidgetPadding
- pref
.height();
512 x
= location_in_parent
.x() + kResizeWidgetPadding
;
513 if (x
+ pref
.height() / 2 > windows_
.window1
->bounds().right() &&
514 x
+ pref
.height() / 2 > windows_
.window2
->bounds().right()) {
515 x
= location_in_parent
.x() - kResizeWidgetPadding
- pref
.width();
517 y
= windows_
.window1
->bounds().bottom() - pref
.height() / 2;
519 return gfx::Rect(x
, y
, pref
.width(), pref
.height());
522 bool MultiWindowResizeController::IsOverResizeWidget(
523 const gfx::Point
& location_in_screen
) const {
524 return resize_widget_
->GetWindowBoundsInScreen().Contains(
528 bool MultiWindowResizeController::IsOverWindows(
529 const gfx::Point
& location_in_screen
) const {
530 if (IsOverResizeWidget(location_in_screen
))
533 if (windows_
.direction
== TOP_BOTTOM
) {
534 if (!ContainsScreenX(windows_
.window1
, location_in_screen
.x()) ||
535 !ContainsScreenX(windows_
.window2
, location_in_screen
.x())) {
539 if (!ContainsScreenY(windows_
.window1
, location_in_screen
.y()) ||
540 !ContainsScreenY(windows_
.window2
, location_in_screen
.y())) {
545 // Check whether |location_in_screen| is in the event target's resize region.
546 // This is tricky because a window's resize region can extend outside a
548 gfx::Point
location_in_root(location_in_screen
);
549 aura::Window
* root
= windows_
.window1
->GetRootWindow();
550 ::wm::ConvertPointFromScreen(root
, &location_in_root
);
551 ui::MouseEvent
test_event(ui::ET_MOUSE_MOVED
, location_in_root
,
552 location_in_root
, ui::EF_NONE
, ui::EF_NONE
);
553 ui::EventTarget
* event_handler
= static_cast<ui::EventTarget
*>(root
)
555 ->FindTargetForEvent(root
, &test_event
);
556 if (event_handler
== windows_
.window1
) {
557 return IsOverComponent(
560 windows_
.direction
== TOP_BOTTOM
? HTBOTTOM
: HTRIGHT
);
561 } else if (event_handler
== windows_
.window2
) {
562 return IsOverComponent(
565 windows_
.direction
== TOP_BOTTOM
? HTTOP
: HTLEFT
);
570 bool MultiWindowResizeController::IsOverComponent(
571 aura::Window
* window
,
572 const gfx::Point
& location_in_screen
,
573 int component
) const {
574 gfx::Point
window_loc(location_in_screen
);
575 ::wm::ConvertPointFromScreen(window
, &window_loc
);
576 return window
->delegate()->GetNonClientComponent(window_loc
) == component
;