Home | History | Annotate | Download | only in workspace
      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_ash.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/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"
     28 
     29 using aura::Window;
     30 
     31 namespace ash {
     32 namespace internal {
     33 
     34 namespace {
     35 
     36 // Delay before showing.
     37 const int kShowDelayMS = 400;
     38 
     39 // Delay before hiding.
     40 const int kHideDelayMS = 500;
     41 
     42 // Padding from the bottom/right edge the resize widget is shown at.
     43 const int kResizeWidgetPadding = 15;
     44 
     45 bool ContainsX(Window* window, int x) {
     46   return window->bounds().x() <= x && window->bounds().right() >= x;
     47 }
     48 
     49 bool ContainsY(Window* window, int y) {
     50   return window->bounds().y() <= y && window->bounds().bottom() >= y;
     51 }
     52 
     53 bool Intersects(int x1, int max_1, int x2, int max_2) {
     54   return x2 <= max_1 && max_2 > x1;
     55 }
     56 
     57 }  // namespace
     58 
     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 {
     62  public:
     63   explicit ResizeView(MultiWindowResizeController* controller,
     64                       Direction direction)
     65       : controller_(controller),
     66         direction_(direction),
     67         image_(NULL) {
     68     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
     69     int image_id =
     70         direction == TOP_BOTTOM ? IDR_AURA_MULTI_WINDOW_RESIZE_H :
     71                                   IDR_AURA_MULTI_WINDOW_RESIZE_V;
     72     image_ = rb.GetImageNamed(image_id).ToImageSkia();
     73   }
     74 
     75   // views::View overrides:
     76   virtual gfx::Size GetPreferredSize() OVERRIDE {
     77     return gfx::Size(image_->width(), image_->height());
     78   }
     79   virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
     80     canvas->DrawImageInt(*image_, 0, 0);
     81   }
     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);
     86     return true;
     87   }
     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());
     92     return true;
     93   }
     94   virtual void OnMouseReleased(const ui::MouseEvent& event) OVERRIDE {
     95     controller_->CompleteResize(event.flags());
     96   }
     97   virtual void OnMouseCaptureLost() OVERRIDE {
     98     controller_->CancelResize();
     99   }
    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(
    104         component);
    105   }
    106 
    107  private:
    108   MultiWindowResizeController* controller_;
    109   const Direction direction_;
    110   const gfx::ImageSkia* image_;
    111 
    112   DISALLOW_COPY_AND_ASSIGN(ResizeView);
    113 };
    114 
    115 // MouseWatcherHost implementation for MultiWindowResizeController. Forwards
    116 // Contains() to MultiWindowResizeController.
    117 class MultiWindowResizeController::ResizeMouseWatcherHost :
    118    public views::MouseWatcherHost {
    119  public:
    120   ResizeMouseWatcherHost(MultiWindowResizeController* host) : host_(host) {}
    121 
    122   // MouseWatcherHost overrides:
    123   virtual bool Contains(const gfx::Point& point_in_screen,
    124                         MouseEventType type) OVERRIDE {
    125     return host_->IsOverWindows(point_in_screen);
    126   }
    127 
    128  private:
    129   MultiWindowResizeController* host_;
    130 
    131   DISALLOW_COPY_AND_ASSIGN(ResizeMouseWatcherHost);
    132 };
    133 
    134 MultiWindowResizeController::ResizeWindows::ResizeWindows()
    135     : window1(NULL),
    136       window2(NULL),
    137       direction(TOP_BOTTOM){
    138 }
    139 
    140 MultiWindowResizeController::ResizeWindows::~ResizeWindows() {
    141 }
    142 
    143 bool MultiWindowResizeController::ResizeWindows::Equals(
    144     const ResizeWindows& other) const {
    145   return window1 == other.window1 &&
    146       window2 == other.window2 &&
    147       direction == other.direction;
    148 }
    149 
    150 MultiWindowResizeController::MultiWindowResizeController() {
    151 }
    152 
    153 MultiWindowResizeController::~MultiWindowResizeController() {
    154   window_resizer_.reset();
    155   Hide();
    156 }
    157 
    158 void MultiWindowResizeController::Show(Window* window,
    159                                        int component,
    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.
    165   if (resize_widget_)
    166     return;
    167 
    168   ResizeWindows windows(DetermineWindows(window, component, point_in_window));
    169   if (IsShowing()) {
    170     if (windows_.Equals(windows))
    171       return;  // Over the same windows.
    172     DelayedHide();
    173   }
    174 
    175   if (!windows.is_valid())
    176     return;
    177   Hide();
    178   windows_ = windows;
    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())
    185     return;
    186   show_timer_.Start(
    187       FROM_HERE, base::TimeDelta::FromMilliseconds(kShowDelayMS),
    188       this, &MultiWindowResizeController::ShowIfValidMouseLocation);
    189 }
    190 
    191 void MultiWindowResizeController::Hide() {
    192   hide_timer_.Stop();
    193   if (window_resizer_)
    194     return;  // Ignore hides while actively resizing.
    195 
    196   if (windows_.window1) {
    197     windows_.window1->RemoveObserver(this);
    198     windows_.window1 = NULL;
    199   }
    200   if (windows_.window2) {
    201     windows_.window2->RemoveObserver(this);
    202     windows_.window2 = NULL;
    203   }
    204 
    205   show_timer_.Stop();
    206 
    207   if (!resize_widget_)
    208     return;
    209 
    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();
    215 }
    216 
    217 void MultiWindowResizeController::MouseMovedOutOfHost() {
    218   Hide();
    219 }
    220 
    221 void MultiWindowResizeController::OnWindowDestroying(
    222     aura::Window* window) {
    223   // Have to explicitly reset the WindowResizer, otherwise Hide() does nothing.
    224   window_resizer_.reset();
    225   Hide();
    226 }
    227 
    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);
    237 }
    238 
    239 MultiWindowResizeController::ResizeWindows
    240 MultiWindowResizeController::DetermineWindows(
    241     Window* window,
    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) {
    248     case HTRIGHT:
    249       result.direction = LEFT_RIGHT;
    250       result.window1 = window;
    251       result.window2 = FindWindowByEdge(
    252           window, HTLEFT, window->bounds().right(), point_in_parent.y());
    253       break;
    254     case HTLEFT:
    255       result.direction = LEFT_RIGHT;
    256       result.window1 = FindWindowByEdge(
    257           window, HTRIGHT, window->bounds().x(), point_in_parent.y());
    258       result.window2 = window;
    259       break;
    260     case HTTOP:
    261       result.direction = TOP_BOTTOM;
    262       result.window1 = FindWindowByEdge(
    263           window, HTBOTTOM, point_in_parent.x(), window->bounds().y());
    264       result.window2 = window;
    265       break;
    266     case HTBOTTOM:
    267       result.direction = TOP_BOTTOM;
    268       result.window1 = window;
    269       result.window2 = FindWindowByEdge(
    270           window, HTTOP, point_in_parent.x(), window->bounds().bottom());
    271       break;
    272     default:
    273       break;
    274   }
    275   return result;
    276 }
    277 
    278 Window* MultiWindowResizeController::FindWindowByEdge(
    279     Window* window_to_ignore,
    280     int edge_want,
    281     int x,
    282     int y) const {
    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) {
    287     Window* window = *i;
    288     if (window == window_to_ignore || !window->IsVisible())
    289       continue;
    290     switch (edge_want) {
    291       case HTLEFT:
    292         if (ContainsY(window, y) && window->bounds().x() == x)
    293           return window;
    294         break;
    295       case HTRIGHT:
    296         if (ContainsY(window, y) && window->bounds().right() == x)
    297           return window;
    298         break;
    299       case HTTOP:
    300         if (ContainsX(window, x) && window->bounds().y() == y)
    301           return window;
    302         break;
    303       case HTBOTTOM:
    304         if (ContainsX(window, x) && window->bounds().bottom() == y)
    305           return window;
    306         break;
    307       default:
    308         NOTREACHED();
    309     }
    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))
    313       return NULL;
    314   }
    315   return NULL;
    316 }
    317 
    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) {
    327     Window* other = *i;
    328     if (other == window || !other->IsVisible())
    329       continue;
    330     switch (direction) {
    331       case TOP_BOTTOM:
    332         if (other->bounds().y() == bottom &&
    333             Intersects(other->bounds().x(), other->bounds().right(),
    334                        window->bounds().x(), window->bounds().right())) {
    335           return other;
    336         }
    337         break;
    338       case LEFT_RIGHT:
    339         if (other->bounds().x() == right &&
    340             Intersects(other->bounds().y(), other->bounds().bottom(),
    341                        window->bounds().y(), window->bounds().bottom())) {
    342           return other;
    343         }
    344         break;
    345       default:
    346         NOTREACHED();
    347     }
    348   }
    349   return NULL;
    350 }
    351 
    352 void MultiWindowResizeController::FindWindowsTouching(
    353     aura::Window* start,
    354     Direction direction,
    355     std::vector<aura::Window*>* others) const {
    356   while (start) {
    357     start = FindWindowTouching(start, direction);
    358     if (start)
    359       others->push_back(start);
    360   }
    361 }
    362 
    363 void MultiWindowResizeController::DelayedHide() {
    364   if (hide_timer_.IsRunning())
    365     return;
    366 
    367   hide_timer_.Start(FROM_HERE,
    368                     base::TimeDelta::FromMilliseconds(kHideDelayMS),
    369                     this, &MultiWindowResizeController::Hide);
    370 }
    371 
    372 void MultiWindowResizeController::ShowIfValidMouseLocation() {
    373   if (DetermineWindowsFromScreenPoint(windows_.window1).Equals(windows_) ||
    374       DetermineWindowsFromScreenPoint(windows_.window2).Equals(windows_)) {
    375     ShowNow();
    376   } else {
    377     Hide();
    378   }
    379 }
    380 
    381 void MultiWindowResizeController::ShowNow() {
    382   DCHECK(!resize_widget_.get());
    383   DCHECK(windows_.is_valid());
    384   show_timer_.Stop();
    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::GetActiveRootWindow(),
    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),
    408                            this));
    409   mouse_watcher_->set_notify_on_exit_time(
    410       base::TimeDelta::FromMilliseconds(kHideDelayMS));
    411   mouse_watcher_->Start();
    412 }
    413 
    414 bool MultiWindowResizeController::IsShowing() const {
    415   return resize_widget_.get() || show_timer_.IsRunning();
    416 }
    417 
    418 void MultiWindowResizeController::StartResize(
    419     const gfx::Point& location_in_screen) {
    420   DCHECK(!window_resizer_.get());
    421   DCHECK(windows_.is_valid());
    422   hide_timer_.Stop();
    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]);
    434   }
    435   int component = windows_.direction == LEFT_RIGHT ? HTRIGHT : HTBOTTOM;
    436   window_resizer_.reset(WorkspaceWindowResizer::Create(
    437       windows_.window1,
    438       location_in_parent,
    439       component,
    440       aura::client::WINDOW_MOVE_SOURCE_MOUSE,
    441       windows));
    442 }
    443 
    444 void MultiWindowResizeController::Resize(const gfx::Point& location_in_screen,
    445                                          int event_flags) {
    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));
    453 
    454   if (windows_.direction == LEFT_RIGHT)
    455     bounds.set_y(show_bounds_in_screen_.y());
    456   else
    457     bounds.set_x(show_bounds_in_screen_.x());
    458   resize_widget_->SetBounds(bounds);
    459 }
    460 
    461 void MultiWindowResizeController::CompleteResize(int event_flags) {
    462   window_resizer_->CompleteDrag(event_flags);
    463   window_resizer_.reset();
    464 
    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)) {
    468     Hide();
    469   } else {
    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();
    476   }
    477 }
    478 
    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();
    484   Hide();
    485 }
    486 
    487 gfx::Rect MultiWindowResizeController::CalculateResizeWidgetBounds(
    488     const gfx::Point& location_in_parent) const {
    489   gfx::Size pref = resize_widget_->GetContentsView()->GetPreferredSize();
    490   int x = 0, y = 0;
    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();
    497     }
    498   } else {
    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();
    503     }
    504     y = windows_.window1->bounds().bottom() - pref.height() / 2;
    505   }
    506   return gfx::Rect(x, y, pref.width(), pref.height());
    507 }
    508 
    509 bool MultiWindowResizeController::IsOverWindows(
    510     const gfx::Point& location_in_screen) const {
    511   if (window_resizer_)
    512     return true;  // Ignore hides while actively resizing.
    513 
    514   if (resize_widget_->GetWindowBoundsInScreen().Contains(location_in_screen))
    515     return true;
    516 
    517   int hit1, hit2;
    518   if (windows_.direction == TOP_BOTTOM) {
    519     hit1 = HTBOTTOM;
    520     hit2 = HTTOP;
    521   } else {
    522     hit1 = HTRIGHT;
    523     hit2 = HTLEFT;
    524   }
    525 
    526   return IsOverWindow(windows_.window1, location_in_screen, hit1) ||
    527       IsOverWindow(windows_.window2, location_in_screen, hit2);
    528 }
    529 
    530 bool MultiWindowResizeController::IsOverWindow(
    531     aura::Window* window,
    532     const gfx::Point& location_in_screen,
    533     int component) const {
    534   if (!window->delegate())
    535     return false;
    536 
    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;
    542 }
    543 
    544 }  // namespace internal
    545 }  // namespace ash
    546