• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ash/wm/workspace/multi_window_resize_controller.h"
6 
7 #include "ash/screen_util.h"
8 #include "ash/shell.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/window.h"
17 #include "ui/aura/window_delegate.h"
18 #include "ui/aura/window_event_dispatcher.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/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 
29 using aura::Window;
30 
31 namespace ash {
32 namespace {
33 
34 // Delay before showing.
35 const int kShowDelayMS = 400;
36 
37 // Delay before hiding.
38 const int kHideDelayMS = 500;
39 
40 // Padding from the bottom/right edge the resize widget is shown at.
41 const int kResizeWidgetPadding = 15;
42 
ContainsX(Window * window,int x)43 bool ContainsX(Window* window, int x) {
44   return window->bounds().x() <= x && window->bounds().right() >= x;
45 }
46 
ContainsY(Window * window,int y)47 bool ContainsY(Window* window, int y) {
48   return window->bounds().y() <= y && window->bounds().bottom() >= y;
49 }
50 
Intersects(int x1,int max_1,int x2,int max_2)51 bool Intersects(int x1, int max_1, int x2, int max_2) {
52   return x2 <= max_1 && max_2 > x1;
53 }
54 
55 }  // namespace
56 
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 {
60  public:
ResizeView(MultiWindowResizeController * controller,Direction direction)61   explicit ResizeView(MultiWindowResizeController* controller,
62                       Direction direction)
63       : controller_(controller),
64         direction_(direction),
65         image_(NULL) {
66     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
67     int image_id =
68         direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
69                                   IDR_AURA_MULTI_WINDOW_RESIZE_V;
70     image_ = rb.GetImageNamed(image_id).ToImageSkia();
71   }
72 
73   // views::View overrides:
GetPreferredSize() const74   virtual gfx::Size GetPreferredSize() const OVERRIDE {
75     return gfx::Size(image_->width(), image_->height());
76   }
OnPaint(gfx::Canvas * canvas)77   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
78     canvas->DrawImageInt(*image_, 0, 0);
79   }
OnMousePressed(const ui::MouseEvent & event)80   virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
81     gfx::Point location(event.location());
82     views::View::ConvertPointToScreen(this, &location);
83     controller_->StartResize(location);
84     return true;
85   }
OnMouseDragged(const ui::MouseEvent & event)86   virtual 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());
90     return true;
91   }
OnMouseReleased(const ui::MouseEvent & event)92   virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
93     controller_->CompleteResize();
94   }
OnMouseCaptureLost()95   virtual void OnMouseCaptureLost() OVERRIDE {
96     controller_->CancelResize();
97   }
GetCursor(const ui::MouseEvent & event)98   virtual gfx::NativeCursor GetCursor(
99       const ui::MouseEvent& event) OVERRIDE {
100     int component = (direction_ == LEFT_RIGHT) ? HTRIGHT : HTBOTTOM;
101     return ::wm::CompoundEventFilter::CursorForWindowComponent(
102         component);
103   }
104 
105  private:
106   MultiWindowResizeController* controller_;
107   const Direction direction_;
108   const gfx::ImageSkia* image_;
109 
110   DISALLOW_COPY_AND_ASSIGN(ResizeView);
111 };
112 
113 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
114 // Contains() to MultiWindowResizeController.
115 class MultiWindowResizeController::ResizeMouseWatcherHost :
116    public views::MouseWatcherHost {
117  public:
ResizeMouseWatcherHost(MultiWindowResizeController * host)118   ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
119 
120   // MouseWatcherHost overrides:
Contains(const gfx::Point & point_in_screen,MouseEventType type)121   virtual bool Contains(const gfx::Point& point_in_screen,
122                         MouseEventType type) OVERRIDE {
123     return host_->IsOverWindows(point_in_screen);
124   }
125 
126  private:
127   MultiWindowResizeController* host_;
128 
129   DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
130 };
131 
ResizeWindows()132 MultiWindowResizeController::ResizeWindows::ResizeWindows()
133     : window1(NULL),
134       window2(NULL),
135       direction(TOP_BOTTOM){
136 }
137 
~ResizeWindows()138 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
139 }
140 
Equals(const ResizeWindows & other) const141 bool MultiWindowResizeController::ResizeWindows::Equals(
142     const ResizeWindows& other) const {
143   return window1 == other.window1 &&
144       window2 == other.window2 &&
145       direction == other.direction;
146 }
147 
MultiWindowResizeController()148 MultiWindowResizeController::MultiWindowResizeController() {
149 }
150 
~MultiWindowResizeController()151 MultiWindowResizeController::~MultiWindowResizeController() {
152   window_resizer_.reset();
153   Hide();
154 }
155 
Show(Window * window,int component,const gfx::Point & point_in_window)156 void MultiWindowResizeController::Show(Window* window,
157                                        int component,
158                                        const gfx::Point& point_in_window) {
159   // When the resize widget is showing we ignore Show() requests. Instead we
160   // only care about mouse movements from MouseWatcher. This is necessary as
161   // WorkspaceEventHandler only sees mouse movements over the windows, not all
162   // windows or over the desktop.
163   if (resize_widget_)
164     return;
165 
166   ResizeWindows windows(DetermineWindows(window, component, point_in_window));
167   if (IsShowing()) {
168     if (windows_.Equals(windows))
169       return;  // Over the same windows.
170     DelayedHide();
171   }
172 
173   if (!windows.is_valid())
174     return;
175   Hide();
176   windows_ = windows;
177   windows_.window1->AddObserver(this);
178   windows_.window2->AddObserver(this);
179   show_location_in_parent_ = point_in_window;
180   Window::ConvertPointToTarget(
181       window, window->parent(), &show_location_in_parent_);
182   if (show_timer_.IsRunning())
183     return;
184   show_timer_.Start(
185       FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
186       this, &MultiWindowResizeController::ShowIfValidMouseLocation);
187 }
188 
Hide()189 void MultiWindowResizeController::Hide() {
190   hide_timer_.Stop();
191   if (window_resizer_)
192     return;  // Ignore hides while actively resizing.
193 
194   if (windows_.window1) {
195     windows_.window1->RemoveObserver(this);
196     windows_.window1 = NULL;
197   }
198   if (windows_.window2) {
199     windows_.window2->RemoveObserver(this);
200     windows_.window2 = NULL;
201   }
202 
203   show_timer_.Stop();
204 
205   if (!resize_widget_)
206     return;
207 
208   for (size_t i = 0; i < windows_.other_windows.size(); ++i)
209     windows_.other_windows[i]->RemoveObserver(this);
210   mouse_watcher_.reset();
211   resize_widget_.reset();
212   windows_ = ResizeWindows();
213 }
214 
MouseMovedOutOfHost()215 void MultiWindowResizeController::MouseMovedOutOfHost() {
216   Hide();
217 }
218 
OnWindowDestroying(aura::Window * window)219 void MultiWindowResizeController::OnWindowDestroying(
220     aura::Window* window) {
221   // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
222   window_resizer_.reset();
223   Hide();
224 }
225 
226 MultiWindowResizeController::ResizeWindows
DetermineWindowsFromScreenPoint(aura::Window * window) const227 MultiWindowResizeController::DetermineWindowsFromScreenPoint(
228     aura::Window* window) const {
229   gfx::Point mouse_location(
230       gfx::Screen::GetScreenFor(window)->GetCursorScreenPoint());
231   wm::ConvertPointFromScreen(window, &mouse_location);
232   const int component =
233       window->delegate()->GetNonClientComponent(mouse_location);
234   return DetermineWindows(window, component, mouse_location);
235 }
236 
237 MultiWindowResizeController::ResizeWindows
DetermineWindows(Window * window,int window_component,const gfx::Point & point) const238 MultiWindowResizeController::DetermineWindows(
239     Window* window,
240     int window_component,
241     const gfx::Point& point) const {
242   ResizeWindows result;
243   gfx::Point point_in_parent(point);
244   Window::ConvertPointToTarget(window, window->parent(), &point_in_parent);
245   switch (window_component) {
246     case HTRIGHT:
247       result.direction = LEFT_RIGHT;
248       result.window1 = window;
249       result.window2 = FindWindowByEdge(
250           window, HTLEFT, window->bounds().right(), point_in_parent.y());
251       break;
252     case HTLEFT:
253       result.direction = LEFT_RIGHT;
254       result.window1 = FindWindowByEdge(
255           window, HTRIGHT, window->bounds().x(), point_in_parent.y());
256       result.window2 = window;
257       break;
258     case HTTOP:
259       result.direction = TOP_BOTTOM;
260       result.window1 = FindWindowByEdge(
261           window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
262       result.window2 = window;
263       break;
264     case HTBOTTOM:
265       result.direction = TOP_BOTTOM;
266       result.window1 = window;
267       result.window2 = FindWindowByEdge(
268           window, HTTOP, point_in_parent.x(), window->bounds().bottom());
269       break;
270     default:
271       break;
272   }
273   return result;
274 }
275 
FindWindowByEdge(Window * window_to_ignore,int edge_want,int x,int y) const276 Window* MultiWindowResizeController::FindWindowByEdge(
277     Window* window_to_ignore,
278     int edge_want,
279     int x,
280     int y) const {
281   Window* parent = window_to_ignore->parent();
282   const Window::Windows& windows(parent->children());
283   for (Window::Windows::const_reverse_iterator i = windows.rbegin();
284        i != windows.rend(); ++i) {
285     Window* window = *i;
286     if (window == window_to_ignore || !window->IsVisible())
287       continue;
288     switch (edge_want) {
289       case HTLEFT:
290         if (ContainsY(window, y) && window->bounds().x() == x)
291           return window;
292         break;
293       case HTRIGHT:
294         if (ContainsY(window, y) && window->bounds().right() == x)
295           return window;
296         break;
297       case HTTOP:
298         if (ContainsX(window, x) && window->bounds().y() == y)
299           return window;
300         break;
301       case HTBOTTOM:
302         if (ContainsX(window, x) && window->bounds().bottom() == y)
303           return window;
304         break;
305       default:
306         NOTREACHED();
307     }
308     // Window doesn't contain the edge, but if window contains |point|
309     // it's obscuring any other window that could be at the location.
310     if (window->bounds().Contains(x, y))
311       return NULL;
312   }
313   return NULL;
314 }
315 
FindWindowTouching(aura::Window * window,Direction direction) const316 aura::Window* MultiWindowResizeController::FindWindowTouching(
317     aura::Window* window,
318     Direction direction) const {
319   int right = window->bounds().right();
320   int bottom = window->bounds().bottom();
321   Window* parent = window->parent();
322   const Window::Windows& windows(parent->children());
323   for (Window::Windows::const_reverse_iterator i = windows.rbegin();
324        i != windows.rend(); ++i) {
325     Window* other = *i;
326     if (other == window || !other->IsVisible())
327       continue;
328     switch (direction) {
329       case TOP_BOTTOM:
330         if (other->bounds().y() == bottom &&
331             Intersects(other->bounds().x(), other->bounds().right(),
332                        window->bounds().x(), window->bounds().right())) {
333           return other;
334         }
335         break;
336       case LEFT_RIGHT:
337         if (other->bounds().x() == right &&
338             Intersects(other->bounds().y(), other->bounds().bottom(),
339                        window->bounds().y(), window->bounds().bottom())) {
340           return other;
341         }
342         break;
343       default:
344         NOTREACHED();
345     }
346   }
347   return NULL;
348 }
349 
FindWindowsTouching(aura::Window * start,Direction direction,std::vector<aura::Window * > * others) const350 void MultiWindowResizeController::FindWindowsTouching(
351     aura::Window* start,
352     Direction direction,
353     std::vector<aura::Window*>* others) const {
354   while (start) {
355     start = FindWindowTouching(start, direction);
356     if (start)
357       others->push_back(start);
358   }
359 }
360 
DelayedHide()361 void MultiWindowResizeController::DelayedHide() {
362   if (hide_timer_.IsRunning())
363     return;
364 
365   hide_timer_.Start(FROM_HERE,
366                     base::TimeDelta::FromMilliseconds(kHideDelayMS),
367                     this, &MultiWindowResizeController::Hide);
368 }
369 
ShowIfValidMouseLocation()370 void MultiWindowResizeController::ShowIfValidMouseLocation() {
371   if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
372       DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
373     ShowNow();
374   } else {
375     Hide();
376   }
377 }
378 
ShowNow()379 void MultiWindowResizeController::ShowNow() {
380   DCHECK(!resize_widget_.get());
381   DCHECK(windows_.is_valid());
382   show_timer_.Stop();
383   resize_widget_.reset(new views::Widget);
384   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
385   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
386   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
387   params.parent = Shell::GetContainer(Shell::GetTargetRootWindow(),
388                                       kShellWindowId_AlwaysOnTopContainer);
389   ResizeView* view = new ResizeView(this, windows_.direction);
390   resize_widget_->set_focus_on_creation(false);
391   resize_widget_->Init(params);
392   ::wm::SetWindowVisibilityAnimationType(
393       resize_widget_->GetNativeWindow(),
394       ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
395   resize_widget_->GetNativeWindow()->SetName("MultiWindowResizeController");
396   resize_widget_->SetContentsView(view);
397   show_bounds_in_screen_ = ScreenUtil::ConvertRectToScreen(
398       windows_.window1->parent(),
399       CalculateResizeWidgetBounds(show_location_in_parent_));
400   resize_widget_->SetBounds(show_bounds_in_screen_);
401   resize_widget_->Show();
402   mouse_watcher_.reset(new views::MouseWatcher(
403                            new ResizeMouseWatcherHost(this),
404                            this));
405   mouse_watcher_->set_notify_on_exit_time(
406       base::TimeDelta::FromMilliseconds(kHideDelayMS));
407   mouse_watcher_->Start();
408 }
409 
IsShowing() const410 bool MultiWindowResizeController::IsShowing() const {
411   return resize_widget_.get() || show_timer_.IsRunning();
412 }
413 
StartResize(const gfx::Point & location_in_screen)414 void MultiWindowResizeController::StartResize(
415     const gfx::Point& location_in_screen) {
416   DCHECK(!window_resizer_.get());
417   DCHECK(windows_.is_valid());
418   hide_timer_.Stop();
419   gfx::Point location_in_parent(location_in_screen);
420   aura::client::GetScreenPositionClient(windows_.window2->GetRootWindow())->
421       ConvertPointFromScreen(windows_.window2->parent(), &location_in_parent);
422   std::vector<aura::Window*> windows;
423   windows.push_back(windows_.window2);
424   DCHECK(windows_.other_windows.empty());
425   FindWindowsTouching(windows_.window2, windows_.direction,
426                       &windows_.other_windows);
427   for (size_t i = 0; i < windows_.other_windows.size(); ++i) {
428     windows_.other_windows[i]->AddObserver(this);
429     windows.push_back(windows_.other_windows[i]);
430   }
431   int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
432   wm::WindowState* window_state = wm::GetWindowState(windows_.window1);
433   window_state->CreateDragDetails(windows_.window1,
434                                   location_in_parent,
435                                   component,
436                                   aura::client::WINDOW_MOVE_SOURCE_MOUSE);
437   window_resizer_.reset(WorkspaceWindowResizer::Create(window_state, windows));
438 }
439 
Resize(const gfx::Point & location_in_screen,int event_flags)440 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
441                                          int event_flags) {
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 = ScreenUtil::ConvertRectToScreen(
447       windows_.window1->parent(),
448       CalculateResizeWidgetBounds(location_in_parent));
449 
450   if (windows_.direction == LEFT_RIGHT)
451     bounds.set_y(show_bounds_in_screen_.y());
452   else
453     bounds.set_x(show_bounds_in_screen_.x());
454   resize_widget_->SetBounds(bounds);
455 }
456 
CompleteResize()457 void MultiWindowResizeController::CompleteResize() {
458   window_resizer_->CompleteDrag();
459   wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
460   window_resizer_.reset();
461 
462   // Mouse may still be over resizer, if not hide.
463   gfx::Point screen_loc = Shell::GetScreen()->GetCursorScreenPoint();
464   if (!resize_widget_->GetWindowBoundsInScreen().Contains(screen_loc)) {
465     Hide();
466   } else {
467     // If the mouse is over the resizer we need to remove observers on any of
468     // the |other_windows|. If we start another resize we'll recalculate the
469     // |other_windows| and invoke AddObserver() as necessary.
470     for (size_t i = 0; i < windows_.other_windows.size(); ++i)
471       windows_.other_windows[i]->RemoveObserver(this);
472     windows_.other_windows.clear();
473   }
474 }
475 
CancelResize()476 void MultiWindowResizeController::CancelResize() {
477   if (!window_resizer_)
478     return;  // Happens if window was destroyed and we nuked the WindowResizer.
479   window_resizer_->RevertDrag();
480   wm::GetWindowState(window_resizer_->GetTarget())->DeleteDragDetails();
481   window_resizer_.reset();
482   Hide();
483 }
484 
CalculateResizeWidgetBounds(const gfx::Point & location_in_parent) const485 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
486     const gfx::Point& location_in_parent) const {
487   gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
488   int x = 0, y = 0;
489   if (windows_.direction == LEFT_RIGHT) {
490     x = windows_.window1->bounds().right() - pref.width() / 2;
491     y = location_in_parent.y() + kResizeWidgetPadding;
492     if (y + pref.height() / 2 > windows_.window1->bounds().bottom() &&
493         y + pref.height() / 2 > windows_.window2->bounds().bottom()) {
494       y = location_in_parent.y() - kResizeWidgetPadding - pref.height();
495     }
496   } else {
497     x = location_in_parent.x() + kResizeWidgetPadding;
498     if (x + pref.height() / 2 > windows_.window1->bounds().right() &&
499         x + pref.height() / 2 > windows_.window2->bounds().right()) {
500       x = location_in_parent.x() - kResizeWidgetPadding - pref.width();
501     }
502     y = windows_.window1->bounds().bottom() - pref.height() / 2;
503   }
504   return gfx::Rect(x, y, pref.width(), pref.height());
505 }
506 
IsOverWindows(const gfx::Point & location_in_screen) const507 bool MultiWindowResizeController::IsOverWindows(
508     const gfx::Point& location_in_screen) const {
509   if (window_resizer_)
510     return true;  // Ignore hides while actively resizing.
511 
512   if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
513     return true;
514 
515   int hit1, hit2;
516   if (windows_.direction == TOP_BOTTOM) {
517     hit1 = HTBOTTOM;
518     hit2 = HTTOP;
519   } else {
520     hit1 = HTRIGHT;
521     hit2 = HTLEFT;
522   }
523 
524   return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
525       IsOverWindow(windows_.window2, location_in_screen, hit2);
526 }
527 
IsOverWindow(aura::Window * window,const gfx::Point & location_in_screen,int component) const528 bool MultiWindowResizeController::IsOverWindow(
529     aura::Window* window,
530     const gfx::Point& location_in_screen,
531     int component) const {
532   if (!window->delegate())
533     return false;
534 
535   gfx::Point window_loc(location_in_screen);
536   aura::Window::ConvertPointToTarget(
537       window->GetRootWindow(), window, &window_loc);
538   return window->ContainsPoint(window_loc) &&
539       window->delegate()->GetNonClientComponent(window_loc) == component;
540 }
541 
542 }  // namespace ash
543