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 gfx::Size
GetPreferredSize() const override
{
75 return gfx::Size(image_
->width(), image_
->height());
77 void OnPaint(gfx::Canvas
* canvas
) override
{
78 canvas
->DrawImageInt(*image_
, 0, 0);
80 bool OnMousePressed(const ui::MouseEvent
& event
) override
{
81 gfx::Point
location(event
.location());
82 views::View::ConvertPointToScreen(this, &location
);
83 controller_
->StartResize(location
);
86 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 void OnMouseReleased(const ui::MouseEvent
& event
) override
{
93 controller_
->CompleteResize();
95 void OnMouseCaptureLost() override
{ controller_
->CancelResize(); }
96 gfx::NativeCursor
GetCursor(const ui::MouseEvent
& event
) override
{
97 int component
= (direction_
== LEFT_RIGHT
) ? HTRIGHT
: HTBOTTOM
;
98 return ::wm::CompoundEventFilter::CursorForWindowComponent(
103 MultiWindowResizeController
* controller_
;
104 const Direction direction_
;
105 const gfx::ImageSkia
* image_
;
107 DISALLOW_COPY_AND_ASSIGN(ResizeView
);
110 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
111 // Contains() to MultiWindowResizeController.
112 class MultiWindowResizeController::ResizeMouseWatcherHost
:
113 public views::MouseWatcherHost
{
115 ResizeMouseWatcherHost(MultiWindowResizeController
* host
) : host_(host
) {}
117 // MouseWatcherHost overrides:
118 bool Contains(const gfx::Point
& point_in_screen
,
119 MouseEventType type
) override
{
120 return host_
->IsOverWindows(point_in_screen
);
124 MultiWindowResizeController
* host_
;
126 DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost
);
129 MultiWindowResizeController::ResizeWindows::ResizeWindows()
132 direction(TOP_BOTTOM
){
135 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
138 bool MultiWindowResizeController::ResizeWindows::Equals(
139 const ResizeWindows
& other
) const {
140 return window1
== other
.window1
&&
141 window2
== other
.window2
&&
142 direction
== other
.direction
;
145 MultiWindowResizeController::MultiWindowResizeController() {
148 MultiWindowResizeController::~MultiWindowResizeController() {
149 window_resizer_
.reset();
153 void MultiWindowResizeController::Show(Window
* window
,
155 const gfx::Point
& point_in_window
) {
156 // When the resize widget is showing we ignore Show() requests. Instead we
157 // only care about mouse movements from MouseWatcher. This is necessary as
158 // WorkspaceEventHandler only sees mouse movements over the windows, not all
159 // windows or over the desktop.
163 ResizeWindows
windows(DetermineWindows(window
, component
, point_in_window
));
165 if (windows_
.Equals(windows
))
166 return; // Over the same windows.
170 if (!windows
.is_valid())
174 windows_
.window1
->AddObserver(this);
175 windows_
.window2
->AddObserver(this);
176 show_location_in_parent_
= point_in_window
;
177 Window::ConvertPointToTarget(
178 window
, window
->parent(), &show_location_in_parent_
);
179 if (show_timer_
.IsRunning())
182 FROM_HERE
, base::TimeDelta::FromMilliseconds(kShowDelayMS
),
183 this, &MultiWindowResizeController::ShowIfValidMouseLocation
);
186 void MultiWindowResizeController::Hide() {
189 return; // Ignore hides while actively resizing.
191 if (windows_
.window1
) {
192 windows_
.window1
->RemoveObserver(this);
193 windows_
.window1
= NULL
;
195 if (windows_
.window2
) {
196 windows_
.window2
->RemoveObserver(this);
197 windows_
.window2
= NULL
;
205 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
)
206 windows_
.other_windows
[i
]->RemoveObserver(this);
207 mouse_watcher_
.reset();
208 resize_widget_
.reset();
209 windows_
= ResizeWindows();
212 void MultiWindowResizeController::MouseMovedOutOfHost() {
216 void MultiWindowResizeController::OnWindowDestroying(
217 aura::Window
* window
) {
218 // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
219 window_resizer_
.reset();
223 MultiWindowResizeController::ResizeWindows
224 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
225 aura::Window
* window
) const {
226 gfx::Point
mouse_location(
227 gfx::Screen::GetScreenFor(window
)->GetCursorScreenPoint());
228 ::wm::ConvertPointFromScreen(window
, &mouse_location
);
229 const int component
=
230 window
->delegate()->GetNonClientComponent(mouse_location
);
231 return DetermineWindows(window
, component
, mouse_location
);
234 MultiWindowResizeController::ResizeWindows
235 MultiWindowResizeController::DetermineWindows(
237 int window_component
,
238 const gfx::Point
& point
) const {
239 ResizeWindows result
;
240 gfx::Point
point_in_parent(point
);
241 Window::ConvertPointToTarget(window
, window
->parent(), &point_in_parent
);
242 switch (window_component
) {
244 result
.direction
= LEFT_RIGHT
;
245 result
.window1
= window
;
246 result
.window2
= FindWindowByEdge(
247 window
, HTLEFT
, window
->bounds().right(), point_in_parent
.y());
250 result
.direction
= LEFT_RIGHT
;
251 result
.window1
= FindWindowByEdge(
252 window
, HTRIGHT
, window
->bounds().x(), point_in_parent
.y());
253 result
.window2
= window
;
256 result
.direction
= TOP_BOTTOM
;
257 result
.window1
= FindWindowByEdge(
258 window
, HTBOTTOM
, point_in_parent
.x(), window
->bounds().y());
259 result
.window2
= window
;
262 result
.direction
= TOP_BOTTOM
;
263 result
.window1
= window
;
264 result
.window2
= FindWindowByEdge(
265 window
, HTTOP
, point_in_parent
.x(), window
->bounds().bottom());
273 Window
* MultiWindowResizeController::FindWindowByEdge(
274 Window
* window_to_ignore
,
278 Window
* parent
= window_to_ignore
->parent();
279 const Window::Windows
& windows(parent
->children());
280 for (Window::Windows::const_reverse_iterator i
= windows
.rbegin();
281 i
!= windows
.rend(); ++i
) {
283 if (window
== window_to_ignore
|| !window
->IsVisible())
287 if (ContainsY(window
, y
) && window
->bounds().x() == x
)
291 if (ContainsY(window
, y
) && window
->bounds().right() == x
)
295 if (ContainsX(window
, x
) && window
->bounds().y() == y
)
299 if (ContainsX(window
, x
) && window
->bounds().bottom() == y
)
305 // Window doesn't contain the edge, but if window contains |point|
306 // it's obscuring any other window that could be at the location.
307 if (window
->bounds().Contains(x
, y
))
313 aura::Window
* MultiWindowResizeController::FindWindowTouching(
314 aura::Window
* window
,
315 Direction direction
) const {
316 int right
= window
->bounds().right();
317 int bottom
= window
->bounds().bottom();
318 Window
* parent
= window
->parent();
319 const Window::Windows
& windows(parent
->children());
320 for (Window::Windows::const_reverse_iterator i
= windows
.rbegin();
321 i
!= windows
.rend(); ++i
) {
323 if (other
== window
|| !other
->IsVisible())
327 if (other
->bounds().y() == bottom
&&
328 Intersects(other
->bounds().x(), other
->bounds().right(),
329 window
->bounds().x(), window
->bounds().right())) {
334 if (other
->bounds().x() == right
&&
335 Intersects(other
->bounds().y(), other
->bounds().bottom(),
336 window
->bounds().y(), window
->bounds().bottom())) {
347 void MultiWindowResizeController::FindWindowsTouching(
350 std::vector
<aura::Window
*>* others
) const {
352 start
= FindWindowTouching(start
, direction
);
354 others
->push_back(start
);
358 void MultiWindowResizeController::DelayedHide() {
359 if (hide_timer_
.IsRunning())
362 hide_timer_
.Start(FROM_HERE
,
363 base::TimeDelta::FromMilliseconds(kHideDelayMS
),
364 this, &MultiWindowResizeController::Hide
);
367 void MultiWindowResizeController::ShowIfValidMouseLocation() {
368 if (DetermineWindowsFromScreenPoint(windows_
.window1
).Equals(windows_
) ||
369 DetermineWindowsFromScreenPoint(windows_
.window2
).Equals(windows_
)) {
376 void MultiWindowResizeController::ShowNow() {
377 DCHECK(!resize_widget_
.get());
378 DCHECK(windows_
.is_valid());
380 resize_widget_
.reset(new views::Widget
);
381 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_POPUP
);
382 params
.opacity
= views::Widget::InitParams::TRANSLUCENT_WINDOW
;
383 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
384 params
.parent
= Shell::GetContainer(Shell::GetTargetRootWindow(),
385 kShellWindowId_AlwaysOnTopContainer
);
386 ResizeView
* view
= new ResizeView(this, windows_
.direction
);
387 resize_widget_
->set_focus_on_creation(false);
388 resize_widget_
->Init(params
);
389 ::wm::SetWindowVisibilityAnimationType(
390 resize_widget_
->GetNativeWindow(),
391 ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE
);
392 resize_widget_
->GetNativeWindow()->SetName("MultiWindowResizeController");
393 resize_widget_
->SetContentsView(view
);
394 show_bounds_in_screen_
= ScreenUtil::ConvertRectToScreen(
395 windows_
.window1
->parent(),
396 CalculateResizeWidgetBounds(show_location_in_parent_
));
397 resize_widget_
->SetBounds(show_bounds_in_screen_
);
398 resize_widget_
->Show();
399 mouse_watcher_
.reset(new views::MouseWatcher(
400 new ResizeMouseWatcherHost(this),
402 mouse_watcher_
->set_notify_on_exit_time(
403 base::TimeDelta::FromMilliseconds(kHideDelayMS
));
404 mouse_watcher_
->Start();
407 bool MultiWindowResizeController::IsShowing() const {
408 return resize_widget_
.get() || show_timer_
.IsRunning();
411 void MultiWindowResizeController::StartResize(
412 const gfx::Point
& location_in_screen
) {
413 DCHECK(!window_resizer_
.get());
414 DCHECK(windows_
.is_valid());
416 gfx::Point
location_in_parent(location_in_screen
);
417 aura::client::GetScreenPositionClient(windows_
.window2
->GetRootWindow())->
418 ConvertPointFromScreen(windows_
.window2
->parent(), &location_in_parent
);
419 std::vector
<aura::Window
*> windows
;
420 windows
.push_back(windows_
.window2
);
421 DCHECK(windows_
.other_windows
.empty());
422 FindWindowsTouching(windows_
.window2
, windows_
.direction
,
423 &windows_
.other_windows
);
424 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
) {
425 windows_
.other_windows
[i
]->AddObserver(this);
426 windows
.push_back(windows_
.other_windows
[i
]);
428 int component
= windows_
.direction
== LEFT_RIGHT
? HTRIGHT
: HTBOTTOM
;
429 wm::WindowState
* window_state
= wm::GetWindowState(windows_
.window1
);
430 window_state
->CreateDragDetails(windows_
.window1
,
433 aura::client::WINDOW_MOVE_SOURCE_MOUSE
);
434 window_resizer_
.reset(WorkspaceWindowResizer::Create(window_state
, windows
));
437 void MultiWindowResizeController::Resize(const gfx::Point
& location_in_screen
,
439 gfx::Point
location_in_parent(location_in_screen
);
440 aura::client::GetScreenPositionClient(windows_
.window1
->GetRootWindow())->
441 ConvertPointFromScreen(windows_
.window1
->parent(), &location_in_parent
);
442 window_resizer_
->Drag(location_in_parent
, event_flags
);
443 gfx::Rect bounds
= ScreenUtil::ConvertRectToScreen(
444 windows_
.window1
->parent(),
445 CalculateResizeWidgetBounds(location_in_parent
));
447 if (windows_
.direction
== LEFT_RIGHT
)
448 bounds
.set_y(show_bounds_in_screen_
.y());
450 bounds
.set_x(show_bounds_in_screen_
.x());
451 resize_widget_
->SetBounds(bounds
);
454 void MultiWindowResizeController::CompleteResize() {
455 window_resizer_
->CompleteDrag();
456 wm::GetWindowState(window_resizer_
->GetTarget())->DeleteDragDetails();
457 window_resizer_
.reset();
459 // Mouse may still be over resizer, if not hide.
460 gfx::Point screen_loc
= Shell::GetScreen()->GetCursorScreenPoint();
461 if (!resize_widget_
->GetWindowBoundsInScreen().Contains(screen_loc
)) {
464 // If the mouse is over the resizer we need to remove observers on any of
465 // the |other_windows|. If we start another resize we'll recalculate the
466 // |other_windows| and invoke AddObserver() as necessary.
467 for (size_t i
= 0; i
< windows_
.other_windows
.size(); ++i
)
468 windows_
.other_windows
[i
]->RemoveObserver(this);
469 windows_
.other_windows
.clear();
473 void MultiWindowResizeController::CancelResize() {
474 if (!window_resizer_
)
475 return; // Happens if window was destroyed and we nuked the WindowResizer.
476 window_resizer_
->RevertDrag();
477 wm::GetWindowState(window_resizer_
->GetTarget())->DeleteDragDetails();
478 window_resizer_
.reset();
482 gfx::Rect
MultiWindowResizeController::CalculateResizeWidgetBounds(
483 const gfx::Point
& location_in_parent
) const {
484 gfx::Size pref
= resize_widget_
->GetContentsView()->GetPreferredSize();
486 if (windows_
.direction
== LEFT_RIGHT
) {
487 x
= windows_
.window1
->bounds().right() - pref
.width() / 2;
488 y
= location_in_parent
.y() + kResizeWidgetPadding
;
489 if (y
+ pref
.height() / 2 > windows_
.window1
->bounds().bottom() &&
490 y
+ pref
.height() / 2 > windows_
.window2
->bounds().bottom()) {
491 y
= location_in_parent
.y() - kResizeWidgetPadding
- pref
.height();
494 x
= location_in_parent
.x() + kResizeWidgetPadding
;
495 if (x
+ pref
.height() / 2 > windows_
.window1
->bounds().right() &&
496 x
+ pref
.height() / 2 > windows_
.window2
->bounds().right()) {
497 x
= location_in_parent
.x() - kResizeWidgetPadding
- pref
.width();
499 y
= windows_
.window1
->bounds().bottom() - pref
.height() / 2;
501 return gfx::Rect(x
, y
, pref
.width(), pref
.height());
504 bool MultiWindowResizeController::IsOverWindows(
505 const gfx::Point
& location_in_screen
) const {
507 return true; // Ignore hides while actively resizing.
509 if (resize_widget_
->GetWindowBoundsInScreen().Contains(location_in_screen
))
513 if (windows_
.direction
== TOP_BOTTOM
) {
521 return IsOverWindow(windows_
.window1
, location_in_screen
, hit1
) ||
522 IsOverWindow(windows_
.window2
, location_in_screen
, hit2
);
525 bool MultiWindowResizeController::IsOverWindow(
526 aura::Window
* window
,
527 const gfx::Point
& location_in_screen
,
528 int component
) const {
529 if (!window
->delegate())
532 gfx::Point
window_loc(location_in_screen
);
533 aura::Window::ConvertPointToTarget(
534 window
->GetRootWindow(), window
, &window_loc
);
535 return window
->ContainsPoint(window_loc
) &&
536 window
->delegate()->GetNonClientComponent(window_loc
) == component
;