blob: 95d990786f5dc1fb1631db443fbd0c34c329d917 [file] [log] [blame]
// Copyright 2020 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 "ash/shelf/login_shelf_gesture_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/contextual_nudge.h"
#include "ash/shelf/drag_handle.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/color_palette.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// The upward velocity threshold for the swipe up from the login shelf to be
// reported as fling gesture.
constexpr float kVelocityToHomeScreenThreshold = 1000.f;
// The delay between the time the login shelf gesture nudge is shown, and the
// time it starts animating.
constexpr base::TimeDelta kNudgeAnimationEntranceDelay =
base::TimeDelta::FromMilliseconds(500);
// The duration of different parts of the nudge animation.
constexpr base::TimeDelta kNudgeAnimationStageDuration =
base::TimeDelta::FromMilliseconds(600);
// The duration of the animation that moves the drag handle and the contextual
// nudge to their initial position when the user cancels the nudge animation by
// tapping the contextual nudge.
constexpr base::TimeDelta kNudgeStopAnimationDuration =
base::TimeDelta::FromMilliseconds(150);
// The interval between the end of one nudge animation sequence, and the start
// of the next nudge animation sequence.
constexpr base::TimeDelta kAnimationInterval = base::TimeDelta::FromSeconds(5);
// The offset drag handle and nudge widget have from the default position during
// the nudge animation sequence.
constexpr int kNudgeAnimationBaseOffset = -8;
// The number of times drag handle is moved up and down during single nudge
// animation cycle.
constexpr int kNudgeAnimationThrobIntervals = 3;
// The maximal offset drag handle has from the base position during throb
// section of the nudge animation.
constexpr int kNudgeAnimationThrobAmplitude = 6;
// Implicit animation observer that runs a callback once the animations
// complete, and then deletes itself.
class ImplicitAnimationCallbackRunner : public ui::ImplicitAnimationObserver {
public:
explicit ImplicitAnimationCallbackRunner(base::OnceClosure callback)
: callback_(std::move(callback)) {}
~ImplicitAnimationCallbackRunner() override {
StopObservingImplicitAnimations();
}
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override {
StopObservingImplicitAnimations();
std::move(callback_).Run();
delete this;
}
private:
base::OnceClosure callback_;
};
} // namespace
LoginShelfGestureController::LoginShelfGestureController(
Shelf* shelf,
DragHandle* drag_handle,
const base::string16& gesture_nudge,
const base::RepeatingClosure fling_handler,
base::OnceClosure exit_handler)
: shelf_(shelf),
drag_handle_(drag_handle),
fling_handler_(fling_handler),
exit_handler_(std::move(exit_handler)) {
DCHECK(fling_handler_);
DCHECK(exit_handler_);
const bool is_oobe = Shell::Get()->session_controller()->GetSessionState() ==
session_manager::SessionState::OOBE;
const SkColor nudge_text_color =
is_oobe ? gfx::kGoogleGrey700 : gfx::kGoogleGrey100;
nudge_ = new ContextualNudge(
drag_handle, nullptr /*parent_window*/, ContextualNudge::Position::kTop,
gfx::Insets(8), gesture_nudge, nudge_text_color,
base::BindRepeating(&LoginShelfGestureController::HandleNudgeTap,
weak_factory_.GetWeakPtr()));
nudge_->GetWidget()->Show();
nudge_->GetWidget()->AddObserver(this);
ScheduleNudgeAnimation(kNudgeAnimationEntranceDelay);
}
LoginShelfGestureController::~LoginShelfGestureController() {
if (nudge_) {
nudge_->GetWidget()->RemoveObserver(this);
nudge_->GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kUnspecified);
}
nudge_ = nullptr;
std::move(exit_handler_).Run();
CHECK(!IsInObserverList());
}
bool LoginShelfGestureController::HandleGestureEvent(
const ui::GestureEvent& event_in_screen) {
if (event_in_screen.type() == ui::ET_GESTURE_SCROLL_BEGIN)
return MaybeStartGestureDrag(event_in_screen);
// If the previous events in the gesture sequence did not start handling the
// gesture, try again.
if (event_in_screen.type() == ui::ET_GESTURE_SCROLL_UPDATE)
return active_ || MaybeStartGestureDrag(event_in_screen);
if (!active_)
return false;
if (event_in_screen.type() == ui::ET_SCROLL_FLING_START) {
EndDrag(event_in_screen);
return true;
}
// Ending non-fling gesture, or unexpected event (if different than
// SCROLL_END), mark the controller as inactive, but report the event as
// handled in the former case only.
active_ = false;
return event_in_screen.type() == ui::ET_GESTURE_SCROLL_END;
}
void LoginShelfGestureController::OnWidgetDestroying(views::Widget* widget) {
nudge_ = nullptr;
nudge_animation_timer_.Stop();
}
bool LoginShelfGestureController::MaybeStartGestureDrag(
const ui::GestureEvent& event_in_screen) {
DCHECK(event_in_screen.type() == ui::ET_GESTURE_SCROLL_BEGIN ||
event_in_screen.type() == ui::ET_GESTURE_SCROLL_UPDATE);
// Ignore downward swipe for scroll begin.
if (event_in_screen.type() == ui::ET_GESTURE_SCROLL_BEGIN &&
event_in_screen.details().scroll_y_hint() >= 0) {
return false;
}
// Ignore downward swipe for scroll update.
if (event_in_screen.type() == ui::ET_GESTURE_SCROLL_UPDATE &&
event_in_screen.details().scroll_y() >= 0) {
return false;
}
// Ignore swipes that are outside of the shelf bounds.
if (event_in_screen.location().y() <
shelf_->shelf_widget()->GetWindowBoundsInScreen().y()) {
return false;
}
active_ = true;
return true;
}
void LoginShelfGestureController::EndDrag(
const ui::GestureEvent& event_in_screen) {
DCHECK_EQ(event_in_screen.type(), ui::ET_SCROLL_FLING_START);
active_ = false;
// If the drag ends below the shelf, do not go to home screen (theoratically
// it may happen in kExtended hotseat case when drag can start and end below
// the shelf).
if (event_in_screen.location().y() >=
shelf_->shelf_widget()->GetWindowBoundsInScreen().y()) {
return;
}
const int velocity_y = event_in_screen.details().velocity_y();
if (velocity_y > -kVelocityToHomeScreenThreshold)
return;
fling_handler_.Run();
}
void LoginShelfGestureController::ScheduleNudgeAnimation(
base::TimeDelta delay) {
if (!nudge_ || animation_stopped_)
return;
nudge_animation_timer_.Start(
FROM_HERE, delay,
base::BindOnce(
&LoginShelfGestureController::RunNudgeAnimation,
base::Unretained(this),
base::BindOnce(&LoginShelfGestureController::ScheduleNudgeAnimation,
weak_factory_.GetWeakPtr(), kAnimationInterval)));
}
void LoginShelfGestureController::RunNudgeAnimation(
base::OnceClosure callback) {
auto animate_entrance = [](ui::Layer* layer) {
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
settings.SetTransitionDuration(kNudgeAnimationStageDuration);
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
gfx::Transform transform;
transform.Translate(0, kNudgeAnimationBaseOffset);
layer->SetTransform(transform);
};
animate_entrance(nudge_->GetWidget()->GetLayer());
animate_entrance(drag_handle_->layer());
auto animate_throb = [](ui::Layer* layer, bool down) {
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetTweenType(gfx::Tween::EASE_IN_OUT_2);
settings.SetTransitionDuration(kNudgeAnimationStageDuration);
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
gfx::Transform transform;
transform.Translate(0, kNudgeAnimationBaseOffset +
kNudgeAnimationThrobAmplitude * (down ? 1 : 0));
layer->SetTransform(transform);
};
for (int i = 0; i < kNudgeAnimationThrobIntervals; ++i) {
animate_throb(drag_handle_->layer(), /*down=*/true);
animate_throb(drag_handle_->layer(), /*down=*/false);
// Keep the animation going for the nudge, even though it's kept in place
// The primary goal is to "pause" the animation while drag handle is
// throbbing, and prevent the last animation stage from starting too soon.
animate_throb(nudge_->GetWidget()->GetLayer(), /*down=*/false);
animate_throb(nudge_->GetWidget()->GetLayer(), /*down=*/false);
}
auto animate_exit = [](ui::Layer* layer, base::OnceClosure callback) {
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetTweenType(gfx::Tween::EASE_IN_OUT_2);
settings.SetTransitionDuration(kNudgeAnimationStageDuration);
settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
if (callback) {
settings.AddObserver(
new ImplicitAnimationCallbackRunner(std::move(callback)));
}
layer->SetTransform(gfx::Transform());
};
animate_exit(nudge_->GetWidget()->GetLayer(), base::OnceClosure());
animate_exit(drag_handle_->layer(), std::move(callback));
}
void LoginShelfGestureController::HandleNudgeTap() {
if (animation_stopped_)
return;
animation_stopped_ = true;
nudge_animation_timer_.Stop();
auto animate_exit = [](ui::Layer* layer) {
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.SetTweenType(gfx::Tween::FAST_OUT_LINEAR_IN);
settings.SetTransitionDuration(kNudgeStopAnimationDuration);
settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
layer->SetTransform(gfx::Transform());
};
animate_exit(nudge_->GetWidget()->GetLayer());
animate_exit(drag_handle_->layer());
}
} // namespace ash