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.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
388 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
389 params.parent = Shell::GetContainer(
390 Shell::GetTargetRootWindow(),
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(
440 aura::client::WINDOW_MOVE_SOURCE_MOUSE,
444 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
446 gfx::Point location_in_parent(location_in_screen);
447 aura::client::GetScreenPositionClient(windows_.window1->GetRootWindow())->
448 ConvertPointFromScreen(windows_.window1->parent(), &location_in_parent);
449 window_resizer_->Drag(location_in_parent, event_flags);
450 gfx::Rect bounds = ScreenAsh::ConvertRectToScreen(
451 windows_.window1->parent(),
452 CalculateResizeWidgetBounds(location_in_parent));
454 if (windows_.direction == LEFT_RIGHT)
455 bounds.set_y(show_bounds_in_screen_.y());
457 bounds.set_x(show_bounds_in_screen_.x());
458 resize_widget_->SetBounds(bounds);
461 void MultiWindowResizeController::CompleteResize(int event_flags) {
462 window_resizer_->CompleteDrag(event_flags);
463 window_resizer_.reset();
465 // Mouse may still be over resizer, if not hide.
466 gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
467 if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
470 // If the mouse is over the resizer we need to remove observers on any of
471 // the |other_windows|. If we start another resize we'll recalculate the
472 // |other_windows| and invoke AddObserver() as necessary.
473 for (size_t i = 0; i < windows_.other_windows.size(); ++i)
474 windows_.other_windows[i]->RemoveObserver(this);
475 windows_.other_windows.clear();
479 void MultiWindowResizeController::CancelResize() {
480 if (!window_resizer_)
481 return; // Happens if window was destroyed and we nuked the WindowResizer.
482 window_resizer_->RevertDrag();
483 window_resizer_.reset();
487 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
488 const gfx::Point& location_in_parent) const {
489 gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
491 if (windows_.direction == LEFT_RIGHT) {
492 x = windows_.window1->bounds().right() - pref.width() / 2;
493 y = location_in_parent.y() + kResizeWidgetPadding;
494 if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
495 y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
496 y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
499 x = location_in_parent.x() + kResizeWidgetPadding;
500 if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
501 x + pref.height() / 2 > windows_.window2->bounds().right()) {
502 x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
504 y = windows_.window1->bounds().bottom() - pref.height() / 2;
506 return gfx::Rect(x, y, pref.width(), pref.height());
509 bool MultiWindowResizeController::IsOverWindows(
510 const gfx::Point& location_in_screen) const {
512 return true; // Ignore hides while actively resizing.
514 if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
518 if (windows_.direction == TOP_BOTTOM) {
526 return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
527 IsOverWindow(windows_.window2, location_in_screen, hit2);
530 bool MultiWindowResizeController::IsOverWindow(
531 aura::Window* window,
532 const gfx::Point& location_in_screen,
533 int component) const {
534 if (!window->delegate())
537 gfx::Point window_loc(location_in_screen);
538 aura::Window::ConvertPointToTarget(
539 window->GetRootWindow(), window, &window_loc);
540 return window->HitTest(window_loc) &&
541 window->delegate()->GetNonClientComponent(window_loc) == component;
544 } // namespace internal