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/workspace_window_resizer.h"
      6 
      7 #include <algorithm>
      8 #include <cmath>
      9 #include <utility>
     10 #include <vector>
     11 
     12 #include "ash/ash_switches.h"
     13 #include "ash/display/display_controller.h"
     14 #include "ash/root_window_controller.h"
     15 #include "ash/screen_ash.h"
     16 #include "ash/shell.h"
     17 #include "ash/shell_window_ids.h"
     18 #include "ash/wm/coordinate_conversion.h"
     19 #include "ash/wm/default_window_resizer.h"
     20 #include "ash/wm/dock/docked_window_layout_manager.h"
     21 #include "ash/wm/dock/docked_window_resizer.h"
     22 #include "ash/wm/drag_window_resizer.h"
     23 #include "ash/wm/panels/panel_window_resizer.h"
     24 #include "ash/wm/window_state.h"
     25 #include "ash/wm/window_util.h"
     26 #include "ash/wm/workspace/phantom_window_controller.h"
     27 #include "ash/wm/workspace/snap_sizer.h"
     28 #include "base/command_line.h"
     29 #include "base/memory/weak_ptr.h"
     30 #include "ui/aura/client/aura_constants.h"
     31 #include "ui/aura/client/screen_position_client.h"
     32 #include "ui/aura/client/window_types.h"
     33 #include "ui/aura/root_window.h"
     34 #include "ui/aura/window.h"
     35 #include "ui/aura/window_delegate.h"
     36 #include "ui/base/hit_test.h"
     37 #include "ui/compositor/layer.h"
     38 #include "ui/gfx/screen.h"
     39 #include "ui/gfx/transform.h"
     40 
     41 namespace ash {
     42 
     43 scoped_ptr<WindowResizer> CreateWindowResizer(
     44     aura::Window* window,
     45     const gfx::Point& point_in_parent,
     46     int window_component,
     47     aura::client::WindowMoveSource source) {
     48   DCHECK(window);
     49   wm::WindowState* window_state = wm::GetWindowState(window);
     50   // No need to return a resizer when the window cannot get resized or when a
     51   // resizer already exists for this window.
     52   if ((!window_state->CanResize() && window_component != HTCAPTION) ||
     53       window_state->window_resizer()) {
     54     return scoped_ptr<WindowResizer>();
     55   }
     56 
     57   // TODO(varkha): The chaining of window resizers causes some of the logic
     58   // to be repeated and the logic flow difficult to control. With some windows
     59   // classes using reparenting during drag operations it becomes challenging to
     60   // implement proper transition from one resizer to another during or at the
     61   // end of the drag. This also causes http://crbug.com/247085.
     62   // It seems the only thing the panel or dock resizer needs to do is notify the
     63   // layout manager when a docked window is being dragged. We should have a
     64   // better way of doing this, perhaps by having a way of observing drags or
     65   // having a generic drag window wrapper which informs a layout manager that a
     66   // drag has started or stopped.
     67   // It may be possible to refactor and eliminate chaining.
     68   WindowResizer* window_resizer = NULL;
     69   if (window->parent() &&
     70       (window->parent()->id() == internal::kShellWindowId_DefaultContainer ||
     71        window->parent()->id() == internal::kShellWindowId_DockedContainer ||
     72        window->parent()->id() == internal::kShellWindowId_PanelContainer)) {
     73     // Allow dragging maximized windows if it's not tracked by workspace. This
     74     // is set by tab dragging code.
     75     if (!window_state->IsNormalShowState() &&
     76         (window_component != HTCAPTION ||
     77          !window_state->is_dragged())) {
     78       return scoped_ptr<WindowResizer>();
     79     }
     80     window_resizer = internal::WorkspaceWindowResizer::Create(
     81         window,
     82         point_in_parent,
     83         window_component,
     84         source,
     85         std::vector<aura::Window*>());
     86   } else if (window_state->IsNormalShowState()) {
     87     window_resizer = DefaultWindowResizer::Create(
     88         window, point_in_parent, window_component, source);
     89   }
     90   if (window_resizer) {
     91     window_resizer = internal::DragWindowResizer::Create(
     92         window_resizer, window, point_in_parent, window_component, source);
     93   }
     94   if (window_resizer && window->type() == aura::client::WINDOW_TYPE_PANEL) {
     95     window_resizer = PanelWindowResizer::Create(
     96         window_resizer, window, point_in_parent, window_component, source);
     97   }
     98   if (switches::UseDockedWindows() &&
     99       window_resizer && window->parent() &&
    100       !window->transient_parent() &&
    101       (window->parent()->id() == internal::kShellWindowId_DefaultContainer ||
    102        window->parent()->id() == internal::kShellWindowId_DockedContainer ||
    103        window->parent()->id() == internal::kShellWindowId_PanelContainer)) {
    104     window_resizer = internal::DockedWindowResizer::Create(
    105         window_resizer, window, point_in_parent, window_component, source);
    106   }
    107   window_state->set_window_resizer_(window_resizer);
    108   return make_scoped_ptr<WindowResizer>(window_resizer);
    109 }
    110 
    111 namespace internal {
    112 
    113 namespace {
    114 
    115 // Snapping distance used instead of WorkspaceWindowResizer::kScreenEdgeInset
    116 // when resizing a window using touchscreen.
    117 const int kScreenEdgeInsetForTouchResize = 32;
    118 
    119 // Returns true if the window should stick to the edge.
    120 bool ShouldStickToEdge(int distance_from_edge, int sticky_size) {
    121   if (CommandLine::ForCurrentProcess()->HasSwitch(
    122           switches::kAshEnableStickyEdges)) {
    123     // TODO(varkha): Consider keeping snapping behavior for touch drag.
    124     return distance_from_edge < 0 &&
    125            distance_from_edge > -sticky_size;
    126   }
    127   return distance_from_edge < sticky_size &&
    128          distance_from_edge > -sticky_size * 2;
    129 }
    130 
    131 // Returns the coordinate along the secondary axis to snap to.
    132 int CoordinateAlongSecondaryAxis(SecondaryMagnetismEdge edge,
    133                                  int leading,
    134                                  int trailing,
    135                                  int none) {
    136   switch (edge) {
    137     case SECONDARY_MAGNETISM_EDGE_LEADING:
    138       return leading;
    139     case SECONDARY_MAGNETISM_EDGE_TRAILING:
    140       return trailing;
    141     case SECONDARY_MAGNETISM_EDGE_NONE:
    142       return none;
    143   }
    144   NOTREACHED();
    145   return none;
    146 }
    147 
    148 // Returns the origin for |src| when magnetically attaching to |attach_to| along
    149 // the edges |edges|. |edges| is a bitmask of the MagnetismEdges.
    150 gfx::Point OriginForMagneticAttach(const gfx::Rect& src,
    151                                    const gfx::Rect& attach_to,
    152                                    const MatchedEdge& edge) {
    153   int x = 0, y = 0;
    154   switch (edge.primary_edge) {
    155     case MAGNETISM_EDGE_TOP:
    156       y = attach_to.bottom();
    157       break;
    158     case MAGNETISM_EDGE_LEFT:
    159       x = attach_to.right();
    160       break;
    161     case MAGNETISM_EDGE_BOTTOM:
    162       y = attach_to.y() - src.height();
    163       break;
    164     case MAGNETISM_EDGE_RIGHT:
    165       x = attach_to.x() - src.width();
    166       break;
    167   }
    168   switch (edge.primary_edge) {
    169     case MAGNETISM_EDGE_TOP:
    170     case MAGNETISM_EDGE_BOTTOM:
    171       x = CoordinateAlongSecondaryAxis(
    172           edge.secondary_edge, attach_to.x(), attach_to.right() - src.width(),
    173           src.x());
    174       break;
    175     case MAGNETISM_EDGE_LEFT:
    176     case MAGNETISM_EDGE_RIGHT:
    177       y = CoordinateAlongSecondaryAxis(
    178           edge.secondary_edge, attach_to.y(), attach_to.bottom() - src.height(),
    179           src.y());
    180       break;
    181   }
    182   return gfx::Point(x, y);
    183 }
    184 
    185 // Returns the bounds for a magnetic attach when resizing. |src| is the bounds
    186 // of window being resized, |attach_to| the bounds of the window to attach to
    187 // and |edge| identifies the edge to attach to.
    188 gfx::Rect BoundsForMagneticResizeAttach(const gfx::Rect& src,
    189                                         const gfx::Rect& attach_to,
    190                                         const MatchedEdge& edge) {
    191   int x = src.x();
    192   int y = src.y();
    193   int w = src.width();
    194   int h = src.height();
    195   gfx::Point attach_origin(OriginForMagneticAttach(src, attach_to, edge));
    196   switch (edge.primary_edge) {
    197     case MAGNETISM_EDGE_LEFT:
    198       x = attach_origin.x();
    199       w = src.right() - x;
    200       break;
    201     case MAGNETISM_EDGE_RIGHT:
    202       w += attach_origin.x() - src.x();
    203       break;
    204     case MAGNETISM_EDGE_TOP:
    205       y = attach_origin.y();
    206       h = src.bottom() - y;
    207       break;
    208     case MAGNETISM_EDGE_BOTTOM:
    209       h += attach_origin.y() - src.y();
    210       break;
    211   }
    212   switch (edge.primary_edge) {
    213     case MAGNETISM_EDGE_LEFT:
    214     case MAGNETISM_EDGE_RIGHT:
    215       if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) {
    216         y = attach_origin.y();
    217         h = src.bottom() - y;
    218       } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) {
    219         h += attach_origin.y() - src.y();
    220       }
    221       break;
    222     case MAGNETISM_EDGE_TOP:
    223     case MAGNETISM_EDGE_BOTTOM:
    224       if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_LEADING) {
    225         x = attach_origin.x();
    226         w = src.right() - x;
    227       } else if (edge.secondary_edge == SECONDARY_MAGNETISM_EDGE_TRAILING) {
    228         w += attach_origin.x() - src.x();
    229       }
    230       break;
    231   }
    232   return gfx::Rect(x, y, w, h);
    233 }
    234 
    235 // Converts a window component edge to the magnetic edge to snap to.
    236 uint32 WindowComponentToMagneticEdge(int window_component) {
    237   switch (window_component) {
    238     case HTTOPLEFT:
    239       return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_TOP;
    240     case HTTOPRIGHT:
    241       return MAGNETISM_EDGE_TOP | MAGNETISM_EDGE_RIGHT;
    242     case HTBOTTOMLEFT:
    243       return MAGNETISM_EDGE_LEFT | MAGNETISM_EDGE_BOTTOM;
    244     case HTBOTTOMRIGHT:
    245       return MAGNETISM_EDGE_RIGHT | MAGNETISM_EDGE_BOTTOM;
    246     case HTTOP:
    247       return MAGNETISM_EDGE_TOP;
    248     case HTBOTTOM:
    249       return MAGNETISM_EDGE_BOTTOM;
    250     case HTRIGHT:
    251       return MAGNETISM_EDGE_RIGHT;
    252     case HTLEFT:
    253       return MAGNETISM_EDGE_LEFT;
    254     default:
    255       break;
    256   }
    257   return 0;
    258 }
    259 
    260 }  // namespace
    261 
    262 // static
    263 const int WorkspaceWindowResizer::kMinOnscreenSize = 20;
    264 
    265 // static
    266 const int WorkspaceWindowResizer::kMinOnscreenHeight = 32;
    267 
    268 // static
    269 const int WorkspaceWindowResizer::kScreenEdgeInset = 8;
    270 
    271 // static
    272 const int WorkspaceWindowResizer::kStickyDistancePixels = 64;
    273 
    274 // static
    275 WorkspaceWindowResizer* WorkspaceWindowResizer::instance_ = NULL;
    276 
    277 // Represents the width or height of a window with constraints on its minimum
    278 // and maximum size. 0 represents a lack of a constraint.
    279 class WindowSize {
    280  public:
    281   WindowSize(int size, int min, int max)
    282       : size_(size),
    283         min_(min),
    284         max_(max) {
    285     // Grow the min/max bounds to include the starting size.
    286     if (is_underflowing())
    287       min_ = size_;
    288     if (is_overflowing())
    289       max_ = size_;
    290   }
    291 
    292   bool is_at_capacity(bool shrinking) {
    293     return size_ == (shrinking ? min_ : max_);
    294   }
    295 
    296   int size() const {
    297     return size_;
    298   }
    299 
    300   bool has_min() const {
    301     return min_ != 0;
    302   }
    303 
    304   bool has_max() const {
    305     return max_ != 0;
    306   }
    307 
    308   bool is_valid() const {
    309     return !is_overflowing() && !is_underflowing();
    310   }
    311 
    312   bool is_overflowing() const {
    313     return has_max() && size_ > max_;
    314   }
    315 
    316   bool is_underflowing() const {
    317     return has_min() && size_ < min_;
    318   }
    319 
    320   // Add |amount| to this WindowSize not exceeding min or max size constraints.
    321   // Returns by how much |size_| + |amount| exceeds the min/max constraints.
    322   int Add(int amount) {
    323     DCHECK(is_valid());
    324     int new_value = size_ + amount;
    325 
    326     if (has_min() && new_value < min_) {
    327       size_ = min_;
    328       return new_value - min_;
    329     }
    330 
    331     if (has_max() && new_value > max_) {
    332       size_ = max_;
    333       return new_value - max_;
    334     }
    335 
    336     size_ = new_value;
    337     return 0;
    338   }
    339 
    340  private:
    341   int size_;
    342   int min_;
    343   int max_;
    344 };
    345 
    346 WorkspaceWindowResizer::~WorkspaceWindowResizer() {
    347   if (did_lock_cursor_) {
    348     Shell* shell = Shell::GetInstance();
    349     shell->cursor_manager()->UnlockCursor();
    350   }
    351   if (instance_ == this)
    352     instance_ = NULL;
    353 }
    354 
    355 // static
    356 WorkspaceWindowResizer* WorkspaceWindowResizer::Create(
    357     aura::Window* window,
    358     const gfx::Point& location_in_parent,
    359     int window_component,
    360     aura::client::WindowMoveSource source,
    361     const std::vector<aura::Window*>& attached_windows) {
    362   Details details(window, location_in_parent, window_component, source);
    363   return details.is_resizable ?
    364       new WorkspaceWindowResizer(details, attached_windows) : NULL;
    365 }
    366 
    367 void WorkspaceWindowResizer::Drag(const gfx::Point& location_in_parent,
    368                                   int event_flags) {
    369   last_mouse_location_ = location_in_parent;
    370 
    371   int sticky_size;
    372   if (event_flags & ui::EF_CONTROL_DOWN) {
    373     sticky_size = 0;
    374   } else if (CommandLine::ForCurrentProcess()->HasSwitch(
    375       switches::kAshEnableStickyEdges)) {
    376     sticky_size = kStickyDistancePixels;
    377   } else if ((details_.bounds_change & kBoundsChange_Resizes) &&
    378       details_.source == aura::client::WINDOW_MOVE_SOURCE_TOUCH) {
    379     sticky_size = kScreenEdgeInsetForTouchResize;
    380   } else {
    381     sticky_size = kScreenEdgeInset;
    382   }
    383   // |bounds| is in |window()->parent()|'s coordinates.
    384   gfx::Rect bounds = CalculateBoundsForDrag(details_, location_in_parent);
    385   if (window_state()->IsNormalShowState())
    386     AdjustBoundsForMainWindow(sticky_size, &bounds);
    387 
    388   if (bounds != window()->bounds()) {
    389     if (!did_move_or_resize_) {
    390       if (!details_.restore_bounds.IsEmpty())
    391         window_state()->ClearRestoreBounds();
    392       RestackWindows();
    393     }
    394     did_move_or_resize_ = true;
    395   }
    396 
    397   gfx::Point location_in_screen = location_in_parent;
    398   wm::ConvertPointToScreen(window()->parent(), &location_in_screen);
    399 
    400   aura::Window* root = NULL;
    401   gfx::Display display =
    402       ScreenAsh::FindDisplayContainingPoint(location_in_screen);
    403   // Track the last screen that the pointer was on to keep the snap phantom
    404   // window there.
    405   if (display.is_valid()) {
    406     root = Shell::GetInstance()->display_controller()->
    407         GetRootWindowForDisplayId(display.id());
    408   }
    409   if (!attached_windows_.empty())
    410     LayoutAttachedWindows(&bounds);
    411   if (bounds != window()->bounds()) {
    412     // SetBounds needs to be called to update the layout which affects where the
    413     // phantom window is drawn. Keep track if the window was destroyed during
    414     // the drag and quit early if so.
    415     base::WeakPtr<WorkspaceWindowResizer> resizer(
    416         weak_ptr_factory_.GetWeakPtr());
    417     window()->SetBounds(bounds);
    418     if (!resizer)
    419       return;
    420   }
    421   const bool in_original_root = !root || root == window()->GetRootWindow();
    422   // Hide a phantom window for snapping if the cursor is in another root window.
    423   if (in_original_root) {
    424     UpdateSnapPhantomWindow(location_in_parent, bounds);
    425   } else {
    426     snap_type_ = SNAP_NONE;
    427     snap_phantom_window_controller_.reset();
    428     snap_sizer_.reset();
    429     SetDraggedWindowDocked(false);
    430   }
    431 }
    432 
    433 void WorkspaceWindowResizer::CompleteDrag(int event_flags) {
    434   window_state()->set_bounds_changed_by_user(true);
    435   snap_phantom_window_controller_.reset();
    436   if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
    437     return;
    438 
    439   bool snapped = false;
    440   // When the window is not in the normal show state, we do not snap the window.
    441   // This happens when the user minimizes or maximizes the window by keyboard
    442   // shortcut while dragging it. If the window is the result of dragging a tab
    443   // out of a maximized window, it's already in the normal show state when this
    444   // is called, so it does not matter.
    445   if (window_state()->IsNormalShowState() &&
    446       (window()->type() != aura::client::WINDOW_TYPE_PANEL ||
    447        !window_state()->panel_attached() ||
    448        dock_layout_->is_dragged_window_docked()) &&
    449       (snap_type_ == SNAP_LEFT || snap_type_ == SNAP_RIGHT)) {
    450     if (!window_state()->HasRestoreBounds()) {
    451       gfx::Rect initial_bounds = ScreenAsh::ConvertRectToScreen(
    452           window()->parent(), details_.initial_bounds_in_parent);
    453       window_state()->SetRestoreBoundsInScreen(
    454           details_.restore_bounds.IsEmpty() ?
    455           initial_bounds :
    456           details_.restore_bounds);
    457     }
    458     DCHECK(snap_sizer_);
    459     if (window_state()->CanResize() &&
    460         !dock_layout_->is_dragged_window_docked()) {
    461       snap_sizer_->SnapWindowToTargetBounds();
    462       snapped = true;
    463     }
    464   }
    465   if (window_state()->IsSnapped() && !snapped)
    466     window_state()->Restore();
    467 }
    468 
    469 void WorkspaceWindowResizer::RevertDrag() {
    470   window_state()->set_bounds_changed_by_user(initial_bounds_changed_by_user_);
    471   snap_phantom_window_controller_.reset();
    472 
    473   if (!did_move_or_resize_)
    474     return;
    475 
    476   window()->SetBounds(details_.initial_bounds_in_parent);
    477   if (!details_.restore_bounds.IsEmpty()) {
    478     window_state()->SetRestoreBoundsInScreen(details_.restore_bounds);
    479   }
    480 
    481   if (details_.window_component == HTRIGHT) {
    482     int last_x = details_.initial_bounds_in_parent.right();
    483     for (size_t i = 0; i < attached_windows_.size(); ++i) {
    484       gfx::Rect bounds(attached_windows_[i]->bounds());
    485       bounds.set_x(last_x);
    486       bounds.set_width(initial_size_[i]);
    487       attached_windows_[i]->SetBounds(bounds);
    488       last_x = attached_windows_[i]->bounds().right();
    489     }
    490   } else {
    491     int last_y = details_.initial_bounds_in_parent.bottom();
    492     for (size_t i = 0; i < attached_windows_.size(); ++i) {
    493       gfx::Rect bounds(attached_windows_[i]->bounds());
    494       bounds.set_y(last_y);
    495       bounds.set_height(initial_size_[i]);
    496       attached_windows_[i]->SetBounds(bounds);
    497       last_y = attached_windows_[i]->bounds().bottom();
    498     }
    499   }
    500 }
    501 
    502 aura::Window* WorkspaceWindowResizer::GetTarget() {
    503   return details_.window;
    504 }
    505 
    506 const gfx::Point& WorkspaceWindowResizer::GetInitialLocation() const {
    507   return details_.initial_location_in_parent;
    508 }
    509 
    510 WorkspaceWindowResizer::WorkspaceWindowResizer(
    511     const Details& details,
    512     const std::vector<aura::Window*>& attached_windows)
    513     : details_(details),
    514       attached_windows_(attached_windows),
    515       did_lock_cursor_(false),
    516       did_move_or_resize_(false),
    517       initial_bounds_changed_by_user_(
    518           details.window_state->bounds_changed_by_user()),
    519       total_min_(0),
    520       total_initial_size_(0),
    521       snap_type_(SNAP_NONE),
    522       num_mouse_moves_since_bounds_change_(0),
    523       magnetism_window_(NULL),
    524       weak_ptr_factory_(this) {
    525   DCHECK(details_.is_resizable);
    526 
    527   // A mousemove should still show the cursor even if the window is
    528   // being moved or resized with touch, so do not lock the cursor.
    529   if (details.source != aura::client::WINDOW_MOVE_SOURCE_TOUCH) {
    530     Shell* shell = Shell::GetInstance();
    531     shell->cursor_manager()->LockCursor();
    532     did_lock_cursor_ = true;
    533   }
    534 
    535   aura::Window* dock_container = Shell::GetContainer(
    536       window()->GetRootWindow(), kShellWindowId_DockedContainer);
    537   dock_layout_ = static_cast<DockedWindowLayoutManager*>(
    538       dock_container->layout_manager());
    539 
    540   // Only support attaching to the right/bottom.
    541   DCHECK(attached_windows_.empty() ||
    542          (details.window_component == HTRIGHT ||
    543           details.window_component == HTBOTTOM));
    544 
    545   // TODO: figure out how to deal with window going off the edge.
    546 
    547   // Calculate sizes so that we can maintain the ratios if we need to resize.
    548   int total_available = 0;
    549   for (size_t i = 0; i < attached_windows_.size(); ++i) {
    550     gfx::Size min(attached_windows_[i]->delegate()->GetMinimumSize());
    551     int initial_size = PrimaryAxisSize(attached_windows_[i]->bounds().size());
    552     initial_size_.push_back(initial_size);
    553     // If current size is smaller than the min, use the current size as the min.
    554     // This way we don't snap on resize.
    555     int min_size = std::min(initial_size,
    556                             std::max(PrimaryAxisSize(min), kMinOnscreenSize));
    557     total_min_ += min_size;
    558     total_initial_size_ += initial_size;
    559     total_available += std::max(min_size, initial_size) - min_size;
    560   }
    561   instance_ = this;
    562 }
    563 
    564 gfx::Rect WorkspaceWindowResizer::GetFinalBounds(
    565     const gfx::Rect& bounds) const {
    566   if (snap_phantom_window_controller_.get() &&
    567       snap_phantom_window_controller_->IsShowing()) {
    568     return snap_phantom_window_controller_->bounds_in_screen();
    569   }
    570   return bounds;
    571 }
    572 
    573 void WorkspaceWindowResizer::LayoutAttachedWindows(
    574     gfx::Rect* bounds) {
    575   gfx::Rect work_area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
    576   int initial_size = PrimaryAxisSize(details_.initial_bounds_in_parent.size());
    577   int current_size = PrimaryAxisSize(bounds->size());
    578   int start = PrimaryAxisCoordinate(bounds->right(), bounds->bottom());
    579   int end = PrimaryAxisCoordinate(work_area.right(), work_area.bottom());
    580 
    581   int delta = current_size - initial_size;
    582   int available_size = end - start;
    583   std::vector<int> sizes;
    584   int leftovers = CalculateAttachedSizes(delta, available_size, &sizes);
    585 
    586   // leftovers > 0 means that the attached windows can't grow to compensate for
    587   // the shrinkage of the main window. This line causes the attached windows to
    588   // be moved so they are still flush against the main window, rather than the
    589   // main window being prevented from shrinking.
    590   leftovers = std::min(0, leftovers);
    591   // Reallocate any leftover pixels back into the main window. This is
    592   // necessary when, for example, the main window shrinks, but none of the
    593   // attached windows can grow without exceeding their max size constraints.
    594   // Adding the pixels back to the main window effectively prevents the main
    595   // window from resizing too far.
    596   if (details_.window_component == HTRIGHT)
    597     bounds->set_width(bounds->width() + leftovers);
    598   else
    599     bounds->set_height(bounds->height() + leftovers);
    600 
    601   DCHECK_EQ(attached_windows_.size(), sizes.size());
    602   int last = PrimaryAxisCoordinate(bounds->right(), bounds->bottom());
    603   for (size_t i = 0; i < attached_windows_.size(); ++i) {
    604     gfx::Rect attached_bounds(attached_windows_[i]->bounds());
    605     if (details_.window_component == HTRIGHT) {
    606       attached_bounds.set_x(last);
    607       attached_bounds.set_width(sizes[i]);
    608     } else {
    609       attached_bounds.set_y(last);
    610       attached_bounds.set_height(sizes[i]);
    611     }
    612     attached_windows_[i]->SetBounds(attached_bounds);
    613     last += sizes[i];
    614   }
    615 }
    616 
    617 int WorkspaceWindowResizer::CalculateAttachedSizes(
    618     int delta,
    619     int available_size,
    620     std::vector<int>* sizes) const {
    621   std::vector<WindowSize> window_sizes;
    622   CreateBucketsForAttached(&window_sizes);
    623 
    624   // How much we need to grow the attached by (collectively).
    625   int grow_attached_by = 0;
    626   if (delta > 0) {
    627     // If the attached windows don't fit when at their initial size, we will
    628     // have to shrink them by how much they overflow.
    629     if (total_initial_size_ >= available_size)
    630       grow_attached_by = available_size - total_initial_size_;
    631   } else {
    632     // If we're shrinking, we grow the attached so the total size remains
    633     // constant.
    634     grow_attached_by = -delta;
    635   }
    636 
    637   int leftover_pixels = 0;
    638   while (grow_attached_by != 0) {
    639     int leftovers = GrowFairly(grow_attached_by, window_sizes);
    640     if (leftovers == grow_attached_by) {
    641       leftover_pixels = leftovers;
    642       break;
    643     }
    644     grow_attached_by = leftovers;
    645   }
    646 
    647   for (size_t i = 0; i < window_sizes.size(); ++i)
    648     sizes->push_back(window_sizes[i].size());
    649 
    650   return leftover_pixels;
    651 }
    652 
    653 int WorkspaceWindowResizer::GrowFairly(
    654     int pixels,
    655     std::vector<WindowSize>& sizes) const {
    656   bool shrinking = pixels < 0;
    657   std::vector<WindowSize*> nonfull_windows;
    658   for (size_t i = 0; i < sizes.size(); ++i) {
    659     if (!sizes[i].is_at_capacity(shrinking))
    660       nonfull_windows.push_back(&sizes[i]);
    661   }
    662   std::vector<float> ratios;
    663   CalculateGrowthRatios(nonfull_windows, &ratios);
    664 
    665   int remaining_pixels = pixels;
    666   bool add_leftover_pixels_to_last = true;
    667   for (size_t i = 0; i < nonfull_windows.size(); ++i) {
    668     int grow_by = pixels * ratios[i];
    669     // Put any leftover pixels into the last window.
    670     if (i == nonfull_windows.size() - 1 && add_leftover_pixels_to_last)
    671       grow_by = remaining_pixels;
    672     int remainder = nonfull_windows[i]->Add(grow_by);
    673     int consumed = grow_by - remainder;
    674     remaining_pixels -= consumed;
    675     if (nonfull_windows[i]->is_at_capacity(shrinking) && remainder > 0) {
    676       // Because this window overflowed, some of the pixels in
    677       // |remaining_pixels| aren't there due to rounding errors. Rather than
    678       // unfairly giving all those pixels to the last window, we refrain from
    679       // allocating them so that this function can be called again to distribute
    680       // the pixels fairly.
    681       add_leftover_pixels_to_last = false;
    682     }
    683   }
    684   return remaining_pixels;
    685 }
    686 
    687 void WorkspaceWindowResizer::CalculateGrowthRatios(
    688     const std::vector<WindowSize*>& sizes,
    689     std::vector<float>* out_ratios) const {
    690   DCHECK(out_ratios->empty());
    691   int total_value = 0;
    692   for (size_t i = 0; i < sizes.size(); ++i)
    693     total_value += sizes[i]->size();
    694 
    695   for (size_t i = 0; i < sizes.size(); ++i)
    696     out_ratios->push_back(
    697         (static_cast<float>(sizes[i]->size())) / total_value);
    698 }
    699 
    700 void WorkspaceWindowResizer::CreateBucketsForAttached(
    701     std::vector<WindowSize>* sizes) const {
    702   for (size_t i = 0; i < attached_windows_.size(); i++) {
    703     int initial_size = initial_size_[i];
    704     aura::WindowDelegate* delegate = attached_windows_[i]->delegate();
    705     int min = PrimaryAxisSize(delegate->GetMinimumSize());
    706     int max = PrimaryAxisSize(delegate->GetMaximumSize());
    707 
    708     sizes->push_back(WindowSize(initial_size, min, max));
    709   }
    710 }
    711 
    712 void WorkspaceWindowResizer::MagneticallySnapToOtherWindows(gfx::Rect* bounds) {
    713   if (UpdateMagnetismWindow(*bounds, kAllMagnetismEdges)) {
    714     gfx::Point point = OriginForMagneticAttach(
    715         ScreenAsh::ConvertRectToScreen(window()->parent(), *bounds),
    716         magnetism_window_->GetBoundsInScreen(),
    717         magnetism_edge_);
    718     aura::client::GetScreenPositionClient(window()->GetRootWindow())->
    719         ConvertPointFromScreen(window()->parent(), &point);
    720     bounds->set_origin(point);
    721   }
    722 }
    723 
    724 void WorkspaceWindowResizer::MagneticallySnapResizeToOtherWindows(
    725     gfx::Rect* bounds) {
    726   const uint32 edges = WindowComponentToMagneticEdge(details_.window_component);
    727   if (UpdateMagnetismWindow(*bounds, edges)) {
    728     *bounds = ScreenAsh::ConvertRectFromScreen(
    729         window()->parent(),
    730         BoundsForMagneticResizeAttach(
    731             ScreenAsh::ConvertRectToScreen(window()->parent(), *bounds),
    732             magnetism_window_->GetBoundsInScreen(),
    733             magnetism_edge_));
    734   }
    735 }
    736 
    737 bool WorkspaceWindowResizer::UpdateMagnetismWindow(const gfx::Rect& bounds,
    738                                                    uint32 edges) {
    739   // |bounds| are in coordinates of original window's parent.
    740   gfx::Rect bounds_in_screen =
    741       ScreenAsh::ConvertRectToScreen(window()->parent(), bounds);
    742   MagnetismMatcher matcher(bounds_in_screen, edges);
    743 
    744   // If we snapped to a window then check it first. That way we don't bounce
    745   // around when close to multiple edges.
    746   if (magnetism_window_) {
    747     if (window_tracker_.Contains(magnetism_window_) &&
    748         matcher.ShouldAttach(magnetism_window_->GetBoundsInScreen(),
    749                              &magnetism_edge_)) {
    750       return true;
    751     }
    752     window_tracker_.Remove(magnetism_window_);
    753     magnetism_window_ = NULL;
    754   }
    755 
    756   // Avoid magnetically snapping windows that are not resizable.
    757   // TODO(oshima): change this to window.type() == TYPE_NORMAL.
    758   if (!window_state()->CanResize())
    759     return false;
    760 
    761   aura::Window::Windows root_windows = Shell::GetAllRootWindows();
    762   for (aura::Window::Windows::iterator iter = root_windows.begin();
    763        iter != root_windows.end(); ++iter) {
    764     const aura::Window* root_window = *iter;
    765     // Test all children from the desktop in each root window.
    766     const aura::Window::Windows& children = Shell::GetContainer(
    767         root_window, kShellWindowId_DefaultContainer)->children();
    768     for (aura::Window::Windows::const_reverse_iterator i = children.rbegin();
    769          i != children.rend() && !matcher.AreEdgesObscured(); ++i) {
    770       wm::WindowState* other_state = wm::GetWindowState(*i);
    771       if (other_state->window() == window() ||
    772           !other_state->window()->IsVisible() ||
    773           !other_state->IsNormalShowState() ||
    774           !other_state->CanResize()) {
    775         continue;
    776       }
    777       if (matcher.ShouldAttach(
    778               other_state->window()->GetBoundsInScreen(), &magnetism_edge_)) {
    779         magnetism_window_ = other_state->window();
    780         window_tracker_.Add(magnetism_window_);
    781         return true;
    782       }
    783     }
    784   }
    785   return false;
    786 }
    787 
    788 void WorkspaceWindowResizer::AdjustBoundsForMainWindow(
    789     int sticky_size,
    790     gfx::Rect* bounds) {
    791   gfx::Point last_mouse_location_in_screen = last_mouse_location_;
    792   wm::ConvertPointToScreen(window()->parent(), &last_mouse_location_in_screen);
    793   gfx::Display display = Shell::GetScreen()->GetDisplayNearestPoint(
    794       last_mouse_location_in_screen);
    795   gfx::Rect work_area =
    796       ScreenAsh::ConvertRectFromScreen(window()->parent(), display.work_area());
    797   if (details_.window_component == HTCAPTION) {
    798     // Adjust the bounds to the work area where the mouse cursor is located.
    799     // Always keep kMinOnscreenHeight or the window height (whichever is less)
    800     // on the bottom.
    801     int max_y = work_area.bottom() - std::min(kMinOnscreenHeight,
    802                                               bounds->height());
    803     if (bounds->y() > max_y) {
    804       bounds->set_y(max_y);
    805     } else if (bounds->y() <= work_area.y()) {
    806       // Don't allow dragging above the top of the display until the mouse
    807       // cursor reaches the work area above if any.
    808       bounds->set_y(work_area.y());
    809     }
    810 
    811     if (sticky_size > 0) {
    812       // Possibly stick to edge except when a mouse pointer is outside the
    813       // work area.
    814       if (!(display.work_area().Contains(last_mouse_location_in_screen) &&
    815             StickToWorkAreaOnMove(work_area, sticky_size, bounds))) {
    816         MagneticallySnapToOtherWindows(bounds);
    817       }
    818     }
    819   } else if (sticky_size > 0) {
    820     MagneticallySnapResizeToOtherWindows(bounds);
    821     if (!magnetism_window_ && sticky_size > 0)
    822       StickToWorkAreaOnResize(work_area, sticky_size, bounds);
    823   }
    824 
    825   if (attached_windows_.empty())
    826     return;
    827 
    828   if (details_.window_component == HTRIGHT) {
    829     bounds->set_width(std::min(bounds->width(),
    830                                work_area.right() - total_min_ - bounds->x()));
    831   } else {
    832     DCHECK_EQ(HTBOTTOM, details_.window_component);
    833     bounds->set_height(std::min(bounds->height(),
    834                                 work_area.bottom() - total_min_ - bounds->y()));
    835   }
    836 }
    837 
    838 bool WorkspaceWindowResizer::StickToWorkAreaOnMove(
    839     const gfx::Rect& work_area,
    840     int sticky_size,
    841     gfx::Rect* bounds) const {
    842   const int left_edge = work_area.x();
    843   const int right_edge = work_area.right();
    844   const int top_edge = work_area.y();
    845   const int bottom_edge = work_area.bottom();
    846   bool updated = false;
    847   if (ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) {
    848     bounds->set_x(left_edge);
    849     updated = true;
    850   } else if (ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) {
    851     bounds->set_x(right_edge - bounds->width());
    852     updated = true;
    853   }
    854   if (ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) {
    855     bounds->set_y(top_edge);
    856     updated = true;
    857   } else if (ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size) &&
    858              bounds->height() < (bottom_edge - top_edge)) {
    859     // Only snap to the bottom if the window is smaller than the work area.
    860     // Doing otherwise can lead to window snapping in weird ways as it bounces
    861     // between snapping to top then bottom.
    862     bounds->set_y(bottom_edge - bounds->height());
    863     updated = true;
    864   }
    865   return updated;
    866 }
    867 
    868 void WorkspaceWindowResizer::StickToWorkAreaOnResize(
    869     const gfx::Rect& work_area,
    870     int sticky_size,
    871     gfx::Rect* bounds) const {
    872   const uint32 edges = WindowComponentToMagneticEdge(details_.window_component);
    873   const int left_edge = work_area.x();
    874   const int right_edge = work_area.right();
    875   const int top_edge = work_area.y();
    876   const int bottom_edge = work_area.bottom();
    877   if (edges & MAGNETISM_EDGE_TOP &&
    878       ShouldStickToEdge(bounds->y() - top_edge, sticky_size)) {
    879     bounds->set_height(bounds->bottom() - top_edge);
    880     bounds->set_y(top_edge);
    881   }
    882   if (edges & MAGNETISM_EDGE_LEFT &&
    883       ShouldStickToEdge(bounds->x() - left_edge, sticky_size)) {
    884     bounds->set_width(bounds->right() - left_edge);
    885     bounds->set_x(left_edge);
    886   }
    887   if (edges & MAGNETISM_EDGE_BOTTOM &&
    888       ShouldStickToEdge(bottom_edge - bounds->bottom(), sticky_size)) {
    889     bounds->set_height(bottom_edge - bounds->y());
    890   }
    891   if (edges & MAGNETISM_EDGE_RIGHT &&
    892       ShouldStickToEdge(right_edge - bounds->right(), sticky_size)) {
    893     bounds->set_width(right_edge - bounds->x());
    894   }
    895 }
    896 
    897 int WorkspaceWindowResizer::PrimaryAxisSize(const gfx::Size& size) const {
    898   return PrimaryAxisCoordinate(size.width(), size.height());
    899 }
    900 
    901 int WorkspaceWindowResizer::PrimaryAxisCoordinate(int x, int y) const {
    902   switch (details_.window_component) {
    903     case HTRIGHT:
    904       return x;
    905     case HTBOTTOM:
    906       return y;
    907     default:
    908       NOTREACHED();
    909   }
    910   return 0;
    911 }
    912 
    913 void WorkspaceWindowResizer::UpdateSnapPhantomWindow(const gfx::Point& location,
    914                                                      const gfx::Rect& bounds) {
    915   if (!did_move_or_resize_ || details_.window_component != HTCAPTION)
    916     return;
    917 
    918   SnapType last_type = snap_type_;
    919   snap_type_ = GetSnapType(location);
    920   if (snap_type_ == SNAP_NONE || snap_type_ != last_type) {
    921     snap_phantom_window_controller_.reset();
    922     snap_sizer_.reset();
    923     if (snap_type_ == SNAP_NONE) {
    924       SetDraggedWindowDocked(false);
    925       return;
    926     }
    927   }
    928   const bool can_dock = dock_layout_->CanDockWindow(window(), snap_type_);
    929   const bool can_snap = window_state()->CanSnap();
    930   if (!can_snap && !can_dock) {
    931     snap_type_ = SNAP_NONE;
    932     snap_phantom_window_controller_.reset();
    933     snap_sizer_.reset();
    934     SetDraggedWindowDocked(false);
    935     return;
    936   }
    937   SnapSizer::Edge edge = (snap_type_ == SNAP_LEFT) ?
    938       SnapSizer::LEFT_EDGE : SnapSizer::RIGHT_EDGE;
    939   if (!snap_sizer_) {
    940     snap_sizer_.reset(new SnapSizer(window_state(),
    941                                     location,
    942                                     edge,
    943                                     internal::SnapSizer::OTHER_INPUT));
    944   } else {
    945     snap_sizer_->Update(location);
    946   }
    947 
    948   // Update phantom window with snapped or docked guide bounds.
    949   // Windows that cannot be snapped or are less wide than kMaxDockWidth can get
    950   // docked without going through a snapping sequence.
    951   gfx::Rect phantom_bounds;
    952   if (can_snap &&
    953       (!can_dock ||
    954        window()->bounds().width() > DockedWindowLayoutManager::kMaxDockWidth))
    955     phantom_bounds = snap_sizer_->target_bounds();
    956   const bool should_dock = can_dock &&
    957       (phantom_bounds.IsEmpty() ||
    958        snap_sizer_->end_of_sequence() ||
    959        dock_layout_->is_dragged_window_docked());
    960   SetDraggedWindowDocked(should_dock);
    961   snap_type_ = GetSnapType(location);
    962   if (dock_layout_->is_dragged_window_docked()) {
    963     phantom_bounds = ScreenAsh::ConvertRectFromScreen(
    964         window()->parent(), dock_layout_->dragged_bounds());
    965   }
    966 
    967   if (phantom_bounds.IsEmpty()) {
    968     snap_phantom_window_controller_.reset();
    969     return;
    970   }
    971 
    972   if (!snap_phantom_window_controller_) {
    973     snap_phantom_window_controller_.reset(
    974         new PhantomWindowController(window()));
    975   }
    976   snap_phantom_window_controller_->Show(ScreenAsh::ConvertRectToScreen(
    977       window()->parent(), phantom_bounds));
    978 }
    979 
    980 void WorkspaceWindowResizer::RestackWindows() {
    981   if (attached_windows_.empty())
    982     return;
    983   // Build a map from index in children to window, returning if there is a
    984   // window with a different parent.
    985   typedef std::map<size_t, aura::Window*> IndexToWindowMap;
    986   IndexToWindowMap map;
    987   aura::Window* parent = window()->parent();
    988   const aura::Window::Windows& windows(parent->children());
    989   map[std::find(windows.begin(), windows.end(), window()) -
    990       windows.begin()] = window();
    991   for (std::vector<aura::Window*>::const_iterator i =
    992            attached_windows_.begin(); i != attached_windows_.end(); ++i) {
    993     if ((*i)->parent() != parent)
    994       return;
    995     size_t index =
    996         std::find(windows.begin(), windows.end(), *i) - windows.begin();
    997     map[index] = *i;
    998   }
    999 
   1000   // Reorder the windows starting at the topmost.
   1001   parent->StackChildAtTop(map.rbegin()->second);
   1002   for (IndexToWindowMap::const_reverse_iterator i = map.rbegin();
   1003        i != map.rend(); ) {
   1004     aura::Window* window = i->second;
   1005     ++i;
   1006     if (i != map.rend())
   1007       parent->StackChildBelow(i->second, window);
   1008   }
   1009 }
   1010 
   1011 SnapType WorkspaceWindowResizer::GetSnapType(
   1012     const gfx::Point& location) const {
   1013   // TODO: this likely only wants total display area, not the area of a single
   1014   // display.
   1015   gfx::Rect area(ScreenAsh::GetDisplayWorkAreaBoundsInParent(window()));
   1016   if (details_.source == aura::client::WINDOW_MOVE_SOURCE_TOUCH) {
   1017     // Increase tolerance for touch-snapping near the screen edges. This is only
   1018     // necessary when the work area left or right edge is same as screen edge.
   1019     gfx::Rect display_bounds(ScreenAsh::GetDisplayBoundsInParent(window()));
   1020     int inset_left = 0;
   1021     if (area.x() == display_bounds.x())
   1022       inset_left = kScreenEdgeInsetForTouchResize;
   1023     int inset_right = 0;
   1024     if (area.right() == display_bounds.right())
   1025       inset_right = kScreenEdgeInsetForTouchResize;
   1026     area.Inset(inset_left, 0, inset_right, 0);
   1027   }
   1028   if (location.x() <= area.x())
   1029     return SNAP_LEFT;
   1030   if (location.x() >= area.right() - 1)
   1031     return SNAP_RIGHT;
   1032   return SNAP_NONE;
   1033 }
   1034 
   1035 void WorkspaceWindowResizer::SetDraggedWindowDocked(bool should_dock) {
   1036   if (should_dock &&
   1037       dock_layout_->GetAlignmentOfWindow(window()) != DOCKED_ALIGNMENT_NONE) {
   1038     if (!dock_layout_->is_dragged_window_docked()) {
   1039       window_state()->set_bounds_changed_by_user(false);
   1040       dock_layout_->DockDraggedWindow(window());
   1041     }
   1042   } else {
   1043     if (dock_layout_->is_dragged_window_docked()) {
   1044       dock_layout_->UndockDraggedWindow();
   1045       window_state()->set_bounds_changed_by_user(true);
   1046     }
   1047   }
   1048 }
   1049 
   1050 }  // namespace internal
   1051 }  // namespace ash
   1052