Home | History | Annotate | Download | only in wm
      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/window_resizer.h"
      6 
      7 #include "ash/screen_ash.h"
      8 #include "ash/shell.h"
      9 #include "ash/shell_window_ids.h"
     10 #include "ash/wm/dock/docked_window_layout_manager.h"
     11 #include "ash/wm/property_util.h"
     12 #include "ash/wm/window_util.h"
     13 #include "ui/aura/client/aura_constants.h"
     14 #include "ui/aura/root_window.h"
     15 #include "ui/aura/window.h"
     16 #include "ui/aura/window_delegate.h"
     17 #include "ui/base/hit_test.h"
     18 #include "ui/base/ui_base_types.h"
     19 #include "ui/compositor/scoped_layer_animation_settings.h"
     20 #include "ui/gfx/display.h"
     21 #include "ui/gfx/screen.h"
     22 
     23 namespace ash {
     24 
     25 namespace {
     26 
     27 int GetPositionChangeDirectionForWindowComponent(int window_component) {
     28   int pos_change_direction = WindowResizer::kBoundsChangeDirection_None;
     29   switch (window_component) {
     30     case HTTOPLEFT:
     31     case HTBOTTOMRIGHT:
     32     case HTGROWBOX:
     33     case HTCAPTION:
     34       pos_change_direction |=
     35           WindowResizer::kBoundsChangeDirection_Horizontal |
     36           WindowResizer::kBoundsChangeDirection_Vertical;
     37       break;
     38     case HTTOP:
     39     case HTTOPRIGHT:
     40     case HTBOTTOM:
     41       pos_change_direction |= WindowResizer::kBoundsChangeDirection_Vertical;
     42       break;
     43     case HTBOTTOMLEFT:
     44     case HTRIGHT:
     45     case HTLEFT:
     46       pos_change_direction |= WindowResizer::kBoundsChangeDirection_Horizontal;
     47       break;
     48     default:
     49       break;
     50   }
     51   return pos_change_direction;
     52 }
     53 
     54 int GetSizeChangeDirectionForWindowComponent(int window_component) {
     55   int size_change_direction = WindowResizer::kBoundsChangeDirection_None;
     56   switch (window_component) {
     57     case HTTOPLEFT:
     58     case HTTOPRIGHT:
     59     case HTBOTTOMLEFT:
     60     case HTBOTTOMRIGHT:
     61     case HTGROWBOX:
     62     case HTCAPTION:
     63       size_change_direction |=
     64           WindowResizer::kBoundsChangeDirection_Horizontal |
     65           WindowResizer::kBoundsChangeDirection_Vertical;
     66       break;
     67     case HTTOP:
     68     case HTBOTTOM:
     69       size_change_direction |= WindowResizer::kBoundsChangeDirection_Vertical;
     70       break;
     71     case HTRIGHT:
     72     case HTLEFT:
     73       size_change_direction |= WindowResizer::kBoundsChangeDirection_Horizontal;
     74       break;
     75     default:
     76       break;
     77   }
     78   return size_change_direction;
     79 }
     80 
     81 // Returns true for resize components along the right edge, where a drag in
     82 // positive x will make the window larger.
     83 bool IsRightEdge(int window_component) {
     84   return window_component == HTTOPRIGHT ||
     85       window_component == HTRIGHT ||
     86       window_component == HTBOTTOMRIGHT ||
     87       window_component == HTGROWBOX;
     88 }
     89 
     90 }  // namespace
     91 
     92 // static
     93 const int WindowResizer::kBoundsChange_None = 0;
     94 // static
     95 const int WindowResizer::kBoundsChange_Repositions = 1;
     96 // static
     97 const int WindowResizer::kBoundsChange_Resizes = 2;
     98 
     99 // static
    100 const int WindowResizer::kBoundsChangeDirection_None = 0;
    101 // static
    102 const int WindowResizer::kBoundsChangeDirection_Horizontal = 1;
    103 // static
    104 const int WindowResizer::kBoundsChangeDirection_Vertical = 2;
    105 
    106 WindowResizer::Details::Details()
    107     : window(NULL),
    108       window_component(HTNOWHERE),
    109       bounds_change(0),
    110       position_change_direction(0),
    111       size_change_direction(0),
    112       is_resizable(false),
    113       source(aura::client::WINDOW_MOVE_SOURCE_MOUSE) {
    114 }
    115 
    116 WindowResizer::Details::Details(aura::Window* window,
    117                                 const gfx::Point& location,
    118                                 int window_component,
    119                                 aura::client::WindowMoveSource source)
    120     : window(window),
    121       initial_bounds_in_parent(window->bounds()),
    122       restore_bounds(gfx::Rect()),
    123       initial_location_in_parent(location),
    124       initial_opacity(window->layer()->opacity()),
    125       window_component(window_component),
    126       bounds_change(GetBoundsChangeForWindowComponent(window_component)),
    127       position_change_direction(
    128           GetPositionChangeDirectionForWindowComponent(window_component)),
    129       size_change_direction(
    130           GetSizeChangeDirectionForWindowComponent(window_component)),
    131       is_resizable(bounds_change != kBoundsChangeDirection_None),
    132       source(source) {
    133   if (wm::IsWindowNormal(window) &&
    134       GetRestoreBoundsInScreen(window) &&
    135       window_component == HTCAPTION)
    136     restore_bounds = *GetRestoreBoundsInScreen(window);
    137 }
    138 
    139 WindowResizer::Details::~Details() {
    140 }
    141 
    142 WindowResizer::WindowResizer() {
    143 }
    144 
    145 WindowResizer::~WindowResizer() {
    146 }
    147 
    148 // static
    149 int WindowResizer::GetBoundsChangeForWindowComponent(int component) {
    150   int bounds_change = WindowResizer::kBoundsChange_None;
    151   switch (component) {
    152     case HTTOPLEFT:
    153     case HTTOP:
    154     case HTTOPRIGHT:
    155     case HTLEFT:
    156     case HTBOTTOMLEFT:
    157       bounds_change |= WindowResizer::kBoundsChange_Repositions |
    158                       WindowResizer::kBoundsChange_Resizes;
    159       break;
    160     case HTCAPTION:
    161       bounds_change |= WindowResizer::kBoundsChange_Repositions;
    162       break;
    163     case HTRIGHT:
    164     case HTBOTTOMRIGHT:
    165     case HTBOTTOM:
    166     case HTGROWBOX:
    167       bounds_change |= WindowResizer::kBoundsChange_Resizes;
    168       break;
    169     default:
    170       break;
    171   }
    172   return bounds_change;
    173 }
    174 
    175 // static
    176 gfx::Rect WindowResizer::CalculateBoundsForDrag(
    177     const Details& details,
    178     const gfx::Point& passed_location) {
    179   if (!details.is_resizable)
    180     return details.initial_bounds_in_parent;
    181 
    182   gfx::Point location = passed_location;
    183   aura::Window* dock_container = Shell::GetContainer(
    184       details.window->GetRootWindow(),
    185       internal::kShellWindowId_DockedContainer);
    186   DCHECK_EQ(dock_container->id(), internal::kShellWindowId_DockedContainer);
    187   internal::DockedWindowLayoutManager* dock_layout =
    188       static_cast<internal::DockedWindowLayoutManager*>(
    189           dock_container->layout_manager());
    190 
    191   int delta_x = location.x() - details.initial_location_in_parent.x();
    192   int delta_y = location.y() - details.initial_location_in_parent.y();
    193 
    194   AdjustDeltaForTouchResize(details, &delta_x, &delta_y);
    195 
    196   // The minimize size constraint may limit how much we change the window
    197   // position.  For example, dragging the left edge to the right should stop
    198   // repositioning the window when the minimize size is reached.
    199   gfx::Size size = GetSizeForDrag(details, &delta_x, &delta_y);
    200   gfx::Point origin = GetOriginForDrag(details, delta_x, delta_y);
    201   gfx::Rect new_bounds(origin, size);
    202 
    203   // Sizing has to keep the result on the screen. Note that this correction
    204   // has to come first since it might have an impact on the origin as well as
    205   // on the size.
    206   if (details.bounds_change & kBoundsChange_Resizes) {
    207     gfx::Rect work_area =
    208         Shell::GetScreen()->GetDisplayNearestWindow(details.window).work_area();
    209     work_area.Union(dock_layout->docked_bounds());
    210     work_area = ScreenAsh::ConvertRectFromScreen(details.window->parent(),
    211                                                  work_area);
    212     if (details.size_change_direction & kBoundsChangeDirection_Horizontal) {
    213       if (IsRightEdge(details.window_component) &&
    214           new_bounds.right() < work_area.x() + kMinimumOnScreenArea) {
    215         int delta = work_area.x() + kMinimumOnScreenArea - new_bounds.right();
    216         new_bounds.set_width(new_bounds.width() + delta);
    217       } else if (new_bounds.x() > work_area.right() - kMinimumOnScreenArea) {
    218         int width = new_bounds.right() - work_area.right() +
    219                     kMinimumOnScreenArea;
    220         new_bounds.set_x(work_area.right() - kMinimumOnScreenArea);
    221         new_bounds.set_width(width);
    222       }
    223     }
    224     if (details.size_change_direction & kBoundsChangeDirection_Vertical) {
    225       if (!IsBottomEdge(details.window_component) &&
    226           new_bounds.y() > work_area.bottom() - kMinimumOnScreenArea) {
    227         int height = new_bounds.bottom() - work_area.bottom() +
    228                      kMinimumOnScreenArea;
    229         new_bounds.set_y(work_area.bottom() - kMinimumOnScreenArea);
    230         new_bounds.set_height(height);
    231       } else if (details.window_component == HTBOTTOM ||
    232                  details.window_component == HTBOTTOMRIGHT ||
    233                  details.window_component == HTBOTTOMLEFT) {
    234         // Update bottom edge to stay in the work area when we are resizing
    235         // by dragging the bottom edge or corners.
    236         if (new_bounds.bottom() > work_area.bottom())
    237           new_bounds.Inset(0, 0, 0,
    238                            new_bounds.bottom() - work_area.bottom());
    239       }
    240     }
    241     if (details.bounds_change & kBoundsChange_Repositions &&
    242         new_bounds.y() < 0) {
    243       int delta = new_bounds.y();
    244       new_bounds.set_y(0);
    245       new_bounds.set_height(new_bounds.height() + delta);
    246     }
    247   }
    248 
    249   if (details.bounds_change & kBoundsChange_Repositions) {
    250     // When we might want to reposition a window which is also restored to its
    251     // previous size, to keep the cursor within the dragged window.
    252     if (!details.restore_bounds.IsEmpty()) {
    253       // However - it is not desirable to change the origin if the window would
    254       // be still hit by the cursor.
    255       if (details.initial_location_in_parent.x() >
    256           details.initial_bounds_in_parent.x() + details.restore_bounds.width())
    257         new_bounds.set_x(location.x() - details.restore_bounds.width() / 2);
    258     }
    259 
    260     // Make sure that |new_bounds| doesn't leave any of the displays.  Note that
    261     // the |work_area| above isn't good for this check since it is the work area
    262     // for the current display but the window can move to a different one.
    263     aura::Window* parent = details.window->parent();
    264     gfx::Rect new_bounds_in_screen =
    265         ScreenAsh::ConvertRectToScreen(parent, new_bounds);
    266     const gfx::Display& display =
    267         Shell::GetScreen()->GetDisplayMatching(new_bounds_in_screen);
    268     gfx::Rect screen_work_area = display.work_area();
    269     screen_work_area.Union(dock_layout->docked_bounds());
    270     screen_work_area.Inset(kMinimumOnScreenArea, 0);
    271     if (!screen_work_area.Intersects(new_bounds_in_screen)) {
    272       // Make sure that the x origin does not leave the current display.
    273       new_bounds_in_screen.set_x(
    274           std::max(screen_work_area.x() - new_bounds.width(),
    275                    std::min(screen_work_area.right(),
    276                             new_bounds_in_screen.x())));
    277       new_bounds =
    278           ScreenAsh::ConvertRectFromScreen(parent, new_bounds_in_screen);
    279     }
    280   }
    281 
    282   return new_bounds;
    283 }
    284 
    285 // static
    286 bool WindowResizer::IsBottomEdge(int window_component) {
    287   return window_component == HTBOTTOMLEFT ||
    288       window_component == HTBOTTOM ||
    289       window_component == HTBOTTOMRIGHT ||
    290       window_component == HTGROWBOX;
    291 }
    292 
    293 // static
    294 void WindowResizer::AdjustDeltaForTouchResize(const Details& details,
    295                                               int* delta_x,
    296                                               int* delta_y) {
    297   if (details.source != aura::client::WINDOW_MOVE_SOURCE_TOUCH ||
    298       !(details.bounds_change & kBoundsChange_Resizes))
    299     return;
    300 
    301   if (details.size_change_direction & kBoundsChangeDirection_Horizontal) {
    302     if (IsRightEdge(details.window_component)) {
    303       *delta_x += details.initial_location_in_parent.x() -
    304           details.initial_bounds_in_parent.right();
    305     } else {
    306       *delta_x += details.initial_location_in_parent.x() -
    307           details.initial_bounds_in_parent.x();
    308     }
    309   }
    310   if (details.size_change_direction & kBoundsChangeDirection_Vertical) {
    311     if (IsBottomEdge(details.window_component)) {
    312       *delta_y += details.initial_location_in_parent.y() -
    313           details.initial_bounds_in_parent.bottom();
    314     } else {
    315       *delta_y += details.initial_location_in_parent.y() -
    316           details.initial_bounds_in_parent.y();
    317     }
    318   }
    319 }
    320 
    321 // static
    322 gfx::Point WindowResizer::GetOriginForDrag(const Details& details,
    323                                            int delta_x,
    324                                            int delta_y) {
    325   gfx::Point origin = details.initial_bounds_in_parent.origin();
    326   if (details.bounds_change & kBoundsChange_Repositions) {
    327     int pos_change_direction =
    328         GetPositionChangeDirectionForWindowComponent(details.window_component);
    329     if (pos_change_direction & kBoundsChangeDirection_Horizontal)
    330       origin.Offset(delta_x, 0);
    331     if (pos_change_direction & kBoundsChangeDirection_Vertical)
    332       origin.Offset(0, delta_y);
    333   }
    334   return origin;
    335 }
    336 
    337 // static
    338 gfx::Size WindowResizer::GetSizeForDrag(const Details& details,
    339                                         int* delta_x,
    340                                         int* delta_y) {
    341   gfx::Size size = details.initial_bounds_in_parent.size();
    342   if (details.bounds_change & kBoundsChange_Resizes) {
    343     gfx::Size min_size = details.window->delegate()->GetMinimumSize();
    344     size.SetSize(GetWidthForDrag(details, min_size.width(), delta_x),
    345                  GetHeightForDrag(details, min_size.height(), delta_y));
    346   } else if (!details.restore_bounds.IsEmpty()) {
    347     size = details.restore_bounds.size();
    348   }
    349   return size;
    350 }
    351 
    352 // static
    353 int WindowResizer::GetWidthForDrag(const Details& details,
    354                                    int min_width,
    355                                    int* delta_x) {
    356   int width = details.initial_bounds_in_parent.width();
    357   if (details.size_change_direction & kBoundsChangeDirection_Horizontal) {
    358     // Along the right edge, positive delta_x increases the window size.
    359     int x_multiplier = IsRightEdge(details.window_component) ? 1 : -1;
    360     width += x_multiplier * (*delta_x);
    361 
    362     // Ensure we don't shrink past the minimum width and clamp delta_x
    363     // for the window origin computation.
    364     if (width < min_width) {
    365       width = min_width;
    366       *delta_x = -x_multiplier * (details.initial_bounds_in_parent.width() -
    367                                   min_width);
    368     }
    369 
    370     // And don't let the window go bigger than the display.
    371     int max_width = Shell::GetScreen()->GetDisplayNearestWindow(
    372         details.window).bounds().width();
    373     gfx::Size max_size = details.window->delegate()->GetMaximumSize();
    374     if (max_size.width() != 0)
    375       max_width = std::min(max_width, max_size.width());
    376     if (width > max_width) {
    377       width = max_width;
    378       *delta_x = -x_multiplier * (details.initial_bounds_in_parent.width() -
    379                                   max_width);
    380     }
    381   }
    382   return width;
    383 }
    384 
    385 // static
    386 int WindowResizer::GetHeightForDrag(const Details& details,
    387                                     int min_height,
    388                                     int* delta_y) {
    389   int height = details.initial_bounds_in_parent.height();
    390   if (details.size_change_direction & kBoundsChangeDirection_Vertical) {
    391     // Along the bottom edge, positive delta_y increases the window size.
    392     int y_multiplier = IsBottomEdge(details.window_component) ? 1 : -1;
    393     height += y_multiplier * (*delta_y);
    394 
    395     // Ensure we don't shrink past the minimum height and clamp delta_y
    396     // for the window origin computation.
    397     if (height < min_height) {
    398       height = min_height;
    399       *delta_y = -y_multiplier * (details.initial_bounds_in_parent.height() -
    400                                   min_height);
    401     }
    402 
    403     // And don't let the window go bigger than the display.
    404     int max_height = Shell::GetScreen()->GetDisplayNearestWindow(
    405         details.window).bounds().height();
    406     gfx::Size max_size = details.window->delegate()->GetMaximumSize();
    407     if (max_size.height() != 0)
    408       max_height = std::min(max_height, max_size.height());
    409     if (height > max_height) {
    410       height = max_height;
    411       *delta_y = -y_multiplier * (details.initial_bounds_in_parent.height() -
    412                                   max_height);
    413     }
    414   }
    415   return height;
    416 }
    417 
    418 }  // namespace aura
    419