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
17namespace {
18
19// The minimum distance that the docked panel gets dragged up in order to
20// make it free-floating.
21const 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.
25const int kDockDetachedPanelThreshold = 30;
26
27// The minimum distance and overlap (in pixels) between two panels such that
28// they can be stacked/snapped together.
29const int kGluePanelsDistanceThreshold = 15;
30const 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.
34const int kSnapPanelToScreenEdgeThreshold = 25;
35
36int 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
53int 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|.
72int 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|.
79int GetHorizontalDistance(const gfx::Rect& left_bounds,
80                          const gfx::Rect& right_bounds) {
81  return abs(right_bounds.x() - left_bounds.right());
82}
83
84void 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
106int PanelDragController::GetDetachDockedPanelThresholdForTesting() {
107  return kDetachDockedPanelThreshold;
108}
109
110// static
111int PanelDragController::GetDockDetachedPanelThresholdForTesting() {
112  return kDockDetachedPanelThreshold;
113}
114
115// static
116int PanelDragController::GetGluePanelDistanceThresholdForTesting() {
117  return kGluePanelsDistanceThreshold;
118}
119
120// static
121int PanelDragController::GetGluePanelOverlapThresholdForTesting() {
122  return kGluePanelsOverlapThreshold;
123}
124
125// static
126int PanelDragController::GetSnapPanelToScreenEdgeThresholdForTesting() {
127  return kSnapPanelToScreenEdgeThreshold;
128}
129
130PanelDragController::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
137PanelDragController::~PanelDragController() {
138}
139
140void 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
156void 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
207void 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
266void 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
276gfx::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
298void 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
330void 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
358void 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.
405bool 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.
517bool 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
594void 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
647Panel* 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
765void 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