#include "ash/style/pill_button.h"
#include <optional>
#include "ash/public/cpp/style/color_provider.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/blurred_background_shield.h"
#include "ash/style/color_util.h"
#include "ash/style/style_util.h"
#include "ash/style/typography.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/color/color_variant.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/background.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/highlight_path_generator.h"
namespace ash {
namespace {
constexpr int kPillButtonHeight = 32;
constexpr int kPillButtonLargeHeight = 36;
constexpr int kPillButtonMinimumWidth = 56;
constexpr int kIconSize = 20;
constexpr int kIconPillButtonImageLabelSpacingDp = 8;
constexpr int kFocusRingPadding = 2 + views::FocusRing::kDefaultHaloThickness +
views::FocusRing::kDefaultHaloInset;
constexpr PillButton::TypeFlag kButtonColorVariant =
PillButton::kDefault | PillButton::kDefaultElevated | PillButton::kPrimary |
PillButton::kSecondary | PillButton::kFloating | PillButton::kAlert |
PillButton::kAccent;
bool IsFloatingPillButton(PillButton::Type type) {
return type & PillButton::kFloating;
}
bool IsIconPillButton(PillButton::Type type) {
return type & (PillButton::kIconLeading | PillButton::kIconFollowing);
}
int GetButtonHeight(PillButton::Type type) {
return (type & PillButton::kLarge) ? kPillButtonLargeHeight
: kPillButtonHeight;
}
bool MaybeUpdateColorVariant(
std::optional<ui::ColorVariant>& target_color_variant,
ui::ColorVariant color_variant) {
if (target_color_variant && target_color_variant == color_variant) {
return false;
}
target_color_variant = color_variant;
return true;
}
std::optional<ui::ColorId> GetDefaultBackgroundColorId(PillButton::Type type) {
std::optional<ui::ColorId> color_id;
switch (type & kButtonColorVariant) {
case PillButton::kDefault:
color_id = cros_tokens::kCrosSysSystemOnBase;
break;
case PillButton::kDefaultElevated:
color_id = cros_tokens::kCrosSysSystemBaseElevated;
break;
case PillButton::kPrimary:
color_id = cros_tokens::kCrosSysPrimary;
break;
case PillButton::kSecondary:
color_id = kColorAshSecondaryButtonBackgroundColor;
break;
case PillButton::kAlert:
color_id = cros_tokens::kCrosSysError;
break;
case PillButton::kAccent:
color_id = kColorAshControlBackgroundColorInactive;
break;
default:
NOTREACHED() << "Invalid and floating pill button type: " << type;
}
return color_id;
}
std::optional<ui::ColorId> GetDefaultButtonTextIconColorId(
PillButton::Type type) {
std::optional<ui::ColorId> color_id;
switch (type & kButtonColorVariant) {
case PillButton::kDefault:
color_id = cros_tokens::kCrosSysOnSurface;
break;
case PillButton::kDefaultElevated:
color_id = cros_tokens::kCrosSysOnSurface;
break;
case PillButton::kPrimary:
color_id = cros_tokens::kCrosSysOnPrimary;
break;
case PillButton::kSecondary:
color_id = cros_tokens::kCrosSysOnSecondaryContainer;
break;
case PillButton::kFloating:
color_id = cros_tokens::kCrosSysPrimary;
break;
case PillButton::kAlert:
color_id = cros_tokens::kCrosSysOnError;
break;
case PillButton::kAccent:
case PillButton::kAccent | PillButton::kFloating:
color_id = kColorAshButtonLabelColorBlue;
break;
default:
NOTREACHED() << "Invalid pill button type: " << type;
}
return color_id;
}
}
PillButton::PillButton(PressedCallback callback,
const std::u16string& text,
PillButton::Type type,
const gfx::VectorIcon* icon,
int horizontal_spacing,
int padding_reduction_for_icon)
: views::LabelButton(std::move(callback), text),
type_(type),
icon_(icon),
horizontal_spacing_(horizontal_spacing),
padding_reduction_for_icon_(padding_reduction_for_icon) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
label()->SetSubpixelRenderingEnabled(false);
TypographyProvider::Get()->StyleLabel(TypographyToken::kLegacyButton2,
*label());
StyleUtil::SetUpInkDropForButton(this, gfx::Insets(),
false,
false,
gfx::kPlaceholderColor);
auto* focus_ring = views::FocusRing::Get(this);
focus_ring->SetOutsetFocusRingDisabled(true);
focus_ring->SetColorId(ui::kColorAshFocusRing);
SetImageLabelSpacing(kIconPillButtonImageLabelSpacingDp);
Init();
UpdateTooltipText();
enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
&PillButton::UpdateBackgroundColor, base::Unretained(this)));
}
PillButton::~PillButton() = default;
gfx::Size PillButton::CalculatePreferredSize(
const views::SizeBounds& available_size) const {
int button_width =
label()
->GetPreferredSize(views::SizeBounds(label()->width(), {}))
.width();
if (IsIconPillButton(type_)) {
button_width += horizontal_spacing_ + GetHorizontalSpacingWithIcon();
button_width += kIconSize + GetImageLabelSpacing();
} else {
button_width += 2 * horizontal_spacing_;
}
const int height = GetButtonHeight(type_);
gfx::Size size(button_width, height);
size.SetToMax(gfx::Size(kPillButtonMinimumWidth, height));
return size;
}
gfx::Insets PillButton::GetInsets() const {
const int vertical_spacing = (GetButtonHeight(type_) - kIconSize) / 2;
const int icon_padding = IsIconPillButton(type_)
? GetHorizontalSpacingWithIcon()
: horizontal_spacing_;
if (type_ & kIconFollowing) {
return gfx::Insets::TLBR(vertical_spacing, horizontal_spacing_,
vertical_spacing, icon_padding);
}
return gfx::Insets::TLBR(vertical_spacing, icon_padding, vertical_spacing,
horizontal_spacing_);
}
void PillButton::UpdateBackgroundColor() {
if (IsFloatingPillButton(type_)) {
return;
}
ui::ColorVariant background_color;
if (!GetEnabled()) {
background_color = cros_tokens::kCrosSysDisabledContainer;
} else if (background_color_) {
background_color = background_color_.value();
} else {
auto default_color_id = GetDefaultBackgroundColorId(type_);
DCHECK(default_color_id);
background_color = default_color_id.value();
}
const float corner_radius = GetButtonHeight(type_) / 2.0f;
if (enable_background_blur_) {
if (background()) {
SetBackground(nullptr);
}
if (!blurred_background_) {
blurred_background_ = std::make_unique<BlurredBackgroundShield>(
this, background_color, ColorProvider::kBackgroundBlurSigma,
gfx::RoundedCornersF(corner_radius),
false);
return;
}
} else if (blurred_background_) {
blurred_background_.reset();
}
if (enable_background_blur_) {
blurred_background_->SetColor(background_color);
} else {
SetBackground(views::CreateRoundedRectBackground(
background_color, gfx::RoundedCornersF(corner_radius)));
}
}
views::PropertyEffects PillButton::UpdateStyleToIndicateDefaultStatus() {
return views::PropertyEffects::kNone;
}
void PillButton::SetText(std::u16string_view text) {
std::u16string old_label_text(GetText());
views::LabelButton::SetText(text);
if (use_label_as_default_tooltip_ && old_label_text == GetTooltipText()) {
SetTooltipText(std::u16string(GetText()));
}
}
void PillButton::OnSetTooltipText(const std::u16string& tooltip_text) {
views::LabelButton::OnSetTooltipText(tooltip_text);
if (GetTooltipText() == GetText()) {
return;
}
original_tooltip_text_ = GetTooltipText();
UpdateTooltipText();
}
void PillButton::SetBackgroundColor(ui::ColorVariant background_color) {
if (MaybeUpdateColorVariant(background_color_, background_color)) {
UpdateBackgroundColor();
}
}
void PillButton::SetButtonTextColor(ui::ColorVariant text_color) {
if (MaybeUpdateColorVariant(text_color_, text_color)) {
UpdateTextColor();
}
}
void PillButton::SetIconColor(ui::ColorVariant icon_color) {
if (MaybeUpdateColorVariant(icon_color_, icon_color)) {
UpdateIconColor();
}
}
void PillButton::SetPillButtonType(Type type) {
if (type_ == type)
return;
type_ = type;
Init();
}
void PillButton::SetUseDefaultLabelFont() {
label()->SetFontList(TypographyProvider::Get()->ResolveTypographyToken(
TypographyToken::kLegacyBody2));
}
void PillButton::SetEnableBackgroundBlur(bool enable) {
if (enable_background_blur_ == enable) {
return;
}
enable_background_blur_ = enable;
UpdateBackgroundColor();
}
void PillButton::SetTextWithStringId(int message_id) {
SetText(l10n_util::GetStringUTF16(message_id));
}
void PillButton::SetUseLabelAsDefaultTooltip(
bool use_label_as_default_tooltip) {
use_label_as_default_tooltip_ = use_label_as_default_tooltip;
UpdateTooltipText();
}
void PillButton::Init() {
if (type_ & kIconFollowing) {
SetHorizontalAlignment(gfx::ALIGN_RIGHT);
} else {
SetHorizontalAlignment(gfx::ALIGN_CENTER);
}
const int height = GetButtonHeight(type_);
views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
height / 2.f);
views::FocusRing::Get(this)->SetPathGenerator(
std::make_unique<views::RoundRectHighlightPathGenerator>(
gfx::Insets(-kFocusRingPadding), height / 2.f + kFocusRingPadding));
if (IsFloatingPillButton(type_)) {
SetBackground(nullptr);
}
UpdateBackgroundColor();
UpdateTextColor();
UpdateIconColor();
PreferredSizeChanged();
}
void PillButton::UpdateTextColor() {
SetTextColor(views::Button::STATE_DISABLED, cros_tokens::kCrosSysDisabled);
if (text_color_) {
SetEnabledTextColors(text_color_);
} else {
auto default_color_id = GetDefaultButtonTextIconColorId(type_);
DCHECK(default_color_id);
SetEnabledTextColors(default_color_id.value());
}
}
void PillButton::UpdateIconColor() {
if (!IsIconPillButton(type_))
return;
if (!icon_) {
return;
}
SetImageModel(views::Button::STATE_DISABLED,
ui::ImageModel::FromVectorIcon(
*icon_, cros_tokens::kCrosSysDisabled, kIconSize));
if (icon_color_) {
SetImageModel(
views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(*icon_, *icon_color_, kIconSize));
} else {
auto default_color_id = GetDefaultButtonTextIconColorId(type_);
DCHECK(default_color_id);
SetImageModel(views::Button::STATE_NORMAL,
ui::ImageModel::FromVectorIcon(
*icon_, default_color_id.value(), kIconSize));
}
}
int PillButton::GetHorizontalSpacingWithIcon() const {
return std::max(horizontal_spacing_ - padding_reduction_for_icon_, 0);
}
void PillButton::UpdateTooltipText() {
const auto& tooltip = GetTooltipText();
if (use_label_as_default_tooltip_ && tooltip.empty()) {
SetTooltipText(std::u16string(GetText()));
} else {
if (tooltip == GetText()) {
SetTooltipText(original_tooltip_text_);
}
}
}
BEGIN_METADATA(PillButton)
END_METADATA
}