Commit 5498e5ed authored by Min Chen's avatar Min Chen Committed by Commit Bot

Fling the drag window inside split view preview area.

Snapping or dropping the drag window into overview if fling the drag window
inside preview area should based on the fling direction and velocity.
- Fling to the snap position should always snap the window.
- Fling to the opposite snap position should consider the velocity. If the
  velocity is large enough, going to drop the window into overview. Otherwise,
  still snap the window.

Fling the window when splitview is active.
- Fling the window in the area where there is no snapped window, the same as
  preview area is shown.
- Fling the window in the area where there is a snapped window. Fling to the
  opposite position of the snapped window's snap position should drop the window
  into overview.

See recorded video:
https://drive.google.com/file/d/0B5I0jFeLxqIieDRSQXNwVWwtcGhCUWtJaHM3MkVfaUJwR3hr/view?usp=sharing

Bug: 876354
Change-Id: I316456cb3bcdf086b8181c44efdc5a383cadc97f
Reviewed-on: https://chromium-review.googlesource.com/1186155Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarXiaoqian Dai <xdai@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Commit-Queue: Min Chen <minch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#586302}
parent 80f9e6e7
......@@ -216,21 +216,35 @@ std::unique_ptr<views::Widget> CreateNewSelectorItemWidget(
// window dragging.
gfx::Rect GetGridBoundsInScreenDuringDragging(aura::Window* dragged_window,
IndicatorState indicator_state) {
SplitViewController* split_view_controller =
Shell::Get()->split_view_controller();
switch (indicator_state) {
case IndicatorState::kPreviewAreaLeft:
return Shell::Get()
->split_view_controller()
->GetSnappedWindowBoundsInScreen(dragged_window,
SplitViewController::RIGHT);
return split_view_controller->GetSnappedWindowBoundsInScreen(
dragged_window, SplitViewController::RIGHT);
case IndicatorState::kPreviewAreaRight:
return Shell::Get()
->split_view_controller()
->GetSnappedWindowBoundsInScreen(dragged_window,
SplitViewController::LEFT);
return split_view_controller->GetSnappedWindowBoundsInScreen(
dragged_window, SplitViewController::LEFT);
default:
return Shell::Get()
->split_view_controller()
->GetDisplayWorkAreaBoundsInScreen(dragged_window);
return split_view_controller->GetDisplayWorkAreaBoundsInScreen(
dragged_window);
}
}
// Gets the expected grid bounds according to current splitview state.
gfx::Rect GetGridBoundsInScreenAfterDragging(aura::Window* dragged_window) {
SplitViewController* split_view_controller =
Shell::Get()->split_view_controller();
switch (split_view_controller->state()) {
case SplitViewController::LEFT_SNAPPED:
return split_view_controller->GetSnappedWindowBoundsInScreen(
dragged_window, SplitViewController::RIGHT);
case SplitViewController::RIGHT_SNAPPED:
return split_view_controller->GetSnappedWindowBoundsInScreen(
dragged_window, SplitViewController::LEFT);
default:
return split_view_controller->GetDisplayWorkAreaBoundsInScreen(
dragged_window);
}
}
......@@ -750,9 +764,11 @@ void WindowGrid::OnWindowDragEnded(aura::Window* dragged_window,
// Called to reset caption and title visibility after dragging.
OnSelectorItemDragEnded();
// Need to call PositionWindows() here as the above two functions AddItem()
// and RemoveWindowSelectorItem() are called without repositioning windows.
PositionWindows(/*animate=*/true);
// Update the grid bounds and reposition windows. Since the grid bounds might
// be updated based on the preview area during drag, but the window finally
// didn't be snapped to the preview area.
SetBoundsAndUpdatePositions(
GetGridBoundsInScreenAfterDragging(dragged_window));
}
bool WindowGrid::IsNewSelectorItemWindow(aura::Window* window) const {
......
......@@ -2697,17 +2697,19 @@ class SplitViewAppDraggingTest : public SplitViewControllerTest {
}
void SendScrollStartAndUpdate(const gfx::Point& start,
float scroll_delta,
float scroll_y,
base::TimeTicks& timestamp,
aura::Window* window) {
aura::Window* window,
float scroll_x = 0.f) {
SendGestureEventToController(
start.x(), start.y(), timestamp,
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 0), window);
timestamp += base::TimeDelta::FromMilliseconds(100);
SendGestureEventToController(
start.x(), start.y() + scroll_delta, timestamp,
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 0, scroll_delta),
start.x() + scroll_x, start.y() + scroll_y, timestamp,
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, scroll_x,
scroll_y),
window);
}
......@@ -2716,16 +2718,23 @@ class SplitViewAppDraggingTest : public SplitViewControllerTest {
base::TimeTicks& timestamp,
aura::Window* window,
bool is_fling = false,
float velocity_y = 0.f) {
float velocity_y = 0.f,
float velocity_x = 0.f) {
timestamp += base::TimeDelta::FromMilliseconds(100);
ui::GestureEventDetails details =
is_fling
? ui::GestureEventDetails(ui::ET_SCROLL_FLING_START, 0, velocity_y)
: ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END);
is_fling ? ui::GestureEventDetails(ui::ET_SCROLL_FLING_START,
velocity_x, velocity_y)
: ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END);
SendGestureEventToController(start.x(), start.y() + scroll_delta, timestamp,
details, window);
}
IndicatorState GetIndicatorState() {
return controller_->drag_delegate_for_testing()
->split_view_drag_indicators_for_testing()
->current_indicator_state();
}
private:
void SendGestureEventToController(int x,
int y,
......@@ -2906,4 +2915,143 @@ TEST_F(SplitViewAppDraggingTest, DisplayConfigurationChangeTest) {
EXPECT_FALSE(wm::GetWindowState(window.get())->is_dragged());
}
// Tests the functionalities that fling the window when preview area is shown.
TEST_F(SplitViewAppDraggingTest, FlingWhenPreviewAreaIsShown) {
std::unique_ptr<aura::Window> window = CreateTestWindowWithWidget();
EXPECT_TRUE(wm::GetWindowState(window.get())->IsMaximized());
gfx::Rect display_bounds =
split_view_controller()->GetDisplayWorkAreaBoundsInScreen(window.get());
const float long_scroll_delta = display_bounds.height() / 4 + 5;
float large_velocity =
TabletModeAppWindowDragController::kFlingToOverviewThreshold + 10.f;
float small_velocity =
TabletModeAppWindowDragController::kFlingToOverviewThreshold - 10.f;
gfx::Point start(gfx::Point(0, 0));
base::TimeTicks timestamp = base::TimeTicks::Now();
// Fling to the right with large enough velocity when trying to snap the
// window to the left should drop the window to overview.
SendScrollStartAndUpdate(start, long_scroll_delta, timestamp, window.get());
EXPECT_EQ(IndicatorState::kPreviewAreaLeft, GetIndicatorState());
EndScrollSequence(start, long_scroll_delta, timestamp, window.get(),
/*is_fling=*/true, /*velocity_y*/ 0,
/*velocity_x=*/large_velocity);
WindowSelectorController* window_selector_controller =
Shell::Get()->window_selector_controller();
WindowSelector* window_selector =
window_selector_controller->window_selector();
EXPECT_TRUE(window_selector_controller->IsSelecting());
EXPECT_TRUE(window_selector->IsWindowInOverview(window.get()));
ToggleOverview();
EXPECT_TRUE(wm::GetWindowState(window.get())->IsMaximized());
// Fling to the right with small velocity when trying to snap the
// window to the left should still snap the window to left.
SendScrollStartAndUpdate(start, long_scroll_delta, timestamp, window.get());
EXPECT_EQ(IndicatorState::kPreviewAreaLeft, GetIndicatorState());
EndScrollSequence(start, long_scroll_delta, timestamp, window.get(),
/*is_fling=*/true, /*velocity_y*/ 0,
/*velocity_x=*/small_velocity);
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
EndSplitView();
EXPECT_TRUE(wm::GetWindowState(window.get())->IsMaximized());
// Fling to the left with large enough velocity when trying to snap the window
// to the right should drop the window to overvie.
start = gfx::Point(display_bounds.right(), 0);
SendScrollStartAndUpdate(start, long_scroll_delta, timestamp, window.get());
EXPECT_EQ(IndicatorState::kPreviewAreaRight, GetIndicatorState());
EndScrollSequence(start, long_scroll_delta, timestamp, window.get(),
/*is_fling=*/true, /*velocity_y*/ 0,
/*velocity_x=*/-large_velocity);
window_selector = window_selector_controller->window_selector();
EXPECT_TRUE(window_selector_controller->IsSelecting());
EXPECT_TRUE(window_selector->IsWindowInOverview(window.get()));
ToggleOverview();
// Fling to the left with small velocity when trying to snap the
// window to the right should still snap the window to right.
SendScrollStartAndUpdate(start, long_scroll_delta, timestamp, window.get());
EXPECT_EQ(IndicatorState::kPreviewAreaRight, GetIndicatorState());
EndScrollSequence(start, long_scroll_delta, timestamp, window.get(),
/*is_fling=*/true, /*velocity_y*/ 0,
/*velocity_x=*/-small_velocity);
EXPECT_TRUE(wm::GetWindowState(window.get())->IsSnapped());
}
// Tests the functionalities that fling a window when splitview is active.
TEST_F(SplitViewAppDraggingTest, FlingWhenSplitViewIsActive) {
std::unique_ptr<aura::Window> window1 = CreateTestWindowWithWidget();
std::unique_ptr<aura::Window> window2 = CreateTestWindowWithWidget();
split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT);
split_view_controller()->SnapWindow(window2.get(),
SplitViewController::RIGHT);
gfx::Rect display_bounds =
split_view_controller()->GetDisplayWorkAreaBoundsInScreen(window1.get());
const float long_scroll_y = display_bounds.bottom() - 10;
float large_velocity =
TabletModeAppWindowDragController::kFlingToOverviewThreshold + 10.f;
gfx::Point start(gfx::Point(0, 0));
// Fling the window in left snapping area to left should still snap the
// window.
base::TimeTicks timestamp = base::TimeTicks::Now();
SendScrollStartAndUpdate(start, long_scroll_y, timestamp, window1.get());
EndScrollSequence(start, long_scroll_y, timestamp, window1.get(),
/*is_fling=*/true, /*velocity_y=*/0,
/*velocity_x=*/-large_velocity);
EXPECT_TRUE(wm::GetWindowState(window1.get())->IsSnapped());
EXPECT_EQ(SplitViewController::BOTH_SNAPPED,
split_view_controller()->state());
// Fling the window in left snapping area to right should drop the window
// into overview.
SendScrollStartAndUpdate(start, long_scroll_y, timestamp, window1.get());
EndScrollSequence(start, long_scroll_y, timestamp, window1.get(),
/*is_fling=*/true, /*velocity_y=*/0,
/*velocity_x=*/large_velocity);
WindowSelectorController* selector_controller =
Shell::Get()->window_selector_controller();
EXPECT_TRUE(selector_controller->window_selector()->IsWindowInOverview(
window1.get()));
EXPECT_EQ(SplitViewController::RIGHT_SNAPPED,
split_view_controller()->state());
ToggleOverview();
// Fling the window in right snapping area to left should drop the window into
// overview.
EXPECT_EQ(SplitViewController::BOTH_SNAPPED,
split_view_controller()->state());
const int scroll_x = display_bounds.CenterPoint().x() + 10;
SendScrollStartAndUpdate(start, long_scroll_y, timestamp, window1.get(),
scroll_x);
gfx::Point end(scroll_x, 0);
EndScrollSequence(end, long_scroll_y, timestamp, window1.get(),
/*is_fling=*/true, /*velocity_y=*/0,
/*velocity_x=*/-large_velocity);
EXPECT_TRUE(selector_controller->window_selector()->IsWindowInOverview(
window1.get()));
EXPECT_EQ(SplitViewController::RIGHT_SNAPPED,
split_view_controller()->state());
ToggleOverview();
// Fling the window in right snapping area to right should snap the window to
// right side.
EXPECT_EQ(SplitViewController::BOTH_SNAPPED,
split_view_controller()->state());
SendScrollStartAndUpdate(start, long_scroll_y, timestamp, window1.get(),
scroll_x);
EndScrollSequence(end, long_scroll_y, timestamp, window1.get(),
/*is_fling=*/true, /*velocity_y=*/0,
/*velocity_x=*/large_velocity);
EXPECT_EQ(split_view_controller()->right_window(), window1.get());
EXPECT_TRUE(selector_controller->window_selector()->IsWindowInOverview(
window2.get()));
EXPECT_EQ(SplitViewController::RIGHT_SNAPPED,
split_view_controller()->state());
}
} // namespace ash
......@@ -105,11 +105,58 @@ void TabletModeAppWindowDragController::UpdateWindowDrag(
TabletModeWindowDragDelegate::UpdateDraggedWindowType::UPDATE_TRANSFORM);
}
bool TabletModeAppWindowDragController::ShouldFlingIntoOverview(
ui::GestureEvent* event) {
if (event->type() != ui::ET_SCROLL_FLING_START)
return false;
SplitViewController* split_view_controller =
Shell::Get()->split_view_controller();
const gfx::Point location_in_screen = GetEventLocationInScreen(event);
const IndicatorState indicator_state =
drag_delegate_->GetIndicatorState(location_in_screen);
const bool is_landscape =
split_view_controller->IsCurrentScreenOrientationLandscape();
const float velocity = is_landscape ? event->details().velocity_x()
: event->details().velocity_y();
// Drop the window into overview if fling with large enough velocity to the
// opposite snap position when preview area is shown.
if (split_view_controller->IsCurrentScreenOrientationPrimary()) {
if (indicator_state == IndicatorState::kPreviewAreaLeft)
return velocity > kFlingToOverviewThreshold;
else if (indicator_state == IndicatorState::kPreviewAreaRight)
return -velocity > kFlingToOverviewThreshold;
} else {
if (indicator_state == IndicatorState::kPreviewAreaLeft)
return -velocity > kFlingToOverviewThreshold;
else if (indicator_state == IndicatorState::kPreviewAreaRight)
return velocity > kFlingToOverviewThreshold;
}
const SplitViewController::State snap_state = split_view_controller->state();
const int end_position =
is_landscape ? location_in_screen.x() : location_in_screen.y();
// Fling the window when splitview is active. Since each snapping area in
// splitview has a corresponding snap position. Fling the window to the
// opposite position of the area's snap position with large enough velocity
// should drop the window into overview grid.
if (snap_state == SplitViewController::LEFT_SNAPPED ||
snap_state == SplitViewController::RIGHT_SNAPPED) {
return end_position > split_view_controller->divider_position()
? -velocity > kFlingToOverviewThreshold
: velocity > kFlingToOverviewThreshold;
}
// Consider only the velocity_y if splitview is not active and preview area is
// not shown.
return event->details().velocity_y() > kFlingToOverviewThreshold;
}
void TabletModeAppWindowDragController::EndWindowDrag(
ui::GestureEvent* event,
wm::WmToplevelWindowEventHandler::DragResult result) {
if (event->type() == ui::ET_SCROLL_FLING_START &&
event->details().velocity_y() > kFlingToOverviewThreshold) {
if (ShouldFlingIntoOverview(event)) {
DCHECK(Shell::Get()->window_selector_controller()->IsSelecting());
Shell::Get()->window_selector_controller()->window_selector()->AddItem(
drag_delegate_->dragged_window(), /*reposition=*/true,
......
......@@ -33,6 +33,10 @@ class ASH_EXPORT TabletModeAppWindowDragController
// window may be 1) maximized, or 2) snapped in splitscren.
bool DragWindowFromTop(ui::GestureEvent* event);
TabletModeWindowDragDelegate* drag_delegate_for_testing() {
return drag_delegate_.get();
}
private:
// Gesture window drag related functions. Used in DragWindowFromTop.
bool StartWindowDrag(ui::GestureEvent* event);
......@@ -40,6 +44,9 @@ class ASH_EXPORT TabletModeAppWindowDragController
void EndWindowDrag(ui::GestureEvent* event,
wm::WmToplevelWindowEventHandler::DragResult result);
// Returns true if fling event should drop the window into overview grid.
bool ShouldFlingIntoOverview(ui::GestureEvent* event);
// display::DisplayObserver:
void OnDisplayMetricsChanged(const display::Display& display,
uint32_t metrics) override;
......
......@@ -215,6 +215,51 @@ void TabletModeWindowDragDelegate::EndWindowDrag(
did_move_ = false;
}
IndicatorState TabletModeWindowDragDelegate::GetIndicatorState(
const gfx::Point& location_in_screen) const {
SplitViewController::SnapPosition snap_position =
GetSnapPosition(location_in_screen);
const bool can_snap = split_view_controller_->CanSnap(dragged_window_);
if (snap_position != SplitViewController::NONE &&
!split_view_controller_->IsSplitViewModeActive() && can_snap) {
return snap_position == SplitViewController::LEFT
? IndicatorState::kPreviewAreaLeft
: IndicatorState::kPreviewAreaRight;
}
// Do not show the drag indicators if split view mode is active.
if (split_view_controller_->IsSplitViewModeActive())
return IndicatorState::kNone;
// If the event location hasn't passed the indicator vertical threshold, do
// not show the drag indicators.
const gfx::Rect work_area_bounds =
display::Screen::GetScreen()
->GetDisplayNearestWindow(dragged_window_)
.work_area();
if (!did_move_ && location_in_screen.y() <
GetIndicatorsVerticalThreshold(work_area_bounds)) {
return IndicatorState::kNone;
}
// If the event location has passed the maximize vertical threshold, and the
// event location is not in snap indicator area, and overview mode is not
// active at the moment, do not show the drag indicators.
if (location_in_screen.y() >=
GetMaximizeVerticalThreshold(work_area_bounds) &&
snap_position == SplitViewController::NONE &&
!Shell::Get()->window_selector_controller()->IsSelecting()) {
return IndicatorState::kNone;
}
// No top drag indicator if in portrait screen orientation.
if (split_view_controller_->IsCurrentScreenOrientationLandscape())
return can_snap ? IndicatorState::kDragArea : IndicatorState::kCannotSnap;
return can_snap ? IndicatorState::kDragAreaRight
: IndicatorState::kCannotSnapRight;
}
bool TabletModeWindowDragDelegate::ShouldOpenOverviewWhenDragStarts() {
DCHECK(dragged_window_);
return true;
......@@ -296,51 +341,6 @@ SplitViewController::SnapPosition TabletModeWindowDragDelegate::GetSnapPosition(
return SplitViewController::NONE;
}
IndicatorState TabletModeWindowDragDelegate::GetIndicatorState(
const gfx::Point& location_in_screen) const {
SplitViewController::SnapPosition snap_position =
GetSnapPosition(location_in_screen);
const bool can_snap = split_view_controller_->CanSnap(dragged_window_);
if (snap_position != SplitViewController::NONE &&
!split_view_controller_->IsSplitViewModeActive() && can_snap) {
return snap_position == SplitViewController::LEFT
? IndicatorState::kPreviewAreaLeft
: IndicatorState::kPreviewAreaRight;
}
// Do not show the drag indicators if split view mode is active.
if (split_view_controller_->IsSplitViewModeActive())
return IndicatorState::kNone;
// If the event location hasn't passed the indicator vertical threshold, do
// not show the drag indicators.
const gfx::Rect work_area_bounds =
display::Screen::GetScreen()
->GetDisplayNearestWindow(dragged_window_)
.work_area();
if (!did_move_ && location_in_screen.y() <
GetIndicatorsVerticalThreshold(work_area_bounds)) {
return IndicatorState::kNone;
}
// If the event location has passed the maximize vertical threshold, and the
// event location is not in snap indicator area, and overview mode is not
// active at the moment, do not show the drag indicators.
if (location_in_screen.y() >=
GetMaximizeVerticalThreshold(work_area_bounds) &&
snap_position == SplitViewController::NONE &&
!Shell::Get()->window_selector_controller()->IsSelecting()) {
return IndicatorState::kNone;
}
// No top drag indicator if in portrait screen orientation.
if (split_view_controller_->IsCurrentScreenOrientationLandscape())
return can_snap ? IndicatorState::kDragArea : IndicatorState::kCannotSnap;
return can_snap ? IndicatorState::kDragAreaRight
: IndicatorState::kCannotSnapRight;
}
void TabletModeWindowDragDelegate::UpdateDraggedWindowTransform(
const gfx::Point& location_in_screen) {
DCHECK(Shell::Get()->window_selector_controller()->IsSelecting());
......
......@@ -50,6 +50,9 @@ class TabletModeWindowDragDelegate {
void EndWindowDrag(wm::WmToplevelWindowEventHandler::DragResult result,
const gfx::Point& location_in_screen);
// Returns the IndicatorState according to |location_in_screen|.
IndicatorState GetIndicatorState(const gfx::Point& location_in_screen) const;
aura::Window* dragged_window() { return dragged_window_; }
SplitViewDragIndicators* split_view_drag_indicators_for_testing() {
......@@ -82,9 +85,6 @@ class TabletModeWindowDragDelegate {
SplitViewController::SnapPosition GetSnapPosition(
const gfx::Point& location_in_screen) const;
// Returns the IndicatorState according to |location_in_screen|.
IndicatorState GetIndicatorState(const gfx::Point& location_in_screen) const;
// Update the dragged window's transform during dragging.
void UpdateDraggedWindowTransform(const gfx::Point& location_in_screen);
......
......@@ -1163,10 +1163,10 @@ TEST_F(ClientControlledShellSurfaceTest, DragWindowFromTopInTabletMode) {
start, end, base::TimeDelta::FromMilliseconds(100), 2);
EXPECT_TRUE(ash::wm::GetWindowState(window)->IsMaximized());
// FLING the window with large veloicty (larger than
// kFlingToOverviewThreshold) will drop the window into overview.
// FLING the window not inisde preview area with large enough y veloicty
// (larger than kFlingToOverviewThreshold) will drop the window into overview.
EXPECT_FALSE(shell->window_selector_controller()->IsSelecting());
end = gfx::Point(0, 210);
end = gfx::Point(400, 210);
const base::TimeDelta duration =
event_generator->CalculateScrollDurationForFlingVelocity(
start, end,
......@@ -1181,6 +1181,7 @@ TEST_F(ClientControlledShellSurfaceTest, DragWindowFromTopInTabletMode) {
// Drag the window long enough (pass one fourth of the screen vertical
// height) to snap the window to splitscreen.
end = gfx::Point(0, 210);
shell->window_selector_controller()->ToggleOverview();
EXPECT_FALSE(shell->window_selector_controller()->IsSelecting());
EXPECT_TRUE(ash::wm::GetWindowState(window)->IsMaximized());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment