home_to_overview_nudge_controller.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/shelf/home_to_overview_nudge_controller.h"
  5. #include "ash/controls/contextual_nudge.h"
  6. #include "ash/controls/contextual_tooltip.h"
  7. #include "ash/public/cpp/shell_window_ids.h"
  8. #include "ash/session/session_controller_impl.h"
  9. #include "ash/shelf/hotseat_widget.h"
  10. #include "ash/shelf/scrollable_shelf_view.h"
  11. #include "ash/shelf/shelf.h"
  12. #include "ash/shell.h"
  13. #include "ash/strings/grit/ash_strings.h"
  14. #include "ash/style/ash_color_provider.h"
  15. #include "ash/wm/mru_window_tracker.h"
  16. #include "base/bind.h"
  17. #include "base/location.h"
  18. #include "base/time/time.h"
  19. #include "third_party/skia/include/core/SkColor.h"
  20. #include "ui/aura/window.h"
  21. #include "ui/base/l10n/l10n_util.h"
  22. #include "ui/compositor/layer.h"
  23. #include "ui/compositor/layer_animation_observer.h"
  24. #include "ui/compositor/layer_animator.h"
  25. #include "ui/compositor/scoped_layer_animation_settings.h"
  26. #include "ui/gfx/animation/tween.h"
  27. #include "ui/gfx/geometry/transform.h"
  28. namespace ash {
  29. namespace {
  30. // The amount of time after home shelf is shown before showing the nudge.
  31. constexpr base::TimeDelta kShowDelay = base::Seconds(2);
  32. // The duration of nudge opacity animations.
  33. constexpr base::TimeDelta kNudgeFadeDuration = base::Milliseconds(300);
  34. // The duration of the nudge opacity and transform animations when the nudge
  35. // gets hidden on user tap.
  36. constexpr base::TimeDelta kNudgeHideOnTapDuration = base::Milliseconds(150);
  37. // The duration of a single component of the nudge position animation - the
  38. // nudge is transformed vertically up and down for a preset number of
  39. // iterations.
  40. constexpr base::TimeDelta kNudgeTransformComponentDuration =
  41. base::Milliseconds(600);
  42. // The baseline vertical offset from default kShown state bounds added to
  43. // hotseat position when the nudge is shown - this is the offset that the
  44. // hotseat will have once show throb animation completes.
  45. constexpr int kHotseatBaselineNudgeOffset = -22;
  46. // The number of times the nudge should be moved up and down when the nudge is
  47. // shown.
  48. constexpr int kNudgeShowThrobIterations = 5;
  49. // The vertical max vertical ofsset from the baseline position during nudge show
  50. // animation.
  51. constexpr int kNudgeShowThrobAmplitude = 6;
  52. // The vertical distance between the nudge widget and the hotseat.
  53. constexpr int kNudgeMargins = 4;
  54. gfx::Tween::Type GetHideTransformTween(
  55. HomeToOverviewNudgeController::HideTransition transition) {
  56. switch (transition) {
  57. case HomeToOverviewNudgeController::HideTransition::kShelfStateChange:
  58. case HomeToOverviewNudgeController::HideTransition::kNudgeTimeout:
  59. return gfx::Tween::EASE_OUT_2;
  60. case HomeToOverviewNudgeController::HideTransition::kUserTap:
  61. return gfx::Tween::FAST_OUT_LINEAR_IN;
  62. }
  63. }
  64. base::TimeDelta GetHideTransformDuration(
  65. HomeToOverviewNudgeController::HideTransition transition) {
  66. switch (transition) {
  67. case HomeToOverviewNudgeController::HideTransition::kShelfStateChange:
  68. case HomeToOverviewNudgeController::HideTransition::kNudgeTimeout:
  69. return kNudgeTransformComponentDuration;
  70. case HomeToOverviewNudgeController::HideTransition::kUserTap:
  71. return kNudgeHideOnTapDuration;
  72. }
  73. }
  74. base::TimeDelta GetHideFadeDuration(
  75. HomeToOverviewNudgeController::HideTransition transition) {
  76. switch (transition) {
  77. case HomeToOverviewNudgeController::HideTransition::kShelfStateChange:
  78. return base::TimeDelta();
  79. case HomeToOverviewNudgeController::HideTransition::kUserTap:
  80. return kNudgeHideOnTapDuration;
  81. case HomeToOverviewNudgeController::HideTransition::kNudgeTimeout:
  82. return kNudgeFadeDuration;
  83. }
  84. }
  85. class ObserverToCloseWidget : public ui::ImplicitAnimationObserver {
  86. public:
  87. explicit ObserverToCloseWidget(views::Widget* widget) : widget_(widget) {}
  88. ObserverToCloseWidget(const ObserverToCloseWidget& other) = delete;
  89. ObserverToCloseWidget& operator=(const ObserverToCloseWidget& other) = delete;
  90. ~ObserverToCloseWidget() override { StopObservingImplicitAnimations(); }
  91. // ui::ImplicitAnimationObserver:
  92. void OnImplicitAnimationsCompleted() override {
  93. widget_->Close();
  94. delete this;
  95. }
  96. private:
  97. views::Widget* const widget_;
  98. };
  99. } // namespace
  100. HomeToOverviewNudgeController::HomeToOverviewNudgeController(
  101. HotseatWidget* hotseat_widget)
  102. : hotseat_widget_(hotseat_widget) {}
  103. HomeToOverviewNudgeController::~HomeToOverviewNudgeController() = default;
  104. void HomeToOverviewNudgeController::SetNudgeAllowedForCurrentShelf(
  105. bool allowed) {
  106. if (nudge_allowed_for_shelf_state_ == allowed)
  107. return;
  108. nudge_allowed_for_shelf_state_ = allowed;
  109. if (!nudge_allowed_for_shelf_state_) {
  110. nudge_show_timer_.Stop();
  111. nudge_hide_timer_.Stop();
  112. HideNudge(HideTransition::kShelfStateChange);
  113. return;
  114. }
  115. // Make sure that the overview, if opened, would show at least two app
  116. // windows.
  117. MruWindowTracker::WindowList windows =
  118. Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
  119. if (windows.size() < 2)
  120. return;
  121. DCHECK(!nudge_);
  122. PrefService* pref_service =
  123. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  124. if (!contextual_tooltip::ShouldShowNudge(
  125. pref_service, contextual_tooltip::TooltipType::kHomeToOverview,
  126. nullptr)) {
  127. return;
  128. }
  129. nudge_hide_timer_.Stop();
  130. nudge_show_timer_.Start(
  131. FROM_HERE, kShowDelay,
  132. base::BindOnce(&HomeToOverviewNudgeController::ShowNudge,
  133. base::Unretained(this)));
  134. }
  135. void HomeToOverviewNudgeController::OnWidgetDestroying(views::Widget* widget) {
  136. nudge_ = nullptr;
  137. widget_observations_.RemoveAllObservations();
  138. }
  139. void HomeToOverviewNudgeController::OnWidgetBoundsChanged(
  140. views::Widget* widget,
  141. const gfx::Rect& new_bounds) {
  142. if (widget == hotseat_widget_)
  143. UpdateNudgeAnchorBounds();
  144. }
  145. bool HomeToOverviewNudgeController::HasShowTimerForTesting() const {
  146. return nudge_show_timer_.IsRunning();
  147. }
  148. void HomeToOverviewNudgeController::FireShowTimerForTesting() {
  149. nudge_show_timer_.FireNow();
  150. }
  151. bool HomeToOverviewNudgeController::HasHideTimerForTesting() const {
  152. return nudge_hide_timer_.IsRunning();
  153. }
  154. void HomeToOverviewNudgeController::FireHideTimerForTesting() {
  155. nudge_hide_timer_.FireNow();
  156. }
  157. void HomeToOverviewNudgeController::ShowNudge() {
  158. DCHECK(!nudge_);
  159. // The nudge is effectively anchored below the hotseat widget, but the nudge
  160. // center is not generally aligned with the hotseat widget center. The nudge
  161. // should be horizontally centered in the screen, which might not be the
  162. // case for the hotseat widget bounds on home screen.
  163. // To work around this, HomeToOverviewNudgeController will update the anchor
  164. // bounds directly - see UpdateNudgeAnchorBounds().
  165. nudge_ = new ContextualNudge(
  166. nullptr, hotseat_widget_->GetNativeWindow()->parent(),
  167. ContextualNudge::Position::kBottom, gfx::Insets(kNudgeMargins),
  168. l10n_util::GetStringUTF16(IDS_ASH_HOME_TO_OVERVIEW_CONTEXTUAL_NUDGE),
  169. AshColorProvider::Get()->GetContentLayerColor(
  170. AshColorProvider::ContentLayerType::kTextColorPrimary),
  171. base::BindRepeating(&HomeToOverviewNudgeController::HandleNudgeTap,
  172. weak_factory_.GetWeakPtr()));
  173. UpdateNudgeAnchorBounds();
  174. widget_observations_.AddObservation(nudge_->GetWidget());
  175. widget_observations_.AddObservation(hotseat_widget_);
  176. nudge_->GetWidget()->Show();
  177. nudge_->GetWidget()->GetLayer()->SetTransform(gfx::Transform());
  178. nudge_->label()->layer()->SetOpacity(0.0f);
  179. hotseat_widget_->GetLayerForNudgeAnimation()->SetTransform(gfx::Transform());
  180. base::TimeDelta total_animation_duration;
  181. // Initial animation - nudge slides in form the bottom, and hotseat moves up.
  182. auto animate_initial_transform = [](ui::Layer* layer) -> base::TimeDelta {
  183. ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
  184. settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
  185. settings.SetTransitionDuration(kNudgeTransformComponentDuration);
  186. settings.SetPreemptionStrategy(
  187. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  188. gfx::Transform transform;
  189. transform.Translate(0, kHotseatBaselineNudgeOffset);
  190. layer->SetTransform(transform);
  191. return layer->GetAnimator()->GetTransitionDuration();
  192. };
  193. total_animation_duration +=
  194. animate_initial_transform(hotseat_widget_->GetLayerForNudgeAnimation());
  195. animate_initial_transform(nudge_->GetWidget()->GetLayer());
  196. // Additionally the nudge label should fade in.
  197. {
  198. ui::ScopedLayerAnimationSettings settings(
  199. nudge_->label()->layer()->GetAnimator());
  200. settings.SetTweenType(gfx::Tween::LINEAR);
  201. settings.SetTransitionDuration(kNudgeFadeDuration);
  202. settings.SetPreemptionStrategy(
  203. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  204. nudge_->label()->layer()->SetOpacity(1.0f);
  205. }
  206. auto enqueue_loop_transform = [](ui::Layer* layer,
  207. bool up) -> base::TimeDelta {
  208. ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
  209. settings.SetTweenType(gfx::Tween::EASE_IN_OUT_2);
  210. settings.SetTransitionDuration(kNudgeTransformComponentDuration);
  211. // Use enqueue preemption strategy, as the animation is expected to run
  212. // after other previously scheduled animations.
  213. settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
  214. gfx::Transform transform;
  215. transform.Translate(0, kHotseatBaselineNudgeOffset +
  216. (up ? 0 : 1) * kNudgeShowThrobAmplitude);
  217. layer->SetTransform(transform);
  218. return layer->GetAnimator()->GetTransitionDuration();
  219. };
  220. // Enqueue series of animated up-down transforms on the nudge and the hotseat.
  221. // The final position should match the position after the initial animated
  222. // transform.
  223. for (int i = 0; i < kNudgeShowThrobIterations; ++i) {
  224. total_animation_duration += enqueue_loop_transform(
  225. hotseat_widget_->GetLayerForNudgeAnimation(), false /*up*/);
  226. enqueue_loop_transform(nudge_->GetWidget()->GetLayer(), false /*up*/);
  227. total_animation_duration += enqueue_loop_transform(
  228. hotseat_widget_->GetLayerForNudgeAnimation(), true /*up*/);
  229. enqueue_loop_transform(nudge_->GetWidget()->GetLayer(), true /*up*/);
  230. }
  231. PrefService* pref_service =
  232. Shell::Get()->session_controller()->GetLastActiveUserPrefService();
  233. base::TimeDelta nudge_duration = contextual_tooltip::GetNudgeTimeout(
  234. pref_service, contextual_tooltip::TooltipType::kHomeToOverview);
  235. contextual_tooltip::HandleNudgeShown(
  236. pref_service, contextual_tooltip::TooltipType::kHomeToOverview);
  237. // If the nudge has a timeout, schedule a task to hide it. The timeout should
  238. // start when the animation sequence finishes.
  239. if (!nudge_duration.is_zero()) {
  240. nudge_hide_timer_.Start(
  241. FROM_HERE, nudge_duration + total_animation_duration,
  242. base::BindOnce(&HomeToOverviewNudgeController::HideNudge,
  243. base::Unretained(this), HideTransition::kNudgeTimeout));
  244. }
  245. }
  246. void HomeToOverviewNudgeController::HideNudge(HideTransition transition) {
  247. if (!nudge_)
  248. return;
  249. auto animate_hide_transform = [](HideTransition transition,
  250. ui::Layer* layer) {
  251. ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
  252. settings.SetTweenType(GetHideTransformTween(transition));
  253. settings.SetTransitionDuration(GetHideTransformDuration(transition));
  254. settings.SetPreemptionStrategy(
  255. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  256. layer->SetTransform(gfx::Transform());
  257. };
  258. animate_hide_transform(transition,
  259. hotseat_widget_->GetLayerForNudgeAnimation());
  260. animate_hide_transform(transition, nudge_->GetWidget()->GetLayer());
  261. {
  262. ui::ScopedLayerAnimationSettings settings(
  263. nudge_->label()->layer()->GetAnimator());
  264. settings.SetTweenType(gfx::Tween::LINEAR);
  265. settings.SetTransitionDuration(GetHideFadeDuration(transition));
  266. settings.SetPreemptionStrategy(
  267. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  268. settings.AddObserver(new ObserverToCloseWidget(nudge_->GetWidget()));
  269. nudge_->label()->layer()->SetOpacity(0.0f);
  270. }
  271. widget_observations_.RemoveAllObservations();
  272. nudge_ = nullptr;
  273. // Invalidated nudge tap handler callbacks.
  274. weak_factory_.InvalidateWeakPtrs();
  275. }
  276. void HomeToOverviewNudgeController::UpdateNudgeAnchorBounds() {
  277. // Update the nudge anchor bounds - use the hotseat bounds vertical
  278. // coordinates, so the nudge follows the vertical hotseat position, but the
  279. // shelf window horizontal coordinates to center nudge horizontally in the
  280. // shelf widget bounds (the hotseat widget parent).
  281. const gfx::Rect hotseat_bounds =
  282. hotseat_widget_->GetNativeWindow()->GetTargetBounds();
  283. const gfx::Rect shelf_bounds =
  284. hotseat_widget_->GetNativeWindow()->parent()->GetTargetBounds();
  285. nudge_->UpdateAnchorRect(
  286. gfx::Rect(gfx::Point(shelf_bounds.x(), hotseat_bounds.y()),
  287. gfx::Size(shelf_bounds.width(), hotseat_bounds.height())));
  288. }
  289. void HomeToOverviewNudgeController::HandleNudgeTap() {
  290. HideNudge(HideTransition::kUserTap);
  291. }
  292. } // namespace ash