user_nudge_controller.cc 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright 2021 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/capture_mode/user_nudge_controller.h"
  5. #include "ash/capture_mode/capture_mode_controller.h"
  6. #include "ash/capture_mode/capture_mode_session.h"
  7. #include "ash/capture_mode/capture_mode_util.h"
  8. #include "ash/public/cpp/shell_window_ids.h"
  9. #include "ash/style/dark_light_mode_controller_impl.h"
  10. #include "base/bind.h"
  11. #include "base/check.h"
  12. #include "base/time/time.h"
  13. #include "ui/aura/window.h"
  14. #include "ui/gfx/geometry/transform.h"
  15. #include "ui/gfx/geometry/transform_util.h"
  16. #include "ui/views/animation/animation_builder.h"
  17. #include "ui/views/animation/animation_sequence_block.h"
  18. namespace ash {
  19. namespace {
  20. constexpr float kBaseRingOpacity = 0.21f;
  21. constexpr float kRippleRingOpacity = 0.5f;
  22. constexpr float kBaseRingScaleUpFactor = 1.1f;
  23. constexpr float kRippleRingScaleUpFactor = 3.0f;
  24. constexpr float kHighlightedViewScaleUpFactor = 1.2f;
  25. constexpr base::TimeDelta kVisibilityChangeDuration = base::Milliseconds(200);
  26. constexpr base::TimeDelta kScaleUpDuration = base::Milliseconds(500);
  27. constexpr base::TimeDelta kScaleDownDelay = base::Milliseconds(650);
  28. constexpr base::TimeDelta kScaleDownOffset = kScaleUpDuration + kScaleDownDelay;
  29. constexpr base::TimeDelta kScaleDownDuration = base::Milliseconds(1350);
  30. constexpr base::TimeDelta kRippleAnimationDuration = base::Milliseconds(2000);
  31. constexpr base::TimeDelta kDelayToShowNudge = base::Milliseconds(1000);
  32. constexpr base::TimeDelta kDelayToRepeatNudge = base::Milliseconds(2500);
  33. // Returns the given `view`'s layer bounds in root coordinates ignoring any
  34. // transforms it or any of its ancestors may have.
  35. gfx::Rect GetViewLayerBoundsInRootNoTransform(views::View* view) {
  36. auto* layer = view->layer();
  37. DCHECK(layer);
  38. gfx::Point origin;
  39. while (layer) {
  40. const auto layer_origin = layer->bounds().origin();
  41. origin.Offset(layer_origin.x(), layer_origin.y());
  42. layer = layer->parent();
  43. }
  44. return gfx::Rect(origin, view->layer()->size());
  45. }
  46. } // namespace
  47. UserNudgeController::UserNudgeController(CaptureModeSession* session,
  48. views::View* view_to_be_highlighted)
  49. : capture_session_(session),
  50. view_to_be_highlighted_(view_to_be_highlighted) {
  51. view_to_be_highlighted_->SetPaintToLayer();
  52. view_to_be_highlighted_->layer()->SetFillsBoundsOpaquely(false);
  53. // Rings are created initially with 0 opacity. Calling SetVisible() will
  54. // animate them towards their correct state.
  55. const SkColor ring_color =
  56. DarkLightModeControllerImpl::Get()->IsDarkModeEnabled() ? SK_ColorWHITE
  57. : SK_ColorBLACK;
  58. base_ring_.SetColor(ring_color);
  59. base_ring_.SetFillsBoundsOpaquely(false);
  60. base_ring_.SetOpacity(0);
  61. ripple_ring_.SetColor(ring_color);
  62. ripple_ring_.SetFillsBoundsOpaquely(false);
  63. ripple_ring_.SetOpacity(0);
  64. Reposition();
  65. }
  66. UserNudgeController::~UserNudgeController() {
  67. if (should_dismiss_nudge_forever_)
  68. CaptureModeController::Get()->DisableUserNudgeForever();
  69. capture_session_->capture_toast_controller()->MaybeDismissCaptureToast(
  70. CaptureToastType::kUserNudge,
  71. /*animate=*/false);
  72. }
  73. void UserNudgeController::Reposition() {
  74. auto* parent_window = GetParentWindow();
  75. auto* parent_layer = parent_window->layer();
  76. if (parent_layer != base_ring_.parent()) {
  77. parent_layer->Add(&base_ring_);
  78. parent_layer->Add(&ripple_ring_);
  79. }
  80. const auto view_bounds_in_root =
  81. GetViewLayerBoundsInRootNoTransform(view_to_be_highlighted_);
  82. base_ring_.SetBounds(view_bounds_in_root);
  83. base_ring_.SetRoundedCornerRadius(
  84. gfx::RoundedCornersF(view_bounds_in_root.width() / 2.f));
  85. ripple_ring_.SetBounds(view_bounds_in_root);
  86. ripple_ring_.SetRoundedCornerRadius(
  87. gfx::RoundedCornersF(view_bounds_in_root.width() / 2.f));
  88. }
  89. void UserNudgeController::SetVisible(bool visible) {
  90. if (is_visible_ == visible)
  91. return;
  92. is_visible_ = visible;
  93. auto* capture_toast_controller = capture_session_->capture_toast_controller();
  94. views::AnimationBuilder builder;
  95. builder.SetPreemptionStrategy(
  96. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
  97. if (!is_visible_) {
  98. // We should no longer repeat the nudge animation.
  99. timer_.Stop();
  100. // We should also stop any ongoing animation on the `base_ring_` in
  101. // particular since we observe this animation ending to schedule a repeat.
  102. // See OnBaseRingAnimationEnded().
  103. base_ring_.GetAnimator()->AbortAllAnimations();
  104. // Animate all animation layers and the toast widget to 0 opacity.
  105. builder.Once()
  106. .SetDuration(kVisibilityChangeDuration)
  107. .SetOpacity(&base_ring_, 0, gfx::Tween::FAST_OUT_SLOW_IN)
  108. .SetOpacity(&ripple_ring_, 0, gfx::Tween::FAST_OUT_SLOW_IN);
  109. capture_toast_controller->MaybeDismissCaptureToast(
  110. CaptureToastType::kUserNudge);
  111. return;
  112. }
  113. // Animate the `base_ring_` and the `toast_widget_` to their default shown
  114. // opacity. Note that we don't need to show the `ripple_ring_` since it only
  115. // shows as the nudge animation is being performed.
  116. // Once those elements reach their default shown opacity, we perform the nudge
  117. // animation.
  118. builder
  119. .OnEnded(base::BindOnce(&UserNudgeController::PerformNudgeAnimations,
  120. weak_ptr_factory_.GetWeakPtr()))
  121. .Once()
  122. .SetDuration(kDelayToShowNudge)
  123. .SetOpacity(&base_ring_, kBaseRingOpacity, gfx::Tween::FAST_OUT_SLOW_IN);
  124. capture_toast_controller->ShowCaptureToast(CaptureToastType::kUserNudge);
  125. }
  126. void UserNudgeController::PerformNudgeAnimations() {
  127. PerformBaseRingAnimation();
  128. PerformRippleRingAnimation();
  129. PerformViewScaleAnimation();
  130. }
  131. void UserNudgeController::PerformBaseRingAnimation() {
  132. // The `base_ring_` should scale up around the center of the
  133. // `view_to_be_highlighted_` to grab the user's attention, and then scales
  134. // back down to its original size.
  135. const gfx::Transform scale_up_transform =
  136. capture_mode_util::GetScaleTransformAboutCenter(&base_ring_,
  137. kBaseRingScaleUpFactor);
  138. views::AnimationBuilder()
  139. .SetPreemptionStrategy(
  140. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  141. .OnEnded(base::BindOnce(&UserNudgeController::OnBaseRingAnimationEnded,
  142. base::Unretained(this)))
  143. .Once()
  144. .SetDuration(kScaleUpDuration)
  145. .SetTransform(&base_ring_, scale_up_transform,
  146. gfx::Tween::ACCEL_40_DECEL_20)
  147. .Offset(kScaleDownOffset)
  148. .SetDuration(kScaleDownDuration)
  149. .SetTransform(&base_ring_, gfx::Transform(),
  150. gfx::Tween::FAST_OUT_SLOW_IN_3);
  151. }
  152. void UserNudgeController::PerformRippleRingAnimation() {
  153. // The ripple scales up to 3x the size of the `view_to_be_highlighted_` and
  154. // around its center while fading out.
  155. ripple_ring_.SetOpacity(kRippleRingOpacity);
  156. ripple_ring_.SetTransform(gfx::Transform());
  157. const gfx::Transform scale_up_transform =
  158. capture_mode_util::GetScaleTransformAboutCenter(&ripple_ring_,
  159. kRippleRingScaleUpFactor);
  160. views::AnimationBuilder()
  161. .SetPreemptionStrategy(
  162. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  163. .Once()
  164. .SetDuration(kRippleAnimationDuration)
  165. .SetOpacity(&ripple_ring_, 0, gfx::Tween::ACCEL_0_80_DECEL_80)
  166. .SetTransform(&ripple_ring_, scale_up_transform,
  167. gfx::Tween::ACCEL_0_40_DECEL_100);
  168. }
  169. void UserNudgeController::PerformViewScaleAnimation() {
  170. // The `view_to_be_highlighted_` scales up and down around its own center in
  171. // a similar fashion to that of the `base_ring_`.
  172. auto* view_layer = view_to_be_highlighted_->layer();
  173. const gfx::Transform scale_up_transform =
  174. capture_mode_util::GetScaleTransformAboutCenter(
  175. view_layer, kHighlightedViewScaleUpFactor);
  176. views::AnimationBuilder()
  177. .SetPreemptionStrategy(
  178. ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
  179. .Once()
  180. .SetDuration(kScaleUpDuration)
  181. .SetTransform(view_layer, scale_up_transform,
  182. gfx::Tween::ACCEL_40_DECEL_20)
  183. .Offset(kScaleDownOffset)
  184. .SetDuration(kScaleDownDuration)
  185. .SetTransform(view_layer, gfx::Transform(),
  186. gfx::Tween::FAST_OUT_SLOW_IN_3);
  187. }
  188. void UserNudgeController::OnBaseRingAnimationEnded() {
  189. timer_.Start(FROM_HERE, kDelayToRepeatNudge,
  190. base::BindOnce(&UserNudgeController::PerformNudgeAnimations,
  191. weak_ptr_factory_.GetWeakPtr()));
  192. }
  193. aura::Window* UserNudgeController::GetParentWindow() const {
  194. auto* root_window =
  195. view_to_be_highlighted_->GetWidget()->GetNativeWindow()->GetRootWindow();
  196. DCHECK(root_window);
  197. return root_window->GetChildById(kShellWindowId_OverlayContainer);
  198. }
  199. } // namespace ash