ash_notification_expand_button.cc 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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/system/message_center/ash_notification_expand_button.h"
  5. #include "ash/public/cpp/metrics_util.h"
  6. #include "ash/resources/vector_icons/vector_icons.h"
  7. #include "ash/strings/grit/ash_strings.h"
  8. #include "ash/style/ash_color_provider.h"
  9. #include "ash/system/message_center/message_center_constants.h"
  10. #include "ash/system/message_center/message_center_utils.h"
  11. #include "ash/system/tray/tray_popup_utils.h"
  12. #include "base/metrics/histogram_functions.h"
  13. #include "ui/base/l10n/l10n_util.h"
  14. #include "ui/base/metadata/metadata_impl_macros.h"
  15. #include "ui/color/color_id.h"
  16. #include "ui/compositor/animation_throughput_reporter.h"
  17. #include "ui/compositor/layer.h"
  18. #include "ui/gfx/animation/tween.h"
  19. #include "ui/gfx/geometry/rect.h"
  20. #include "ui/gfx/image/image_skia_operations.h"
  21. #include "ui/gfx/paint_vector_icon.h"
  22. #include "ui/gfx/text_constants.h"
  23. #include "ui/views/animation/animation_builder.h"
  24. #include "ui/views/animation/animation_sequence_block.h"
  25. #include "ui/views/controls/highlight_path_generator.h"
  26. #include "ui/views/controls/image_view.h"
  27. #include "ui/views/controls/label.h"
  28. #include "ui/views/layout/box_layout.h"
  29. #include "ui/views/layout/layout_types.h"
  30. #include "ui/views/view_class_properties.h"
  31. namespace ash {
  32. BEGIN_METADATA(AshNotificationExpandButton, views::Button)
  33. END_METADATA
  34. AshNotificationExpandButton::AshNotificationExpandButton(
  35. PressedCallback callback)
  36. : Button(std::move(callback)) {
  37. SetLayoutManager(std::make_unique<views::BoxLayout>(
  38. views::BoxLayout::Orientation::kHorizontal));
  39. auto label = std::make_unique<views::Label>();
  40. label->SetFontList(gfx::FontList({kGoogleSansFont}, gfx::Font::NORMAL,
  41. kNotificationExpandButtonLabelFontSize,
  42. gfx::Font::Weight::MEDIUM));
  43. label->SetProperty(views::kMarginsKey, kNotificationExpandButtonLabelInsets);
  44. label->SetElideBehavior(gfx::ElideBehavior::NO_ELIDE);
  45. label->SetText(base::NumberToString16(total_grouped_notifications_));
  46. label->SetVisible(ShouldShowLabel());
  47. label_ = AddChildView(std::move(label));
  48. UpdateIcons();
  49. auto image = std::make_unique<views::ImageView>();
  50. image->SetProperty(views::kMarginsKey, kNotificationExpandButtonImageInsets);
  51. image->SetImage(expanded_ ? expanded_image_ : collapsed_image_);
  52. image_ = AddChildView(std::move(image));
  53. views::InstallRoundRectHighlightPathGenerator(
  54. this, kNotificationExpandButtonFocusInsets,
  55. kNotificationExpandButtonCornerRadius);
  56. SetAccessibleName(l10n_util::GetStringUTF16(
  57. expanded_ ? IDS_ASH_NOTIFICATION_COLLAPSE_TOOLTIP
  58. : IDS_ASH_NOTIFICATION_EXPAND_TOOLTIP));
  59. message_center_utils::InitLayerForAnimations(label_);
  60. message_center_utils::InitLayerForAnimations(image_);
  61. views::FocusRing::Get(this)->SetColorId(ui::kColorAshFocusRing);
  62. SetPaintToLayer(ui::LAYER_SOLID_COLOR);
  63. layer()->SetFillsBoundsOpaquely(false);
  64. layer()->SetRoundedCornerRadius(gfx::RoundedCornersF{kTrayItemCornerRadius});
  65. layer()->SetIsFastRoundedCorner(true);
  66. }
  67. AshNotificationExpandButton::~AshNotificationExpandButton() = default;
  68. void AshNotificationExpandButton::SetExpanded(bool expanded) {
  69. if (expanded_ == expanded)
  70. return;
  71. previous_bounds_ = GetContentsBounds();
  72. expanded_ = expanded;
  73. label_->SetText(base::NumberToString16(total_grouped_notifications_));
  74. label_->SetVisible(ShouldShowLabel());
  75. image_->SetImage(expanded_ ? expanded_image_ : collapsed_image_);
  76. image_->SetTooltipText(l10n_util::GetStringUTF16(
  77. expanded_ ? IDS_ASH_NOTIFICATION_COLLAPSE_TOOLTIP
  78. : IDS_ASH_NOTIFICATION_EXPAND_TOOLTIP));
  79. SetAccessibleName(l10n_util::GetStringUTF16(
  80. expanded_ ? IDS_ASH_NOTIFICATION_COLLAPSE_TOOLTIP
  81. : IDS_ASH_NOTIFICATION_EXPAND_TOOLTIP));
  82. }
  83. bool AshNotificationExpandButton::ShouldShowLabel() const {
  84. return !expanded_ && total_grouped_notifications_;
  85. }
  86. void AshNotificationExpandButton::UpdateGroupedNotificationsCount(int count) {
  87. total_grouped_notifications_ = count;
  88. label_->SetText(base::NumberToString16(total_grouped_notifications_));
  89. label_->SetVisible(ShouldShowLabel());
  90. }
  91. void AshNotificationExpandButton::UpdateIcons() {
  92. expanded_image_ = gfx::CreateVectorIcon(
  93. kUnifiedMenuExpandIcon, kNotificationExpandButtonChevronIconSize,
  94. AshColorProvider::Get()->GetContentLayerColor(
  95. AshColorProvider::ContentLayerType::kIconColorPrimary));
  96. collapsed_image_ = gfx::ImageSkiaOperations::CreateRotatedImage(
  97. gfx::CreateVectorIcon(
  98. kUnifiedMenuExpandIcon, kNotificationExpandButtonChevronIconSize,
  99. AshColorProvider::Get()->GetContentLayerColor(
  100. AshColorProvider::ContentLayerType::kIconColorPrimary)),
  101. SkBitmapOperations::ROTATION_180_CW);
  102. }
  103. void AshNotificationExpandButton::AnimateExpandCollapse() {
  104. // If the button is not used for grouped notification, there's no animation to
  105. // perform here.
  106. if (!total_grouped_notifications_)
  107. return;
  108. int bounds_animation_duration;
  109. gfx::Tween::Type bounds_animation_tween_type;
  110. if (label_->GetVisible()) {
  111. if (label_->layer()->GetAnimator()->is_animating()) {
  112. // Label's fade out animation might still be running. If that's the case,
  113. // we need to abort this and reset visibility for fade in animation.
  114. label_->layer()->GetAnimator()->AbortAllAnimations();
  115. label_->SetVisible(true);
  116. }
  117. // Fade in animation when label is visible.
  118. message_center_utils::FadeInView(
  119. label_, kExpandButtonFadeInLabelDelayMs,
  120. kExpandButtonFadeInLabelDurationMs, gfx::Tween::LINEAR,
  121. "Ash.NotificationView.ExpandButtonLabel.FadeIn.AnimationSmoothness");
  122. bounds_animation_duration = kExpandButtonShowLabelBoundsChangeDurationMs;
  123. bounds_animation_tween_type = gfx::Tween::LINEAR_OUT_SLOW_IN;
  124. } else {
  125. // In this case, `total_grouped_notifications_` is not zero and label is not
  126. // visible. This means the label switch from visible to invisible and we
  127. // should do fade out animation.
  128. label_fading_out_ = true;
  129. message_center_utils::FadeOutView(
  130. label_,
  131. base::BindRepeating(
  132. [](base::WeakPtr<AshNotificationExpandButton> parent,
  133. views::Label* label) {
  134. if (parent) {
  135. label->layer()->SetOpacity(1.0f);
  136. label->SetVisible(false);
  137. parent->set_label_fading_out(false);
  138. }
  139. },
  140. weak_factory_.GetWeakPtr(), label_),
  141. 0, kExpandButtonFadeOutLabelDurationMs, gfx::Tween::LINEAR,
  142. "Ash.NotificationView.ExpandButtonLabel.FadeOut.AnimationSmoothness");
  143. bounds_animation_duration = kExpandButtonHideLabelBoundsChangeDurationMs;
  144. bounds_animation_tween_type = gfx::Tween::ACCEL_20_DECEL_100;
  145. }
  146. AnimateBoundsChange(
  147. bounds_animation_duration, bounds_animation_tween_type,
  148. "Ash.NotificationView.ExpandButton.BoundsChange.AnimationSmoothness");
  149. }
  150. void AshNotificationExpandButton::AnimateSingleToGroupNotification() {
  151. message_center_utils::FadeInView(
  152. label_, /*delay_in_ms=*/0, kConvertFromSingleToGroupFadeInDurationMs,
  153. gfx::Tween::LINEAR,
  154. "Ash.NotificationView.ExpandButton.ConvertSingleToGroup.FadeIn."
  155. "AnimationSmoothness");
  156. AnimateBoundsChange(
  157. kConvertFromSingleToGroupBoundsChangeDurationMs,
  158. gfx::Tween::ACCEL_20_DECEL_100,
  159. "Ash.NotificationView.ExpandButton.ConvertSingleToGroup.BoundsChange."
  160. "AnimationSmoothness");
  161. }
  162. void AshNotificationExpandButton::OnThemeChanged() {
  163. views::Button::OnThemeChanged();
  164. UpdateIcons();
  165. image_->SetImage(expanded_ ? expanded_image_ : collapsed_image_);
  166. SkColor background_color = AshColorProvider::Get()->GetControlsLayerColor(
  167. AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive);
  168. layer()->SetColor(background_color);
  169. }
  170. gfx::Size AshNotificationExpandButton::CalculatePreferredSize() const {
  171. gfx::Size size = Button::CalculatePreferredSize();
  172. // When label is fading out, it is still visible but we should not consider
  173. // its size in our calculation here, so that size change animation can be
  174. // performed correctly.
  175. if (label_fading_out_) {
  176. return gfx::Size(size.width() - label_->GetPreferredSize().width() -
  177. kNotificationExpandButtonLabelInsets.width(),
  178. size.height());
  179. }
  180. return size;
  181. }
  182. void AshNotificationExpandButton::AnimateBoundsChange(
  183. int duration_in_ms,
  184. gfx::Tween::Type tween_type,
  185. const std::string& animation_histogram_name) {
  186. // Perform size change animation with layer bounds animation, setting the
  187. // bounds to its previous state and then animating to current state. At the
  188. // same time, we move `image_` in the opposite direction so that it appears to
  189. // stay in the same location when the parent's bounds is moving.
  190. const gfx::Rect target_bounds = layer()->GetTargetBounds();
  191. const gfx::Rect image_target_bounds = image_->layer()->GetTargetBounds();
  192. // This value is used to add extra width to the view's bounds. We will animate
  193. // the view with this extra width to its target state.
  194. int extra_width = previous_bounds_.width() - target_bounds.width();
  195. ui::AnimationThroughputReporter reporter(
  196. layer()->GetAnimator(),
  197. metrics_util::ForSmoothness(base::BindRepeating(
  198. [](const std::string& animation_histogram_name, int smoothness) {
  199. base::UmaHistogramPercentage(animation_histogram_name, smoothness);
  200. },
  201. animation_histogram_name)));
  202. layer()->SetBounds(
  203. gfx::Rect(target_bounds.x() - extra_width, target_bounds.y(),
  204. target_bounds.width() + extra_width, target_bounds.height()));
  205. image_->layer()->SetBounds(
  206. gfx::Rect(image_target_bounds.x() + extra_width, image_target_bounds.y(),
  207. image_target_bounds.width(), image_target_bounds.height()));
  208. views::AnimationBuilder()
  209. .Once()
  210. .SetDuration(base::Milliseconds(duration_in_ms))
  211. .SetBounds(this, target_bounds, tween_type)
  212. .SetBounds(image_, image_target_bounds, tween_type);
  213. }
  214. } // namespace ash