Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / controls / button / label_button.cc
blob2f85fb68a951e53c3990e687d48e31c70b1732eb
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/lazy_instance.h"
8 #include "base/logging.h"
9 #include "ui/gfx/animation/throb_animation.h"
10 #include "ui/gfx/canvas.h"
11 #include "ui/gfx/color_utils.h"
12 #include "ui/gfx/font_list.h"
13 #include "ui/gfx/geometry/vector2d.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/style/platform_style.h"
19 #include "ui/views/window/dialog_delegate.h"
21 namespace {
23 // The default spacing between the icon and text.
24 const int kSpacing = 5;
26 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
27 // Default text and shadow colors for STYLE_BUTTON.
28 const SkColor kStyleButtonTextColor = SK_ColorBLACK;
29 const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
30 #endif
32 const gfx::FontList& GetDefaultNormalFontList() {
33 static base::LazyInstance<gfx::FontList>::Leaky font_list =
34 LAZY_INSTANCE_INITIALIZER;
35 return font_list.Get();
38 const gfx::FontList& GetDefaultBoldFontList() {
39 static base::LazyInstance<gfx::FontList>::Leaky font_list =
40 LAZY_INSTANCE_INITIALIZER;
41 if ((font_list.Get().GetFontStyle() & gfx::Font::BOLD) == 0) {
42 font_list.Get() = font_list.Get().
43 DeriveWithStyle(font_list.Get().GetFontStyle() | gfx::Font::BOLD);
44 DCHECK_NE(font_list.Get().GetFontStyle() & gfx::Font::BOLD, 0);
46 return font_list.Get();
49 } // namespace
51 namespace views {
53 // static
54 const int LabelButton::kHoverAnimationDurationMs = 170;
56 // static
57 const char LabelButton::kViewClassName[] = "LabelButton";
59 LabelButton::LabelButton(ButtonListener* listener, const base::string16& text)
60 : CustomButton(listener),
61 image_(new ImageView()),
62 label_(new Label()),
63 cached_normal_font_list_(GetDefaultNormalFontList()),
64 cached_bold_font_list_(GetDefaultBoldFontList()),
65 button_state_images_(),
66 button_state_colors_(),
67 explicitly_set_colors_(),
68 is_default_(false),
69 style_(STYLE_TEXTBUTTON),
70 border_is_themed_border_(true),
71 image_label_spacing_(kSpacing) {
72 SetAnimationDuration(kHoverAnimationDurationMs);
73 SetText(text);
75 AddChildView(image_);
76 image_->set_interactive(false);
78 AddChildView(label_);
79 label_->SetFontList(cached_normal_font_list_);
80 label_->SetAutoColorReadabilityEnabled(false);
81 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
83 // Inset the button focus rect from the actual border; roughly match Windows.
84 SetFocusPainter(
85 Painter::CreateDashedFocusPainterWithInsets(gfx::Insets(3, 3, 3, 3)));
88 LabelButton::~LabelButton() {}
90 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
91 if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
92 return button_state_images_[STATE_NORMAL];
93 return button_state_images_[for_state];
96 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
97 button_state_images_[for_state] = image;
98 UpdateImage();
101 const base::string16& LabelButton::GetText() const {
102 return label_->text();
105 void LabelButton::SetText(const base::string16& text) {
106 SetAccessibleName(text);
107 label_->SetText(text);
110 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
111 button_state_colors_[for_state] = color;
112 if (for_state == STATE_DISABLED)
113 label_->SetDisabledColor(color);
114 else if (for_state == state())
115 label_->SetEnabledColor(color);
116 explicitly_set_colors_[for_state] = true;
119 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
120 label_->SetShadows(shadows);
123 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
124 label_->SetSubpixelRenderingEnabled(enabled);
127 bool LabelButton::GetTextMultiLine() const {
128 return label_->multi_line();
131 void LabelButton::SetTextMultiLine(bool text_multi_line) {
132 label_->SetMultiLine(text_multi_line);
135 const gfx::FontList& LabelButton::GetFontList() const {
136 return label_->font_list();
139 void LabelButton::SetFontList(const gfx::FontList& font_list) {
140 cached_normal_font_list_ = font_list;
141 cached_bold_font_list_ = font_list.DeriveWithStyle(
142 font_list.GetFontStyle() | gfx::Font::BOLD);
144 // STYLE_BUTTON uses bold text to indicate default buttons.
145 label_->SetFontList(
146 style_ == STYLE_BUTTON && is_default_ ?
147 cached_bold_font_list_ : cached_normal_font_list_);
150 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
151 label_->SetElideBehavior(elide_behavior);
154 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
155 return label_->horizontal_alignment();
158 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
159 label_->SetHorizontalAlignment(alignment);
160 InvalidateLayout();
163 void LabelButton::SetMinSize(const gfx::Size& min_size) {
164 min_size_ = min_size;
165 ResetCachedPreferredSize();
168 void LabelButton::SetMaxSize(const gfx::Size& max_size) {
169 max_size_ = max_size;
170 ResetCachedPreferredSize();
173 void LabelButton::SetIsDefault(bool is_default) {
174 if (is_default == is_default_)
175 return;
176 is_default_ = is_default;
177 ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
178 is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
180 // STYLE_BUTTON uses bold text to indicate default buttons.
181 if (style_ == STYLE_BUTTON) {
182 label_->SetFontList(
183 is_default ? cached_bold_font_list_ : cached_normal_font_list_);
187 void LabelButton::SetStyle(ButtonStyle style) {
188 // All callers currently pass STYLE_BUTTON, and should only call this once, to
189 // change from the default style.
190 DCHECK_EQ(style, STYLE_BUTTON);
191 DCHECK_EQ(style_, STYLE_TEXTBUTTON);
192 DCHECK(!GetWidget()) << "Can't change button style after adding to a Widget.";
194 style_ = style;
196 SetFocusPainter(nullptr);
197 label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
198 SetFocusable(true);
199 SetMinSize(gfx::Size(70, 33));
201 // Themed borders will be set once the button is added to a Widget, since that
202 // provides the value of GetNativeTheme().
205 void LabelButton::SetImageLabelSpacing(int spacing) {
206 if (spacing == image_label_spacing_)
207 return;
208 image_label_spacing_ = spacing;
209 ResetCachedPreferredSize();
210 InvalidateLayout();
213 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
214 focus_painter_ = focus_painter.Pass();
217 gfx::Size LabelButton::GetPreferredSize() const {
218 if (cached_preferred_size_valid_)
219 return cached_preferred_size_;
221 // Use a temporary label copy for sizing to avoid calculation side-effects.
222 Label label(GetText(), cached_normal_font_list_);
223 label.SetShadows(label_->shadows());
224 label.SetMultiLine(GetTextMultiLine());
226 if (style() == STYLE_BUTTON) {
227 // Some text appears wider when rendered normally than when rendered bold.
228 // Accommodate the widest, as buttons may show bold and shouldn't resize.
229 const int current_width = label.GetPreferredSize().width();
230 label.SetFontList(cached_bold_font_list_);
231 if (label.GetPreferredSize().width() < current_width)
232 label.SetFontList(cached_normal_font_list_);
235 // Calculate the required size.
236 const gfx::Size image_size(image_->GetPreferredSize());
237 gfx::Size size(label.GetPreferredSize());
238 if (image_size.width() > 0 && size.width() > 0)
239 size.Enlarge(image_label_spacing_, 0);
240 size.SetToMax(gfx::Size(0, image_size.height()));
241 const gfx::Insets insets(GetInsets());
242 size.Enlarge(image_size.width() + insets.width(), insets.height());
244 // Make the size at least as large as the minimum size needed by the border.
245 size.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size());
247 // Increase the minimum size monotonically with the preferred size.
248 size.SetToMax(min_size_);
249 min_size_ = size;
251 // Return the largest known size clamped to the maximum size (if valid).
252 if (max_size_.width() > 0)
253 size.set_width(std::min(max_size_.width(), size.width()));
254 if (max_size_.height() > 0)
255 size.set_height(std::min(max_size_.height(), size.height()));
257 // Cache this computed size, as recomputing it is an expensive operation.
258 cached_preferred_size_valid_ = true;
259 cached_preferred_size_ = size;
260 return cached_preferred_size_;
263 int LabelButton::GetHeightForWidth(int w) const {
264 w -= GetInsets().width();
265 const gfx::Size image_size(image_->GetPreferredSize());
266 w -= image_size.width();
267 if (image_size.width() > 0 && !GetText().empty())
268 w -= image_label_spacing_;
270 int height = std::max(image_size.height(), label_->GetHeightForWidth(w));
271 if (border())
272 height = std::max(height, border()->GetMinimumSize().height());
274 height = std::max(height, min_size_.height());
275 if (max_size_.height() > 0)
276 height = std::min(height, max_size_.height());
277 return height;
280 void LabelButton::Layout() {
281 gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
282 if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
283 adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
284 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
286 // By default, GetChildAreaBounds() ignores the top and bottom border, but we
287 // want the image to respect it.
288 gfx::Rect child_area(GetChildAreaBounds());
289 gfx::Rect label_area(child_area);
291 gfx::Insets insets(GetInsets());
292 child_area.Inset(insets);
293 // Labels can paint over the vertical component of the border insets.
294 label_area.Inset(insets.left(), 0, insets.right(), 0);
296 gfx::Size image_size(image_->GetPreferredSize());
297 image_size.SetToMin(child_area.size());
299 // The label takes any remaining width after sizing the image, unless both
300 // views are centered. In that case, using the tighter preferred label width
301 // avoids wasted space within the label that would look like awkward padding.
302 gfx::Size label_size(label_area.size());
303 if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
304 label_size.set_width(std::max(child_area.width() -
305 image_size.width() - image_label_spacing_, 0));
306 if (adjusted_alignment == gfx::ALIGN_CENTER) {
307 // Ensure multi-line labels paired with images use their available width.
308 label_size.set_width(
309 std::min(label_size.width(), label_->GetPreferredSize().width()));
313 gfx::Point image_origin(child_area.origin());
314 image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
315 if (adjusted_alignment == gfx::ALIGN_CENTER) {
316 const int spacing = (image_size.width() > 0 && label_size.width() > 0) ?
317 image_label_spacing_ : 0;
318 const int total_width = image_size.width() + label_size.width() +
319 spacing;
320 image_origin.Offset((child_area.width() - total_width) / 2, 0);
321 } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
322 image_origin.Offset(child_area.width() - image_size.width(), 0);
325 gfx::Point label_origin(label_area.origin());
326 if (!image_size.IsEmpty() && adjusted_alignment != gfx::ALIGN_RIGHT) {
327 label_origin.set_x(image_origin.x() + image_size.width() +
328 image_label_spacing_);
331 image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
332 label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
335 const char* LabelButton::GetClassName() const {
336 return kViewClassName;
339 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
340 return PlatformStyle::CreateLabelButtonBorder(style());
343 void LabelButton::SetBorder(scoped_ptr<Border> border) {
344 border_is_themed_border_ = false;
345 View::SetBorder(border.Pass());
346 ResetCachedPreferredSize();
349 gfx::Rect LabelButton::GetChildAreaBounds() {
350 return GetLocalBounds();
353 void LabelButton::OnPaint(gfx::Canvas* canvas) {
354 View::OnPaint(canvas);
355 Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
358 void LabelButton::OnFocus() {
359 View::OnFocus();
360 // Typically the border renders differently when focused.
361 SchedulePaint();
364 void LabelButton::OnBlur() {
365 View::OnBlur();
366 // Typically the border renders differently when focused.
367 SchedulePaint();
370 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
371 params->button.checked = false;
372 params->button.indeterminate = false;
373 params->button.is_default = is_default_;
374 params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
375 params->button.has_border = false;
376 params->button.classic_state = 0;
377 params->button.background_color = label_->background_color();
380 void LabelButton::ResetColorsFromNativeTheme() {
381 const ui::NativeTheme* theme = GetNativeTheme();
382 SkColor colors[STATE_COUNT] = {
383 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
384 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
385 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
386 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
389 // Certain styles do not change text color when hovered or pressed.
390 bool constant_text_color = false;
391 // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
392 if (color_utils::IsInvertedColorScheme()) {
393 constant_text_color = true;
394 colors[STATE_NORMAL] = SK_ColorWHITE;
395 label_->SetBackgroundColor(SK_ColorBLACK);
396 label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK));
397 label_->SetAutoColorReadabilityEnabled(true);
398 label_->SetShadows(gfx::ShadowValues());
399 } else if (style() == STYLE_BUTTON) {
400 // TODO(erg): This is disabled on desktop linux because of the binary asset
401 // confusion. These details should either be pushed into ui::NativeThemeWin
402 // or should be obsoleted by rendering buttons with paint calls instead of
403 // with static assets. http://crbug.com/350498
404 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
405 constant_text_color = true;
406 colors[STATE_NORMAL] = kStyleButtonTextColor;
407 label_->SetBackgroundColor(theme->GetSystemColor(
408 ui::NativeTheme::kColorId_ButtonBackgroundColor));
409 label_->SetAutoColorReadabilityEnabled(false);
410 label_->SetShadows(gfx::ShadowValues(
411 1, gfx::ShadowValue(gfx::Vector2d(0, 1), 0, kStyleButtonShadowColor)));
412 #endif
413 label_->set_background(NULL);
414 } else {
415 label_->set_background(NULL);
418 if (constant_text_color)
419 colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
421 for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
422 if (!explicitly_set_colors_[state]) {
423 SetTextColor(static_cast<ButtonState>(state), colors[state]);
424 explicitly_set_colors_[state] = false;
429 void LabelButton::UpdateImage() {
430 image_->SetImage(GetImage(state()));
431 ResetCachedPreferredSize();
434 void LabelButton::UpdateThemedBorder() {
435 // Don't override borders set by others.
436 if (!border_is_themed_border_)
437 return;
439 SetBorder(PlatformStyle::CreateThemedLabelButtonBorder(this));
440 border_is_themed_border_ = true;
443 void LabelButton::StateChanged() {
444 const gfx::Size previous_image_size(image_->GetPreferredSize());
445 UpdateImage();
446 const SkColor color = button_state_colors_[state()];
447 if (state() != STATE_DISABLED && label_->enabled_color() != color)
448 label_->SetEnabledColor(color);
449 label_->SetEnabled(state() != STATE_DISABLED);
450 if (image_->GetPreferredSize() != previous_image_size)
451 Layout();
454 void LabelButton::ChildPreferredSizeChanged(View* child) {
455 ResetCachedPreferredSize();
456 PreferredSizeChanged();
459 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
460 ResetColorsFromNativeTheme();
461 UpdateThemedBorder();
462 // Invalidate the layout to pickup the new insets from the border.
463 InvalidateLayout();
466 ui::NativeTheme::Part LabelButton::GetThemePart() const {
467 return ui::NativeTheme::kPushButton;
470 gfx::Rect LabelButton::GetThemePaintRect() const {
471 return GetLocalBounds();
474 ui::NativeTheme::State LabelButton::GetThemeState(
475 ui::NativeTheme::ExtraParams* params) const {
476 GetExtraParams(params);
477 switch (state()) {
478 case STATE_NORMAL: return ui::NativeTheme::kNormal;
479 case STATE_HOVERED: return ui::NativeTheme::kHovered;
480 case STATE_PRESSED: return ui::NativeTheme::kPressed;
481 case STATE_DISABLED: return ui::NativeTheme::kDisabled;
482 case STATE_COUNT: NOTREACHED() << "Unknown state: " << state();
484 return ui::NativeTheme::kNormal;
487 const gfx::Animation* LabelButton::GetThemeAnimation() const {
488 return hover_animation_.get();
491 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
492 ui::NativeTheme::ExtraParams* params) const {
493 GetExtraParams(params);
494 return ui::NativeTheme::kNormal;
497 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
498 ui::NativeTheme::ExtraParams* params) const {
499 GetExtraParams(params);
500 return ui::NativeTheme::kHovered;
503 void LabelButton::ResetCachedPreferredSize() {
504 cached_preferred_size_valid_ = false;
505 cached_preferred_size_ = gfx::Size();
508 } // namespace views