0

Display split view indicators when dragging Mohnstrudel tabs

Snapping the dragged tab to the correct position will be implemented in
a follow-up change.

This also adds a function GetSnapPositionForLocation() that takes just a
location and ignores window-specific properties, unlike
GetSnapPosition(). This is necessary since the DragImageView used by
DragDropController is not snappable itself. Instead, it is just a
placeholder for a new browser window that will be snapped.

Bug: 1069869
Change-Id: I4b9946c9e1f6484a7ec79f8ad71db518d9dd9c7c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2150025
Commit-Queue: Collin Baker <collinbaker@chromium.org>
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: Xiaoqian Dai <xdai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#763510}
This commit is contained in:
Collin Baker
2020-04-28 20:18:16 +00:00
committed by Commit Bot
parent 5e2ee5539f
commit 7f9a165b51
4 changed files with 160 additions and 57 deletions

@ -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

@ -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_;

@ -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

@ -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,