• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetHorizontalOverlap(const gfx::Rect & bounds1,const gfx::Rect & bounds2)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 
GetVerticalOverlap(const gfx::Rect & bounds1,const gfx::Rect & bounds2)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|.
GetVerticalDistance(const gfx::Rect & top_bounds,const gfx::Rect & 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|.
GetHorizontalDistance(const gfx::Rect & left_bounds,const gfx::Rect & 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 
SetPreviewModeForPanelAndBelow(Panel * panel,bool in_preview)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
GetDetachDockedPanelThresholdForTesting()106 int PanelDragController::GetDetachDockedPanelThresholdForTesting() {
107   return kDetachDockedPanelThreshold;
108 }
109 
110 // static
GetDockDetachedPanelThresholdForTesting()111 int PanelDragController::GetDockDetachedPanelThresholdForTesting() {
112   return kDockDetachedPanelThreshold;
113 }
114 
115 // static
GetGluePanelDistanceThresholdForTesting()116 int PanelDragController::GetGluePanelDistanceThresholdForTesting() {
117   return kGluePanelsDistanceThreshold;
118 }
119 
120 // static
GetGluePanelOverlapThresholdForTesting()121 int PanelDragController::GetGluePanelOverlapThresholdForTesting() {
122   return kGluePanelsOverlapThreshold;
123 }
124 
125 // static
GetSnapPanelToScreenEdgeThresholdForTesting()126 int PanelDragController::GetSnapPanelToScreenEdgeThresholdForTesting() {
127   return kSnapPanelToScreenEdgeThreshold;
128 }
129 
PanelDragController(PanelManager * panel_manager)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 
~PanelDragController()137 PanelDragController::~PanelDragController() {
138 }
139 
StartDragging(Panel * panel,const gfx::Point & mouse_location)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 
Drag(const gfx::Point & mouse_location)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 
EndDragging(bool cancelled)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 
OnPanelClosed(Panel * panel)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 
GetPanelPositionForMouseLocation(const gfx::Point & mouse_location) const276 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 
TryDetach(const gfx::Point & target_position)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 
TryDock(const gfx::Point & target_position)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 
TryStack(const gfx::Point & target_position)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.
TryUnstackFromTop(const gfx::Point & target_position)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.
TryUnstackFromBottom(const gfx::Point & target_position)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 
TrySnap(gfx::Point * target_position)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 
FindPanelToGlue(const gfx::Point & potential_position,GlueAction action,gfx::Rect * target_bounds,GlueEdge * target_edge) const647 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 
MovePanelAndBelowToCollection(Panel * panel,PanelCollection * target_collection,PanelCollection::PositioningMask positioning_mask) const765 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