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