File: tablet_mode_tuck_education.cc

package info (click to toggle)
chromium 120.0.6099.224-1~deb11u1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,112,112 kB
  • sloc: cpp: 32,907,025; ansic: 8,148,123; javascript: 3,679,536; python: 2,031,248; asm: 959,718; java: 804,675; xml: 617,256; sh: 111,417; objc: 100,835; perl: 88,443; cs: 53,032; makefile: 29,579; fortran: 24,137; php: 21,162; tcl: 21,147; sql: 20,809; ruby: 17,735; pascal: 12,864; yacc: 8,045; lisp: 3,388; lex: 1,323; ada: 727; awk: 329; jsp: 267; csh: 117; exp: 43; sed: 37
file content (259 lines) | stat: -rw-r--r-- 9,513 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#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 {

// The vertical distance from the window header bar to the nudge.
constexpr int kNudgeYOffset = 8;
// The time it takes for the nudge to fade in or out.
constexpr base::TimeDelta kNudgeFadeDuration = base::Milliseconds(100);
// The time after the second bounce finishes until the nudge starts to fade
// out.
constexpr base::TimeDelta kNudgeFadeOutDelay = base::Seconds(1);

// The maximum distance the window translates by during the bounce.
constexpr float kBounceDistance = 50.0f;
// The delay before each bounce.
constexpr base::TimeDelta kBounceDelay = base::Seconds(1);
// The time for the window to move from its starting position to its furthest
// position.
constexpr base::TimeDelta kBounceStartDuration = base::Milliseconds(400);
// The time for the window to move from its furthest position to back to its
// original position.
constexpr base::TimeDelta kBounceEndDuration = base::Milliseconds(700);

// RoundedLabel construction values.
constexpr int kLabelHorizontalPadding = 16;
constexpr int kLabelHeight = 36;
constexpr float kRoundedDivisor = 2.f;

// The nudge will not be shown if it already been shown 3 times, or if 24 hours
// have not yet passed since it was last shown.
constexpr int kNudgeMaxShownCount = 3;
constexpr base::TimeDelta kNudgeTimeBetweenShown = base::Hours(24);

// The name of an integer pref that counts the number of times we have shown
// the nudge.
const char kTuckEducationShownCount[] =
    "ash.wm_nudge.tuck_education_nudge_count";
// The name of a time pref that stores the time we last showed the nudge.
const char kTuckEducationLastShown[] =
    "ash.wm_nudge.tuck_education_nudge_last_shown";

// Clock that can be overridden for testing.
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::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::CreateThemedRoundedRectBackground(
      ui::kColorSysSurface3, kLabelHeight / kRoundedDivisor));
  nudge_label->SetPreferredSize(gfx::Size(
      nudge_label->GetPreferredSize().width() + kLabelHorizontalPadding * 2,
      kLabelHeight));

  widget->SetContentsView(std::move(nudge_label));

  return widget;
}

}  // namespace

TabletModeTuckEducation::TabletModeTuckEducation(aura::Window* floated_window) {
  // Observe for end of floating crossfade animation to begin education.
  window_ = floated_window;
  window_observation_.Observe(window_.get());
}

TabletModeTuckEducation::~TabletModeTuckEducation() = default;

// static
void TabletModeTuckEducation::RegisterProfilePrefs(
    PrefRegistrySimple* registry) {
  registry->RegisterIntegerPref(kTuckEducationShownCount, 0);
  registry->RegisterTimePref(kTuckEducationLastShown, base::Time());
}

// static
bool TabletModeTuckEducation::CanActivateTuckEducation() {
  auto* pref_service =
      Shell::Get()->session_controller()->GetActivePrefService();
  CHECK(pref_service);
  // Nudge has already been shown three times. No need to educate anymore.
  if (pref_service->GetInteger(kTuckEducationShownCount) >=
      kNudgeMaxShownCount) {
    return false;
  }

  // Nudge has been shown within the last 24 hours already.
  if (GetTime() - pref_service->GetTime(kTuckEducationLastShown) <
      kNudgeTimeBetweenShown) {
    return false;
  }

  return true;
}

// static
void TabletModeTuckEducation::OnWindowTucked() {
  auto* pref_service =
      Shell::Get()->session_controller()->GetActivePrefService();
  // Set the count to the maximum value so the education doesn't show anymore.
  pref_service->SetInteger(kTuckEducationShownCount, kNudgeMaxShownCount);
}

void TabletModeTuckEducation::OnWindowTransformed(
    aura::Window* window,
    ui::PropertyChangeReason reason) {
  // Floating a window causes a crossfade animation that can interfere with
  // other animations or actions performed on the window at the same time. We
  // observe for the crossfade to finish before performing the bounce.
  bool animating = window->layer()->GetAnimator()->IsAnimatingProperty(
      ui::LayerAnimationElement::TRANSFORM);
  if (!nudge_widget_ && !animating &&
      reason == ui::PropertyChangeReason::FROM_ANIMATION) {
    ActivateTuckEducation();
  }
}

void TabletModeTuckEducation::ActivateTuckEducation() {
  // No need to observe for the transform animation (crossfade) to finish as
  // soon as it has happened once.
  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()));

  // Move window towards edge of screen.
  const display::Display display =
      display::Screen::GetScreen()->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);

  // Start nudge at the top of the window (not animated).
  const gfx::Transform nudge_start_transform = gfx::Transform::MakeTranslation(
      0, -window_->GetProperty(aura::client::kTopViewInset));

  // Move the window back to its starting position, and the nudge to just below
  // the header bar.
  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)
      // Set initial nudge position at the top of the window.
      .Once()
      .SetTransform(nudge_layer, nudge_start_transform)
      // Fade nudge in and drop down to actual position.
      .Then()
      .SetDuration(kNudgeFadeDuration)
      .SetOpacity(nudge_layer, 1.0f, gfx::Tween::LINEAR)
      .SetTransform(nudge_layer, reset_transform)
      // Delay before first bounce.
      .Then()
      .SetDuration(kBounceDelay)
      // First bounce.
      .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)
      // Delay before second bounce.
      .Then()
      .SetDuration(kBounceDelay)
      // Second bounce.
      .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)
      // Delay before fading out.
      .Then()
      .SetDuration(kNudgeFadeOutDelay)
      // Fade nudge out.
      .Then()
      .SetDuration(kNudgeFadeDuration)
      .SetOpacity(nudge_layer, 0.0f, gfx::Tween::LINEAR);

  // Update the preferences.
  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();
  }
}

// static
void TabletModeTuckEducation::SetOverrideClockForTesting(
    base::Clock* test_clock) {
  g_clock_override = test_clock;
}

}  // namespace ash