| // 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_, kPropertyEffectsNone); |
| } |
| |
| 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_, kPropertyEffectsNone); |
| } |
| |
| 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_, kPropertyEffectsNone); |
| } |
| |
| 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_, kPropertyEffectsNone); |
| } |
| |
| 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_, kPropertyEffectsPaint); |
| } |
| |
| 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 kPropertyEffectsNone; |
| } |
| |
| 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 |