diff --git a/ash/drag_drop/drag_drop_controller.cc b/ash/drag_drop/drag_drop_controller.cc index 37e154e0f15fa..abecd57f1c547 100644 --- a/ash/drag_drop/drag_drop_controller.cc +++ b/ash/drag_drop/drag_drop_controller.cc @@ -10,10 +10,16 @@ #include "ash/drag_drop/drag_drop_tracker.h" #include "ash/drag_drop/drag_image_view.h" #include "ash/public/cpp/ash_features.h" +#include "ash/screen_util.h" #include "ash/shell.h" #include "ash/shell_delegate.h" +#include "ash/wm/splitview/split_view_constants.h" +#include "ash/wm/splitview/split_view_drag_indicators.h" +#include "ash/wm/splitview/split_view_utils.h" #include "base/bind.h" #include "base/metrics/histogram_macros.h" +#include "base/no_destructor.h" +#include "base/optional.h" #include "base/pickle.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" @@ -58,6 +64,16 @@ const int kCancelAnimationFrameRate = 60; static const float kTouchDragImageScale = 1.2f; static const int kTouchDragImageVerticalOffset = -25; +// The following distances are copied from tablet_mode_window_drag_delegate.cc. +// TODO(https://crbug.com/1069869): share these constants. + +// Items dragged to within |kDistanceFromEdgeDp| of the screen will get snapped +// even if they have not moved by |kMinimumDragToSnapDistanceDp|. +constexpr float kDistanceFromEdgeDp = 16.f; +// The minimum distance that an item must be moved before it is snapped. This +// prevents accidental snaps. +constexpr float kMinimumDragToSnapDistanceDp = 96.f; + // Adjusts the drag image bounds such that the new bounds are scaled by |scale| // and translated by the |drag_image_offset| and and additional // |vertical_offset|. @@ -86,6 +102,37 @@ void DispatchGestureEndToWindow(aura::Window* window) { window->delegate()->OnGestureEvent(&gesture_end); } } + +bool IsChromeTabDrag(const ui::OSExchangeData& drag_data) { + if (!features::IsWebUITabStripTabDragIntegrationEnabled()) + return false; + + base::Pickle pickle; + drag_data.GetPickledData(ui::ClipboardFormatType::GetWebCustomDataType(), + &pickle); + base::PickleIterator iter(pickle); + + uint32_t entry_count = 0; + if (!iter.ReadUInt32(&entry_count)) + return false; + + for (uint32_t i = 0; i < entry_count; ++i) { + base::StringPiece16 type; + base::StringPiece16 data; + if (!iter.ReadStringPiece16(&type) || !iter.ReadStringPiece16(&data)) + return false; + + // TODO(https://crbug.com/1069869): share this constant between Ash + // and Chrome instead of hardcoding it in both places. + static const base::NoDestructor<base::string16> chrome_tab_type( + base::ASCIIToUTF16("application/vnd.chromium.tab")); + if (type == *chrome_tab_type) + return true; + } + + return false; +} + } // namespace class DragDropTrackerDelegate : public aura::WindowDelegate { @@ -200,6 +247,7 @@ int DragDropController::StartDragAndDrop( drag_data_ = std::move(data); drag_operation_ = operation; current_drag_actions_ = 0; + is_chrome_tab_drag_ = IsChromeTabDrag(*drag_data_); start_location_ = screen_location; current_location_ = screen_location; @@ -215,6 +263,14 @@ int DragDropController::StartDragAndDrop( for (aura::client::DragDropClientObserver& observer : observers_) observer.OnDragStarted(); + if (is_chrome_tab_drag_) { + // TODO(crbug.com/1069869): move tab dragging logic to delegate or observer. + split_view_drag_indicators_ = std::make_unique<SplitViewDragIndicators>( + Shell::GetPrimaryRootWindow()); + split_view_drag_indicators_->SetDraggedWindow( + drag_image_->GetWidget()->GetNativeView()); + } + if (should_block_during_drag_drop_) { base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed); quit_closure_ = run_loop.QuitClosure(); @@ -486,16 +542,48 @@ void DragDropController::DragUpdate(aura::Window* target, observer.OnDragActionsChanged(op); } + gfx::Point root_location_in_screen = event.root_location(); + ::wm::ConvertPointToScreen(target->GetRootWindow(), &root_location_in_screen); + DCHECK(drag_image_.get()); if (drag_image_->GetVisible()) { - gfx::Point root_location_in_screen = event.root_location(); - ::wm::ConvertPointToScreen(target->GetRootWindow(), - &root_location_in_screen); current_location_ = root_location_in_screen; drag_image_->SetScreenPosition(root_location_in_screen - drag_image_offset_); drag_image_->SetTouchDragOperation(op); } + + if (is_chrome_tab_drag_) { + aura::Window* const drag_image_window = + drag_image_->GetWidget()->GetNativeView(); + const gfx::Rect area = + screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( + drag_image_window); + + // These should only be seen in tablet mode which precludes dragging between + // displays. + DCHECK_EQ(drag_image_window->GetRootWindow(), target->GetRootWindow()); + + SplitViewController::SnapPosition snap_position = + ::ash::GetSnapPositionForLocation( + Shell::GetPrimaryRootWindow(), root_location_in_screen, + start_location_, + /*snap_distance_from_edge=*/kDistanceFromEdgeDp, + /*minimum_drag_distance=*/kMinimumDragToSnapDistanceDp, + /*horizontal_edge_inset=*/area.width() * + kHighlightScreenPrimaryAxisRatio + + kHighlightScreenEdgePaddingDp, + /*vertical_edge_inset=*/area.height() * + kHighlightScreenPrimaryAxisRatio + + kHighlightScreenEdgePaddingDp); + split_view_drag_indicators_->SetWindowDraggingState( + SplitViewDragIndicators::ComputeWindowDraggingState( + true, SplitViewDragIndicators::WindowDraggingState::kFromTop, + snap_position)); + + // TODO(https://crbug.com/1069869): scale source window up/down similar to + // |TabletModeBrowserWindowDragDelegate::UpdateSourceWindow()|. + } } void DragDropController::Drop(aura::Window* target, @@ -512,8 +600,6 @@ void DragDropController::Drop(aura::Window* target, aura::client::DragDropDelegate* delegate = aura::client::GetDragDropDelegate(target); if (delegate) { - const bool is_chrome_tab_drag = IsChromeTabDrag(); - ui::DropTargetEvent e(*drag_data_.get(), event.location_f(), event.root_location_f(), drag_operation_); e.set_flags(event.flags()); @@ -521,9 +607,11 @@ void DragDropController::Drop(aura::Window* target, ui::OSExchangeData copied_data(drag_data_->provider().Clone()); drag_operation_ = delegate->OnPerformDrop(e, std::move(drag_data_)); - if (drag_operation_ == 0 && is_chrome_tab_drag) { + if (drag_operation_ == 0 && is_chrome_tab_drag_) { Shell::Get()->shell_delegate()->CreateBrowserForTabDrop( drag_source_window_, copied_data); + // TODO(https://crbug.com/1069869): snap the created browser if + // necessary. StartCanceledAnimation(kCancelAnimationDuration); } else if (drag_operation_ == 0) { StartCanceledAnimation(kCancelAnimationDuration); @@ -633,42 +721,17 @@ void DragDropController::Cleanup() { drag_window_->RemoveObserver(this); drag_window_ = NULL; drag_data_.reset(); + + if (is_chrome_tab_drag_) { + split_view_drag_indicators_->SetWindowDraggingState( + SplitViewDragIndicators::WindowDraggingState::kNoDrag); + split_view_drag_indicators_.reset(); + is_chrome_tab_drag_ = false; + } + // Cleanup can be called again while deleting DragDropTracker, so delete // the pointer with a local variable to avoid double free. std::unique_ptr<DragDropTracker> holder = std::move(drag_drop_tracker_); } -bool DragDropController::IsChromeTabDrag() { - if (!features::IsWebUITabStripTabDragIntegrationEnabled()) - return false; - - if (!drag_data_) - return false; - base::Pickle pickle; - drag_data_->GetPickledData(ui::ClipboardFormatType::GetWebCustomDataType(), - &pickle); - base::PickleIterator iter(pickle); - - uint32_t entry_count = 0; - if (!iter.ReadUInt32(&entry_count)) - return false; - - for (uint32_t i = 0; i < entry_count; ++i) { - base::StringPiece16 type; - base::StringPiece16 data; - if (!iter.ReadStringPiece16(&type) || !iter.ReadStringPiece16(&data)) { - return false; - } - - // TODO(https://crbug.com/1069869): share this constant between Ash - // and Chrome instead of hardcoding it in both places. - static const base::string16 chrome_tab_type = - base::ASCIIToUTF16("application/vnd.chromium.tab"); - if (type == chrome_tab_type) - return true; - } - - return false; -} - } // namespace ash diff --git a/ash/drag_drop/drag_drop_controller.h b/ash/drag_drop/drag_drop_controller.h index 87a9542af1250..2d1475f462577 100644 --- a/ash/drag_drop/drag_drop_controller.h +++ b/ash/drag_drop/drag_drop_controller.h @@ -34,6 +34,7 @@ namespace ash { class DragDropTracker; class DragDropTrackerDelegate; class DragImageView; +class SplitViewDragIndicators; class ASH_EXPORT DragDropController : public aura::client::DragDropClient, public ui::EventHandler, @@ -111,8 +112,6 @@ class ASH_EXPORT DragDropController : public aura::client::DragDropClient, // Helper method to reset everything. void Cleanup(); - bool IsChromeTabDrag(); - bool enabled_ = false; std::unique_ptr<DragImageView> drag_image_; gfx::Vector2d drag_image_offset_; @@ -120,6 +119,10 @@ class ASH_EXPORT DragDropController : public aura::client::DragDropClient, int drag_operation_; int current_drag_actions_ = 0; + // Tab drag specific members. + bool is_chrome_tab_drag_ = false; + std::unique_ptr<SplitViewDragIndicators> split_view_drag_indicators_; + // Window that is currently under the drag cursor. aura::Window* drag_window_; diff --git a/ash/wm/splitview/split_view_utils.cc b/ash/wm/splitview/split_view_utils.cc index a71e49f4b4a9d..ecaebff6e2636 100644 --- a/ash/wm/splitview/split_view_utils.cc +++ b/ash/wm/splitview/split_view_utils.cc @@ -412,19 +412,16 @@ void ShowAppCannotSnapToast() { kAppCannotSnapToastDurationMs, base::Optional<base::string16>())); } -SplitViewController::SnapPosition GetSnapPosition( +SplitViewController::SnapPosition GetSnapPositionForLocation( aura::Window* root_window, - aura::Window* window, const gfx::Point& location_in_screen, - const gfx::Point& initial_location_in_screen, + const base::Optional<gfx::Point>& initial_location_in_screen, int snap_distance_from_edge, int minimum_drag_distance, int horizontal_edge_inset, int vertical_edge_inset) { - if (!ShouldAllowSplitView() || - !SplitViewController::Get(root_window)->CanSnapWindow(window)) { + if (!ShouldAllowSplitView()) return SplitViewController::NONE; - } const bool horizontal = SplitViewController::IsLayoutHorizontal(); const bool right_side_up = SplitViewController::IsLayoutRightSideUp(); @@ -476,9 +473,9 @@ SplitViewController::SnapPosition GetSnapPosition( drag_end_near_edge = true; } - if (!drag_end_near_edge && window->GetRootWindow() == root_window) { + if (!drag_end_near_edge && initial_location_in_screen) { // Check how far the window has been dragged. - const auto distance = location_in_screen - initial_location_in_screen; + const auto distance = location_in_screen - *initial_location_in_screen; const int primary_axis_distance = horizontal ? distance.x() : distance.y(); const bool is_left_or_top = SplitViewController::IsPhysicalLeftOrTop(snap_position); @@ -491,4 +488,27 @@ SplitViewController::SnapPosition GetSnapPosition( return snap_position; } +SplitViewController::SnapPosition GetSnapPosition( + aura::Window* root_window, + aura::Window* window, + const gfx::Point& location_in_screen, + const gfx::Point& initial_location_in_screen, + int snap_distance_from_edge, + int minimum_drag_distance, + int horizontal_edge_inset, + int vertical_edge_inset) { + if (!SplitViewController::Get(root_window)->CanSnapWindow(window)) { + return SplitViewController::NONE; + } + + base::Optional<gfx::Point> initial_location_in_current_screen = base::nullopt; + if (window->GetRootWindow() == root_window) + initial_location_in_current_screen = initial_location_in_screen; + + return GetSnapPositionForLocation( + root_window, location_in_screen, initial_location_in_current_screen, + snap_distance_from_edge, minimum_drag_distance, horizontal_edge_inset, + vertical_edge_inset); +} + } // namespace ash diff --git a/ash/wm/splitview/split_view_utils.h b/ash/wm/splitview/split_view_utils.h index d5065514e31a3..f9d8b02d9bfe9 100644 --- a/ash/wm/splitview/split_view_utils.h +++ b/ash/wm/splitview/split_view_utils.h @@ -7,6 +7,7 @@ #include "ash/ash_export.h" #include "ash/wm/splitview/split_view_controller.h" +#include "base/optional.h" #include "ui/aura/window_observer.h" #include "ui/compositor/layer_animation_observer.h" #include "ui/gfx/transform.h" @@ -138,14 +139,30 @@ ASH_EXPORT bool ShouldAllowSplitView(); // not compatible. ASH_EXPORT void ShowAppCannotSnapToast(); -// Returns the desired snap position. To be able to get snapped (meaning the -// return value is not |SplitViewController::NONE|), |window| must 1) first of -// all satisfy |SplitViewController::CanSnapWindow| on the split view controller -// for |root_window|, and 2) secondly be dragged either inside -// |snap_distance_from_edge| or dragged toward the edge for at least -// |minimum_drag_distance| distance until it's dragged into a suitable edge of -// the work area of |root_window| (i.e., |horizontal_edge_inset| if dragged -// horizontally to snap, or |vertical_edge_inset| if dragged vertically). +// Calculates the snap position for a dragged window at |location_in_screen|, +// ignoring any properties of the window itself. The |root_window| is of the +// current screen. |initial_location_in_screen| is the location at drag start if +// the drag began in |root_window|, and is empty otherwise. To be snappable +// (meaning the return value is not |SplitViewController::NONE|), +// |location_in_screen| must be either inside |snap_distance_from_edge| or +// dragged toward the edge for at least |minimum_drag_distance| distance until +// it's dragged into a suitable edge of the work area of |root_window| (i.e., +// |horizontal_edge_inset| if dragged horizontally to snap, or +// |vertical_edge_inset| if dragged vertically). +ASH_EXPORT SplitViewController::SnapPosition GetSnapPositionForLocation( + aura::Window* root_window, + const gfx::Point& location_in_screen, + const base::Optional<gfx::Point>& initial_location_in_screen, + int snap_distance_from_edge, + int minimum_drag_distance, + int horizontal_edge_inset, + int vertical_edge_inset); + +// Returns the desired snap position. To be snappable, |window| must 1) +// satisfy |SplitViewController::CanSnapWindow| for |root_window|, and +// 2) be snappable according to |GetSnapPositionForLocation| above. +// |initial_location_in_screen| is the window location at drag start in +// its initial window. Otherwise, the arguments are the same as above. ASH_EXPORT SplitViewController::SnapPosition GetSnapPosition( aura::Window* root_window, aura::Window* window,