// 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/game_dashboard/game_dashboard_button.h"
#include <memory>
#include <string>
#include <utility>
#include "ash/bubble/bubble_utils.h"
#include "ash/game_dashboard/game_dashboard_context.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/dark_light_mode_controller_impl.h"
#include "ash/style/rounded_container.h"
#include "ash/style/style_util.h"
#include "ash/style/typography.h"
#include "base/check.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/ui/frame/frame_header.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
constexpr float kContainerCornerRadius = 13.0f;
constexpr int kBorderThickness = 1;
constexpr int kIconHeight = 20;
constexpr gfx::Insets kArrowMargins = gfx::Insets::TLBR(0, 6, 0, 0);
constexpr gfx::Insets kButtonBorderInsets = gfx::Insets::TLBR(0, 12, 0, 8);
constexpr gfx::Insets kGamepadIconMargins = gfx::Insets::TLBR(0, 0, 0, 8);
// 8% opacity for button border.
constexpr SkAlpha kAlphaForButtonBorder =
base::saturated_cast<SkAlpha>(std::numeric_limits<SkAlpha>::max() * 0.08);
ui::ColorId GetBackgroundEnabledColorId(bool is_recording) {
return is_recording ? cros_tokens::kCrosSysSystemNegativeContainer
: cros_tokens::kCrosSysPrimaryContainer;
}
ui::ColorId GetIconAndLabelEnabledColorId(bool is_recording) {
return is_recording ? cros_tokens::kCrosSysSystemOnNegativeContainer
: cros_tokens::kCrosSysOnPrimaryContainer;
}
} // namespace
GameDashboardButton::GameDashboardButton(PressedCallback callback)
: views::Button(std::move(callback)) {
auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>());
layout->set_cross_axis_alignment(
views::BoxLayout::CrossAxisAlignment::kCenter);
layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
layout->set_inside_border_insets(kButtonBorderInsets);
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
ink_drop_container_ =
AddChildView(std::make_unique<views::InkDropContainerView>());
// Add the gamepad icon view.
gamepad_icon_view_ = AddChildView(std::make_unique<views::ImageView>());
gamepad_icon_view_->SetProperty(views::kMarginsKey, kGamepadIconMargins);
// Add the title view.
title_view_ =
AddChildView(bubble_utils::CreateLabel(TypographyToken::kCrosButton2));
// Add the arrow icon view.
arrow_icon_view_ = AddChildView(std::make_unique<views::ImageView>());
arrow_icon_view_->SetProperty(views::kMarginsKey, kArrowMargins);
// Set up highlight ink drop and focus ring.
StyleUtil::InstallRoundedCornerHighlightPathGenerator(
this, gfx::RoundedCornersF(kContainerCornerRadius));
auto* ink_drop = views::InkDrop::Get(this);
ink_drop->SetMode(views::InkDropHost::InkDropMode::ON);
ink_drop->GetInkDrop()->SetShowHighlightOnHover(false);
ink_drop->SetVisibleOpacity(1.0f); // The colors already contain opacity.
views::FocusRing::Get(this)->SetColorId(cros_tokens::kCrosSysPrimary);
}
GameDashboardButton::~GameDashboardButton() = default;
void GameDashboardButton::SetToggled(bool toggled) {
if (toggled == toggled_) {
return;
}
toggled_ = toggled;
UpdateArrowIcon();
}
void GameDashboardButton::OnRecordingStarted() {
CHECK(!is_recording_);
is_recording_ = true;
UpdateViews();
}
void GameDashboardButton::OnRecordingEnded() {
if (!is_recording_) {
return;
}
is_recording_ = false;
UpdateViews();
}
void GameDashboardButton::UpdateRecordingDuration(
const std::u16string& duration) {
DCHECK(title_view_);
SetTitle(l10n_util::GetStringFUTF16(
IDS_ASH_GAME_DASHBOARD_GAME_DASHBOARD_BUTTON_RECORDING, duration));
}
void GameDashboardButton::AddedToWidget() {
views::Button::AddedToWidget();
UpdateViews();
}
void GameDashboardButton::ChildPreferredSizeChanged(views::View* child) {
PreferredSizeChanged();
}
void GameDashboardButton::OnThemeChanged() {
views::View::OnThemeChanged();
// No need to update the theme before this view is added to the widget.
if (GetWidget()) {
UpdateViews();
}
}
void GameDashboardButton::AddLayerToRegion(ui::Layer* new_layer,
views::LayerRegion region) {
DCHECK(ink_drop_container_);
// This routes background layers to `ink_drop_container_` instead of `this` to
// avoid painting effects underneath our background.
ink_drop_container_->AddLayerToRegion(new_layer, region);
}
void GameDashboardButton::RemoveLayerFromRegions(ui::Layer* old_layer) {
DCHECK(ink_drop_container_);
// This routes background layers to `ink_drop_container_` instead of `this` to
// avoid painting effects underneath our background.
ink_drop_container_->RemoveLayerFromRegions(old_layer);
}
void GameDashboardButton::StateChanged(ButtonState old_state) {
UpdateViews();
}
void GameDashboardButton::UpdateArrowIcon() {
DCHECK(arrow_icon_view_);
const gfx::VectorIcon& arrow_icon =
toggled_ ? kGdButtonUpArrowIcon : kGdButtonDownArrowIcon;
arrow_icon_view_->SetImage(ui::ImageModel::FromVectorIcon(
arrow_icon,
GetEnabled() ? GetIconAndLabelEnabledColorId(is_recording_)
: cros_tokens::kCrosSysDisabled,
kIconHeight));
}
void GameDashboardButton::UpdateViews() {
DCHECK(GetWidget());
// Don't update `title_view_` because it will be updated by
// `UpdateRecordingDuration()`.
if (!is_recording_) {
SetTitle(l10n_util::GetStringUTF16(
IDS_ASH_GAME_DASHBOARD_GAME_DASHBOARD_BUTTON_TITLE));
}
views::InkDrop::Get(this)->SetBaseColorId(
is_recording_ ? cros_tokens::kCrosSysRippleNeutralOnProminent
: cros_tokens::kCrosSysRipplePrimary);
SetBorder(views::CreateRoundedRectBorder(
kBorderThickness, kContainerCornerRadius, gfx::Insets(),
SkColorSetA(DarkLightModeControllerImpl::Get()->IsDarkModeEnabled()
? cros_tokens::kCrosSysWhite
: cros_tokens::kCrosSysBlack,
kAlphaForButtonBorder)));
const bool enabled = GetEnabled();
SetBackground(views::CreateThemedRoundedRectBackground(
enabled ? GetBackgroundEnabledColorId(is_recording_)
: cros_tokens::kCrosSysDisabledContainer,
kContainerCornerRadius));
const auto icon_and_label_color =
enabled ? GetIconAndLabelEnabledColorId(is_recording_)
: cros_tokens::kCrosSysDisabled;
gamepad_icon_view_->SetImage(ui::ImageModel::FromVectorIcon(
chromeos::kGameDashboardGamepadIcon, icon_and_label_color, kIconHeight));
title_view_->SetEnabledColorId(icon_and_label_color);
UpdateArrowIcon();
}
void GameDashboardButton::SetTitle(const std::u16string& title_text) {
SetTooltipText(title_text);
title_view_->SetText(title_text);
}
BEGIN_METADATA(GameDashboardButton)
END_METADATA
} // namespace ash