// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/panels/panel_drag_controller.h" #include "base/logging.h" #include "chrome/browser/ui/panels/detached_panel_collection.h" #include "chrome/browser/ui/panels/detached_panel_drag_handler.h" #include "chrome/browser/ui/panels/docked_panel_collection.h" #include "chrome/browser/ui/panels/docked_panel_drag_handler.h" #include "chrome/browser/ui/panels/panel.h" #include "chrome/browser/ui/panels/panel_manager.h" #include "chrome/browser/ui/panels/stacked_panel_collection.h" #include "chrome/browser/ui/panels/stacked_panel_drag_handler.h" namespace { // The minimum distance that the docked panel gets dragged up in order to // make it free-floating. const int kDetachDockedPanelThreshold = 100; // Indicates how close the bottom of the detached panel is to the bottom of // the docked area such that the detached panel becomes docked. const int kDockDetachedPanelThreshold = 30; // The minimum distance and overlap (in pixels) between two panels such that // they can be stacked/snapped together. const int kGluePanelsDistanceThreshold = 15; const int kGluePanelsOverlapThreshold = 10; // The minimum distance between the panel edge and the screen (or work area) // edge such that the panel can snap to the screen (or work area) edge. const int kSnapPanelToScreenEdgeThreshold = 25; int GetHorizontalOverlap(const gfx::Rect& bounds1, const gfx::Rect& bounds2) { // Check for no overlap. if (bounds1.right() <= bounds2.x() || bounds1.x() >= bounds2.right()) return 0; // Check for complete overlap. if (bounds2.x() <= bounds1.x() && bounds1.right() <= bounds2.right()) return bounds1.width(); if (bounds1.x() <= bounds2.x() && bounds2.right() <= bounds1.right()) return bounds2.width(); // Compute the overlap part. return (bounds1.x() < bounds2.x()) ? (bounds1.right() - bounds2.x()) : (bounds2.right() - bounds1.x()); } int GetVerticalOverlap(const gfx::Rect& bounds1, const gfx::Rect& bounds2) { // Check for no overlap. if (bounds1.bottom() <= bounds2.y() || bounds1.y() >= bounds2.bottom()) return 0; // Check for complete overlap. if (bounds2.y() <= bounds1.y() && bounds1.bottom() <= bounds2.bottom()) return bounds1.height(); if (bounds1.y() <= bounds2.y() && bounds2.bottom() <= bounds1.bottom()) return bounds2.height(); // Compute the overlap part. return (bounds1.y() < bounds2.y()) ? (bounds1.bottom() - bounds2.y()) : (bounds2.bottom() - bounds1.y()); } // Return the vertical distance between the bottom edge of |top_bounds| and // the top edge of |bottom_bounds|. int GetVerticalDistance(const gfx::Rect& top_bounds, const gfx::Rect& bottom_bounds) { return abs(bottom_bounds.y() - top_bounds.bottom()); } // Return the vertical distance between the right edge of |left_bounds| and // the left edge of |right_bounds|. int GetHorizontalDistance(const gfx::Rect& left_bounds, const gfx::Rect& right_bounds) { return abs(right_bounds.x() - left_bounds.right()); } void SetPreviewModeForPanelAndBelow(Panel* panel, bool in_preview) { StackedPanelCollection* stack = panel->stack(); if (stack) { bool panel_found = false; for (StackedPanelCollection::Panels::const_iterator iter = stack->panels().begin(); iter != stack->panels().end(); ++iter) { Panel* current_panel = *iter; if (!panel_found && current_panel != panel) continue; panel_found = true; if (in_preview != current_panel->in_preview_mode()) current_panel->SetPreviewMode(in_preview); } } else { panel->SetPreviewMode(in_preview); } } } // namespace // static int PanelDragController::GetDetachDockedPanelThresholdForTesting() { return kDetachDockedPanelThreshold; } // static int PanelDragController::GetDockDetachedPanelThresholdForTesting() { return kDockDetachedPanelThreshold; } // static int PanelDragController::GetGluePanelDistanceThresholdForTesting() { return kGluePanelsDistanceThreshold; } // static int PanelDragController::GetGluePanelOverlapThresholdForTesting() { return kGluePanelsOverlapThreshold; } // static int PanelDragController::GetSnapPanelToScreenEdgeThresholdForTesting() { return kSnapPanelToScreenEdgeThreshold; } PanelDragController::PanelDragController(PanelManager* panel_manager) : panel_manager_(panel_manager), panel_stacking_enabled_(PanelManager::IsPanelStackingEnabled()), dragging_panel_(NULL), dragging_panel_original_collection_(NULL) { } PanelDragController::~PanelDragController() { } void PanelDragController::StartDragging(Panel* panel, const gfx::Point& mouse_location) { DCHECK(!dragging_panel_); offset_from_mouse_location_on_drag_start_ = mouse_location - panel->GetBounds().origin(); dragging_panel_ = panel; SetPreviewModeForPanelAndBelow(dragging_panel_, true); // Keep track of original collection and placement for the case that the drag // is cancelled. dragging_panel_original_collection_ = dragging_panel_->collection(); dragging_panel_original_collection_->SavePanelPlacement(dragging_panel_); } void PanelDragController::Drag(const gfx::Point& mouse_location) { if (!dragging_panel_) return; gfx::Point target_position = GetPanelPositionForMouseLocation(mouse_location); if (panel_stacking_enabled_) { // Check if the dragging panel can be moved out the stack. Note that this // has to be done first and we should continue processing it for the case // that the drag also triggers stacking and docking. // Note that the panel can only be unstacked from top or bottom. So if // unstacking from top succeeds, there is no need to check for unstacking // from bottom. if (!TryUnstackFromTop(target_position)) TryUnstackFromBottom(target_position); // Check if the dragging panel can stack with other panel or stack. TryStack(target_position); } // Check if the dragging panel can be docked. TryDock(target_position); // Check if the dragging panel can be detached. TryDetach(target_position); // Check if the dragging panel can snap to other panel or edge of the working // area. if (panel_stacking_enabled_) TrySnap(&target_position); // At last, handle the drag via its collection's specific handler. switch (dragging_panel_->collection()->type()) { case PanelCollection::DOCKED: DockedPanelDragHandler::HandleDrag(dragging_panel_, target_position); break; case PanelCollection::DETACHED: DetachedPanelDragHandler::HandleDrag(dragging_panel_, target_position); break; case PanelCollection::STACKED: StackedPanelDragHandler::HandleDrag( dragging_panel_, target_position, dragging_panel_->collection() == dragging_panel_original_collection_); break; default: NOTREACHED(); break; } } void PanelDragController::EndDragging(bool cancelled) { if (!dragging_panel_) return; PanelCollection* current_collection = dragging_panel_->collection(); if (cancelled) { // Restore the dragging panel to its original collection if needed. // Note that the bounds of dragging panel is updated later by calling // RestorePanelToSavedPlacement. if (current_collection != dragging_panel_original_collection_) { PanelCollection::PositioningMask positioning_mask = static_cast( PanelCollection::DEFAULT_POSITION | PanelCollection::DO_NOT_UPDATE_BOUNDS); MovePanelAndBelowToCollection(dragging_panel_, dragging_panel_original_collection_, positioning_mask); } // End the preview mode. SetPreviewModeForPanelAndBelow(dragging_panel_, false); // Restore the dragging panel to its original placement. dragging_panel_original_collection_->RestorePanelToSavedPlacement(); } else { // The saved placement is no longer needed. dragging_panel_original_collection_->DiscardSavedPanelPlacement(); // Finalizing the drag. if (current_collection->type() == PanelCollection::STACKED) StackedPanelDragHandler::FinalizeDrag(dragging_panel_); // End the preview mode. SetPreviewModeForPanelAndBelow(dragging_panel_, false); // This could cause the panel to be moved to its finalized position. current_collection->RefreshLayout(); // This could cause the detached panel, that still keeps its minimized state // when it gets detached due to unstacking, to expand. This could occur // when the stack has more than 2 panels and the 2nd top panel is unstacked // from the top panel: the top panel is detached while all other panels // remain in the stack. if (current_collection != panel_manager_->detached_collection()) panel_manager_->detached_collection()->RefreshLayout(); } // If the origianl collection is a stack and it becomes empty, remove it. if (dragging_panel_original_collection_->type() == PanelCollection::STACKED) { StackedPanelCollection* original_stack = static_cast( dragging_panel_original_collection_); if (original_stack->num_panels() == 0) panel_manager_->RemoveStack(original_stack); } dragging_panel_ = NULL; } void PanelDragController::OnPanelClosed(Panel* panel) { // Abort the drag only if the panel being closed is currently being dragged. if (dragging_panel_ != panel) return; dragging_panel_original_collection_->DiscardSavedPanelPlacement(); dragging_panel_original_collection_ = NULL; dragging_panel_ = NULL; } gfx::Point PanelDragController::GetPanelPositionForMouseLocation( const gfx::Point& mouse_location) const { // The target panel position is computed based on the fact that the panel // should follow the mouse movement. gfx::Point target_position = mouse_location - offset_from_mouse_location_on_drag_start_; gfx::Rect target_bounds(target_position, dragging_panel_->GetBounds().size()); // Make sure that the panel's titlebar cannot be moved under the taskbar or // OSX menu bar that is aligned to top screen edge. gfx::Rect display_area = panel_manager_->display_settings_provider()-> GetDisplayAreaMatching(target_bounds); gfx::Rect work_area = panel_manager_->display_settings_provider()-> GetWorkAreaMatching(target_bounds); if (display_area.Contains(mouse_location) && target_position.y() < work_area.y()) { target_position.set_y(work_area.y()); } return target_position; } void PanelDragController::TryDetach(const gfx::Point& target_position) { // It has to come from the docked collection. if (dragging_panel_->collection()->type() != PanelCollection::DOCKED) return; // The minimized docked panel is not allowed to detach. if (dragging_panel_->IsMinimized()) return; // Panels in the detached collection are always at their full size. gfx::Rect target_bounds(target_position, dragging_panel_->full_size()); // To become detached, the panel should be dragged either out of the main // work area or up high enough to pass certain threshold. gfx::Rect target_work_area = panel_manager_->display_settings_provider()-> GetWorkAreaMatching(target_bounds); gfx::Rect dock_work_area = panel_manager_->docked_collection()->work_area(); if (target_work_area.Contains(dock_work_area) && dock_work_area.bottom() - target_bounds.bottom() < kDetachDockedPanelThreshold) { return; } // Apply new panel bounds. dragging_panel_->SetPanelBoundsInstantly(target_bounds); // Move the panel to new collection. panel_manager_->MovePanelToCollection(dragging_panel_, panel_manager_->detached_collection(), PanelCollection::KNOWN_POSITION); } void PanelDragController::TryDock(const gfx::Point& target_position) { // It has to come from the detached collection. if (dragging_panel_->collection()->type() != PanelCollection::DETACHED) return; gfx::Rect target_bounds(target_position, dragging_panel_->GetBounds().size()); // To become docked, the panel should fall within the main work area and // its bottom should come very close to or fall below the bottom of the main // work area. gfx::Rect target_work_area = panel_manager_->display_settings_provider()-> GetWorkAreaMatching(target_bounds); gfx::Rect dock_work_area = panel_manager_->docked_collection()->work_area(); if (!target_work_area.Contains(dock_work_area) || dock_work_area.bottom() - target_bounds.bottom() > kDockDetachedPanelThreshold) { return; } // Apply new panel bounds. dragging_panel_->SetPanelBoundsInstantly(target_bounds); // Move the panel to new collection. panel_manager_->MovePanelToCollection(dragging_panel_, panel_manager_->docked_collection(), PanelCollection::KNOWN_POSITION); } void PanelDragController::TryStack(const gfx::Point& target_position) { gfx::Rect target_bounds; GlueEdge target_edge; Panel* target_panel = FindPanelToGlue(target_position, STACK, &target_bounds, &target_edge); if (!target_panel) return; StackedPanelCollection* dragging_stack = dragging_panel_->stack(); // Move the panel (and all the panels below if in a stack) to the new // position. gfx::Vector2d delta = target_bounds.origin() - dragging_panel_->GetBounds().origin(); if (dragging_stack) dragging_stack->MoveAllDraggingPanelsInstantly(delta); else dragging_panel_->MoveByInstantly(delta); // If the panel to stack with is not in a stack, create it now. StackedPanelCollection* target_stack = target_panel->stack(); if (!target_stack) { target_stack = panel_manager_->CreateStack(); panel_manager_->MovePanelToCollection(target_panel, target_stack, PanelCollection::DEFAULT_POSITION); } // Move the panel to new collection. // Note that we don't want to refresh the layout now because when we add // a panel to top of other panel, we don't want the bottom panel to change // its width to be same as top panel now. PanelCollection::PositioningMask positioning_mask = static_cast( PanelCollection::NO_LAYOUT_REFRESH | (target_edge == TOP_EDGE ? PanelCollection::TOP_POSITION : PanelCollection::DEFAULT_POSITION)); MovePanelAndBelowToCollection(dragging_panel_, target_stack, positioning_mask); } // Check if a panel or a set of stacked panels (being dragged together from a // stack) can be dragged away from the panel below such that the former panel(s) // are not in the same stack as the latter panel. bool PanelDragController::TryUnstackFromTop(const gfx::Point& target_position) { // It has to be stacked. StackedPanelCollection* dragging_stack = dragging_panel_->stack(); if (!dragging_stack) return false; // Unstacking from top only happens when a panel/stack stacks to the top of // another panel and then moves away while the drag is still in progress. if (dragging_panel_ != dragging_stack->top_panel() || dragging_stack == dragging_panel_original_collection_) return false; // Count the number of panels that might need to unstack. Panel* last_panel_to_unstack = NULL; Panel* panel_below_last_panel_to_unstack = NULL; int num_panels_to_unstack = 0; for (StackedPanelCollection::Panels::const_iterator iter = dragging_stack->panels().begin(); iter != dragging_stack->panels().end(); ++iter) { if (!(*iter)->in_preview_mode()) { panel_below_last_panel_to_unstack = *iter; break; } num_panels_to_unstack++; last_panel_to_unstack = *iter; } DCHECK_GE(num_panels_to_unstack, 1); if (num_panels_to_unstack == dragging_stack->num_panels()) return false; gfx::Vector2d delta = target_position - dragging_panel_->GetBounds().origin(); // The last panel to unstack should be dragged far enough from its below // panel. gfx::Rect target_bounds = last_panel_to_unstack->GetBounds(); target_bounds.Offset(delta); gfx::Rect below_panel_bounds = panel_below_last_panel_to_unstack->GetBounds(); if (GetVerticalDistance(target_bounds, below_panel_bounds) < kGluePanelsDistanceThreshold && GetHorizontalOverlap(target_bounds, below_panel_bounds) > kGluePanelsOverlapThreshold) { return false; } // Move the panel (and all the panels below if in a stack) to the new // position. for (StackedPanelCollection::Panels::const_iterator iter = dragging_stack->panels().begin(); iter != dragging_stack->panels().end(); ++iter) { if (!(*iter)->in_preview_mode()) break; (*iter)->MoveByInstantly(delta); } int num_panels_in_stack = dragging_stack->num_panels(); DCHECK_GE(num_panels_in_stack, 2); // When a panel is removed from its stack, we always make it detached. If it // indeed should go to the docked collection, the subsequent TryDock will then // move it from the detached collection to the docked collection. DetachedPanelCollection* detached_collection = panel_manager_->detached_collection(); // If there're only 2 panels in the stack, both panels should move out of the // stack and the stack should be removed. if (num_panels_in_stack == 2) { DCHECK_EQ(1, num_panels_to_unstack); MovePanelAndBelowToCollection(dragging_panel_, detached_collection, PanelCollection::KNOWN_POSITION); return true; } DCHECK_GE(num_panels_in_stack, 3); // If only one panel (top panel) needs to unstack, move it out of the stack. if (num_panels_to_unstack == 1) { panel_manager_->MovePanelToCollection(dragging_panel_, panel_manager_->detached_collection(), PanelCollection::KNOWN_POSITION); return true; } // If all the panels except the bottom panel need to unstack, simply move // bottom panel out of the stack. if (num_panels_in_stack - num_panels_to_unstack == 1) { panel_manager_->MovePanelToCollection(dragging_stack->bottom_panel(), panel_manager_->detached_collection(), PanelCollection::KNOWN_POSITION); return true; } // Otherwise, move all unstacked panels to a new stack. // Note that all the panels to move should be copied to a local list first // because the stack collection will be modified during the move. std::list panels_to_move; for (StackedPanelCollection::Panels::const_iterator iter = dragging_stack->panels().begin(); iter != dragging_stack->panels().end(); ++iter) { Panel* panel = *iter; if (!panel->in_preview_mode()) break; panels_to_move.push_back(panel); } StackedPanelCollection* new_stack = panel_manager_->CreateStack(); for (std::list::const_iterator iter = panels_to_move.begin(); iter != panels_to_move.end(); ++iter) { panel_manager_->MovePanelToCollection(*iter, new_stack, PanelCollection::KNOWN_POSITION); } return true; } // Check if a panel or a set of stacked panels (being dragged together from a // stack) can be dragged away from the panel above such that the former panel(s) // are not in the same stack as the latter panel. bool PanelDragController::TryUnstackFromBottom( const gfx::Point& target_position) { // It has to be stacked. StackedPanelCollection* dragging_stack = dragging_panel_->stack(); if (!dragging_stack) return false; // It cannot be the top panel. if (dragging_panel_ == dragging_stack->top_panel()) return false; DCHECK_GT(dragging_stack->num_panels(), 1); gfx::Rect target_bounds(target_position, dragging_panel_->GetBounds().size()); // The panel should be dragged far enough from its above panel. Panel* above_panel = dragging_stack->GetPanelAbove(dragging_panel_); DCHECK(above_panel); gfx::Rect above_panel_bounds = above_panel->GetBounds(); if (GetVerticalDistance(above_panel_bounds, target_bounds) < kGluePanelsDistanceThreshold && GetHorizontalOverlap(above_panel_bounds, target_bounds) > kGluePanelsOverlapThreshold) { return false; } // Move the panel (and all the panels below if in a stack) to the new // position. gfx::Vector2d delta = target_position - dragging_panel_->GetBounds().origin(); if (dragging_stack) dragging_stack->MoveAllDraggingPanelsInstantly(delta); else dragging_panel_->MoveByInstantly(delta); // If there're only 2 panels in the stack, both panels should move out the // stack and the stack should be removed. DetachedPanelCollection* detached_collection = panel_manager_->detached_collection(); if (dragging_stack->num_panels() == 2) { MovePanelAndBelowToCollection(dragging_stack->top_panel(), detached_collection, PanelCollection::KNOWN_POSITION); return true; } // There're at least 3 panels. DCHECK_GE(dragging_stack->num_panels(), 3); // If the dragging panel is bottom panel, move it out of the stack. if (dragging_panel_ == dragging_stack->bottom_panel()) { panel_manager_->MovePanelToCollection(dragging_panel_, detached_collection, PanelCollection::KNOWN_POSITION); return true; } // If the dragging panel is the one below the top panel, move top panel // out of the stack. if (dragging_stack->GetPanelAbove(dragging_panel_) == dragging_stack->top_panel()) { panel_manager_->MovePanelToCollection(dragging_stack->top_panel(), detached_collection, PanelCollection::KNOWN_POSITION); return true; } // There're at least 4 panels. DCHECK_GE(dragging_stack->num_panels(), 4); // We can split them into 2 stacks by moving the dragging panel and all panels // below to a new stack while keeping all panels above in the same stack. StackedPanelCollection* new_stack = panel_manager_->CreateStack(); MovePanelAndBelowToCollection(dragging_panel_, new_stack, PanelCollection::KNOWN_POSITION); return true; } void PanelDragController::TrySnap(gfx::Point* target_position) { // Snapping does not apply to docked panels. if (dragging_panel_->collection()->type() == PanelCollection::DOCKED) return; // Check if the panel can snap to other panel. gfx::Rect target_bounds; GlueEdge target_edge; Panel* target_panel = FindPanelToGlue(*target_position, SNAP, &target_bounds, &target_edge); if (target_panel) { *target_position = target_bounds.origin(); return; } // Check if the panel can snap to the left/right edge of the work area. target_bounds.set_origin(*target_position); target_bounds.set_size(dragging_panel_->GetBounds().size()); gfx::Rect work_area = panel_manager_->display_settings_provider()-> GetWorkAreaMatching(target_bounds); if (abs(target_position->x() - work_area.x()) < kSnapPanelToScreenEdgeThreshold) { target_position->set_x(work_area.x()); } else { int width = dragging_panel_->GetBounds().width(); if (abs(work_area.right() - target_position->x() - width) < kSnapPanelToScreenEdgeThreshold) target_position->set_x(work_area.right() - width); } // Check if the panel can snap to the top/bottom edge of the work area. if (abs(target_position->y() - work_area.y()) < kSnapPanelToScreenEdgeThreshold) { target_position->set_y(work_area.y()); } else { // If the panel is in a stack, the height is from the top edge of this panel // to the bottom edge of the last panel in the stack. int height; StackedPanelCollection* stack = dragging_panel_->stack(); if (stack) { height = stack->bottom_panel()->GetBounds().bottom() - dragging_panel_->GetBounds().y(); } else { height = dragging_panel_->GetBounds().height(); } if (abs(work_area.bottom() - target_position->y() - height) < kSnapPanelToScreenEdgeThreshold) target_position->set_y(work_area.bottom() - height); } } Panel* PanelDragController::FindPanelToGlue( const gfx::Point& potential_position, GlueAction action, gfx::Rect* target_bounds, GlueEdge* target_edge) const { int best_distance = kint32max; Panel* best_matching_panel = NULL; // Compute the potential bounds for the dragging panel. gfx::Rect current_dragging_bounds = dragging_panel_->GetBounds(); gfx::Rect potential_dragging_bounds(potential_position, current_dragging_bounds.size()); // Compute the potential bounds for the bottom panel if the dragging panel is // in a stack. If not, it is same as |potential_dragging_bounds|. // This is used to determine if the dragging panel or the bottom panel can // stack to the top of other panel. gfx::Rect current_bottom_bounds; gfx::Rect potential_bottom_bounds; StackedPanelCollection* dragging_stack = dragging_panel_->stack(); if (dragging_stack && dragging_panel_ != dragging_stack->bottom_panel()) { gfx::Vector2d delta = potential_position - current_dragging_bounds.origin(); current_bottom_bounds = dragging_stack->bottom_panel()->GetBounds(); potential_bottom_bounds = current_bottom_bounds; potential_bottom_bounds.Offset(delta); } else { current_bottom_bounds = current_dragging_bounds; potential_bottom_bounds = potential_dragging_bounds; } // Go through all non-docked panels. std::vector panels = panel_manager_->GetDetachedAndStackedPanels(); for (std::vector::const_iterator iter = panels.begin(); iter != panels.end(); ++iter) { Panel* panel = *iter; if (dragging_panel_ == panel) continue; if (dragging_panel_->collection()->type() == PanelCollection::STACKED && dragging_panel_->collection() == panel->collection()) continue; gfx::Rect panel_bounds = panel->GetBounds(); int distance; int overlap; if (action == SNAP) { overlap = GetVerticalOverlap(potential_dragging_bounds, panel_bounds); if (overlap > kGluePanelsOverlapThreshold) { // Can |dragging_panel_| snap to left edge of |panel|? distance = GetHorizontalDistance(potential_dragging_bounds, panel_bounds); if (distance < kGluePanelsDistanceThreshold && distance < best_distance) { best_distance = distance; best_matching_panel = panel; *target_edge = LEFT_EDGE; *target_bounds = potential_dragging_bounds; target_bounds->set_x(panel_bounds.x() - target_bounds->width()); } // Can |dragging_panel_| snap to right edge of |panel|? distance = GetHorizontalDistance(panel_bounds, potential_dragging_bounds); if (distance < kGluePanelsDistanceThreshold && distance < best_distance) { best_distance = distance; best_matching_panel = panel; *target_edge = RIGHT_EDGE; *target_bounds = potential_dragging_bounds; target_bounds->set_x(panel_bounds.right()); } } } else { DCHECK_EQ(STACK, action); StackedPanelCollection* stack = panel->stack(); // Can |dragging_panel_| or the bottom panel in |dragging_panel_|'s stack // stack to top edge of |panel|? If |panel| is in a stack and not top // panel, its top edge is interior edge and thus cannot be aligned with. distance = GetVerticalDistance(potential_bottom_bounds, panel_bounds); overlap = GetHorizontalOverlap(panel_bounds, potential_bottom_bounds); if ((!stack || panel == stack->top_panel()) && distance < kGluePanelsDistanceThreshold && overlap > kGluePanelsOverlapThreshold && distance < best_distance) { best_distance = distance; best_matching_panel = panel; *target_edge = TOP_EDGE; target_bounds->SetRect( potential_dragging_bounds.x(), current_dragging_bounds.y() + panel_bounds.y() - current_bottom_bounds.height() - current_bottom_bounds.y(), potential_dragging_bounds.width(), potential_dragging_bounds.height()); } // Can |dragging_panel_| stack to bottom edge of |panel|? If |panel| is // in a stack and not bottom panel, its bottom edge is interior edge and // thus cannot be aligned with. distance = GetVerticalDistance(panel_bounds, potential_dragging_bounds); overlap = GetHorizontalOverlap(panel_bounds, potential_dragging_bounds); if ((!stack || panel == stack->bottom_panel()) && distance < kGluePanelsDistanceThreshold && overlap > kGluePanelsOverlapThreshold && distance < best_distance) { best_distance = distance; best_matching_panel = panel; *target_edge = BOTTOM_EDGE; target_bounds->SetRect(potential_dragging_bounds.x(), panel_bounds.bottom(), potential_dragging_bounds.width(), potential_dragging_bounds.height()); } } } return best_matching_panel; } void PanelDragController::MovePanelAndBelowToCollection( Panel* panel, PanelCollection* target_collection, PanelCollection::PositioningMask positioning_mask) const { StackedPanelCollection* stack = panel->stack(); if (!stack) { panel_manager_->MovePanelToCollection(panel, target_collection, positioning_mask); return; } // Note that all the panels to move should be copied to a local list first // because the stack collection will be modified during the move. std::list panels_to_move; StackedPanelCollection::Panels::const_iterator iter = stack->panels().begin(); for (; iter != stack->panels().end(); ++iter) if ((*iter) == panel) break; for (; iter != stack->panels().end(); ++iter) { // Note that if the panels are going to be inserted from the top, we need // to reverse the order when copying to the local list. if (positioning_mask & PanelCollection::TOP_POSITION) panels_to_move.push_front(*iter); else panels_to_move.push_back(*iter); } for (std::list::const_iterator panel_iter = panels_to_move.begin(); panel_iter != panels_to_move.end(); ++panel_iter) { panel_manager_->MovePanelToCollection(*panel_iter, target_collection, positioning_mask); } // If the stack becomes empty or has only one panel left, no need to keep // the stack. if (stack && stack->num_panels() <= 1) { if (stack->num_panels() == 1) { panel_manager_->MovePanelToCollection( stack->top_panel(), panel_manager_->detached_collection(), PanelCollection::KNOWN_POSITION); } // Note that if the stack is the original collection, do not remove it now. // This is because the original collection contains the information to // restore the dragging panel to the right place when the drag is cancelled. if (stack != dragging_panel_original_collection_) panel_manager_->RemoveStack(stack); } }