910e62b5创建于 1月15日历史提交
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/controls/button/md_text_button.h"

#include <algorithm>
#include <string_view>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/i18n/case_conversion.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "ui/actions/actions.h"
#include "ui/base/metadata/base_type_conversion.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_variant.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/animation/ink_drop_impl.h"
#include "ui/views/animation/ink_drop_painted_layer_delegates.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/painter.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/style/typography.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/view_utils.h"

namespace views {

MdTextButton::MdTextButton(
    PressedCallback callback,
    std::u16string_view text,
    int button_context,
    bool use_text_color_for_icon,
    std::unique_ptr<LabelButtonImageContainer> image_container)
    : LabelButton(std::move(callback),
                  text,
                  button_context,
                  std::move(image_container)),
      use_text_color_for_icon_(use_text_color_for_icon) {
  InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
  SetHasInkDropActionOnClick(true);
  SetShowInkDropWhenHotTracked(true);
  InkDrop::Get(this)->SetBaseColorCallback(base::BindRepeating(
      [](MdTextButton* host) { return host->GetHoverColor(host->GetStyle()); },
      this));

  constexpr int kImageSpacing = 8;
  SetImageLabelSpacing(kImageSpacing);
  // Highlight button colors already have opacity applied.
  // Set the opacity to 1 so the two values do not compound.
  InkDrop::Get(this)->SetHighlightOpacity(1);

  SetHorizontalAlignment(gfx::ALIGN_CENTER);

  const int minimum_width = LayoutProvider::Get()->GetDistanceMetric(
      DISTANCE_DIALOG_BUTTON_MINIMUM_WIDTH);
  SetMinSize(gfx::Size(minimum_width, 0));
  SetInstallFocusRingOnFocus(true);
  label()->SetAutoColorReadabilityEnabled(false);
  SetRequestFocusOnPress(false);
  SetAnimateOnStateChange(true);

  // Paint to a layer so that the canvas is snapped to pixel boundaries (useful
  // for fractional DSF).
  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);

  // Call this to calculate the border given text.
  UpdatePadding();
}

MdTextButton::~MdTextButton() = default;

void MdTextButton::SetStyle(ui::ButtonStyle button_style) {
  if (style_ == button_style) {
    return;
  }

  style_ = button_style;
  SetProperty(kDrawFocusRingBackgroundOutline,
              button_style == ui::ButtonStyle::kProminent);
  UpdateColors();
}

ui::ButtonStyle MdTextButton::GetStyle() const {
  return style_;
}

void MdTextButton::SetBgColorIdOverride(
    const std::optional<ui::ColorId> color_id) {
  CHECK(!bg_color_override_.has_value());

  if (color_id == bg_color_id_override_) {
    return;
  }
  bg_color_id_override_ = color_id;
  UpdateColors();
  OnPropertyChanged(&bg_color_id_override_, PropertyEffects::kNone);
}

void MdTextButton::SetBgColorOverrideDeprecated(
    const std::optional<SkColor>& color) {
  CHECK(!bg_color_id_override_.has_value());

  if (color == bg_color_override_) {
    return;
  }
  bg_color_override_ = color;
  UpdateColors();
  OnPropertyChanged(&bg_color_override_, PropertyEffects::kNone);
}

std::optional<SkColor> MdTextButton::GetBgColorOverrideDeprecated() const {
  return bg_color_override_;
}

std::optional<ui::ColorId> MdTextButton::GetBgColorIdOverride() const {
  return bg_color_id_override_;
}

void MdTextButton::SetStrokeColorIdOverride(
    const std::optional<ui::ColorId> color_id) {
  CHECK(!stroke_color_override_.has_value());

  if (color_id == stroke_color_id_override_) {
    return;
  }
  stroke_color_id_override_ = color_id;
  UpdateColors();
  OnPropertyChanged(&stroke_color_id_override_, PropertyEffects::kNone);
}

void MdTextButton::SetStrokeColorOverrideDeprecated(
    const std::optional<SkColor>& color) {
  CHECK(!stroke_color_id_override_.has_value());

  if (color == stroke_color_override_) {
    return;
  }
  stroke_color_override_ = color;
  UpdateColors();
  OnPropertyChanged(&stroke_color_override_, PropertyEffects::kNone);
}

std::optional<SkColor> MdTextButton::GetStrokeColorOverrideDeprecated() const {
  return stroke_color_override_;
}

std::optional<ui::ColorId> MdTextButton::GetStrokeColorIdOverride() const {
  return stroke_color_id_override_;
}

void MdTextButton::SetCornerRadii(const gfx::RoundedCornersF& radii) {
  if (radii_ == radii) {
    return;
  }
  radii_ = radii;
  OnCornerRadiusValueChanged();
  OnPropertyChanged(&radii_, PropertyEffects::kPaint);
}

void MdTextButton::SetCornerRadius(float radius) {
  SetCornerRadii(gfx::RoundedCornersF(radius));
}

gfx::RoundedCornersF MdTextButton::GetCornerRadii() const {
  if (radii_.has_value()) {
    return radii_.value();
  }
  return gfx::RoundedCornersF(LayoutProvider::Get()->GetCornerRadiusMetric(
      ShapeContextTokens::kButtonRadius, size()));
}

void MdTextButton::OnThemeChanged() {
  LabelButton::OnThemeChanged();
  UpdateColors();
}

void MdTextButton::StateChanged(ButtonState old_state) {
  LabelButton::StateChanged(old_state);
  UpdateColors();
}

void MdTextButton::SetImageModel(
    ButtonState for_state,
    const std::optional<ui::ImageModel>& image_model) {
  LabelButton::SetImageModel(for_state, image_model);
  UpdatePadding();
}

void MdTextButton::OnFocus() {
  LabelButton::OnFocus();
  UpdateColors();
}

void MdTextButton::OnBlur() {
  LabelButton::OnBlur();
  UpdateColors();
}

void MdTextButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  LabelButton::OnBoundsChanged(previous_bounds);

  // A fully rounded corner radius is calculated based on the button size.
  OnCornerRadiusValueChanged();
}

void MdTextButton::SetEnabledTextColors(std::optional<ui::ColorVariant> color) {
  LabelButton::SetEnabledTextColors(std::move(color));
  UpdateColors();
}

void MdTextButton::SetCustomPadding(const std::optional<gfx::Insets>& padding) {
  custom_padding_ = padding;
  UpdatePadding();
}

std::optional<gfx::Insets> MdTextButton::GetCustomPadding() const {
  return custom_padding_.value_or(CalculateDefaultPadding());
}

void MdTextButton::SetText(std::u16string_view text) {
  LabelButton::SetText(text);
  UpdatePadding();
}

PropertyEffects MdTextButton::UpdateStyleToIndicateDefaultStatus() {
  SetStyle(style_ == ui::ButtonStyle::kProminent || GetIsDefault()
               ? ui::ButtonStyle::kProminent
               : ui::ButtonStyle::kDefault);
  UpdateColors();
  return PropertyEffects::kNone;
}

void MdTextButton::UpdatePadding() {
  // Don't use font-based padding when there's no text visible.
  if (GetText().empty()) {
    SetBorder(NullBorder());
    return;
  }

  SetBorder(
      CreateEmptyBorder(custom_padding_.value_or(CalculateDefaultPadding())));
}

gfx::Insets MdTextButton::CalculateDefaultPadding() const {
  int target_height = LayoutProvider::GetControlHeightForFont(
      label()->GetTextContext(), style::STYLE_PRIMARY, label()->font_list());

  int label_height = label()->GetPreferredSize({}).height();
  DCHECK_GE(target_height, label_height);
  int top_padding = (target_height - label_height) / 2;
  int bottom_padding = (target_height - label_height + 1) / 2;
  DCHECK_EQ(target_height, label_height + top_padding + bottom_padding);

  // TODO(estade): can we get rid of the platform style border hoopla if
  // we apply the MD treatment to all buttons, even GTK buttons?
  int right_padding = LayoutProvider::Get()->GetDistanceMetric(
      DISTANCE_BUTTON_HORIZONTAL_PADDING);
  int left_padding = right_padding;
  if (HasImage(GetVisualState())) {
    constexpr int kLeftPadding = 12;
    left_padding = kLeftPadding;
  }
  return gfx::Insets::TLBR(top_padding, left_padding, bottom_padding,
                           right_padding);
}

void MdTextButton::UpdateTextColor() {
  if (explicitly_set_normal_color()) {
    return;
  }

  style::TextStyle text_style = style::STYLE_PRIMARY;
  if (style_ == ui::ButtonStyle::kProminent) {
    text_style = style::STYLE_DIALOG_BUTTON_DEFAULT;
  } else if (style_ == ui::ButtonStyle::kTonal) {
    text_style = style::STYLE_DIALOG_BUTTON_TONAL;
  }

  const auto& typography_provider = TypographyProvider::Get();
  const auto colors = explicitly_set_colors();
  LabelButton::SetEnabledTextColors(
      typography_provider.GetColorId(label()->GetTextContext(), text_style));
  // Disabled buttons need the disabled color explicitly set.
  // This ensures that label()->GetEnabledColor() returns the correct color as
  // the basis for calculating the stroke color. enabled text color id isn't
  // used since a descendant could have overridden the label enabled color.
  if (GetState() == STATE_DISABLED) {
    LabelButton::SetTextColor(
        STATE_DISABLED, typography_provider.GetColorId(
                            label()->GetTextContext(), style::STYLE_DISABLED));
  }
  set_explicitly_set_colors(colors);
}

void MdTextButton::UpdateBackgroundColor() {
  bool is_disabled = GetVisualState() == STATE_DISABLED;
  const ui::ColorProvider* color_provider = GetColorProvider();
  SkColor bg_color = color_provider->GetColor(ui::kColorButtonBackground);

  if (bg_color_id_override_) {
    bg_color = color_provider->GetColor(bg_color_id_override_.value());
  } else if (bg_color_override_) {
    bg_color = *bg_color_override_;
  } else if (style_ == ui::ButtonStyle::kProminent) {
    bg_color = color_provider->GetColor(
        HasFocus() ? ui::kColorButtonBackgroundProminentFocused
                   : ui::kColorButtonBackgroundProminent);
    if (is_disabled) {
      bg_color =
          color_provider->GetColor(ui::kColorButtonBackgroundProminentDisabled);
    }
  } else if (style_ == ui::ButtonStyle::kTonal) {
    bg_color = color_provider->GetColor(
        HasFocus() ? ui::kColorButtonBackgroundTonalFocused
                   : ui::kColorButtonBackgroundTonal);
    if (is_disabled) {
      bg_color =
          color_provider->GetColor(ui::kColorButtonBackgroundTonalDisabled);
    }
  }

  if (GetState() == STATE_PRESSED) {
    bg_color = GetNativeTheme()->GetSystemButtonPressedColor(bg_color);
  }

  SkColor stroke_color = color_provider->GetColor(
      is_disabled ? ui::kColorButtonBorderDisabled : ui::kColorButtonBorder);
  if (stroke_color_id_override_.has_value()) {
    stroke_color = color_provider->GetColor(stroke_color_id_override_.value());
  } else if (stroke_color_override_) {
    stroke_color = *stroke_color_override_;
  } else if (style_ == ui::ButtonStyle::kProminent ||
             style_ == ui::ButtonStyle::kText ||
             style_ == ui::ButtonStyle::kTonal) {
    stroke_color = SK_ColorTRANSPARENT;
  }

  SetBackground(
      CreateBackgroundFromPainter(Painter::CreateRoundRectWith1PxBorderPainter(
          bg_color, stroke_color, GetCornerRadii(), SkBlendMode::kSrcOver,
          true /* antialias */, true /* should_border_scale */)));
}

void MdTextButton::UpdateIconColor() {
  if (use_text_color_for_icon_ && HasImage(ButtonState::STATE_NORMAL)) {
    const std::optional<ui::ImageModel>& image_model =
        GetImageModel(ButtonState::STATE_NORMAL);
    if (image_model.has_value() && image_model->IsVectorIcon()) {
      LabelButton::SetImageModel(
          ButtonState::STATE_NORMAL,
          ui::ImageModel::FromVectorIcon(
              *image_model->GetVectorIcon().vector_icon(),
              LabelButton::GetCurrentTextColor(),
              image_model->GetVectorIcon().icon_size()));
    }
  }
}

void MdTextButton::UpdateColors() {
  if (GetWidget()) {
    UpdateTextColor();
    UpdateBackgroundColor();
    UpdateIconColor();
    SchedulePaint();
  }
}

SkColor MdTextButton::GetHoverColor(ui::ButtonStyle button_style) {
  switch (button_style) {
    case ui::ButtonStyle::kProminent:
      return GetColorProvider()->GetColor(ui::kColorSysStateHoverOnProminent);
    case ui::ButtonStyle::kDefault:
    case ui::ButtonStyle::kText:
    case ui::ButtonStyle::kTonal:
    default:
      return GetColorProvider()->GetColor(ui::kColorSysStateHoverOnSubtle);
  }
}

void MdTextButton::OnCornerRadiusValueChanged() {
  LabelButton::SetFocusRingCornerRadii(GetCornerRadii());
  // UpdateColors also updates the background border radius.
  UpdateColors();
}

std::unique_ptr<ActionViewInterface> MdTextButton::GetActionViewInterface() {
  return std::make_unique<MdTextButtonActionViewInterface>(this);
}

MdTextButtonActionViewInterface::MdTextButtonActionViewInterface(
    MdTextButton* action_view)
    : LabelButtonActionViewInterface(action_view), action_view_(action_view) {}

void MdTextButtonActionViewInterface::ActionItemChangedImpl(
    actions::ActionItem* action_item) {
  LabelButtonActionViewInterface::ActionItemChangedImpl(action_item);
  action_view_->SetText(action_item->GetText());
  action_view_->SetImageModel(action_view_->GetState(),
                              action_item->GetImage());
}

BEGIN_METADATA(MdTextButton)
ADD_PROPERTY_METADATA(gfx::RoundedCornersF, CornerRadii)
ADD_PROPERTY_METADATA(std::optional<SkColor>, BgColorOverrideDeprecated)
ADD_PROPERTY_METADATA(std::optional<ui::ColorId>, BgColorIdOverride)
ADD_PROPERTY_METADATA(std::optional<gfx::Insets>, CustomPadding)
ADD_PROPERTY_METADATA(ui::ButtonStyle, Style)
END_METADATA

}  // namespace views