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/snap_sizer.h"
      6 
      7 #include <cmath>
      8 
      9 #include "ash/screen_ash.h"
     10 #include "ash/wm/property_util.h"
     11 #include "ash/wm/window_resizer.h"
     12 #include "ash/wm/window_util.h"
     13 #include "ui/aura/window.h"
     14 #include "ui/gfx/screen.h"
     15 
     16 namespace ash {
     17 namespace internal {
     18 
     19 namespace {
     20 
     21 // A list of ideal window width in pixel which will be used to populate the
     22 // |usable_width_| list.
     23 const int kIdealWidth[] = { 1280, 1024, 768, 640 };
     24 
     25 // Windows are initially snapped to the size in |usable_width_| at index 0.
     26 // The index into |usable_width_| is changed if any of the following happen:
     27 // . The user stops moving the mouse for |kDelayBeforeIncreaseMS| and then
     28 //   moves the mouse again.
     29 // . The mouse moves |kPixelsBeforeAdjust| horizontal pixels.
     30 // . The mouse is against the edge of the screen and the mouse is moved
     31 //   |kMovesBeforeAdjust| times.
     32 const int kDelayBeforeIncreaseMS = 500;
     33 const int kMovesBeforeAdjust = 25;
     34 const int kPixelsBeforeAdjust = 100;
     35 
     36 // When the smallest resolution does not fit on the screen, we take this
     37 // fraction of the available space.
     38 const int kMinimumScreenPercent = 90;
     39 
     40 // Create the list of possible width for the current screen configuration:
     41 // Fill the |usable_width_| list with items from |kIdealWidth| which fit on
     42 // the screen and supplement it with the 'half of screen' size. Furthermore,
     43 // add an entry for 90% of the screen size if it is smaller then the biggest
     44 // value in the |kIdealWidth| list (to get a step between the values).
     45 std::vector<int> BuildIdealWidthList(aura::Window* window) {
     46   std::vector<int> ideal_width_list;
     47   gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window));
     48   int half_size = work_area.width() / 2;
     49   int maximum_width = (kMinimumScreenPercent * work_area.width()) / 100;
     50   for (size_t i = 0; i < arraysize(kIdealWidth); i++) {
     51     if (maximum_width >= kIdealWidth[i]) {
     52       if (i && !ideal_width_list.size() && maximum_width != kIdealWidth[i])
     53         ideal_width_list.push_back(maximum_width);
     54       if (half_size > kIdealWidth[i])
     55         ideal_width_list.push_back(half_size);
     56       if (half_size >= kIdealWidth[i])
     57         half_size = 0;
     58       ideal_width_list.push_back(kIdealWidth[i]);
     59     }
     60   }
     61   if (half_size)
     62     ideal_width_list.push_back(half_size);
     63 
     64   return ideal_width_list;
     65 }
     66 
     67 }  // namespace
     68 
     69 SnapSizer::SnapSizer(aura::Window* window,
     70                      const gfx::Point& start,
     71                      Edge edge,
     72                      InputType input_type)
     73     : window_(window),
     74       edge_(edge),
     75       time_last_update_(base::TimeTicks::Now()),
     76       size_index_(0),
     77       resize_disabled_(false),
     78       num_moves_since_adjust_(0),
     79       last_adjust_x_(start.x()),
     80       last_update_x_(start.x()),
     81       start_x_(start.x()),
     82       input_type_(input_type),
     83       usable_width_(BuildIdealWidthList(window)) {
     84   DCHECK(!usable_width_.empty());
     85   target_bounds_ = GetTargetBounds();
     86 }
     87 
     88 SnapSizer::~SnapSizer() {
     89 }
     90 
     91 void SnapSizer::SnapWindow(aura::Window* window, SnapSizer::Edge edge) {
     92   if (!wm::CanSnapWindow(window))
     93     return;
     94   internal::SnapSizer sizer(window, gfx::Point(), edge,
     95       internal::SnapSizer::OTHER_INPUT);
     96   if (wm::IsWindowFullscreen(window) || wm::IsWindowMaximized(window)) {
     97     // Before we can set the bounds we need to restore the window.
     98     // Restoring the window will set the window to its restored bounds.
     99     // To avoid an unnecessary bounds changes (which may have side effects)
    100     // we set the restore bounds to the bounds we want, restore the window,
    101     // then reset the restore bounds. This way no unnecessary bounds
    102     // changes occurs and the original restore bounds is remembered.
    103     gfx::Rect restore = *GetRestoreBoundsInScreen(window);
    104     SetRestoreBoundsInParent(window, sizer.GetSnapBounds(window->bounds()));
    105     wm::RestoreWindow(window);
    106     SetRestoreBoundsInScreen(window, restore);
    107   } else {
    108     window->SetBounds(sizer.GetSnapBounds(window->bounds()));
    109   }
    110 }
    111 
    112 void SnapSizer::Update(const gfx::Point& location) {
    113   // See description above for details on this behavior.
    114   num_moves_since_adjust_++;
    115   if ((base::TimeTicks::Now() - time_last_update_).InMilliseconds() >
    116       kDelayBeforeIncreaseMS) {
    117     ChangeBounds(location.x(),
    118                  CalculateIncrement(location.x(), last_update_x_));
    119   } else {
    120     bool along_edge = AlongEdge(location.x());
    121     int pixels_before_adjust = kPixelsBeforeAdjust;
    122     if (input_type_ == TOUCH_MAXIMIZE_BUTTON_INPUT) {
    123       const gfx::Rect& workspace_bounds = window_->parent()->bounds();
    124       if (start_x_ > location.x()) {
    125         pixels_before_adjust =
    126             std::min(pixels_before_adjust, start_x_ / 10);
    127       } else {
    128         pixels_before_adjust =
    129             std::min(pixels_before_adjust,
    130                      (workspace_bounds.width() - start_x_) / 10);
    131       }
    132     }
    133     if (std::abs(location.x() - last_adjust_x_) >= pixels_before_adjust ||
    134         (along_edge && num_moves_since_adjust_ >= kMovesBeforeAdjust)) {
    135       ChangeBounds(location.x(),
    136                    CalculateIncrement(location.x(), last_adjust_x_));
    137     }
    138   }
    139   last_update_x_ = location.x();
    140   time_last_update_ = base::TimeTicks::Now();
    141 }
    142 
    143 gfx::Rect SnapSizer::GetSnapBounds(const gfx::Rect& bounds) {
    144   int current = 0;
    145   if (!resize_disabled_) {
    146     for (current = usable_width_.size() - 1; current >= 0; current--) {
    147       gfx::Rect target = GetTargetBoundsForSize(current);
    148       if (target == bounds) {
    149         ++current;
    150         break;
    151       }
    152     }
    153   }
    154   return GetTargetBoundsForSize(current % usable_width_.size());
    155 }
    156 
    157 void SnapSizer::SelectDefaultSizeAndDisableResize() {
    158   resize_disabled_ = true;
    159   size_index_ = 0;
    160   target_bounds_ = GetTargetBounds();
    161 }
    162 
    163 gfx::Rect SnapSizer::GetTargetBoundsForSize(size_t size_index) const {
    164   gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window_));
    165   int y = work_area.y();
    166   // We don't align to the bottom of the grid as the launcher may not
    167   // necessarily align to the grid (happens when auto-hidden).
    168   int max_y = work_area.bottom();
    169   int width = 0;
    170   if (resize_disabled_) {
    171     // Make sure that we keep the size of the window smaller then a certain
    172     // fraction of the screen space.
    173     int minimum_size = (kMinimumScreenPercent * work_area.width()) / 100;
    174     width = std::max(std::min(minimum_size, 1024), work_area.width() / 2);
    175   } else {
    176     DCHECK(size_index < usable_width_.size());
    177     width = usable_width_[size_index];
    178   }
    179 
    180   if (edge_ == LEFT_EDGE) {
    181     int x = work_area.x();
    182     int mid_x = x + width;
    183     return gfx::Rect(x, y, mid_x - x, max_y - y);
    184   }
    185   int max_x = work_area.right();
    186   int x = max_x - width;
    187   return gfx::Rect(x , y, max_x - x, max_y - y);
    188 }
    189 
    190 int SnapSizer::CalculateIncrement(int x, int reference_x) const {
    191   if (AlongEdge(x))
    192     return 1;
    193   if (x == reference_x)
    194     return 0;
    195   if (edge_ == LEFT_EDGE) {
    196     if (x < reference_x)
    197       return 1;
    198     return -1;
    199   }
    200   // edge_ == RIGHT_EDGE.
    201   if (x > reference_x)
    202     return 1;
    203   return -1;
    204 }
    205 
    206 void SnapSizer::ChangeBounds(int x, int delta) {
    207   int index = std::min(static_cast<int>(usable_width_.size()) - 1,
    208                        std::max(size_index_ + delta, 0));
    209   if (index != size_index_) {
    210     size_index_ = index;
    211     target_bounds_ = GetTargetBounds();
    212   }
    213   num_moves_since_adjust_ = 0;
    214   last_adjust_x_ = x;
    215 }
    216 
    217 gfx::Rect SnapSizer::GetTargetBounds() const {
    218   return GetTargetBoundsForSize(size_index_);
    219 }
    220 
    221 bool SnapSizer::AlongEdge(int x) const {
    222   gfx::Rect area(ScreenAsh::GetDisplayBoundsInParent(window_));
    223   return (x <= area.x()) || (x >= area.right() - 1);
    224 }
    225 
    226 }  // namespace internal
    227 }  // namespace ash
    228