1 // Copyright (c) 2012 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.
5 #include "ui/views/controls/button/label_button.h"
7 #include "base/logging.h"
8 #include "grit/ui_resources.h"
9 #include "ui/base/resource/resource_bundle.h"
10 #include "ui/gfx/animation/throb_animation.h"
11 #include "ui/gfx/canvas.h"
12 #include "ui/gfx/font_list.h"
13 #include "ui/gfx/sys_color_change_listener.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/views/background.h"
16 #include "ui/views/controls/button/label_button_border.h"
17 #include "ui/views/painter.h"
18 #include "ui/views/window/dialog_delegate.h"
20 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
21 #include "ui/views/linux_ui/linux_ui.h"
26 // The spacing between the icon and text.
27 const int kSpacing
= 5;
29 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
30 // Default text and shadow colors for STYLE_BUTTON.
31 const SkColor kStyleButtonTextColor
= SK_ColorBLACK
;
32 const SkColor kStyleButtonShadowColor
= SK_ColorWHITE
;
40 const int LabelButton::kHoverAnimationDurationMs
= 170;
43 const char LabelButton::kViewClassName
[] = "LabelButton";
45 LabelButton::LabelButton(ButtonListener
* listener
, const base::string16
& text
)
46 : CustomButton(listener
),
47 image_(new ImageView()),
49 button_state_images_(),
50 button_state_colors_(),
51 explicitly_set_colors_(),
53 style_(STYLE_TEXTBUTTON
),
54 border_is_themed_border_(true) {
55 SetAnimationDuration(kHoverAnimationDurationMs
);
57 SetFontList(gfx::FontList());
60 image_
->set_interactive(false);
63 label_
->SetAutoColorReadabilityEnabled(false);
64 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
66 // Initialize the colors, border, and layout.
69 SetAccessibleName(text
);
72 LabelButton::~LabelButton() {}
74 const gfx::ImageSkia
& LabelButton::GetImage(ButtonState for_state
) {
75 if (for_state
!= STATE_NORMAL
&& button_state_images_
[for_state
].isNull())
76 return button_state_images_
[STATE_NORMAL
];
77 return button_state_images_
[for_state
];
80 void LabelButton::SetImage(ButtonState for_state
, const gfx::ImageSkia
& image
) {
81 button_state_images_
[for_state
] = image
;
85 const base::string16
& LabelButton::GetText() const {
86 return label_
->text();
89 void LabelButton::SetText(const base::string16
& text
) {
90 SetAccessibleName(text
);
91 label_
->SetText(text
);
94 void LabelButton::SetTextColor(ButtonState for_state
, SkColor color
) {
95 button_state_colors_
[for_state
] = color
;
96 if (for_state
== STATE_DISABLED
)
97 label_
->SetDisabledColor(color
);
98 else if (for_state
== state())
99 label_
->SetEnabledColor(color
);
100 explicitly_set_colors_
[for_state
] = true;
103 void LabelButton::SetTextShadows(const gfx::ShadowValues
& shadows
) {
104 label_
->set_shadows(shadows
);
107 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled
) {
108 label_
->set_subpixel_rendering_enabled(enabled
);
111 bool LabelButton::GetTextMultiLine() const {
112 return label_
->is_multi_line();
115 void LabelButton::SetTextMultiLine(bool text_multi_line
) {
116 label_
->SetMultiLine(text_multi_line
);
119 const gfx::FontList
& LabelButton::GetFontList() const {
120 return label_
->font_list();
123 void LabelButton::SetFontList(const gfx::FontList
& font_list
) {
124 cached_normal_font_list_
= font_list
;
125 cached_bold_font_list_
= font_list
.DeriveWithStyle(
126 font_list
.GetFontStyle() | gfx::Font::BOLD
);
128 // STYLE_BUTTON uses bold text to indicate default buttons.
130 style_
== STYLE_BUTTON
&& is_default_
?
131 cached_bold_font_list_
: cached_normal_font_list_
);
134 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior
) {
135 label_
->SetElideBehavior(elide_behavior
);
138 gfx::HorizontalAlignment
LabelButton::GetHorizontalAlignment() const {
139 return label_
->GetHorizontalAlignment();
142 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment
) {
143 label_
->SetHorizontalAlignment(alignment
);
147 void LabelButton::SetDirectionalityMode(gfx::DirectionalityMode mode
) {
148 label_
->set_directionality_mode(mode
);
151 void LabelButton::SetIsDefault(bool is_default
) {
152 if (is_default
== is_default_
)
154 is_default_
= is_default
;
155 ui::Accelerator
accel(ui::VKEY_RETURN
, ui::EF_NONE
);
156 is_default_
? AddAccelerator(accel
) : RemoveAccelerator(accel
);
158 // STYLE_BUTTON uses bold text to indicate default buttons.
159 if (style_
== STYLE_BUTTON
) {
161 is_default
? cached_bold_font_list_
: cached_normal_font_list_
);
165 void LabelButton::SetStyle(ButtonStyle style
) {
167 // Inset the button focus rect from the actual border; roughly match Windows.
168 if (style
== STYLE_BUTTON
) {
169 SetFocusPainter(scoped_ptr
<Painter
>());
171 SetFocusPainter(Painter::CreateDashedFocusPainterWithInsets(
172 gfx::Insets(3, 3, 3, 3)));
174 if (style
== STYLE_BUTTON
) {
175 label_
->SetHorizontalAlignment(gfx::ALIGN_CENTER
);
178 if (style
== STYLE_BUTTON
)
179 set_min_size(gfx::Size(70, 33));
181 OnNativeThemeChanged(GetNativeTheme());
184 void LabelButton::SetFocusPainter(scoped_ptr
<Painter
> focus_painter
) {
185 focus_painter_
= focus_painter
.Pass();
188 gfx::Size
LabelButton::GetPreferredSize() const {
189 // Use a temporary label copy for sizing to avoid calculation side-effects.
190 Label
label(GetText(), cached_normal_font_list_
);
191 label
.set_shadows(label_
->shadows());
192 label
.SetMultiLine(GetTextMultiLine());
194 if (style() == STYLE_BUTTON
) {
195 // Some text appears wider when rendered normally than when rendered bold.
196 // Accommodate the widest, as buttons may show bold and shouldn't resize.
197 const int current_width
= label
.GetPreferredSize().width();
198 label
.SetFontList(cached_bold_font_list_
);
199 if (label
.GetPreferredSize().width() < current_width
)
200 label
.SetFontList(cached_normal_font_list_
);
203 // Resize multi-line labels given the current limited available width.
204 const gfx::Size
image_size(image_
->GetPreferredSize());
205 const int image_width
= image_size
.width();
206 if (GetTextMultiLine() && (width() > image_width
+ kSpacing
))
207 label
.SizeToFit(width() - image_width
- (image_width
> 0 ? kSpacing
: 0));
209 // Calculate the required size.
210 gfx::Size
size(label
.GetPreferredSize());
211 if (image_width
> 0 && size
.width() > 0)
212 size
.Enlarge(kSpacing
, 0);
213 size
.SetToMax(gfx::Size(0, image_size
.height()));
214 const gfx::Insets
insets(GetInsets());
215 size
.Enlarge(image_size
.width() + insets
.width(), insets
.height());
217 // Make the size at least as large as the minimum size needed by the border.
218 size
.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size());
220 // Increase the minimum size monotonically with the preferred size.
221 size
.SetToMax(min_size_
);
224 // Return the largest known size clamped to the maximum size (if valid).
225 if (max_size_
.width() > 0)
226 size
.set_width(std::min(max_size_
.width(), size
.width()));
227 if (max_size_
.height() > 0)
228 size
.set_height(std::min(max_size_
.height(), size
.height()));
232 void LabelButton::Layout() {
233 gfx::HorizontalAlignment adjusted_alignment
= GetHorizontalAlignment();
234 if (base::i18n::IsRTL() && adjusted_alignment
!= gfx::ALIGN_CENTER
)
235 adjusted_alignment
= (adjusted_alignment
== gfx::ALIGN_LEFT
) ?
236 gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT
;
238 gfx::Rect
child_area(GetChildAreaBounds());
239 child_area
.Inset(GetInsets());
241 gfx::Size
image_size(image_
->GetPreferredSize());
242 image_size
.SetToMin(child_area
.size());
244 // The label takes any remaining width after sizing the image, unless both
245 // views are centered. In that case, using the tighter preferred label width
246 // avoids wasted space within the label that would look like awkward padding.
247 gfx::Size
label_size(child_area
.size());
248 if (!image_size
.IsEmpty() && !label_size
.IsEmpty()) {
249 label_size
.set_width(
250 std::max(child_area
.width() - image_size
.width() - kSpacing
, 0));
251 if (adjusted_alignment
== gfx::ALIGN_CENTER
) {
252 // Ensure multi-line labels paired with images use their available width.
253 if (GetTextMultiLine())
254 label_
->SizeToFit(label_size
.width());
255 label_size
.set_width(
256 std::min(label_size
.width(), label_
->GetPreferredSize().width()));
260 gfx::Point
image_origin(child_area
.origin());
261 image_origin
.Offset(0, (child_area
.height() - image_size
.height()) / 2);
262 if (adjusted_alignment
== gfx::ALIGN_CENTER
) {
263 const int total_width
= image_size
.width() + label_size
.width() +
264 ((image_size
.width() > 0 && label_size
.width() > 0) ? kSpacing
: 0);
265 image_origin
.Offset((child_area
.width() - total_width
) / 2, 0);
266 } else if (adjusted_alignment
== gfx::ALIGN_RIGHT
) {
267 image_origin
.Offset(child_area
.width() - image_size
.width(), 0);
270 gfx::Point
label_origin(child_area
.origin());
271 if (!image_size
.IsEmpty() && adjusted_alignment
!= gfx::ALIGN_RIGHT
)
272 label_origin
.set_x(image_origin
.x() + image_size
.width() + kSpacing
);
274 image_
->SetBoundsRect(gfx::Rect(image_origin
, image_size
));
275 label_
->SetBoundsRect(gfx::Rect(label_origin
, label_size
));
278 const char* LabelButton::GetClassName() const {
279 return kViewClassName
;
282 scoped_ptr
<LabelButtonBorder
> LabelButton::CreateDefaultBorder() const {
283 return scoped_ptr
<LabelButtonBorder
>(new LabelButtonBorder(style_
));
286 void LabelButton::SetBorder(scoped_ptr
<Border
> border
) {
287 border_is_themed_border_
= false;
288 View::SetBorder(border
.Pass());
291 gfx::Rect
LabelButton::GetChildAreaBounds() {
292 return GetLocalBounds();
295 void LabelButton::OnPaint(gfx::Canvas
* canvas
) {
296 View::OnPaint(canvas
);
297 Painter::PaintFocusPainter(this, canvas
, focus_painter_
.get());
300 void LabelButton::OnFocus() {
302 // Typically the border renders differently when focused.
306 void LabelButton::OnBlur() {
308 // Typically the border renders differently when focused.
312 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams
* params
) const {
313 params
->button
.checked
= false;
314 params
->button
.indeterminate
= false;
315 params
->button
.is_default
= is_default_
;
316 params
->button
.is_focused
= HasFocus() && IsAccessibilityFocusable();
317 params
->button
.has_border
= false;
318 params
->button
.classic_state
= 0;
319 params
->button
.background_color
= label_
->background_color();
322 void LabelButton::ResetColorsFromNativeTheme() {
323 const ui::NativeTheme
* theme
= GetNativeTheme();
324 SkColor colors
[STATE_COUNT
] = {
325 theme
->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor
),
326 theme
->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor
),
327 theme
->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor
),
328 theme
->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor
),
331 // Certain styles do not change text color when hovered or pressed.
332 bool constant_text_color
= false;
333 // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
334 if (gfx::IsInvertedColorScheme()) {
335 constant_text_color
= true;
336 colors
[STATE_NORMAL
] = SK_ColorWHITE
;
337 label_
->SetBackgroundColor(SK_ColorBLACK
);
338 label_
->set_background(Background::CreateSolidBackground(SK_ColorBLACK
));
339 label_
->SetAutoColorReadabilityEnabled(true);
340 label_
->set_shadows(gfx::ShadowValues());
341 } else if (style() == STYLE_BUTTON
) {
342 // TODO(erg): This is disabled on desktop linux because of the binary asset
343 // confusion. These details should either be pushed into ui::NativeThemeWin
344 // or should be obsoleted by rendering buttons with paint calls instead of
345 // with static assets. http://crbug.com/350498
346 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
347 constant_text_color
= true;
348 colors
[STATE_NORMAL
] = kStyleButtonTextColor
;
349 label_
->SetBackgroundColor(theme
->GetSystemColor(
350 ui::NativeTheme::kColorId_ButtonBackgroundColor
));
351 label_
->SetAutoColorReadabilityEnabled(false);
352 label_
->set_shadows(gfx::ShadowValues(1,
353 gfx::ShadowValue(gfx::Point(0, 1), 0, kStyleButtonShadowColor
)));
355 label_
->set_background(NULL
);
357 label_
->set_background(NULL
);
360 if (constant_text_color
)
361 colors
[STATE_HOVERED
] = colors
[STATE_PRESSED
] = colors
[STATE_NORMAL
];
363 for (size_t state
= STATE_NORMAL
; state
< STATE_COUNT
; ++state
) {
364 if (!explicitly_set_colors_
[state
]) {
365 SetTextColor(static_cast<ButtonState
>(state
), colors
[state
]);
366 explicitly_set_colors_
[state
] = false;
371 void LabelButton::UpdateImage() {
372 image_
->SetImage(GetImage(state()));
375 void LabelButton::UpdateThemedBorder() {
376 // Don't override borders set by others.
377 if (!border_is_themed_border_
)
380 scoped_ptr
<LabelButtonBorder
> label_button_border
= CreateDefaultBorder();
382 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
383 views::LinuxUI
* linux_ui
= views::LinuxUI::instance();
385 SetBorder(linux_ui
->CreateNativeBorder(
386 this, label_button_border
.Pass()));
390 SetBorder(label_button_border
.PassAs
<Border
>());
393 border_is_themed_border_
= true;
396 void LabelButton::StateChanged() {
397 const gfx::Size
previous_image_size(image_
->GetPreferredSize());
399 const SkColor color
= button_state_colors_
[state()];
400 if (state() != STATE_DISABLED
&& label_
->enabled_color() != color
)
401 label_
->SetEnabledColor(color
);
402 label_
->SetEnabled(state() != STATE_DISABLED
);
403 if (image_
->GetPreferredSize() != previous_image_size
)
407 void LabelButton::ChildPreferredSizeChanged(View
* child
) {
408 PreferredSizeChanged();
411 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme
* theme
) {
412 ResetColorsFromNativeTheme();
413 UpdateThemedBorder();
414 // Invalidate the layout to pickup the new insets from the border.
418 ui::NativeTheme::Part
LabelButton::GetThemePart() const {
419 return ui::NativeTheme::kPushButton
;
422 gfx::Rect
LabelButton::GetThemePaintRect() const {
423 return GetLocalBounds();
426 ui::NativeTheme::State
LabelButton::GetThemeState(
427 ui::NativeTheme::ExtraParams
* params
) const {
428 GetExtraParams(params
);
430 case STATE_NORMAL
: return ui::NativeTheme::kNormal
;
431 case STATE_HOVERED
: return ui::NativeTheme::kHovered
;
432 case STATE_PRESSED
: return ui::NativeTheme::kPressed
;
433 case STATE_DISABLED
: return ui::NativeTheme::kDisabled
;
434 case STATE_COUNT
: NOTREACHED() << "Unknown state: " << state();
436 return ui::NativeTheme::kNormal
;
439 const gfx::Animation
* LabelButton::GetThemeAnimation() const {
440 return hover_animation_
.get();
443 ui::NativeTheme::State
LabelButton::GetBackgroundThemeState(
444 ui::NativeTheme::ExtraParams
* params
) const {
445 GetExtraParams(params
);
446 return ui::NativeTheme::kNormal
;
449 ui::NativeTheme::State
LabelButton::GetForegroundThemeState(
450 ui::NativeTheme::ExtraParams
* params
) const {
451 GetExtraParams(params
);
452 return ui::NativeTheme::kHovered
;