chromium/ash/style/tab_slider_button.cc

// Copyright 2022 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/style/tab_slider_button.h"

#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/style_util.h"
#include "ash/style/typography.h"
#include "ui/accessibility/ax_enums.mojom-shared.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/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/border.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"

namespace ash {

namespace {

// Color Ids shared by all types of tab slider buttons.
constexpr ui::ColorId kSelectedColorId =
    cros_tokens::kCrosSysSystemOnPrimaryContainer;
constexpr ui::ColorId kDisabledSelectedColorId =
    cros_tokens::kCrosSysSystemOnPrimaryContainerDisabled;
constexpr ui::ColorId kUnselectedColorId = cros_tokens::kCrosSysOnSurface;
constexpr ui::ColorId kDisabledUnselectedColorId =
    cros_tokens::kCrosSysDisabled;
// The padding between the button and the focus ring.
constexpr int kFocusRingPadding = 2;

// Icon slider buttons' layout parameters.
constexpr int kIconButtonSize = 32;
constexpr int kIconSize = 20;

// Label slider buttons' layout parameters.
constexpr int kLabelButtonHeight = 32;
constexpr int kLabelButtonMinWidth = 80;
constexpr gfx::Insets kLabelButtonBorderInsets = gfx::Insets::VH(6, 16);

// Icon + label buttons' layout parameters.
constexpr gfx::Insets kIconLabelButtonVerticalMargins = gfx::Insets(8);
constexpr gfx::Insets kIconLabelButtonHorizontalMargins =
    gfx::Insets::VH(8, 16);
constexpr int kIconLabelSpacing = 6;

}  // namespace

//------------------------------------------------------------------------------
// TabSliderButton:

TabSliderButton::TabSliderButton(PressedCallback callback,
                                 const std::u16string& tooltip_text)
    : views::Button(std::move(callback)) {
  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);

  // Configure the focus ring.
  auto* focus_ring = views::FocusRing::Get(this);
  focus_ring->SetOutsetFocusRingDisabled(true);
  focus_ring->SetColorId(cros_tokens::kCrosSysFocusRing);
  const float halo_inset =
      focus_ring->GetHaloThickness() / 2.f + kFocusRingPadding;
  focus_ring->SetHaloInset(-halo_inset);
  // Set a pill shaped (fully rounded rect) highlight path to focus ring.
  focus_ring->SetPathGenerator(
      std::make_unique<views::PillHighlightPathGenerator>());
  // Set the highlight path to `kHighlightPathGeneratorKey` property for the ink
  // drop to use.
  views::InstallPillHighlightPathGenerator(this);

  SetTooltipText(tooltip_text);
  GetViewAccessibility().SetRole(ax::mojom::Role::kToggleButton);
  GetViewAccessibility().SetCheckedState(selected_
                                             ? ax::mojom::CheckedState::kTrue
                                             : ax::mojom::CheckedState::kFalse);
}

TabSliderButton::~TabSliderButton() = default;

void TabSliderButton::AddedToSlider(TabSlider* tab_slider) {
  DCHECK(tab_slider);
  tab_slider_ = tab_slider;
}

void TabSliderButton::SetSelected(bool selected) {
  if (selected_ == selected) {
    return;
  }

  selected_ = selected;
  GetViewAccessibility().SetCheckedState(selected_
                                             ? ax::mojom::CheckedState::kTrue
                                             : ax::mojom::CheckedState::kFalse);
  if (selected_ && tab_slider_) {
    tab_slider_->OnButtonSelected(this);
  }

  OnSelectedChanged();
}

SkColor TabSliderButton::GetColorIdOnButtonState() {
  const bool enabled = GetEnabled();
  return selected()
             ? (enabled ? kSelectedColorId : kDisabledSelectedColorId)
             : (enabled ? kUnselectedColorId : kDisabledUnselectedColorId);
}

void TabSliderButton::NotifyClick(const ui::Event& event) {
  // Select the button on clicking.
  SetSelected(true);
  views::Button::NotifyClick(event);
}

BEGIN_METADATA(TabSliderButton)
END_METADATA

//------------------------------------------------------------------------------
// IconSliderButton:

IconSliderButton::IconSliderButton(PressedCallback callback,
                                   const gfx::VectorIcon* icon,
                                   const std::u16string& tooltip_text)
    : TabSliderButton(std::move(callback), tooltip_text), icon_(icon) {
  SetPreferredSize(gfx::Size(kIconButtonSize, kIconButtonSize));

  // Replace the pill shaped highlight path of focus ring with a circle shaped
  // highlight path.
  auto* focus_ring = views::FocusRing::Get(this);
  focus_ring->SetOutsetFocusRingDisabled(true);
  focus_ring->SetPathGenerator(
      std::make_unique<views::CircleHighlightPathGenerator>(-gfx::Insets(
          focus_ring->GetHaloThickness() / 2 + kFocusRingPadding)));
  // Set the circle highlight path to `kHighlightPathGeneratorKey` property for
  // the ink drop to use.
  views::InstallCircleHighlightPathGenerator(this);
}

IconSliderButton::~IconSliderButton() = default;

void IconSliderButton::OnSelectedChanged() {
  SchedulePaint();
}

void IconSliderButton::OnThemeChanged() {
  views::View::OnThemeChanged();
  SchedulePaint();
}

void IconSliderButton::PaintButtonContents(gfx::Canvas* canvas) {
  DCHECK(GetWidget());

  // Paint the icon in the color according to the current state.
  const gfx::ImageSkia img = gfx::CreateVectorIcon(
      *icon_, kIconSize,
      GetColorProvider()->GetColor(GetColorIdOnButtonState()));
  const int origin_offset = (kIconButtonSize - kIconSize) / 2;
  canvas->DrawImageInt(img, origin_offset, origin_offset);
}

BEGIN_METADATA(IconSliderButton)
END_METADATA

//------------------------------------------------------------------------------
// LabelSliderButton:

LabelSliderButton::LabelSliderButton(PressedCallback callback,
                                     const std::u16string& text,
                                     const std::u16string& tooltip_text)
    : TabSliderButton(std::move(callback), tooltip_text),
      label_(AddChildView(std::make_unique<views::Label>(text))) {
  SetBorder(views::CreateEmptyBorder(kLabelButtonBorderInsets));
  SetUseDefaultFillLayout(true);
  // Force the label to use requested colors.
  label_->SetAutoColorReadabilityEnabled(false);
  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2, *label_);

  // If custom tooltip is not indicated, use the label text as the tooltip.
  if (tooltip_text.empty()) {
    SetTooltipText(text);
  }
}

LabelSliderButton::~LabelSliderButton() = default;

void LabelSliderButton::UpdateLabelColor() {
  label_->SetEnabledColorId(GetColorIdOnButtonState());
  SchedulePaint();
}

void LabelSliderButton::OnSelectedChanged() {
  // Update label color on selected state changed.
  UpdateLabelColor();
}

gfx::Size LabelSliderButton::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  gfx::Insets insets = GetInsets();
  // The width of the container equals to the label width with horizontal
  // padding.
  views::SizeBound label_available_width = std::max<views::SizeBound>(
      kLabelButtonMinWidth, available_size.width() - insets.width());

  return gfx::Size(
      std::max(
          label_->GetPreferredSize(views::SizeBounds(label_available_width, {}))
                  .width() +
              GetInsets().width(),
          kLabelButtonMinWidth),
      kLabelButtonHeight);
}

void LabelSliderButton::StateChanged(ButtonState old_state) {
  // Update the label color when enabled state changed.
  if (old_state != ButtonState::STATE_DISABLED &&
      GetState() != ButtonState::STATE_DISABLED) {
    return;
  }

  UpdateLabelColor();
}

BEGIN_METADATA(LabelSliderButton)
END_METADATA

//------------------------------------------------------------------------------
// IconLabelSliderButton:

IconLabelSliderButton::IconLabelSliderButton(PressedCallback callback,
                                             const gfx::VectorIcon* icon,
                                             const std::u16string& text,
                                             const std::u16string& tooltip_text,
                                             bool horizontal)
    : TabSliderButton(std::move(callback),
                      tooltip_text.empty() ? text : tooltip_text),
      image_view_(AddChildView(std::make_unique<views::ImageView>())),
      label_(AddChildView(std::make_unique<views::Label>(text))) {
  SetLayoutManager(std::make_unique<views::BoxLayout>(
      horizontal ? views::BoxLayout::Orientation::kHorizontal
                 : views::BoxLayout::Orientation::kVertical,
      /*inside_border_insets=*/
      (horizontal ? kIconLabelButtonHorizontalMargins
                  : kIconLabelButtonVerticalMargins),
      /*between_child_spacing=*/kIconLabelSpacing));

  DCHECK(icon);
  image_view_->SetImage(ui::ImageModel::FromImageGenerator(
      base::BindRepeating(
          [](TabSliderButton* tab_slider_button,
             const gfx::VectorIcon* vector_icon, const ui::ColorProvider*) {
            return gfx::CreateVectorIcon(
                *vector_icon, kIconSize,
                tab_slider_button->GetColorProvider()->GetColor(
                    tab_slider_button->GetColorIdOnButtonState()));
          },
          /*tab_slider_button=*/this, icon),
      gfx::Size(kIconSize, kIconSize)));

  // Force the label to use requested colors.
  label_->SetAutoColorReadabilityEnabled(false);
  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2, *label_);
}

IconLabelSliderButton::~IconLabelSliderButton() = default;

void IconLabelSliderButton::UpdateColors() {
  label_->SetEnabledColorId(GetColorIdOnButtonState());
  // `SchedulePaint()` will result in the `gfx::VectorIcon` for `image_view_`
  // getting re-generated with the proper color.
  SchedulePaint();
}

void IconLabelSliderButton::OnSelectedChanged() {
  UpdateColors();
}

BEGIN_METADATA(IconLabelSliderButton)
END_METADATA

}  // namespace ash