1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/views/controls/button/md_text_button.h"
6 
7 #include <algorithm>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/i18n/case_conversion.h"
12 #include "base/memory/ptr_util.h"
13 #include "build/build_config.h"
14 #include "ui/gfx/canvas.h"
15 #include "ui/gfx/color_palette.h"
16 #include "ui/gfx/color_utils.h"
17 #include "ui/native_theme/native_theme.h"
18 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
19 #include "ui/views/animation/ink_drop_highlight.h"
20 #include "ui/views/animation/ink_drop_impl.h"
21 #include "ui/views/animation/ink_drop_painted_layer_delegates.h"
22 #include "ui/views/background.h"
23 #include "ui/views/border.h"
24 #include "ui/views/controls/focus_ring.h"
25 #include "ui/views/layout/layout_provider.h"
26 #include "ui/views/metadata/metadata_impl_macros.h"
27 #include "ui/views/painter.h"
28 #include "ui/views/style/platform_style.h"
29 #include "ui/views/style/typography.h"
30 
31 namespace views {
32 
MdTextButton(PressedCallback callback,const base::string16 & text,int button_context)33 MdTextButton::MdTextButton(PressedCallback callback,
34                            const base::string16& text,
35                            int button_context)
36     : LabelButton(std::move(callback), text, button_context) {
37   SetInkDropMode(InkDropMode::ON);
38   SetHasInkDropActionOnClick(true);
39   SetShowInkDropWhenHotTracked(true);
40   SetCornerRadius(LayoutProvider::Get()->GetCornerRadiusMetric(EMPHASIS_LOW));
41   SetHorizontalAlignment(gfx::ALIGN_CENTER);
42 
43   const int minimum_width = LayoutProvider::Get()->GetDistanceMetric(
44       DISTANCE_DIALOG_BUTTON_MINIMUM_WIDTH);
45   SetMinSize(gfx::Size(minimum_width, 0));
46   SetInstallFocusRingOnFocus(true);
47   label()->SetAutoColorReadabilityEnabled(false);
48   SetRequestFocusOnPress(false);
49   SetAnimateOnStateChange(true);
50 
51   // Paint to a layer so that the canvas is snapped to pixel boundaries (useful
52   // for fractional DSF).
53   SetPaintToLayer();
54   layer()->SetFillsBoundsOpaquely(false);
55 
56   // Call this to calculate the border given text.
57   UpdatePadding();
58 }
59 
60 MdTextButton::~MdTextButton() = default;
61 
SetProminent(bool is_prominent)62 void MdTextButton::SetProminent(bool is_prominent) {
63   if (is_prominent_ == is_prominent)
64     return;
65 
66   is_prominent_ = is_prominent;
67   UpdateColors();
68   OnPropertyChanged(&is_prominent_, kPropertyEffectsNone);
69 }
70 
GetProminent() const71 bool MdTextButton::GetProminent() const {
72   return is_prominent_;
73 }
74 
SetBgColorOverride(const base::Optional<SkColor> & color)75 void MdTextButton::SetBgColorOverride(const base::Optional<SkColor>& color) {
76   if (color == bg_color_override_)
77     return;
78   bg_color_override_ = color;
79   UpdateColors();
80   OnPropertyChanged(&bg_color_override_, kPropertyEffectsNone);
81 }
82 
GetBgColorOverride() const83 base::Optional<SkColor> MdTextButton::GetBgColorOverride() const {
84   return bg_color_override_;
85 }
86 
SetCornerRadius(float radius)87 void MdTextButton::SetCornerRadius(float radius) {
88   if (corner_radius_ == radius)
89     return;
90   corner_radius_ = radius;
91   SetInkDropSmallCornerRadius(corner_radius_);
92   SetInkDropLargeCornerRadius(corner_radius_);
93   OnPropertyChanged(&corner_radius_, kPropertyEffectsPaint);
94 }
95 
GetCornerRadius() const96 float MdTextButton::GetCornerRadius() const {
97   return corner_radius_;
98 }
99 
OnThemeChanged()100 void MdTextButton::OnThemeChanged() {
101   LabelButton::OnThemeChanged();
102   UpdateColors();
103 }
104 
GetInkDropBaseColor() const105 SkColor MdTextButton::GetInkDropBaseColor() const {
106   return color_utils::DeriveDefaultIconColor(label()->GetEnabledColor());
107 }
108 
StateChanged(ButtonState old_state)109 void MdTextButton::StateChanged(ButtonState old_state) {
110   LabelButton::StateChanged(old_state);
111   UpdateColors();
112 }
113 
OnFocus()114 void MdTextButton::OnFocus() {
115   LabelButton::OnFocus();
116   UpdateColors();
117 }
118 
OnBlur()119 void MdTextButton::OnBlur() {
120   LabelButton::OnBlur();
121   UpdateColors();
122 }
123 
CreateInkDropHighlight() const124 std::unique_ptr<views::InkDropHighlight> MdTextButton::CreateInkDropHighlight()
125     const {
126   const ui::NativeTheme* theme = GetNativeTheme();
127   // The prominent button hover effect is a shadow.
128   constexpr int kYOffset = 1;
129   constexpr int kSkiaBlurRadius = 2;
130   ui::NativeTheme::ColorId fill_color_id;
131   ui::NativeTheme::ColorId shadow_color_id =
132       is_prominent_
133           ? ui::NativeTheme::kColorId_ProminentButtonInkDropShadowColor
134           : ui::NativeTheme::kColorId_ButtonInkDropShadowColor;
135   if (GetState() == STATE_HOVERED) {
136     fill_color_id = is_prominent_
137                         ? ui::NativeTheme::kColorId_ProminentButtonHoverColor
138                         : ui::NativeTheme::kColorId_ButtonHoverColor;
139   } else {
140     fill_color_id =
141         is_prominent_
142             ? ui::NativeTheme::kColorId_ProminentButtonInkDropFillColor
143             : ui::NativeTheme::kColorId_ButtonInkDropFillColor;
144   }
145   std::vector<gfx::ShadowValue> shadows;
146   // The notion of blur that gfx::ShadowValue uses is twice the Skia/CSS value.
147   // Skia counts the number of pixels outside the mask area whereas
148   // gfx::ShadowValue counts together the number of pixels inside and outside
149   // the mask bounds.
150   shadows.emplace_back(gfx::Vector2d(0, kYOffset), 2 * kSkiaBlurRadius,
151                        theme->GetSystemColor(shadow_color_id));
152   auto highlight = std::make_unique<InkDropHighlight>(
153       gfx::RectF(GetLocalBounds()).CenterPoint(),
154       std::make_unique<BorderShadowLayerDelegate>(
155           shadows, GetLocalBounds(), theme->GetSystemColor(fill_color_id),
156           corner_radius_));
157   highlight->set_visible_opacity(1.0f);
158   return highlight;
159 }
160 
SetEnabledTextColors(base::Optional<SkColor> color)161 void MdTextButton::SetEnabledTextColors(base::Optional<SkColor> color) {
162   LabelButton::SetEnabledTextColors(std::move(color));
163   UpdateColors();
164 }
165 
SetCustomPadding(const base::Optional<gfx::Insets> & padding)166 void MdTextButton::SetCustomPadding(
167     const base::Optional<gfx::Insets>& padding) {
168   custom_padding_ = padding;
169   UpdatePadding();
170 }
171 
GetCustomPadding() const172 base::Optional<gfx::Insets> MdTextButton::GetCustomPadding() const {
173   return custom_padding_.value_or(CalculateDefaultPadding());
174 }
175 
SetText(const base::string16 & text)176 void MdTextButton::SetText(const base::string16& text) {
177   LabelButton::SetText(text);
178   UpdatePadding();
179 }
180 
UpdateStyleToIndicateDefaultStatus()181 PropertyEffects MdTextButton::UpdateStyleToIndicateDefaultStatus() {
182   is_prominent_ = is_prominent_ || GetIsDefault();
183   UpdateColors();
184   return kPropertyEffectsNone;
185 }
186 
UpdatePadding()187 void MdTextButton::UpdatePadding() {
188   // Don't use font-based padding when there's no text visible.
189   if (GetText().empty()) {
190     SetBorder(NullBorder());
191     return;
192   }
193 
194   SetBorder(
195       CreateEmptyBorder(custom_padding_.value_or(CalculateDefaultPadding())));
196 }
197 
CalculateDefaultPadding() const198 gfx::Insets MdTextButton::CalculateDefaultPadding() const {
199   // Text buttons default to 28dp in height on all platforms when the base font
200   // is in use, but should grow or shrink if the font size is adjusted up or
201   // down. When the system font size has been adjusted, the base font will be
202   // larger than normal such that 28dp might not be enough, so also enforce a
203   // minimum height of twice the font size.
204   // Example 1:
205   // * Normal button on ChromeOS, 12pt Roboto. Button height of 28dp.
206   // * Button on ChromeOS that has been adjusted to 14pt Roboto. Button height
207   // of 28 + 2 * 2 = 32dp.
208   // * Linux user sets base system font size to 17dp. For a normal button, the
209   // |size_delta| will be zero, so to adjust upwards we double 17 to get 34.
210   int size_delta =
211       label()->font_list().GetFontSize() -
212       style::GetFont(style::CONTEXT_BUTTON_MD, style::STYLE_PRIMARY)
213           .GetFontSize();
214   // TODO(tapted): This should get |target_height| using LayoutProvider::
215   // GetControlHeightForFont().
216   constexpr int kBaseHeight = 32;
217   int target_height = std::max(kBaseHeight + size_delta * 2,
218                                label()->font_list().GetFontSize() * 2);
219 
220   int label_height = label()->GetPreferredSize().height();
221   int top_padding = (target_height - label_height) / 2;
222   int bottom_padding = (target_height - label_height + 1) / 2;
223   DCHECK_EQ(target_height, label_height + top_padding + bottom_padding);
224 
225   // TODO(estade): can we get rid of the platform style border hoopla if
226   // we apply the MD treatment to all buttons, even GTK buttons?
227   const int horizontal_padding = LayoutProvider::Get()->GetDistanceMetric(
228       DISTANCE_BUTTON_HORIZONTAL_PADDING);
229   return gfx::Insets(top_padding, horizontal_padding, bottom_padding,
230                      horizontal_padding);
231 }
232 
UpdateTextColor()233 void MdTextButton::UpdateTextColor() {
234   if (explicitly_set_normal_color())
235     return;
236 
237   SkColor enabled_text_color =
238       style::GetColor(*this, label()->GetTextContext(),
239                       is_prominent_ ? style::STYLE_DIALOG_BUTTON_DEFAULT
240                                     : style::STYLE_PRIMARY);
241 
242   const auto colors = explicitly_set_colors();
243   LabelButton::SetEnabledTextColors(enabled_text_color);
244   // Disabled buttons need the disabled color explicitly set.
245   // This ensures that label()->GetEnabledColor() returns the correct color as
246   // the basis for calculating the stroke color. enabled_text_color isn't used
247   // since a descendant could have overridden the label enabled color.
248   if (GetState() == STATE_DISABLED) {
249     LabelButton::SetTextColor(STATE_DISABLED,
250                               style::GetColor(*this, label()->GetTextContext(),
251                                               style::STYLE_DISABLED));
252   }
253   set_explicitly_set_colors(colors);
254 }
255 
UpdateBackgroundColor()256 void MdTextButton::UpdateBackgroundColor() {
257   bool is_disabled = GetVisualState() == STATE_DISABLED;
258   ui::NativeTheme* theme = GetNativeTheme();
259   SkColor bg_color =
260       theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonColor);
261 
262   if (bg_color_override_) {
263     bg_color = *bg_color_override_;
264   } else if (is_prominent_) {
265     bg_color = theme->GetSystemColor(
266         HasFocus() ? ui::NativeTheme::kColorId_ProminentButtonFocusedColor
267                    : ui::NativeTheme::kColorId_ProminentButtonColor);
268     if (is_disabled) {
269       bg_color = theme->GetSystemColor(
270           ui::NativeTheme::kColorId_ProminentButtonDisabledColor);
271     }
272   }
273 
274   if (GetState() == STATE_PRESSED) {
275     bg_color = theme->GetSystemButtonPressedColor(bg_color);
276   }
277 
278   SkColor stroke_color;
279   if (is_prominent_) {
280     stroke_color = SK_ColorTRANSPARENT;
281   } else {
282     stroke_color = theme->GetSystemColor(
283         is_disabled ? ui::NativeTheme::kColorId_DisabledButtonBorderColor
284                     : ui::NativeTheme::kColorId_ButtonBorderColor);
285   }
286 
287   SetBackground(
288       CreateBackgroundFromPainter(Painter::CreateRoundRectWith1PxBorderPainter(
289           bg_color, stroke_color, corner_radius_)));
290 }
291 
UpdateColors()292 void MdTextButton::UpdateColors() {
293   UpdateTextColor();
294   UpdateBackgroundColor();
295   SchedulePaint();
296 }
297 
298 BEGIN_METADATA(MdTextButton, LabelButton)
299 ADD_PROPERTY_METADATA(bool, Prominent)
300 ADD_PROPERTY_METADATA(float, CornerRadius)
301 ADD_PROPERTY_METADATA(base::Optional<SkColor>, BgColorOverride)
302 ADD_PROPERTY_METADATA(base::Optional<gfx::Insets>, CustomPadding)
303 END_METADATA
304 
305 }  // namespace views
306