chromium/ash/system/focus_mode/sounds/playlist_image_button.cc

// Copyright 2024 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/system/focus_mode/sounds/playlist_image_button.h"

#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/rounded_rect_cutout_path_builder.h"
#include "base/i18n/rtl.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/animated_image_view.h"
#include "ui/views/controls/image_view.h"

namespace ash {
namespace {

constexpr auto kCutoutSize = gfx::SizeF(28.f, 28.f);
constexpr int kCutoutInnerCornerRadius = 16;
constexpr int kCutoutOuterCornerRadius = 10;
constexpr int kSinglePlaylistViewWidth = 72;
constexpr int kIconSize = 20;
constexpr int kMediaActionIconSpacing = 6;
constexpr int kSelectedCurvycutoutSpacing = 4;

std::unique_ptr<lottie::Animation> GetEqualizerAnimation() {
  std::optional<std::vector<uint8_t>> lottie_data =
      ui::ResourceBundle::GetSharedInstance().GetLottieData(
          IDR_FOCUS_MODE_EQUALIZER_ANIMATION);
  CHECK(lottie_data.has_value());

  return std::make_unique<lottie::Animation>(
      cc::SkottieWrapper::UnsafeCreateSerializable(lottie_data.value()));
}

}  // namespace

PlaylistImageButton::PlaylistImageButton() {
  gfx::Size preferred_size(kSinglePlaylistViewWidth, kSinglePlaylistViewWidth);
  SetPreferredSize(preferred_size);

  image_view_ = AddChildView(std::make_unique<views::ImageView>());
  image_view_->SetImageSize(preferred_size);

  selected_curvycutout_icon_ =
      AddChildView(std::make_unique<views::ImageView>());
  selected_curvycutout_icon_->SetImage(ui::ImageModel::FromVectorIcon(
      kSelectedIcon, cros_tokens::kCrosSysPrimary, kIconSize));

  lottie_animation_view_ =
      AddChildView(std::make_unique<views::AnimatedImageView>());
  lottie_animation_view_->SetImageSize(gfx::Size(kIconSize, kIconSize));
  lottie_animation_view_->SetAnimatedImage(GetEqualizerAnimation());

  SetIsSelected(false);
  SetIsPlaying(false);
  SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this));
}

PlaylistImageButton::~PlaylistImageButton() = default;

views::ProposedLayout PlaylistImageButton::CalculateProposedLayout(
    const views::SizeBounds& size_bounds) const {
  views::ProposedLayout layouts;
  if (!size_bounds.is_fully_bounded()) {
    layouts.host_size = GetPreferredSize();
    return layouts;
  }
  auto bounds = GetContentsBounds();
  layouts.child_layouts.emplace_back(image_view_.get(),
                                     image_view_->GetVisible(), bounds);

  // The cutout on `image_view_` isn't flipped in RTL, so we need to make sure
  // that the animation and icon are flipped to the correct side.
  layouts.child_layouts.emplace_back(
      lottie_animation_view_.get(), lottie_animation_view_->GetVisible(),
      gfx::Rect(base::i18n::IsRTL()
                    ? kMediaActionIconSpacing
                    : bounds.right() - kIconSize - kMediaActionIconSpacing,
                bounds.bottom() - kIconSize - kMediaActionIconSpacing,
                kIconSize, kIconSize));

  layouts.child_layouts.emplace_back(
      selected_curvycutout_icon_.get(),
      selected_curvycutout_icon_->GetVisible(),
      gfx::Rect(base::i18n::IsRTL()
                    ? bounds.right() - kIconSize - kSelectedCurvycutoutSpacing
                    : kSelectedCurvycutoutSpacing,
                kSelectedCurvycutoutSpacing, kIconSize, kIconSize));
  layouts.host_size =
      gfx::Size(size_bounds.width().value(), size_bounds.height().value());
  return layouts;
}

void PlaylistImageButton::SetIsPlaying(bool is_playing) {
  is_playing_ = is_playing;
  is_playing_ ? lottie_animation_view_->Play() : lottie_animation_view_->Stop();
  lottie_animation_view_->SetVisible(is_playing_);
}

bool PlaylistImageButton::GetIsSelected() const {
  return is_selected_;
}

void PlaylistImageButton::SetIsSelected(bool is_selected) {
  if (is_selected_ == is_selected) {
    return;
  }

  is_selected_ = is_selected;
  selected_curvycutout_icon_->SetVisible(is_selected_);

  RoundedRectCutoutPathBuilder builder(
      gfx::SizeF(kSinglePlaylistViewWidth, kSinglePlaylistViewWidth));
  if (is_selected_) {
    // Add a cutout.
    builder
        .AddCutout(RoundedRectCutoutPathBuilder::Corner::kUpperLeft,
                   kCutoutSize)
        .CutoutOuterCornerRadius(kCutoutOuterCornerRadius)
        .CutoutInnerCornerRadius(kCutoutInnerCornerRadius);
  }
  image_view_->SetClipPath(builder.Build());

  // Update the accessible description for this view once the selected state
  // changed.
  GetViewAccessibility().SetDescription(l10n_util::GetStringUTF16(
      is_selected
          ? IDS_ASH_STATUS_TRAY_FOCUS_MODE_SOUNDS_PLAYLIST_SELECTED_ACCESSIBLE_DESCRIPTION
          : IDS_ASH_STATUS_TRAY_FOCUS_MODE_SOUNDS_PLAYLIST_UNSELECTED_ACCESSIBLE_DESCRIPTION));
  NotifyAccessibilityEvent(ax::mojom::Event::kStateChanged, true);

  OnPropertyChanged(&is_selected_, views::kPropertyEffectsPaint);
}

void PlaylistImageButton::UpdateContents(const gfx::ImageSkia& image) {
  image_view_->SetImage(image);
}

void PlaylistImageButton::OnSetTooltipText(const std::u16string& tooltip_text) {
  // Set the tooltip text for `image_view_` to show the tooltip when hovering on
  // it.
  image_view_->SetTooltipText(tooltip_text);
}

BEGIN_METADATA(PlaylistImageButton)
ADD_PROPERTY_METADATA(bool, IsSelected)
END_METADATA

}  // namespace ash