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/events/event_utils.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/image/image.h"
24 #include "ui/gfx/screen.h"
25 #include "ui/views/view.h"
26 #include "ui/views/widget/widget.h"
27 #include "ui/views/widget/widget_delegate.h"
28 #include "ui/wm/core/compound_event_filter.h"
29 #include "ui/wm/core/coordinate_conversion.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 x
>= 0 && x
<= window
->bounds().width();
49 bool ContainsScreenX(Window
* window
, int x_in_screen
) {
50 gfx::Point
window_loc(x_in_screen
, 0);
51 ::wm::ConvertPointFromScreen(window
, &window_loc
);
52 return ContainsX(window
, window_loc
.x());
55 bool ContainsY(Window
* window
, int y
) {
56 return y
>= 0 && y
<= window
->bounds().height();
59 bool ContainsScreenY(Window
* window
, int y_in_screen
) {
60 gfx::Point
window_loc(0, y_in_screen
);
61 ::wm::ConvertPointFromScreen(window
, &window_loc
);
62 return ContainsY(window
, window_loc
.y());
65 bool Intersects(int x1
, int max_1
, int x2
, int max_2
) {
66 return x2
<= max_1
&& max_2
> x1
;
71 // View contained in the widget. Passes along mouse events to the
72 // MultiWindowResizeController so that it can start/stop the resize loop.
73 class MultiWindowResizeController::ResizeView
: public views::View
{
75 explicit ResizeView(MultiWindowResizeController
* controller
,
77 : controller_(controller
),
78 direction_(direction
),
80 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
82 direction
== TOP_BOTTOM
? IDR_AURA_MULTI_WINDOW_RESIZE_H
:
83 IDR_AURA_MULTI_WINDOW_RESIZE_V
;
84 image_
= rb
.GetImageNamed(image_id
).ToImageSkia();
87 // views::View overrides:
88 gfx::Size
GetPreferredSize() const override
{
89 return gfx::Size(image_
->width(), image_
->height());
91 void OnPaint(gfx::Canvas
* canvas
) override
{
92 canvas
->DrawImageInt(*image_
, 0, 0);
94 bool OnMousePressed(const ui::MouseEvent
& event
) override
{
95 gfx::Point
location(event
.location());
96 views::View::ConvertPointToScreen(this, &location
);
97 controller_
->StartResize(location
);
100 bool OnMouseDragged(const ui::MouseEvent
& event
) override
{
101 gfx::Point
location(event
.location());
102 views::View::ConvertPointToScreen(this, &location
);
103 controller_
->Resize(location
, event
.flags());
106 void OnMouseReleased(const ui::MouseEvent
& event
) override
{
107 controller_
->CompleteResize();
109 void OnMouseCaptureLost() override
{ controller_
->CancelResize(); }
110 gfx::NativeCursor
GetCursor(const ui::MouseEvent
& event
) override
{
111 int component
= (direction_
== LEFT_RIGHT
) ? HTRIGHT
: HTBOTTOM
;
112 return ::wm::CompoundEventFilter::CursorForWindowComponent(
117 MultiWindowResizeController
* controller_
;
118 const Direction direction_
;
119 const gfx::ImageSkia
* image_
;
121 DISALLOW_COPY_AND_ASSIGN(ResizeView
);
124 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
125 // Contains() to MultiWindowResizeController.
126 class MultiWindowResizeController::ResizeMouseWatcherHost
:
127 public views::MouseWatcherHost
{
129 ResizeMouseWatcherHost(MultiWindowResizeController
* host
) : host_(host
) {}
131 // MouseWatcherHost overrides:
132 bool Contains(const gfx::Point
& point_in_screen
,
133 MouseEventType type
) override
{
134 return (type
== MOUSE_PRESS
)
135 ? host_
->IsOverResizeWidget(point_in_screen
)
136 : host_
->IsOverWindows(point_in_screen
);
140 MultiWindowResizeController
* host_
;
142 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost
);
145 MultiWindowResizeController::ResizeWindows::ResizeWindows()
148 direction(TOP_BOTTOM
){
151 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
154 bool MultiWindowResizeController::ResizeWindows::Equals(
155 const ResizeWindows
& other
) const {
156 return window1
== other
.window1
&&
157 window2
== other
.window2
&&
158 direction
== other
.direction
;
161 MultiWindowResizeController::MultiWindowResizeController() {
164 MultiWindowResizeController::~MultiWindowResizeController() {
165 window_resizer_
.reset();
169 void MultiWindowResizeController::Show(Window
* window
,
171 const gfx::Point
& point_in_window
) {
172 // When the resize widget is showing we ignore Show() requests. Instead we
173 // only care about mouse movements from MouseWatcher. This is necessary as
174 // WorkspaceEventHandler only sees mouse movements over the windows, not all
175 // windows or over the desktop.
179 ResizeWindows
windows(DetermineWindows(window
, component
, point_in_window
));
180 if (IsShowing() && windows_
.Equals(windows
))
184 if (!windows
.is_valid()) {
185 windows_
= ResizeWindows();
190 windows_
.window1
->AddObserver(this);
191 windows_
.window2
->AddObserver(this);
192 show_location_in_parent_
= point_in_window
;
193 Window::ConvertPointToTarget(
194 window
, window
->parent(), &show_location_in_parent_
);
196 FROM_HERE
, base::TimeDelta::FromMilliseconds(kShowDelayMS
),
197 this, &MultiWindowResizeController::ShowIfValidMouseLocation
);
200 void MultiWindowResizeController::Hide() {
202 return; // Ignore hides while actively resizing.
204 if (windows_
.window1
) {
205 windows_
.window1
->RemoveObserver(this);
206 windows_
.window1
= NULL
;
208 if (windows_
.window2
) {
209 windows_
.window2
->RemoveObserver(this);
210 windows_
.window2
= NULL
;
218 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
)
219 windows_
.other_windows
[i
]->RemoveObserver(this);
220 mouse_watcher_
.reset();
221 resize_widget_
.reset();
222 windows_
= ResizeWindows();
225 void MultiWindowResizeController::MouseMovedOutOfHost() {
229 void MultiWindowResizeController::OnWindowDestroying(
230 aura::Window
* window
) {
231 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
232 window_resizer_
.reset();
236 MultiWindowResizeController::ResizeWindows
237 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
238 aura::Window
* window
) const {
239 gfx::Point
mouse_location(
240 gfx::Screen::GetScreenFor(window
)->GetCursorScreenPoint());
241 ::wm::ConvertPointFromScreen(window
, &mouse_location
);
242 const int component
=
243 window
->delegate()->GetNonClientComponent(mouse_location
);
244 return DetermineWindows(window
, component
, mouse_location
);
247 void MultiWindowResizeController::CreateMouseWatcher() {
248 mouse_watcher_
.reset(new views::MouseWatcher(
249 new ResizeMouseWatcherHost(this), this));
250 mouse_watcher_
->set_notify_on_exit_time(
251 base::TimeDelta::FromMilliseconds(kHideDelayMS
));
252 mouse_watcher_
->Start();
255 MultiWindowResizeController::ResizeWindows
256 MultiWindowResizeController::DetermineWindows(
258 int window_component
,
259 const gfx::Point
& point
) const {
260 ResizeWindows result
;
261 gfx::Point
point_in_parent(point
);
262 Window::ConvertPointToTarget(window
, window
->parent(), &point_in_parent
);
263 switch (window_component
) {
265 result
.direction
= LEFT_RIGHT
;
266 result
.window1
= window
;
267 result
.window2
= FindWindowByEdge(
268 window
, HTLEFT
, window
->bounds().right(), point_in_parent
.y());
271 result
.direction
= LEFT_RIGHT
;
272 result
.window1
= FindWindowByEdge(
273 window
, HTRIGHT
, window
->bounds().x(), point_in_parent
.y());
274 result
.window2
= window
;
277 result
.direction
= TOP_BOTTOM
;
278 result
.window1
= FindWindowByEdge(
279 window
, HTBOTTOM
, point_in_parent
.x(), window
->bounds().y());
280 result
.window2
= window
;
283 result
.direction
= TOP_BOTTOM
;
284 result
.window1
= window
;
285 result
.window2
= FindWindowByEdge(
286 window
, HTTOP
, point_in_parent
.x(), window
->bounds().bottom());
294 Window
* MultiWindowResizeController::FindWindowByEdge(
295 Window
* window_to_ignore
,
298 int y_in_parent
) const {
299 Window
* parent
= window_to_ignore
->parent();
300 const Window::Windows
& windows(parent
->children());
301 for (Window::Windows::const_reverse_iterator i
= windows
.rbegin();
302 i
!= windows
.rend(); ++i
) {
304 if (window
== window_to_ignore
|| !window
->IsVisible())
307 // Ignore windows without a delegate. A delegate is necessary to query the
308 // non-client component.
309 if (!window
->delegate())
312 gfx::Point
p(x_in_parent
, y_in_parent
);
313 aura::Window::ConvertPointToTarget(parent
, window
, &p
);
316 if (ContainsY(window
, p
.y()) && p
.x() == 0)
320 if (ContainsY(window
, p
.y()) && p
.x() == window
->bounds().width())
324 if (ContainsX(window
, p
.x()) && p
.y() == 0)
328 if (ContainsX(window
, p
.x()) && p
.y() == window
->bounds().height())
334 // Window doesn't contain the edge, but if window contains |point|
335 // it's obscuring any other window that could be at the location.
336 if (window
->bounds().Contains(x_in_parent
, y_in_parent
))
342 aura::Window
* MultiWindowResizeController::FindWindowTouching(
343 aura::Window
* window
,
344 Direction direction
) const {
345 int right
= window
->bounds().right();
346 int bottom
= window
->bounds().bottom();
347 Window
* parent
= window
->parent();
348 const Window::Windows
& windows(parent
->children());
349 for (Window::Windows::const_reverse_iterator i
= windows
.rbegin();
350 i
!= windows
.rend(); ++i
) {
352 if (other
== window
|| !other
->IsVisible())
356 if (other
->bounds().y() == bottom
&&
357 Intersects(other
->bounds().x(), other
->bounds().right(),
358 window
->bounds().x(), window
->bounds().right())) {
363 if (other
->bounds().x() == right
&&
364 Intersects(other
->bounds().y(), other
->bounds().bottom(),
365 window
->bounds().y(), window
->bounds().bottom())) {
376 void MultiWindowResizeController::FindWindowsTouching(
379 std::vector
<aura::Window
*>* others
) const {
381 start
= FindWindowTouching(start
, direction
);
383 others
->push_back(start
);
387 void MultiWindowResizeController::ShowIfValidMouseLocation() {
388 if (DetermineWindowsFromScreenPoint(windows_
.window1
).Equals(windows_
) ||
389 DetermineWindowsFromScreenPoint(windows_
.window2
).Equals(windows_
)) {
396 void MultiWindowResizeController::ShowNow() {
397 DCHECK(!resize_widget_
.get());
398 DCHECK(windows_
.is_valid());
400 resize_widget_
.reset(new views::Widget
);
401 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
402 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
403 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
404 params
.parent
= Shell::GetContainer(Shell::GetTargetRootWindow(),
405 kShellWindowId_AlwaysOnTopContainer
);
406 ResizeView
* view
= new ResizeView(this, windows_
.direction
);
407 resize_widget_
->set_focus_on_creation(false);
408 resize_widget_
->Init(params
);
409 ::wm::SetWindowVisibilityAnimationType(
410 resize_widget_
->GetNativeWindow(),
411 ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE
);
412 resize_widget_
->GetNativeWindow()->SetName("MultiWindowResizeController");
413 resize_widget_
->SetContentsView(view
);
414 show_bounds_in_screen_
= ScreenUtil::ConvertRectToScreen(
415 windows_
.window1
->parent(),
416 CalculateResizeWidgetBounds(show_location_in_parent_
));
417 resize_widget_
->SetBounds(show_bounds_in_screen_
);
418 resize_widget_
->Show();
419 CreateMouseWatcher();
422 bool MultiWindowResizeController::IsShowing() const {
423 return resize_widget_
.get() || show_timer_
.IsRunning();
426 void MultiWindowResizeController::StartResize(
427 const gfx::Point
& location_in_screen
) {
428 DCHECK(!window_resizer_
.get());
429 DCHECK(windows_
.is_valid());
430 gfx::Point
location_in_parent(location_in_screen
);
431 aura::client::GetScreenPositionClient(windows_
.window2
->GetRootWindow())->
432 ConvertPointFromScreen(windows_
.window2
->parent(), &location_in_parent
);
433 std::vector
<aura::Window
*> windows
;
434 windows
.push_back(windows_
.window2
);
435 DCHECK(windows_
.other_windows
.empty());
436 FindWindowsTouching(windows_
.window2
, windows_
.direction
,
437 &windows_
.other_windows
);
438 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
) {
439 windows_
.other_windows
[i
]->AddObserver(this);
440 windows
.push_back(windows_
.other_windows
[i
]);
442 int component
= windows_
.direction
== LEFT_RIGHT
? HTRIGHT
: HTBOTTOM
;
443 wm::WindowState
* window_state
= wm::GetWindowState(windows_
.window1
);
444 window_state
->CreateDragDetails(windows_
.window1
,
447 aura::client::WINDOW_MOVE_SOURCE_MOUSE
);
448 window_resizer_
.reset(WorkspaceWindowResizer::Create(window_state
, windows
));
450 // Do not hide the resize widget while a drag is active.
451 mouse_watcher_
.reset();
454 void MultiWindowResizeController::Resize(const gfx::Point
& location_in_screen
,
456 gfx::Point
location_in_parent(location_in_screen
);
457 aura::client::GetScreenPositionClient(windows_
.window1
->GetRootWindow())->
458 ConvertPointFromScreen(windows_
.window1
->parent(), &location_in_parent
);
459 window_resizer_
->Drag(location_in_parent
, event_flags
);
460 gfx::Rect bounds
= ScreenUtil::ConvertRectToScreen(
461 windows_
.window1
->parent(),
462 CalculateResizeWidgetBounds(location_in_parent
));
464 if (windows_
.direction
== LEFT_RIGHT
)
465 bounds
.set_y(show_bounds_in_screen_
.y());
467 bounds
.set_x(show_bounds_in_screen_
.x());
468 resize_widget_
->SetBounds(bounds
);
471 void MultiWindowResizeController::CompleteResize() {
472 window_resizer_
->CompleteDrag();
473 wm::GetWindowState(window_resizer_
->GetTarget())->DeleteDragDetails();
474 window_resizer_
.reset();
476 // Mouse may still be over resizer, if not hide.
477 gfx::Point screen_loc
= Shell::GetScreen()->GetCursorScreenPoint();
478 if (!resize_widget_
->GetWindowBoundsInScreen().Contains(screen_loc
)) {
481 // If the mouse is over the resizer we need to remove observers on any of
482 // the |other_windows|. If we start another resize we'll recalculate the
483 // |other_windows| and invoke AddObserver() as necessary.
484 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
)
485 windows_
.other_windows
[i
]->RemoveObserver(this);
486 windows_
.other_windows
.clear();
488 CreateMouseWatcher();
492 void MultiWindowResizeController::CancelResize() {
493 if (!window_resizer_
)
494 return; // Happens if window was destroyed and we nuked the WindowResizer.
495 window_resizer_
->RevertDrag();
496 wm::GetWindowState(window_resizer_
->GetTarget())->DeleteDragDetails();
497 window_resizer_
.reset();
501 gfx::Rect
MultiWindowResizeController::CalculateResizeWidgetBounds(
502 const gfx::Point
& location_in_parent
) const {
503 gfx::Size pref
= resize_widget_
->GetContentsView()->GetPreferredSize();
505 if (windows_
.direction
== LEFT_RIGHT
) {
506 x
= windows_
.window1
->bounds().right() - pref
.width() / 2;
507 y
= location_in_parent
.y() + kResizeWidgetPadding
;
508 if (y
+ pref
.height() / 2 > windows_
.window1
->bounds().bottom() &&
509 y
+ pref
.height() / 2 > windows_
.window2
->bounds().bottom()) {
510 y
= location_in_parent
.y() - kResizeWidgetPadding
- pref
.height();
513 x
= location_in_parent
.x() + kResizeWidgetPadding
;
514 if (x
+ pref
.height() / 2 > windows_
.window1
->bounds().right() &&
515 x
+ pref
.height() / 2 > windows_
.window2
->bounds().right()) {
516 x
= location_in_parent
.x() - kResizeWidgetPadding
- pref
.width();
518 y
= windows_
.window1
->bounds().bottom() - pref
.height() / 2;
520 return gfx::Rect(x
, y
, pref
.width(), pref
.height());
523 bool MultiWindowResizeController::IsOverResizeWidget(
524 const gfx::Point
& location_in_screen
) const {
525 return resize_widget_
->GetWindowBoundsInScreen().Contains(
529 bool MultiWindowResizeController::IsOverWindows(
530 const gfx::Point
& location_in_screen
) const {
531 if (IsOverResizeWidget(location_in_screen
))
534 if (windows_
.direction
== TOP_BOTTOM
) {
535 if (!ContainsScreenX(windows_
.window1
, location_in_screen
.x()) ||
536 !ContainsScreenX(windows_
.window2
, location_in_screen
.x())) {
540 if (!ContainsScreenY(windows_
.window1
, location_in_screen
.y()) ||
541 !ContainsScreenY(windows_
.window2
, location_in_screen
.y())) {
546 // Check whether |location_in_screen| is in the event target's resize region.
547 // This is tricky because a window's resize region can extend outside a
549 gfx::Point
location_in_root(location_in_screen
);
550 aura::Window
* root
= windows_
.window1
->GetRootWindow();
551 ::wm::ConvertPointFromScreen(root
, &location_in_root
);
552 ui::MouseEvent
test_event(ui::ET_MOUSE_MOVED
, location_in_root
,
553 location_in_root
, ui::EventTimeForNow(),
554 ui::EF_NONE
, ui::EF_NONE
);
555 ui::EventTarget
* event_handler
= static_cast<ui::EventTarget
*>(root
)
557 ->FindTargetForEvent(root
, &test_event
);
558 if (event_handler
== windows_
.window1
) {
559 return IsOverComponent(
562 windows_
.direction
== TOP_BOTTOM
? HTBOTTOM
: HTRIGHT
);
563 } else if (event_handler
== windows_
.window2
) {
564 return IsOverComponent(
567 windows_
.direction
== TOP_BOTTOM
? HTTOP
: HTLEFT
);
572 bool MultiWindowResizeController::IsOverComponent(
573 aura::Window
* window
,
574 const gfx::Point
& location_in_screen
,
575 int component
) const {
576 gfx::Point
window_loc(location_in_screen
);
577 ::wm::ConvertPointFromScreen(window
, &window_loc
);
578 return window
->delegate()->GetNonClientComponent(window_loc
) == component
;