Commit 57098dce authored by Toni Barzic's avatar Toni Barzic Committed by Commit Bot

Better coordinate swipe to home gesture and drag handle nudge

Manages the drag handle nudge during window drag from shelf to home or
overview (which may end up in a gesture the nudge is describing).

If the contextual nudge is shown when the window drag starts, the nudge
will not be hidden until the gesture completes (end it will get hidden
when the gesture completes, even if the user does not end up going
home).

If the contextual nudge is scheduled to be shown when the gesture
starts, the show request will be canceled.

Adds logic that prevents drag handle nudge from showing up, unless it's
a result of an explicit user action - tapping the drag handle.

BUG=1058617

Change-Id: I96fa2748308143ec42295dbfb472dc564da5bd03
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2107135
Commit-Queue: Toni Baržić <tbarzic@chromium.org>
Reviewed-by: default avatarManu Cornet <manucornet@chromium.org>
Cr-Commit-Position: refs/heads/master@{#751200}
parent 3bb3e09c
......@@ -12,6 +12,7 @@
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_provider.h"
#include "ash/wm/overview/overview_controller.h"
#include "base/bind.h"
#include "base/debug/stack_trace.h"
#include "base/timer/timer.h"
......@@ -103,11 +104,18 @@ bool DragHandle::DoesIntersectRect(const views::View* target,
}
bool DragHandle::ShowDragHandleNudge() {
// Stop observing overview state if nudge show timer has fired.
if (!show_drag_handle_nudge_timer_.IsRunning())
overview_observer_.RemoveAll();
// Do not show drag handle nudge if it is already shown or drag handle is not
// visible.
if (ShowingNudge() || !GetVisible())
if (ShowingNudge() || window_drag_from_shelf_in_progress_ || !GetVisible())
return false;
showing_nudge_ = true;
StopDragHandleNudgeShowTimer();
PrefService* pref =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
base::TimeDelta nudge_duration = contextual_tooltip::GetNudgeTimeout(
......@@ -127,8 +135,16 @@ bool DragHandle::ShowDragHandleNudge() {
}
void DragHandle::ScheduleShowDragHandleNudge() {
if (showing_nudge_)
if (showing_nudge_ || show_drag_handle_nudge_timer_.IsRunning() ||
window_drag_from_shelf_in_progress_ ||
Shell::Get()->overview_controller()->InOverviewSession()) {
return;
}
// Observe overview controller to detect overview session start - this should
// cancel the scheduled nudge show.
overview_observer_.Add(Shell::Get()->overview_controller());
show_drag_handle_nudge_timer_.Start(
FROM_HERE, kShowNudgeDelay,
base::BindOnce(base::IgnoreResult(&DragHandle::ShowDragHandleNudge),
......@@ -141,7 +157,8 @@ void DragHandle::SetColorAndOpacity(SkColor color, float opacity) {
}
void DragHandle::HideDragHandleNudge(bool hidden_by_tap) {
show_drag_handle_nudge_timer_.Stop();
StopDragHandleNudgeShowTimer();
if (!ShowingNudge())
return;
hide_drag_handle_nudge_timer_.Stop();
......@@ -149,6 +166,28 @@ void DragHandle::HideDragHandleNudge(bool hidden_by_tap) {
showing_nudge_ = false;
}
void DragHandle::SetWindowDragFromShelfInProgress(bool gesture_in_progress) {
if (window_drag_from_shelf_in_progress_ == gesture_in_progress)
return;
window_drag_from_shelf_in_progress_ = gesture_in_progress;
// If the contextual nudge is not yet shown, make sure that any scheduled
// nudge show request is canceled.
if (!ShowingNudge()) {
StopDragHandleNudgeShowTimer();
return;
}
// If the drag handle nudge is shown when the gesture to home or overview
// starts, keep it around until the gesture completes.
if (window_drag_from_shelf_in_progress_) {
hide_drag_handle_nudge_timer_.Stop();
} else {
HideDragHandleNudge(/*hidden_by_tap=*/false);
}
}
void DragHandle::OnGestureEvent(ui::GestureEvent* event) {
if (!features::AreContextualNudgesEnabled())
return;
......@@ -180,6 +219,10 @@ gfx::Rect DragHandle::GetAnchorBoundsInScreen() const {
return anchor_bounds;
}
void DragHandle::OnOverviewModeStarting() {
StopDragHandleNudgeShowTimer();
}
void DragHandle::ShowDragHandleTooltip() {
DCHECK(!drag_handle_nudge_);
drag_handle_nudge_ = new ContextualNudge(
......@@ -251,7 +294,7 @@ void DragHandle::HideDragHandleNudgeHelper(bool hidden_by_tap) {
ui::ScopedLayerAnimationSettings opacity_animation_settings(
opacity_animator);
opacity_animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
opacity_animation_settings.SetTweenType(gfx::Tween::LINEAR);
opacity_animation_settings.SetTransitionDuration(
hidden_by_tap ? kDragHandleHideOnTapAnimationDuration
......@@ -304,4 +347,9 @@ void DragHandle::HandleTapOnNudge() {
HideDragHandleNudge(true /*hidden_by_tap*/);
}
void DragHandle::StopDragHandleNudgeShowTimer() {
show_drag_handle_nudge_timer_.Stop();
overview_observer_.RemoveAll();
}
} // namespace ash
......@@ -8,7 +8,10 @@
#include "ash/ash_export.h"
#include "ash/shelf/contextual_nudge.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_observer.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "base/timer/timer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/views/view.h"
......@@ -17,7 +20,8 @@
namespace ash {
class ASH_EXPORT DragHandle : public views::View,
public views::ViewTargeterDelegate {
public views::ViewTargeterDelegate,
public OverviewObserver {
public:
explicit DragHandle(int drag_handle_corner_radius);
DragHandle(const DragHandle&) = delete;
......@@ -45,10 +49,17 @@ class ASH_EXPORT DragHandle : public views::View,
// tapping the nudge.
void HideDragHandleNudge(bool hidden_by_tap);
// Called when the window drag from shelf starts or ends. The drag handle
// contextual nudge will remain visible while the gesture is in progress.
void SetWindowDragFromShelfInProgress(bool gesture_in_progress);
// views::View:
void OnGestureEvent(ui::GestureEvent* event) override;
gfx::Rect GetAnchorBoundsInScreen() const override;
// OverviewObserver:
void OnOverviewModeStarting() override;
bool ShowingNudge() { return showing_nudge_; }
bool has_show_drag_handle_timer_for_testing() {
......@@ -63,6 +74,10 @@ class ASH_EXPORT DragHandle : public views::View,
return hide_drag_handle_nudge_timer_.IsRunning();
}
void fire_hide_drag_handle_timer_for_testing() {
hide_drag_handle_nudge_timer_.FireNow();
}
ContextualNudge* drag_handle_nudge_for_testing() {
return drag_handle_nudge_;
}
......@@ -90,6 +105,9 @@ class ASH_EXPORT DragHandle : public views::View,
// Handler for tap gesture on the contextual nudge widget. It hides the nudge.
void HandleTapOnNudge();
// Stops the timer to show the drag handle nudge.
void StopDragHandleNudgeShowTimer();
// Timer to hide drag handle nudge if it has a timed life.
base::OneShotTimer hide_drag_handle_nudge_timer_;
......@@ -98,9 +116,16 @@ class ASH_EXPORT DragHandle : public views::View,
bool showing_nudge_ = false;
// Whether window drag from shelf (i.e. gesture from in-app shelf to home or
// overview) is currently in progress. If the contextual nudge is shown when
// the gesture starts, it should remain shown until the gesture ends.
// Set by ShelfLayoutManager using SetWindowDragFromShelfInProgress().
bool window_drag_from_shelf_in_progress_ = false;
// A label used to educate users about swipe gestures on the drag handle.
ContextualNudge* drag_handle_nudge_ = nullptr;
ScopedObserver<OverviewController, OverviewObserver> overview_observer_{this};
base::WeakPtrFactory<DragHandle> weak_factory_{this};
};
......
......@@ -2,10 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/home_screen/drag_window_from_shelf_controller.h"
#include "ash/home_screen/drag_window_from_shelf_controller_test_api.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/contextual_tooltip.h"
#include "ash/shelf/drag_handle.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/test/shelf_layout_manager_test_base.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
......@@ -20,6 +24,10 @@ ShelfWidget* GetShelfWidget() {
return AshTestBase::GetPrimaryShelf()->shelf_widget();
}
ShelfLayoutManager* GetShelfLayoutManager() {
return AshTestBase::GetPrimaryShelf()->shelf_layout_manager();
}
} // namespace
// Test base for unit test related to drag handle contextual nudges.
......@@ -377,4 +385,241 @@ TEST_F(DragHandleContextualNudgeTest, DragHandleNudgeBoundsInScreen) {
EXPECT_LE(nudge_bounds.bottom(), drag_handle_bounds.y());
}
// Tests that drag handle does not hide during the window drag from shelf
// gesture.
TEST_F(DragHandleContextualNudgeTest,
DragHandleNudgeNotHiddenDuringWindowDragFromShelf) {
TabletModeControllerTestApi().EnterTabletMode();
// Creates a widget that will become maximized in tablet mode.
views::Widget* widget = CreateTestWidget();
widget->Maximize();
ShelfWidget* const shelf_widget = GetShelfWidget();
DragHandle* const drag_handle = shelf_widget->GetDragHandle();
ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
drag_handle->fire_show_drag_handle_timer_for_testing();
EXPECT_TRUE(drag_handle->ShowingNudge());
TabletModeControllerTestApi().LeaveTabletMode();
// Advance time for more than a day (which should enable the nudge again).
test_clock_.Advance(base::TimeDelta::FromHours(25));
TabletModeControllerTestApi().EnterTabletMode();
EXPECT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
drag_handle->fire_show_drag_handle_timer_for_testing();
EXPECT_TRUE(drag_handle->has_hide_drag_handle_timer_for_testing());
const gfx::Point start = drag_handle->GetBoundsInScreen().CenterPoint();
// Simulates window drag from shelf gesture, and verifies that the timer to
// hide the drag handle nudge is canceled when the window drag from shelf
// starts.
GetEventGenerator()->GestureScrollSequenceWithCallback(
start, start + gfx::Vector2d(0, -200),
base::TimeDelta::FromMilliseconds(50),
/*num_steps = */ 6,
base::BindRepeating(
[](DragHandle* drag_handle, ui::EventType type,
const gfx::Vector2dF& offset) {
DragWindowFromShelfController* window_drag_controller =
GetShelfLayoutManager()->window_drag_controller_for_testing();
if (window_drag_controller &&
window_drag_controller->dragged_window()) {
EXPECT_FALSE(
drag_handle->has_hide_drag_handle_timer_for_testing());
}
const bool scroll_end = type == ui::ET_GESTURE_SCROLL_END;
EXPECT_EQ(!scroll_end, drag_handle->ShowingNudge());
},
drag_handle));
// The nudge should be hidden when the gesture completes.
EXPECT_FALSE(drag_handle->ShowingNudge());
}
// Tests that window drag from shelf cancels drag handle contextual nudge from
// showing.
TEST_F(DragHandleContextualNudgeTest,
DragHandleNudgeNotShownDuringWindowDragFromShelf) {
TabletModeControllerTestApi().EnterTabletMode();
// Creates a widget that will become maximized in tablet mode.
views::Widget* widget = CreateTestWidget();
widget->Maximize();
ShelfWidget* const shelf_widget = GetShelfWidget();
DragHandle* const drag_handle = shelf_widget->GetDragHandle();
EXPECT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
const gfx::Point start =
GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint();
// Simulates window drag from shelf gesture, and verifies that the timer to
// show the drag handle nudge is canceled when the window drag from shelf
// starts.
GetEventGenerator()->GestureScrollSequenceWithCallback(
start, start + gfx::Vector2d(0, -200),
base::TimeDelta::FromMilliseconds(50),
/*num_steps = */ 6,
base::BindRepeating(
[](DragHandle* drag_handle, ui::EventType type,
const gfx::Vector2dF& offset) {
DragWindowFromShelfController* window_drag_controller =
GetShelfLayoutManager()->window_drag_controller_for_testing();
if (window_drag_controller &&
window_drag_controller->dragged_window()) {
EXPECT_FALSE(
drag_handle->has_show_drag_handle_timer_for_testing());
// Attempt to schedule the nudge should fail.
if (type != ui::ET_GESTURE_SCROLL_END) {
drag_handle->ScheduleShowDragHandleNudge();
EXPECT_FALSE(
drag_handle->has_show_drag_handle_timer_for_testing());
}
}
EXPECT_FALSE(drag_handle->ShowingNudge());
},
drag_handle));
// The nudge should be hidden when the gesture completes.
EXPECT_FALSE(drag_handle->ShowingNudge());
}
// Tests that drag handle nudge gets hidden when the user performs window drag
// from shelf to home.
TEST_F(DragHandleContextualNudgeTest, FlingFromShelfToHomeHidesTheNudge) {
TabletModeControllerTestApi().EnterTabletMode();
// Creates a widget that will become maximized in tablet mode.
views::Widget* widget = CreateTestWidget();
widget->Maximize();
ShelfWidget* const shelf_widget = GetShelfWidget();
DragHandle* const drag_handle = shelf_widget->GetDragHandle();
ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
drag_handle->fire_show_drag_handle_timer_for_testing();
EXPECT_TRUE(drag_handle->ShowingNudge());
const gfx::Point start = drag_handle->GetBoundsInScreen().CenterPoint();
// Simulates window drag from shelf gesture, and verifies that the timer to
// hide the drag handle nudge is canceled when the window drag from shelf
// starts.
GetEventGenerator()->GestureScrollSequenceWithCallback(
start, start + gfx::Vector2d(0, -300),
base::TimeDelta::FromMilliseconds(10),
/*num_steps = */ 6,
base::BindRepeating(
[](DragHandle* drag_handle, ui::EventType type,
const gfx::Vector2dF& offset) {
const bool scroll_end = type == ui::ET_GESTURE_SCROLL_END;
EXPECT_EQ(!scroll_end, drag_handle->ShowingNudge());
},
drag_handle));
// The nudge should be hidden when the gesture completes.
EXPECT_FALSE(drag_handle->ShowingNudge());
GetAppListTestHelper()->CheckVisibility(true);
}
// Tests that drag handle nudge gets hidden when the user performs window drag
// from shelf, even if the gesture does not end up going home.
TEST_F(DragHandleContextualNudgeTest, DragFromShelfToHomeHidesTheNudge) {
TabletModeControllerTestApi().EnterTabletMode();
// Creates a widget that will become maximized in tablet mode.
views::Widget* widget = CreateTestWidget();
widget->Maximize();
ShelfWidget* const shelf_widget = GetShelfWidget();
DragHandle* const drag_handle = shelf_widget->GetDragHandle();
ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
drag_handle->fire_show_drag_handle_timer_for_testing();
EXPECT_TRUE(drag_handle->ShowingNudge());
const gfx::Point start = drag_handle->GetBoundsInScreen().CenterPoint();
// Simulates window drag from shelf gesture, and verifies that the timer to
// hide the drag handle nudge is canceled when the window drag from shelf
// starts.
GetEventGenerator()->GestureScrollSequenceWithCallback(
start, start + gfx::Vector2d(0, -150),
base::TimeDelta::FromMilliseconds(500),
/*num_steps = */ 20,
base::BindRepeating(
[](DragHandle* drag_handle, ui::EventType type,
const gfx::Vector2dF& offset) {
DragWindowFromShelfController* window_drag_controller =
GetShelfLayoutManager()->window_drag_controller_for_testing();
if (window_drag_controller &&
window_drag_controller->dragged_window()) {
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller);
}
const bool scroll_end = type == ui::ET_GESTURE_SCROLL_END;
EXPECT_EQ(!scroll_end, drag_handle->ShowingNudge());
},
drag_handle));
// The nudge should be hidden when the gesture completes.
EXPECT_FALSE(drag_handle->ShowingNudge());
GetAppListTestHelper()->CheckVisibility(false);
EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
}
// Tests that scheduled drag handle nudge show is canceled when overview starts.
TEST_F(DragHandleContextualNudgeTest, OverviewCancelsNudgeShow) {
TabletModeControllerTestApi().EnterTabletMode();
// Creates a widget that will become maximized in tablet mode.
views::Widget* widget = CreateTestWidget();
widget->Maximize();
ShelfWidget* const shelf_widget = GetShelfWidget();
DragHandle* const drag_handle = shelf_widget->GetDragHandle();
ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
Shell::Get()->overview_controller()->StartOverview();
ASSERT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());
}
// Tests that tapping the drag handle can shown drag handle nudge in overview.
TEST_F(DragHandleContextualNudgeTest, DragHandleTapShowNudgeInOverview) {
TabletModeControllerTestApi().EnterTabletMode();
// Creates a widget that will become maximized in tablet mode.
views::Widget* widget = CreateTestWidget();
widget->Maximize();
ShelfWidget* const shelf_widget = GetShelfWidget();
DragHandle* const drag_handle = shelf_widget->GetDragHandle();
ASSERT_TRUE(drag_handle->has_show_drag_handle_timer_for_testing());
drag_handle->fire_show_drag_handle_timer_for_testing();
EXPECT_TRUE(drag_handle->ShowingNudge());
TabletModeControllerTestApi().LeaveTabletMode();
TabletModeControllerTestApi().EnterTabletMode();
Shell::Get()->overview_controller()->StartOverview();
ASSERT_FALSE(drag_handle->has_show_drag_handle_timer_for_testing());
GetEventGenerator()->GestureTapAt(
drag_handle->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(drag_handle->GetVisible());
EXPECT_TRUE(drag_handle->ShowingNudge());
EXPECT_TRUE(drag_handle->has_hide_drag_handle_timer_for_testing());
drag_handle->fire_hide_drag_handle_timer_for_testing();
EXPECT_FALSE(drag_handle->ShowingNudge());
// Tapping the drag handle again will show the nudge again.
GetEventGenerator()->GestureTapAt(
drag_handle->GetBoundsInScreen().CenterPoint());
EXPECT_TRUE(drag_handle->ShowingNudge());
}
} // namespace ash
......@@ -2603,6 +2603,8 @@ bool ShelfLayoutManager::MaybeStartDragWindowFromShelf(
return false;
}
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(true);
aura::Window* window =
GetWindowForDragToHomeOrOverview(event_in_screen.location());
allow_fling_from_overview_to_home_ = !window;
......@@ -2632,6 +2634,8 @@ base::Optional<ShelfWindowDragResult> ShelfLayoutManager::MaybeEndWindowDrag(
if (!IsWindowDragInProgress())
return base::nullopt;
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(false);
DCHECK_EQ(drag_status_, kDragInProgress);
base::Optional<float> velocity_y;
if (event_in_screen.type() == ui::ET_SCROLL_FLING_START) {
......@@ -2653,6 +2657,8 @@ bool ShelfLayoutManager::MaybeEndDragFromOverviewToHome(
return false;
}
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(false);
if (event_in_screen.type() != ui::ET_SCROLL_FLING_START)
return false;
......@@ -2685,6 +2691,7 @@ void ShelfLayoutManager::MaybeCancelWindowDrag() {
return;
DCHECK_EQ(drag_status_, kDragInProgress);
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(false);
window_drag_controller_->CancelDrag();
}
......
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