chromium/ash/style/pill_button.h

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_STYLE_PILL_BUTTON_H_
#define ASH_STYLE_PILL_BUTTON_H_

#include "ash/ash_export.h"
#include "base/memory/raw_ptr.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/color/color_id.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/metadata/view_factory.h"

namespace ash {

class BlurredBackgroundShield;

// A label button with a rounded rectangle background. It can have an icon
// inside as well, and its text and background colors will be different based on
// the type of the button.
class ASH_EXPORT PillButton : public views::LabelButton {
  METADATA_HEADER(PillButton, views::LabelButton)

 public:
  using ColorVariant = absl::variant<SkColor, ui::ColorId>;

  static constexpr int kPillButtonHorizontalSpacing = 16;
  static constexpr int kPaddingReductionForIcon = 4;

  // The PillButton style is defined with 4 features:
  // 1. Color variant defines which background, text, and icon color scheme to
  // be used, for example Default, Floating, Alert, etc.
  // 2. Button size indicates whether we should use the default size 32 or a
  // large size 36.
  // 3. With/without an icon.
  // 4. Icon position: leading or following.
  // For ease of extracting features from a button type, each feature is
  // represented by a different bit mask.
  using TypeFlag = int;

  static constexpr TypeFlag kDefault = 1;
  static constexpr TypeFlag kDefaultElevated = 1 << 1;
  static constexpr TypeFlag kPrimary = 1 << 2;
  static constexpr TypeFlag kSecondary = 1 << 3;
  static constexpr TypeFlag kFloating = 1 << 4;
  static constexpr TypeFlag kAlert = 1 << 5;
  // TODO(crbug.com/1355517): Get rid of `kAccent` after CrosNext is fully
  // launched.
  static constexpr TypeFlag kAccent = 1 << 6;
  static constexpr TypeFlag kLarge = 1 << 7;
  static constexpr TypeFlag kIconLeading = 1 << 8;
  static constexpr TypeFlag kIconFollowing = 1 << 9;

  // Types of the PillButton. Each type is represented as the bitwise OR
  // operation of the feature bit masks. The naming rule of the button type is
  // k{Color Variant}{Button Size}{Icon}{Icon Position}.
  enum Type {
    // PillButton with default text and background colors, a leading icon.
    kDefaultWithIconLeading = kDefault | kIconLeading,
    // PillButton with default text and background colors, a following icon.
    kDefaultWithIconFollowing = kDefault | kIconFollowing,
    // PillButton with default text and background colors, a large button size,
    // a leading icon.
    kDefaultLargeWithIconLeading = kDefault | kLarge | kIconLeading,
    // PillButton with default text and background colors, a large button size,
    // a following icon.
    kDefaultLargeWithIconFollowing = kDefault | kLarge | kIconFollowing,
    // PillButton with default text and background colors, no icon.
    kDefaultWithoutIcon = kDefault,
    // PillButton with default text and background colors, a large button size,
    // no icon.
    kDefaultLargeWithoutIcon = kDefault | kLarge,

    // PillButton with default-elevated text and background colors, a leading
    // icon.
    kDefaultElevatedWithIconLeading = kDefaultElevated | kIconLeading,
    // PillButton with default-elevated text and background colors, a following
    // icon.
    kDefaultElevatedWithIconFollowing = kDefaultElevated | kIconFollowing,
    // PillButton with default-elevated text and background colors, a large
    // button size, a leading icon.
    kDefaultElevatedLargeWithIconLeading =
        kDefaultElevated | kLarge | kIconLeading,
    // PillButton with default-elevated text and background colors, a large
    // button size, a following icon.
    kDefaultElevatedLargeWithIconFollowing =
        kDefaultElevated | kLarge | kIconFollowing,
    // PillButton with default-elevated text and background colors, no icon.
    kDefaultElevatedWithoutIcon = kDefaultElevated,
    // PillButton with default-elevated text and background colors, a large
    // button size,
    // no icon.
    kDefaultElevatedLargeWithoutIcon = kDefaultElevated | kLarge,

    // PillButton with primary text and background colors, a leading icon.
    kPrimaryWithIconLeading = kPrimary | kIconLeading,
    // PillButton with primary text and background colors, a following icon.
    kPrimaryWithIconFollowing = kPrimary | kIconFollowing,
    // PillButton with primary text and background colors, a large button size,
    // a leading icon.
    kPrimaryLargeWithIconLeading = kPrimary | kLarge | kIconLeading,
    // PillButton with primary text and background colors, a large button size,
    // a following icon.
    kPrimaryLargeWithIconFollowing = kPrimary | kLarge | kIconFollowing,
    // PillButton with primary text and background colors, no icon.
    kPrimaryWithoutIcon = kPrimary,
    // PillButton with primary text and background colors, a large button size,
    // no icon.
    kPrimaryLargeWithoutIcon = kPrimary | kLarge,

    // PillButton with secondary text and background colors, a leading icon.
    kSecondaryWithIconLeading = kSecondary | kIconLeading,
    // PillButton with secondary text and background colors, a following icon.
    kSecondaryWithIconFollowing = kSecondary | kIconFollowing,
    // PillButton with secondary text and background colors, a large button
    // size, a leading icon.
    kSecondaryLargeWithIconLeading = kSecondary | kLarge | kIconLeading,
    // PillButton with secondary text and background colors, a large button
    // size, a following icon.
    kSecondaryLargeWithIconFollowing = kSecondary | kLarge | kIconFollowing,
    // PillButton with secondary text and background colors, no icon.
    kSecondaryWithoutIcon = kSecondary,
    // PillButton with secondary text and background colors, a large button
    // size, no icon.
    kSecondaryLargeWithoutIcon = kSecondary | kLarge,

    // PillButton with floating text colors, no background, a leading icon.
    kFloatingWithIconLeading = kFloating | kIconLeading,
    // PillButton with floating text colors, no background, a following icon.
    kFloatingWithIconFollowing = kFloating | kIconFollowing,
    // PillButton with floating text colors, no background, a large button size,
    // a leading icon.
    kFloatingLargeWithIconLeading = kFloating | kLarge | kIconLeading,
    // PillButton with floating text colors, no background, a large button size,
    // a following icon.
    kFloatingLargeWithIconFollowing = kFloating | kLarge | kIconFollowing,
    // PillButton with floating text colors, no background, no icon.
    kFloatingWithoutIcon = kFloating,
    // PillButton with floating text colors, no background, a large button size,
    // no icon.
    kFloatingLargeWithoutIcon = kFloating | kLarge,

    // PillButton with alert text and background colors, a leading icon.
    kAlertWithIconLeading = kAlert | kIconLeading,
    // PillButton with alert text and background colors, a following icon.
    kAlertWithIconFollowing = kAlert | kIconFollowing,
    // PillButton with alert text and background colors, a large button size, a
    // leading icon.
    kAlertLargeWithIconLeading = kAlert | kLarge | kIconLeading,
    // PillButton with alert text and background colors, a large button size, a
    // following icon.
    kAlertLargeWithIconFollowing = kAlert | kLarge | kIconFollowing,
    // PillButton with alert text and background colors, no icon.
    kAlertWithoutIcon = kAlert,
    // PillButton with alert text and background colors, a large button size, no
    // icon.
    kAlertLargeWithoutIcon = kAlert | kLarge,

    // Old button types.
    // TODO(crbug.com/1355517): Get rid of these types after CrosNext is fully
    // launched.
    // PillButton with accent text and background colors, no icon.
    kAccentWithoutIcon = kAccent,
    // PillButton with accent text, no background, no icon.
    kAccentFloatingWithoutIcon = kAccent | kFloating,
  };

  explicit PillButton(
      PressedCallback callback = PressedCallback(),
      const std::u16string& text = std::u16string(),
      Type type = Type::kDefaultWithoutIcon,
      const gfx::VectorIcon* icon = nullptr,
      int horizontal_spacing = kPillButtonHorizontalSpacing,
      int padding_reduction_for_icon = kPaddingReductionForIcon);
  PillButton(const PillButton&) = delete;
  PillButton& operator=(const PillButton&) = delete;
  ~PillButton() override;

  // views::LabelButton:
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;
  gfx::Insets GetInsets() const override;
  void UpdateBackgroundColor() override;
  views::PropertyEffects UpdateStyleToIndicateDefaultStatus() override;
  std::u16string GetTooltipText(const gfx::Point& p) const override;

  // Sets the button's background color, text's color or icon's color. Note, do
  // this only when the button wants to have different colors from the default
  // ones.
  void SetBackgroundColor(const SkColor background_color);
  void SetBackgroundColorId(ui::ColorId background_color_id);
  void SetButtonTextColor(const SkColor text_color);
  void SetButtonTextColorId(ui::ColorId text_color_id);
  void SetIconColor(const SkColor icon_color);
  void SetIconColorId(ui::ColorId icon_color_id);
  // TODO(b/290639214): This method is deprecating. Try not to change button
  // type afterward. If a new button type is needed, please create a new
  // instance.
  void SetPillButtonType(Type type);

  // Sets the button's label to use the default label font, which is smaller
  // and less heavily weighted.
  void SetUseDefaultLabelFont();

  // Sets if the button should enable the background blur. Once the button
  // enables the background blur, it will use `BlurredBackgroundShield` as the
  // background which is performance consuming so only use it as needed.
  void SetEnableBackgroundBlur(bool enable);

  void SetTextWithStringId(int message_id);
  void SetUseLabelAsDefaultTooltip(bool use_label_as_default_tooltip);

 private:
  // Initializes the button layout, focus ring and background according to the
  // button type.
  void Init();

  void UpdateTextColor();
  void UpdateIconColor();

  // Returns the spacing on the side where the icon locates. The value is set
  // smaller to make the spacing on two sides visually look the same.
  int GetHorizontalSpacingWithIcon() const;

  Type type_;
  const raw_ptr<const gfx::VectorIcon> icon_;

  // Horizontal spacing of this button. `kPillButtonHorizontalSpacing` will be
  // set as the default value.
  int horizontal_spacing_;

  // The padding reduced by icon.
  int padding_reduction_for_icon_;

  // Custom colors and color IDs.
  ColorVariant background_color_ = gfx::kPlaceholderColor;
  ColorVariant text_color_ = gfx::kPlaceholderColor;
  ColorVariant icon_color_ = gfx::kPlaceholderColor;

  bool enable_background_blur_ = false;
  std::unique_ptr<BlurredBackgroundShield> blurred_background_;

  // Indicates if we are going to use the label contents for tooltip as default.
  bool use_label_as_default_tooltip_ = true;

  // Called to update background color when the button is enabled/disabled.
  base::CallbackListSubscription enabled_changed_subscription_;
};

BEGIN_VIEW_BUILDER(ASH_EXPORT, PillButton, views::LabelButton)
VIEW_BUILDER_PROPERTY(const SkColor, BackgroundColor)
VIEW_BUILDER_PROPERTY(ui::ColorId, BackgroundColorId)
VIEW_BUILDER_PROPERTY(const SkColor, ButtonTextColor)
VIEW_BUILDER_PROPERTY(ui::ColorId, ButtonTextColorId)
VIEW_BUILDER_PROPERTY(const SkColor, IconColor)
VIEW_BUILDER_PROPERTY(ui::ColorId, IconColorId)
VIEW_BUILDER_PROPERTY(PillButton::Type, PillButtonType)
VIEW_BUILDER_PROPERTY(bool, EnableBackgroundBlur)
VIEW_BUILDER_PROPERTY(int, TextWithStringId)
VIEW_BUILDER_PROPERTY(bool, UseLabelAsDefaultTooltip)
END_VIEW_BUILDER

}  // namespace ash

DEFINE_VIEW_BUILDER(ASH_EXPORT, ash::PillButton)

#endif  // ASH_STYLE_PILL_BUTTON_H_