#include "ash/wm/float/tablet_mode_tuck_education.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/rounded_label.h"
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/color/color_id.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/property_change_reason.h"
#include "ui/display/display.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/views/animation/animation_builder.h"
#include "ui/views/background.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
constexpr int kNudgeYOffset = 8;
constexpr base::TimeDelta kNudgeFadeDuration = base::Milliseconds(100);
constexpr base::TimeDelta kNudgeFadeOutDelay = base::Seconds(1);
constexpr float kBounceDistance = 50.0f;
constexpr base::TimeDelta kBounceDelay = base::Seconds(1);
constexpr base::TimeDelta kBounceStartDuration = base::Milliseconds(400);
constexpr base::TimeDelta kBounceEndDuration = base::Milliseconds(700);
constexpr int kLabelHorizontalPadding = 16;
constexpr int kLabelHeight = 36;
constexpr float kRoundedDivisor = 2.f;
constexpr int kNudgeMaxShownCount = 3;
constexpr base::TimeDelta kNudgeTimeBetweenShown = base::Hours(24);
const char kTuckEducationShownCount[] =
"ash.wm_nudge.tuck_education_nudge_count";
const char kTuckEducationLastShown[] =
"ash.wm_nudge.tuck_education_nudge_last_shown";
base::Clock* g_clock_override = nullptr;
base::Time GetTime() {
return g_clock_override ? g_clock_override->Now() : base::Time::Now();
}
std::unique_ptr<views::Widget> CreateWidget(aura::Window* window) {
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_POPUP);
params.name = "TuckEducationNudgeWidget";
params.accept_events = false;
params.parent = window;
params.child = true;
auto widget = std::make_unique<views::Widget>(std::move(params));
widget->GetLayer()->SetFillsBoundsOpaquely(false);
auto nudge_label = std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_ASH_TUCK_EDUCATIONAL_NUDGE_LABEL));
nudge_label->SetBackground(views::CreateRoundedRectBackground(
ui::kColorSysSurface3, kLabelHeight / kRoundedDivisor));
nudge_label->SetPreferredSize(gfx::Size(
nudge_label->GetPreferredSize().width() + kLabelHorizontalPadding * 2,
kLabelHeight));
widget->SetContentsView(std::move(nudge_label));
return widget;
}
}
TabletModeTuckEducation::TabletModeTuckEducation(aura::Window* floated_window) {
window_ = floated_window;
window_observation_.Observe(window_.get());
}
TabletModeTuckEducation::~TabletModeTuckEducation() = default;
void TabletModeTuckEducation::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterIntegerPref(kTuckEducationShownCount, 0);
registry->RegisterTimePref(kTuckEducationLastShown, base::Time());
}
bool TabletModeTuckEducation::CanActivateTuckEducation() {
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
CHECK(pref_service);
if (pref_service->GetInteger(kTuckEducationShownCount) >=
kNudgeMaxShownCount) {
return false;
}
if (GetTime() - pref_service->GetTime(kTuckEducationLastShown) <
kNudgeTimeBetweenShown) {
return false;
}
return true;
}
void TabletModeTuckEducation::OnWindowTucked() {
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
pref_service->SetInteger(kTuckEducationShownCount, kNudgeMaxShownCount);
}
void TabletModeTuckEducation::OnWindowTransformed(
aura::Window* window,
ui::PropertyChangeReason reason) {
bool animating = window->layer()->GetAnimator()->IsAnimatingProperty(
ui::LayerAnimationElement::TRANSFORM);
if (!nudge_widget_ && !animating &&
reason == ui::PropertyChangeReason::FROM_ANIMATION) {
ActivateTuckEducation();
}
}
void TabletModeTuckEducation::ActivateTuckEducation() {
window_observation_.Reset();
nudge_widget_ = CreateWidget(window_);
nudge_widget_->Show();
auto* nudge_layer = nudge_widget_->GetLayer();
nudge_layer->SetOpacity(0.0f);
const gfx::Size nudge_pref_size =
nudge_widget_->GetContentsView()->GetPreferredSize();
const gfx::Rect window_bounds = window_->GetBoundsInScreen();
nudge_widget_->SetBounds(gfx::Rect(
(window_bounds.width() - nudge_pref_size.width()) / 2,
window_->GetProperty(aura::client::kTopViewInset) + kNudgeYOffset,
nudge_pref_size.width(), nudge_pref_size.height()));
const display::Display display =
display::Screen::Get()->GetDisplayNearestWindow(window_);
const bool bounce_right =
window_bounds.CenterPoint().x() > display.bounds().CenterPoint().x();
const gfx::Transform side_transform = gfx::Transform::MakeTranslation(
bounce_right ? kBounceDistance : -kBounceDistance, 0);
const gfx::Transform nudge_start_transform = gfx::Transform::MakeTranslation(
0, -window_->GetProperty(aura::client::kTopViewInset));
const gfx::Transform reset_transform = gfx::Transform();
views::AnimationBuilder()
.OnAborted(base::BindOnce(&TabletModeTuckEducation::DismissNudge,
weak_factory_.GetWeakPtr()))
.OnEnded(base::BindOnce(&TabletModeTuckEducation::DismissNudge,
weak_factory_.GetWeakPtr()))
.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION)
.Once()
.SetTransform(nudge_layer, nudge_start_transform)
.Then()
.SetDuration(kNudgeFadeDuration)
.SetOpacity(nudge_layer, 1.0f, gfx::Tween::LINEAR)
.SetTransform(nudge_layer, reset_transform)
.Then()
.SetDuration(kBounceDelay)
.Then()
.SetDuration(kBounceStartDuration)
.SetTransform(window_, side_transform, gfx::Tween::ACCEL_20_DECEL_100)
.Then()
.SetDuration(kBounceEndDuration)
.SetTransform(window_, reset_transform, gfx::Tween::ACCEL_20_DECEL_100)
.Then()
.SetDuration(kBounceDelay)
.Then()
.SetDuration(kBounceStartDuration)
.SetTransform(window_, side_transform, gfx::Tween::ACCEL_20_DECEL_100)
.Then()
.SetDuration(kBounceEndDuration)
.SetTransform(window_, reset_transform, gfx::Tween::ACCEL_20_DECEL_100)
.Then()
.SetDuration(kNudgeFadeOutDelay)
.Then()
.SetDuration(kNudgeFadeDuration)
.SetOpacity(nudge_layer, 0.0f, gfx::Tween::LINEAR);
auto* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
pref_service->SetInteger(
kTuckEducationShownCount,
pref_service->GetInteger(kTuckEducationShownCount) + 1);
pref_service->SetTime(kTuckEducationLastShown, base::Time());
}
void TabletModeTuckEducation::DismissNudge() {
window_ = nullptr;
if (nudge_widget_ && !nudge_widget_->IsClosed()) {
nudge_widget_->GetLayer()->GetAnimator()->AbortAllAnimations();
nudge_widget_->CloseNow();
}
}
void TabletModeTuckEducation::SetOverrideClockForTesting(
base::Clock* test_clock) {
g_clock_override = test_clock;
}
}