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