Home | History | Annotate | Download | only in panels
      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 "chrome/browser/ui/panels/panel_drag_controller.h"
      6 
      7 #include "base/logging.h"
      8 #include "chrome/browser/ui/panels/detached_panel_collection.h"
      9 #include "chrome/browser/ui/panels/detached_panel_drag_handler.h"
     10 #include "chrome/browser/ui/panels/docked_panel_collection.h"
     11 #include "chrome/browser/ui/panels/docked_panel_drag_handler.h"
     12 #include "chrome/browser/ui/panels/panel.h"
     13 #include "chrome/browser/ui/panels/panel_manager.h"
     14 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
     15 #include "chrome/browser/ui/panels/stacked_panel_drag_handler.h"
     16 
     17 namespace {
     18 
     19 // The minimum distance that the docked panel gets dragged up in order to
     20 // make it free-floating.
     21 const int kDetachDockedPanelThreshold = 100;
     22 
     23 // Indicates how close the bottom of the detached panel is to the bottom of
     24 // the docked area such that the detached panel becomes docked.
     25 const int kDockDetachedPanelThreshold = 30;
     26 
     27 // The minimum distance and overlap (in pixels) between two panels such that
     28 // they can be stacked/snapped together.
     29 const int kGluePanelsDistanceThreshold = 15;
     30 const int kGluePanelsOverlapThreshold = 10;
     31 
     32 // The minimum distance between the panel edge and the screen (or work area)
     33 // edge such that the panel can snap to the screen (or work area) edge.
     34 const int kSnapPanelToScreenEdgeThreshold = 25;
     35 
     36 int GetHorizontalOverlap(const gfx::Rect& bounds1, const gfx::Rect& bounds2) {
     37   // Check for no overlap.
     38   if (bounds1.right() <= bounds2.x() || bounds1.x() >= bounds2.right())
     39     return 0;
     40 
     41   // Check for complete overlap.
     42   if (bounds2.x() <= bounds1.x() && bounds1.right() <= bounds2.right())
     43     return bounds1.width();
     44 
     45   if (bounds1.x() <= bounds2.x() && bounds2.right() <= bounds1.right())
     46     return bounds2.width();
     47 
     48   // Compute the overlap part.
     49   return (bounds1.x() < bounds2.x()) ? (bounds1.right() - bounds2.x())
     50                                      : (bounds2.right() - bounds1.x());
     51 }
     52 
     53 int GetVerticalOverlap(const gfx::Rect& bounds1, const gfx::Rect& bounds2) {
     54   // Check for no overlap.
     55   if (bounds1.bottom() <= bounds2.y() || bounds1.y() >= bounds2.bottom())
     56     return 0;
     57 
     58   // Check for complete overlap.
     59   if (bounds2.y() <= bounds1.y() && bounds1.bottom() <= bounds2.bottom())
     60     return bounds1.height();
     61 
     62   if (bounds1.y() <= bounds2.y() && bounds2.bottom() <= bounds1.bottom())
     63     return bounds2.height();
     64 
     65   // Compute the overlap part.
     66   return (bounds1.y() < bounds2.y()) ? (bounds1.bottom() - bounds2.y())
     67                                      : (bounds2.bottom() - bounds1.y());
     68 }
     69 
     70 // Return the vertical distance between the bottom edge of |top_bounds| and
     71 // the top edge of |bottom_bounds|.
     72 int GetVerticalDistance(const gfx::Rect& top_bounds,
     73                         const gfx::Rect& bottom_bounds) {
     74   return abs(bottom_bounds.y() - top_bounds.bottom());
     75 }
     76 
     77 // Return the vertical distance between the right edge of |left_bounds| and
     78 // the left edge of |right_bounds|.
     79 int GetHorizontalDistance(const gfx::Rect& left_bounds,
     80                           const gfx::Rect& right_bounds) {
     81   return abs(right_bounds.x() - left_bounds.right());
     82 }
     83 
     84 void SetPreviewModeForPanelAndBelow(Panel* panel, bool in_preview) {
     85   StackedPanelCollection* stack = panel->stack();
     86   if (stack) {
     87     bool panel_found = false;
     88     for (StackedPanelCollection::Panels::const_iterator iter =
     89              stack->panels().begin();
     90          iter != stack->panels().end(); ++iter) {
     91       Panel* current_panel = *iter;
     92       if (!panel_found && current_panel != panel)
     93         continue;
     94       panel_found = true;
     95       if (in_preview != current_panel->in_preview_mode())
     96         current_panel->SetPreviewMode(in_preview);
     97     }
     98   } else {
     99     panel->SetPreviewMode(in_preview);
    100   }
    101 }
    102 
    103 }  // namespace
    104 
    105 // static
    106 int PanelDragController::GetDetachDockedPanelThresholdForTesting() {
    107   return kDetachDockedPanelThreshold;
    108 }
    109 
    110 // static
    111 int PanelDragController::GetDockDetachedPanelThresholdForTesting() {
    112   return kDockDetachedPanelThreshold;
    113 }
    114 
    115 // static
    116 int PanelDragController::GetGluePanelDistanceThresholdForTesting() {
    117   return kGluePanelsDistanceThreshold;
    118 }
    119 
    120 // static
    121 int PanelDragController::GetGluePanelOverlapThresholdForTesting() {
    122   return kGluePanelsOverlapThreshold;
    123 }
    124 
    125 // static
    126 int PanelDragController::GetSnapPanelToScreenEdgeThresholdForTesting() {
    127   return kSnapPanelToScreenEdgeThreshold;
    128 }
    129 
    130 PanelDragController::PanelDragController(PanelManager* panel_manager)
    131     : panel_manager_(panel_manager),
    132       panel_stacking_enabled_(PanelManager::IsPanelStackingEnabled()),
    133       dragging_panel_(NULL),
    134       dragging_panel_original_collection_(NULL) {
    135 }
    136 
    137 PanelDragController::~PanelDragController() {
    138 }
    139 
    140 void PanelDragController::StartDragging(Panel* panel,
    141                                         const gfx::Point& mouse_location) {
    142   DCHECK(!dragging_panel_);
    143 
    144   offset_from_mouse_location_on_drag_start_ =
    145       mouse_location - panel->GetBounds().origin();
    146 
    147   dragging_panel_ = panel;
    148   SetPreviewModeForPanelAndBelow(dragging_panel_, true);
    149 
    150   // Keep track of original collection and placement for the case that the drag
    151   // is cancelled.
    152   dragging_panel_original_collection_ = dragging_panel_->collection();
    153   dragging_panel_original_collection_->SavePanelPlacement(dragging_panel_);
    154 }
    155 
    156 void PanelDragController::Drag(const gfx::Point& mouse_location) {
    157   if (!dragging_panel_)
    158     return;
    159 
    160   gfx::Point target_position = GetPanelPositionForMouseLocation(mouse_location);
    161 
    162   if (panel_stacking_enabled_) {
    163     // Check if the dragging panel can be moved out the stack. Note that this
    164     // has to be done first and we should continue processing it for the case
    165     // that the drag also triggers stacking and docking.
    166     // Note that the panel can only be unstacked from top or bottom. So if
    167     // unstacking from top succeeds, there is no need to check for unstacking
    168     // from bottom.
    169     if (!TryUnstackFromTop(target_position))
    170       TryUnstackFromBottom(target_position);
    171 
    172     // Check if the dragging panel can stack with other panel or stack.
    173     TryStack(target_position);
    174   }
    175 
    176   // Check if the dragging panel can be docked.
    177   TryDock(target_position);
    178 
    179   // Check if the dragging panel can be detached.
    180   TryDetach(target_position);
    181 
    182   // Check if the dragging panel can snap to other panel or edge of the working
    183   // area.
    184   if (panel_stacking_enabled_)
    185     TrySnap(&target_position);
    186 
    187   // At last, handle the drag via its collection's specific handler.
    188   switch (dragging_panel_->collection()->type()) {
    189     case PanelCollection::DOCKED:
    190       DockedPanelDragHandler::HandleDrag(dragging_panel_, target_position);
    191       break;
    192     case PanelCollection::DETACHED:
    193       DetachedPanelDragHandler::HandleDrag(dragging_panel_, target_position);
    194       break;
    195     case PanelCollection::STACKED:
    196       StackedPanelDragHandler::HandleDrag(
    197           dragging_panel_,
    198           target_position,
    199           dragging_panel_->collection() == dragging_panel_original_collection_);
    200       break;
    201     default:
    202       NOTREACHED();
    203       break;
    204   }
    205 }
    206 
    207 void PanelDragController::EndDragging(bool cancelled) {
    208   if (!dragging_panel_)
    209     return;
    210 
    211   PanelCollection* current_collection = dragging_panel_->collection();
    212   if (cancelled) {
    213     // Restore the dragging panel to its original collection if needed.
    214     // Note that the bounds of dragging panel is updated later by calling
    215     // RestorePanelToSavedPlacement.
    216     if (current_collection != dragging_panel_original_collection_) {
    217       PanelCollection::PositioningMask positioning_mask =
    218           static_cast<PanelCollection::PositioningMask>(
    219               PanelCollection::DEFAULT_POSITION |
    220               PanelCollection::DO_NOT_UPDATE_BOUNDS);
    221       MovePanelAndBelowToCollection(dragging_panel_,
    222                                     dragging_panel_original_collection_,
    223                                     positioning_mask);
    224     }
    225 
    226     // End the preview mode.
    227     SetPreviewModeForPanelAndBelow(dragging_panel_, false);
    228 
    229     // Restore the dragging panel to its original placement.
    230     dragging_panel_original_collection_->RestorePanelToSavedPlacement();
    231   } else {
    232     // The saved placement is no longer needed.
    233     dragging_panel_original_collection_->DiscardSavedPanelPlacement();
    234 
    235     // Finalizing the drag.
    236     if (current_collection->type() == PanelCollection::STACKED)
    237       StackedPanelDragHandler::FinalizeDrag(dragging_panel_);
    238 
    239     // End the preview mode.
    240     SetPreviewModeForPanelAndBelow(dragging_panel_, false);
    241 
    242     // This could cause the panel to be moved to its finalized position.
    243     current_collection->RefreshLayout();
    244 
    245     // This could cause the detached panel, that still keeps its minimized state
    246     // when it gets detached due to unstacking, to expand. This could occur
    247     // when the stack has more than 2 panels and the 2nd top panel is unstacked
    248     // from the top panel: the top panel is detached while all other panels
    249     // remain in the stack.
    250     if (current_collection != panel_manager_->detached_collection())
    251       panel_manager_->detached_collection()->RefreshLayout();
    252   }
    253 
    254   // If the origianl collection is a stack and it becomes empty, remove it.
    255   if (dragging_panel_original_collection_->type() == PanelCollection::STACKED) {
    256     StackedPanelCollection* original_stack =
    257         static_cast<StackedPanelCollection*>(
    258             dragging_panel_original_collection_);
    259     if (original_stack->num_panels() == 0)
    260       panel_manager_->RemoveStack(original_stack);
    261   }
    262 
    263   dragging_panel_ = NULL;
    264 }
    265 
    266 void PanelDragController::OnPanelClosed(Panel* panel) {
    267   // Abort the drag only if the panel being closed is currently being dragged.
    268   if (dragging_panel_ != panel)
    269     return;
    270 
    271   dragging_panel_original_collection_->DiscardSavedPanelPlacement();
    272   dragging_panel_original_collection_ = NULL;
    273   dragging_panel_ = NULL;
    274 }
    275 
    276 gfx::Point PanelDragController::GetPanelPositionForMouseLocation(
    277     const gfx::Point& mouse_location) const {
    278   // The target panel position is computed based on the fact that the panel
    279   // should follow the mouse movement.
    280   gfx::Point target_position =
    281       mouse_location - offset_from_mouse_location_on_drag_start_;
    282   gfx::Rect target_bounds(target_position, dragging_panel_->GetBounds().size());
    283 
    284   // Make sure that the panel's titlebar cannot be moved under the taskbar or
    285   // OSX menu bar that is aligned to top screen edge.
    286   gfx::Rect display_area = panel_manager_->display_settings_provider()->
    287       GetDisplayAreaMatching(target_bounds);
    288   gfx::Rect work_area = panel_manager_->display_settings_provider()->
    289       GetWorkAreaMatching(target_bounds);
    290   if (display_area.Contains(mouse_location) &&
    291       target_position.y() < work_area.y()) {
    292     target_position.set_y(work_area.y());
    293   }
    294 
    295   return target_position;
    296 }
    297 
    298 void PanelDragController::TryDetach(const gfx::Point& target_position) {
    299   // It has to come from the docked collection.
    300   if (dragging_panel_->collection()->type() != PanelCollection::DOCKED)
    301     return;
    302 
    303   // The minimized docked panel is not allowed to detach.
    304   if (dragging_panel_->IsMinimized())
    305     return;
    306 
    307   // Panels in the detached collection are always at their full size.
    308   gfx::Rect target_bounds(target_position, dragging_panel_->full_size());
    309 
    310   // To become detached, the panel should be dragged either out of the main
    311   // work area or up high enough to pass certain threshold.
    312   gfx::Rect target_work_area = panel_manager_->display_settings_provider()->
    313       GetWorkAreaMatching(target_bounds);
    314   gfx::Rect dock_work_area = panel_manager_->docked_collection()->work_area();
    315   if (target_work_area.Contains(dock_work_area) &&
    316       dock_work_area.bottom() - target_bounds.bottom() <
    317           kDetachDockedPanelThreshold) {
    318     return;
    319   }
    320 
    321   // Apply new panel bounds.
    322   dragging_panel_->SetPanelBoundsInstantly(target_bounds);
    323 
    324   // Move the panel to new collection.
    325   panel_manager_->MovePanelToCollection(dragging_panel_,
    326                                         panel_manager_->detached_collection(),
    327                                         PanelCollection::KNOWN_POSITION);
    328 }
    329 
    330 void PanelDragController::TryDock(const gfx::Point& target_position) {
    331   // It has to come from the detached collection.
    332   if (dragging_panel_->collection()->type() != PanelCollection::DETACHED)
    333     return;
    334 
    335   gfx::Rect target_bounds(target_position, dragging_panel_->GetBounds().size());
    336 
    337   // To become docked, the panel should fall within the main work area and
    338   // its bottom should come very close to or fall below the bottom of the main
    339   // work area.
    340   gfx::Rect target_work_area = panel_manager_->display_settings_provider()->
    341       GetWorkAreaMatching(target_bounds);
    342   gfx::Rect dock_work_area = panel_manager_->docked_collection()->work_area();
    343   if (!target_work_area.Contains(dock_work_area) ||
    344       dock_work_area.bottom() - target_bounds.bottom() >
    345           kDockDetachedPanelThreshold) {
    346     return;
    347   }
    348 
    349   // Apply new panel bounds.
    350   dragging_panel_->SetPanelBoundsInstantly(target_bounds);
    351 
    352   // Move the panel to new collection.
    353   panel_manager_->MovePanelToCollection(dragging_panel_,
    354                                         panel_manager_->docked_collection(),
    355                                         PanelCollection::KNOWN_POSITION);
    356 }
    357 
    358 void PanelDragController::TryStack(const gfx::Point& target_position) {
    359   gfx::Rect target_bounds;
    360   GlueEdge target_edge;
    361   Panel* target_panel = FindPanelToGlue(target_position,
    362                                         STACK,
    363                                         &target_bounds,
    364                                         &target_edge);
    365   if (!target_panel)
    366     return;
    367 
    368   StackedPanelCollection* dragging_stack = dragging_panel_->stack();
    369 
    370   // Move the panel (and all the panels below if in a stack) to the new
    371   // position.
    372   gfx::Vector2d delta =
    373       target_bounds.origin() - dragging_panel_->GetBounds().origin();
    374   if (dragging_stack)
    375     dragging_stack->MoveAllDraggingPanelsInstantly(delta);
    376   else
    377     dragging_panel_->MoveByInstantly(delta);
    378 
    379   // If the panel to stack with is not in a stack, create it now.
    380   StackedPanelCollection* target_stack = target_panel->stack();
    381   if (!target_stack) {
    382     target_stack = panel_manager_->CreateStack();
    383     panel_manager_->MovePanelToCollection(target_panel,
    384                                           target_stack,
    385                                           PanelCollection::DEFAULT_POSITION);
    386   }
    387 
    388   // Move the panel to new collection.
    389   // Note that we don't want to refresh the layout now because when we add
    390   // a panel to top of other panel, we don't want the bottom panel to change
    391   // its width to be same as top panel now.
    392   PanelCollection::PositioningMask positioning_mask =
    393       static_cast<PanelCollection::PositioningMask>(
    394           PanelCollection::NO_LAYOUT_REFRESH |
    395           (target_edge == TOP_EDGE ? PanelCollection::TOP_POSITION
    396                                    : PanelCollection::DEFAULT_POSITION));
    397   MovePanelAndBelowToCollection(dragging_panel_,
    398                                 target_stack,
    399                                 positioning_mask);
    400 }
    401 
    402 // Check if a panel or a set of stacked panels (being dragged together from a
    403 // stack) can be dragged away from the panel below such that the former panel(s)
    404 // are not in the same stack as the latter panel.
    405 bool PanelDragController::TryUnstackFromTop(const gfx::Point& target_position) {
    406   // It has to be stacked.
    407   StackedPanelCollection* dragging_stack = dragging_panel_->stack();
    408   if (!dragging_stack)
    409     return false;
    410 
    411   // Unstacking from top only happens when a panel/stack stacks to the top of
    412   // another panel and then moves away while the drag is still in progress.
    413   if (dragging_panel_ != dragging_stack->top_panel() ||
    414       dragging_stack == dragging_panel_original_collection_)
    415     return false;
    416 
    417   // Count the number of panels that might need to unstack.
    418   Panel* last_panel_to_unstack = NULL;
    419   Panel* panel_below_last_panel_to_unstack = NULL;
    420   int num_panels_to_unstack = 0;
    421   for (StackedPanelCollection::Panels::const_iterator iter =
    422            dragging_stack->panels().begin();
    423        iter != dragging_stack->panels().end(); ++iter) {
    424     if (!(*iter)->in_preview_mode()) {
    425       panel_below_last_panel_to_unstack = *iter;
    426       break;
    427     }
    428     num_panels_to_unstack++;
    429     last_panel_to_unstack = *iter;
    430   }
    431   DCHECK_GE(num_panels_to_unstack, 1);
    432   if (num_panels_to_unstack == dragging_stack->num_panels())
    433     return false;
    434 
    435   gfx::Vector2d delta = target_position - dragging_panel_->GetBounds().origin();
    436 
    437   // The last panel to unstack should be dragged far enough from its below
    438   // panel.
    439   gfx::Rect target_bounds = last_panel_to_unstack->GetBounds();
    440   target_bounds.Offset(delta);
    441   gfx::Rect below_panel_bounds = panel_below_last_panel_to_unstack->GetBounds();
    442   if (GetVerticalDistance(target_bounds, below_panel_bounds) <
    443           kGluePanelsDistanceThreshold &&
    444       GetHorizontalOverlap(target_bounds, below_panel_bounds) >
    445           kGluePanelsOverlapThreshold) {
    446     return false;
    447   }
    448 
    449   int num_panels_in_stack = dragging_stack->num_panels();
    450   DCHECK_GE(num_panels_in_stack, 2);
    451 
    452   // When a panel is removed from its stack, we always make it detached. If it
    453   // indeed should go to the docked collection, the subsequent TryDock will then
    454   // move it from the detached collection to the docked collection.
    455   DetachedPanelCollection* detached_collection =
    456       panel_manager_->detached_collection();
    457 
    458   // If there're only 2 panels in the stack, both panels should move out of the
    459   // stack and the stack should be removed.
    460   if (num_panels_in_stack == 2) {
    461     DCHECK_EQ(1, num_panels_to_unstack);
    462     MovePanelAndBelowToCollection(dragging_panel_,
    463                                   detached_collection,
    464                                   PanelCollection::KNOWN_POSITION);
    465     dragging_panel_->MoveByInstantly(delta);
    466     return true;
    467   }
    468 
    469   DCHECK_GE(num_panels_in_stack, 3);
    470 
    471   // If only one panel (top panel) needs to unstack, move it out of the stack.
    472   if (num_panels_to_unstack == 1) {
    473     panel_manager_->MovePanelToCollection(dragging_panel_,
    474                                           detached_collection,
    475                                           PanelCollection::KNOWN_POSITION);
    476     dragging_panel_->MoveByInstantly(delta);
    477     return true;
    478   }
    479 
    480   // If all the panels except the bottom panel need to unstack, simply move
    481   // bottom panel out of the stack.
    482   if (num_panels_in_stack - num_panels_to_unstack == 1) {
    483     panel_manager_->MovePanelToCollection(dragging_stack->bottom_panel(),
    484                                           detached_collection,
    485                                           PanelCollection::KNOWN_POSITION);
    486     dragging_panel_->stack()->MoveAllDraggingPanelsInstantly(delta);
    487     return true;
    488   }
    489 
    490   // Otherwise, move all unstacked panels to a new stack.
    491   // Note that all the panels to move should be copied to a local list first
    492   // because the stack collection will be modified during the move.
    493   std::list<Panel*> panels_to_move;
    494   for (StackedPanelCollection::Panels::const_iterator iter =
    495            dragging_stack->panels().begin();
    496        iter != dragging_stack->panels().end(); ++iter) {
    497     Panel* panel = *iter;
    498     if (!panel->in_preview_mode())
    499       break;
    500     panels_to_move.push_back(panel);
    501   }
    502   StackedPanelCollection* new_stack = panel_manager_->CreateStack();
    503   for (std::list<Panel*>::const_iterator iter = panels_to_move.begin();
    504        iter != panels_to_move.end(); ++iter) {
    505     panel_manager_->MovePanelToCollection(*iter,
    506                                           new_stack,
    507                                           PanelCollection::KNOWN_POSITION);
    508   }
    509   dragging_panel_->stack()->MoveAllDraggingPanelsInstantly(delta);
    510 
    511   return true;
    512 }
    513 
    514 // Check if a panel or a set of stacked panels (being dragged together from a
    515 // stack) can be dragged away from the panel above such that the former panel(s)
    516 // are not in the same stack as the latter panel.
    517 bool PanelDragController::TryUnstackFromBottom(
    518     const gfx::Point& target_position) {
    519   // It has to be stacked.
    520   StackedPanelCollection* dragging_stack = dragging_panel_->stack();
    521   if (!dragging_stack)
    522     return false;
    523 
    524   // It cannot be the top panel.
    525   if (dragging_panel_ == dragging_stack->top_panel())
    526     return false;
    527 
    528   DCHECK_GT(dragging_stack->num_panels(), 1);
    529 
    530   gfx::Rect target_bounds(target_position, dragging_panel_->GetBounds().size());
    531 
    532   // The panel should be dragged far enough from its above panel.
    533   Panel* above_panel = dragging_stack->GetPanelAbove(dragging_panel_);
    534   DCHECK(above_panel);
    535   gfx::Rect above_panel_bounds = above_panel->GetBounds();
    536   if (GetVerticalDistance(above_panel_bounds, target_bounds) <
    537           kGluePanelsDistanceThreshold &&
    538       GetHorizontalOverlap(above_panel_bounds, target_bounds) >
    539           kGluePanelsOverlapThreshold) {
    540     return false;
    541   }
    542 
    543   gfx::Vector2d delta = target_position - dragging_panel_->GetBounds().origin();
    544 
    545   // If there're only 2 panels in the stack, both panels should move out the
    546   // stack and the stack should be removed.
    547   DetachedPanelCollection* detached_collection =
    548       panel_manager_->detached_collection();
    549   if (dragging_stack->num_panels() == 2) {
    550     MovePanelAndBelowToCollection(dragging_stack->top_panel(),
    551                                   detached_collection,
    552                                   PanelCollection::KNOWN_POSITION);
    553     dragging_panel_->MoveByInstantly(delta);
    554     return true;
    555   }
    556 
    557   // There're at least 3 panels.
    558   DCHECK_GE(dragging_stack->num_panels(), 3);
    559 
    560   // If the dragging panel is bottom panel, move it out of the stack.
    561   if (dragging_panel_ == dragging_stack->bottom_panel()) {
    562     panel_manager_->MovePanelToCollection(dragging_panel_,
    563                                           detached_collection,
    564                                           PanelCollection::KNOWN_POSITION);
    565     dragging_panel_->MoveByInstantly(delta);
    566     return true;
    567   }
    568 
    569   // If the dragging panel is the one below the top panel, move top panel
    570   // out of the stack.
    571   if (dragging_stack->GetPanelAbove(dragging_panel_) ==
    572       dragging_stack->top_panel()) {
    573     panel_manager_->MovePanelToCollection(dragging_stack->top_panel(),
    574                                           detached_collection,
    575                                           PanelCollection::KNOWN_POSITION);
    576     dragging_panel_->stack()->MoveAllDraggingPanelsInstantly(delta);
    577     return true;
    578   }
    579 
    580   // There're at least 4 panels.
    581   DCHECK_GE(dragging_stack->num_panels(), 4);
    582 
    583   // We can split them into 2 stacks by moving the dragging panel and all panels
    584   // below to a new stack while keeping all panels above in the same stack.
    585   StackedPanelCollection* new_stack = panel_manager_->CreateStack();
    586   MovePanelAndBelowToCollection(dragging_panel_,
    587                                 new_stack,
    588                                 PanelCollection::KNOWN_POSITION);
    589   dragging_panel_->stack()->MoveAllDraggingPanelsInstantly(delta);
    590 
    591   return true;
    592 }
    593 
    594 void PanelDragController::TrySnap(gfx::Point* target_position) {
    595   // Snapping does not apply to docked panels.
    596   if (dragging_panel_->collection()->type() == PanelCollection::DOCKED)
    597     return;
    598 
    599   // Check if the panel can snap to other panel.
    600   gfx::Rect target_bounds;
    601   GlueEdge target_edge;
    602   Panel* target_panel = FindPanelToGlue(*target_position,
    603                                         SNAP,
    604                                         &target_bounds,
    605                                         &target_edge);
    606   if (target_panel) {
    607     *target_position = target_bounds.origin();
    608     return;
    609   }
    610 
    611   // Check if the panel can snap to the left/right edge of the work area.
    612   target_bounds.set_origin(*target_position);
    613   target_bounds.set_size(dragging_panel_->GetBounds().size());
    614   gfx::Rect work_area = panel_manager_->display_settings_provider()->
    615       GetWorkAreaMatching(target_bounds);
    616   if (abs(target_position->x() - work_area.x()) <
    617       kSnapPanelToScreenEdgeThreshold) {
    618     target_position->set_x(work_area.x());
    619   } else {
    620     int width = dragging_panel_->GetBounds().width();
    621     if (abs(work_area.right() - target_position->x() - width) <
    622         kSnapPanelToScreenEdgeThreshold)
    623       target_position->set_x(work_area.right() - width);
    624   }
    625 
    626   // Check if the panel can snap to the top/bottom edge of the work area.
    627   if (abs(target_position->y() - work_area.y()) <
    628       kSnapPanelToScreenEdgeThreshold) {
    629     target_position->set_y(work_area.y());
    630   } else {
    631     // If the panel is in a stack, the height is from the top edge of this panel
    632     // to the bottom edge of the last panel in the stack.
    633     int height;
    634     StackedPanelCollection* stack = dragging_panel_->stack();
    635     if (stack) {
    636       height = stack->bottom_panel()->GetBounds().bottom() -
    637           dragging_panel_->GetBounds().y();
    638     } else {
    639       height = dragging_panel_->GetBounds().height();
    640     }
    641     if (abs(work_area.bottom() - target_position->y() - height) <
    642         kSnapPanelToScreenEdgeThreshold)
    643       target_position->set_y(work_area.bottom() - height);
    644   }
    645 }
    646 
    647 Panel* PanelDragController::FindPanelToGlue(
    648     const gfx::Point& potential_position,
    649     GlueAction action,
    650     gfx::Rect* target_bounds,
    651     GlueEdge* target_edge) const {
    652   int best_distance = kint32max;
    653   Panel* best_matching_panel = NULL;
    654 
    655   // Compute the potential bounds for the dragging panel.
    656   gfx::Rect current_dragging_bounds = dragging_panel_->GetBounds();
    657   gfx::Rect potential_dragging_bounds(potential_position,
    658                                       current_dragging_bounds.size());
    659 
    660   // Compute the potential bounds for the bottom panel if the dragging panel is
    661   // in a stack. If not, it is same as |potential_dragging_bounds|.
    662   // This is used to determine if the dragging panel or the bottom panel can
    663   // stack to the top of other panel.
    664   gfx::Rect current_bottom_bounds;
    665   gfx::Rect potential_bottom_bounds;
    666   StackedPanelCollection* dragging_stack = dragging_panel_->stack();
    667   if (dragging_stack && dragging_panel_ != dragging_stack->bottom_panel()) {
    668     gfx::Vector2d delta = potential_position - current_dragging_bounds.origin();
    669     current_bottom_bounds = dragging_stack->bottom_panel()->GetBounds();
    670     potential_bottom_bounds = current_bottom_bounds;
    671     potential_bottom_bounds.Offset(delta);
    672   } else {
    673     current_bottom_bounds = current_dragging_bounds;
    674     potential_bottom_bounds = potential_dragging_bounds;
    675   }
    676 
    677   // Go through all non-docked panels.
    678   std::vector<Panel*> panels = panel_manager_->GetDetachedAndStackedPanels();
    679   for (std::vector<Panel*>::const_iterator iter = panels.begin();
    680        iter != panels.end(); ++iter) {
    681     Panel* panel = *iter;
    682     if (dragging_panel_ == panel)
    683       continue;
    684     if (dragging_panel_->collection()->type() == PanelCollection::STACKED &&
    685         dragging_panel_->collection() == panel->collection())
    686       continue;
    687     gfx::Rect panel_bounds = panel->GetBounds();
    688     int distance;
    689     int overlap;
    690 
    691     if (action == SNAP) {
    692       overlap = GetVerticalOverlap(potential_dragging_bounds, panel_bounds);
    693       if (overlap > kGluePanelsOverlapThreshold) {
    694         // Can |dragging_panel_| snap to left edge of |panel|?
    695         distance = GetHorizontalDistance(potential_dragging_bounds,
    696                                          panel_bounds);
    697         if (distance < kGluePanelsDistanceThreshold &&
    698             distance < best_distance) {
    699           best_distance = distance;
    700           best_matching_panel = panel;
    701           *target_edge = LEFT_EDGE;
    702           *target_bounds = potential_dragging_bounds;
    703           target_bounds->set_x(panel_bounds.x() - target_bounds->width());
    704         }
    705 
    706         // Can |dragging_panel_| snap to right edge of |panel|?
    707         distance = GetHorizontalDistance(panel_bounds,
    708                                          potential_dragging_bounds);
    709         if (distance < kGluePanelsDistanceThreshold &&
    710             distance < best_distance) {
    711           best_distance = distance;
    712           best_matching_panel = panel;
    713           *target_edge = RIGHT_EDGE;
    714           *target_bounds = potential_dragging_bounds;
    715           target_bounds->set_x(panel_bounds.right());
    716         }
    717       }
    718     } else {
    719       DCHECK_EQ(STACK, action);
    720       StackedPanelCollection* stack = panel->stack();
    721 
    722       // Can |dragging_panel_| or the bottom panel in |dragging_panel_|'s stack
    723       // stack to top edge of |panel|? If |panel| is in a stack and not top
    724       // panel, its top edge is interior edge and thus cannot be aligned with.
    725       distance = GetVerticalDistance(potential_bottom_bounds, panel_bounds);
    726       overlap = GetHorizontalOverlap(panel_bounds, potential_bottom_bounds);
    727       if ((!stack || panel == stack->top_panel()) &&
    728           distance < kGluePanelsDistanceThreshold &&
    729           overlap > kGluePanelsOverlapThreshold &&
    730           distance < best_distance) {
    731         best_distance = distance;
    732         best_matching_panel = panel;
    733         *target_edge = TOP_EDGE;
    734         target_bounds->SetRect(
    735             potential_dragging_bounds.x(),
    736             current_dragging_bounds.y() + panel_bounds.y() -
    737                 current_bottom_bounds.height() - current_bottom_bounds.y(),
    738             potential_dragging_bounds.width(),
    739             potential_dragging_bounds.height());
    740       }
    741 
    742       // Can |dragging_panel_| stack to bottom edge of |panel|? If |panel| is
    743       // in a stack and not bottom panel, its bottom edge is interior edge and
    744       // thus cannot be aligned with.
    745       distance = GetVerticalDistance(panel_bounds, potential_dragging_bounds);
    746       overlap = GetHorizontalOverlap(panel_bounds, potential_dragging_bounds);
    747       if ((!stack || panel == stack->bottom_panel()) &&
    748           distance < kGluePanelsDistanceThreshold &&
    749           overlap > kGluePanelsOverlapThreshold &&
    750           distance < best_distance) {
    751         best_distance = distance;
    752         best_matching_panel = panel;
    753         *target_edge = BOTTOM_EDGE;
    754         target_bounds->SetRect(potential_dragging_bounds.x(),
    755                                panel_bounds.bottom(),
    756                                potential_dragging_bounds.width(),
    757                                potential_dragging_bounds.height());
    758       }
    759     }
    760   }
    761 
    762   return best_matching_panel;
    763 }
    764 
    765 void PanelDragController::MovePanelAndBelowToCollection(
    766     Panel* panel,
    767     PanelCollection* target_collection,
    768     PanelCollection::PositioningMask positioning_mask) const {
    769   StackedPanelCollection* stack = panel->stack();
    770   if (!stack) {
    771     panel_manager_->MovePanelToCollection(panel,
    772                                           target_collection,
    773                                           positioning_mask);
    774     return;
    775   }
    776 
    777   // Note that all the panels to move should be copied to a local list first
    778   // because the stack collection will be modified during the move.
    779   std::list<Panel*> panels_to_move;
    780   StackedPanelCollection::Panels::const_iterator iter = stack->panels().begin();
    781   for (; iter != stack->panels().end(); ++iter)
    782     if ((*iter) == panel)
    783       break;
    784   for (; iter != stack->panels().end(); ++iter) {
    785     // Note that if the panels are going to be inserted from the top, we need
    786     // to reverse the order when copying to the local list.
    787     if (positioning_mask & PanelCollection::TOP_POSITION)
    788       panels_to_move.push_front(*iter);
    789     else
    790       panels_to_move.push_back(*iter);
    791   }
    792   for (std::list<Panel*>::const_iterator panel_iter = panels_to_move.begin();
    793        panel_iter != panels_to_move.end(); ++panel_iter) {
    794     panel_manager_->MovePanelToCollection(*panel_iter,
    795                                           target_collection,
    796                                           positioning_mask);
    797   }
    798 
    799   // If the stack becomes empty or has only one panel left, no need to keep
    800   // the stack.
    801   if (stack && stack->num_panels() <= 1) {
    802     if (stack->num_panels() == 1) {
    803       panel_manager_->MovePanelToCollection(
    804           stack->top_panel(),
    805           panel_manager_->detached_collection(),
    806           PanelCollection::KNOWN_POSITION);
    807     }
    808     // Note that if the stack is the original collection, do not remove it now.
    809     // This is because the original collection contains the information to
    810     // restore the dragging panel to the right place when the drag is cancelled.
    811     if (stack != dragging_panel_original_collection_)
    812       panel_manager_->RemoveStack(stack);
    813   }
    814 }
    815