• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ui/views/controls/button/label_button.h"
6 
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/font_list.h"
12 #include "ui/gfx/sys_color_change_listener.h"
13 #include "ui/native_theme/native_theme.h"
14 #include "ui/views/background.h"
15 #include "ui/views/controls/button/label_button_border.h"
16 #include "ui/views/painter.h"
17 #include "ui/views/window/dialog_delegate.h"
18 
19 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
20 #include "ui/views/linux_ui/linux_ui.h"
21 #endif
22 
23 namespace {
24 
25 // The default spacing between the icon and text.
26 const int kSpacing = 5;
27 
28 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
29 // Default text and shadow colors for STYLE_BUTTON.
30 const SkColor kStyleButtonTextColor = SK_ColorBLACK;
31 const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
32 #endif
33 
GetDefaultNormalFontList()34 const gfx::FontList& GetDefaultNormalFontList() {
35   static base::LazyInstance<gfx::FontList>::Leaky font_list =
36       LAZY_INSTANCE_INITIALIZER;
37   return font_list.Get();
38 }
39 
GetDefaultBoldFontList()40 const gfx::FontList& GetDefaultBoldFontList() {
41   static base::LazyInstance<gfx::FontList>::Leaky font_list =
42       LAZY_INSTANCE_INITIALIZER;
43   if ((font_list.Get().GetFontStyle() & gfx::Font::BOLD) == 0) {
44     font_list.Get() = font_list.Get().
45         DeriveWithStyle(font_list.Get().GetFontStyle() | gfx::Font::BOLD);
46     DCHECK_NE(font_list.Get().GetFontStyle() & gfx::Font::BOLD, 0);
47   }
48   return font_list.Get();
49 }
50 
51 }  // namespace
52 
53 namespace views {
54 
55 // static
56 const int LabelButton::kHoverAnimationDurationMs = 170;
57 
58 // static
59 const char LabelButton::kViewClassName[] = "LabelButton";
60 
LabelButton(ButtonListener * listener,const base::string16 & text)61 LabelButton::LabelButton(ButtonListener* listener, const base::string16& text)
62     : CustomButton(listener),
63       image_(new ImageView()),
64       label_(new Label()),
65       cached_normal_font_list_(GetDefaultNormalFontList()),
66       cached_bold_font_list_(GetDefaultBoldFontList()),
67       button_state_images_(),
68       button_state_colors_(),
69       explicitly_set_colors_(),
70       is_default_(false),
71       style_(STYLE_TEXTBUTTON),
72       border_is_themed_border_(true),
73       image_label_spacing_(kSpacing) {
74   SetAnimationDuration(kHoverAnimationDurationMs);
75   SetText(text);
76 
77   AddChildView(image_);
78   image_->set_interactive(false);
79 
80   AddChildView(label_);
81   label_->SetFontList(cached_normal_font_list_);
82   label_->SetAutoColorReadabilityEnabled(false);
83   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
84 
85   // Initialize the colors, border, and layout.
86   SetStyle(style_);
87 
88   SetAccessibleName(text);
89 }
90 
~LabelButton()91 LabelButton::~LabelButton() {}
92 
GetImage(ButtonState for_state)93 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
94   if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
95     return button_state_images_[STATE_NORMAL];
96   return button_state_images_[for_state];
97 }
98 
SetImage(ButtonState for_state,const gfx::ImageSkia & image)99 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
100   button_state_images_[for_state] = image;
101   UpdateImage();
102 }
103 
GetText() const104 const base::string16& LabelButton::GetText() const {
105   return label_->text();
106 }
107 
SetText(const base::string16 & text)108 void LabelButton::SetText(const base::string16& text) {
109   SetAccessibleName(text);
110   label_->SetText(text);
111 }
112 
SetTextColor(ButtonState for_state,SkColor color)113 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
114   button_state_colors_[for_state] = color;
115   if (for_state == STATE_DISABLED)
116     label_->SetDisabledColor(color);
117   else if (for_state == state())
118     label_->SetEnabledColor(color);
119   explicitly_set_colors_[for_state] = true;
120 }
121 
SetTextShadows(const gfx::ShadowValues & shadows)122 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
123   label_->SetShadows(shadows);
124 }
125 
SetTextSubpixelRenderingEnabled(bool enabled)126 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
127   label_->SetSubpixelRenderingEnabled(enabled);
128 }
129 
GetTextMultiLine() const130 bool LabelButton::GetTextMultiLine() const {
131   return label_->multi_line();
132 }
133 
SetTextMultiLine(bool text_multi_line)134 void LabelButton::SetTextMultiLine(bool text_multi_line) {
135   label_->SetMultiLine(text_multi_line);
136 }
137 
GetFontList() const138 const gfx::FontList& LabelButton::GetFontList() const {
139   return label_->font_list();
140 }
141 
SetFontList(const gfx::FontList & font_list)142 void LabelButton::SetFontList(const gfx::FontList& font_list) {
143   cached_normal_font_list_ = font_list;
144   cached_bold_font_list_ = font_list.DeriveWithStyle(
145       font_list.GetFontStyle() | gfx::Font::BOLD);
146 
147   // STYLE_BUTTON uses bold text to indicate default buttons.
148   label_->SetFontList(
149       style_ == STYLE_BUTTON && is_default_ ?
150       cached_bold_font_list_ : cached_normal_font_list_);
151 }
152 
SetElideBehavior(gfx::ElideBehavior elide_behavior)153 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
154   label_->SetElideBehavior(elide_behavior);
155 }
156 
GetHorizontalAlignment() const157 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
158   return label_->GetHorizontalAlignment();
159 }
160 
SetHorizontalAlignment(gfx::HorizontalAlignment alignment)161 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
162   label_->SetHorizontalAlignment(alignment);
163   InvalidateLayout();
164 }
165 
SetMinSize(const gfx::Size & min_size)166 void LabelButton::SetMinSize(const gfx::Size& min_size) {
167   min_size_ = min_size;
168   ResetCachedPreferredSize();
169 }
170 
SetMaxSize(const gfx::Size & max_size)171 void LabelButton::SetMaxSize(const gfx::Size& max_size) {
172   max_size_ = max_size;
173   ResetCachedPreferredSize();
174 }
175 
SetIsDefault(bool is_default)176 void LabelButton::SetIsDefault(bool is_default) {
177   if (is_default == is_default_)
178     return;
179   is_default_ = is_default;
180   ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
181   is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
182 
183   // STYLE_BUTTON uses bold text to indicate default buttons.
184   if (style_ == STYLE_BUTTON) {
185     label_->SetFontList(
186         is_default ? cached_bold_font_list_ : cached_normal_font_list_);
187   }
188 }
189 
SetStyle(ButtonStyle style)190 void LabelButton::SetStyle(ButtonStyle style) {
191   style_ = style;
192   // Inset the button focus rect from the actual border; roughly match Windows.
193   if (style == STYLE_BUTTON) {
194     SetFocusPainter(scoped_ptr<Painter>());
195   } else {
196     SetFocusPainter(Painter::CreateDashedFocusPainterWithInsets(
197                         gfx::Insets(3, 3, 3, 3)));
198   }
199   if (style == STYLE_BUTTON) {
200     label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
201     SetFocusable(true);
202   }
203   if (style == STYLE_BUTTON)
204     SetMinSize(gfx::Size(70, 33));
205   OnNativeThemeChanged(GetNativeTheme());
206   ResetCachedPreferredSize();
207 }
208 
SetImageLabelSpacing(int spacing)209 void LabelButton::SetImageLabelSpacing(int spacing) {
210   if (spacing == image_label_spacing_)
211     return;
212   image_label_spacing_ = spacing;
213   ResetCachedPreferredSize();
214   InvalidateLayout();
215 }
216 
SetFocusPainter(scoped_ptr<Painter> focus_painter)217 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
218   focus_painter_ = focus_painter.Pass();
219 }
220 
GetPreferredSize() const221 gfx::Size LabelButton::GetPreferredSize() const {
222   if (cached_preferred_size_valid_)
223     return cached_preferred_size_;
224 
225   // Use a temporary label copy for sizing to avoid calculation side-effects.
226   Label label(GetText(), cached_normal_font_list_);
227   label.SetShadows(label_->shadows());
228   label.SetMultiLine(GetTextMultiLine());
229 
230   if (style() == STYLE_BUTTON) {
231     // Some text appears wider when rendered normally than when rendered bold.
232     // Accommodate the widest, as buttons may show bold and shouldn't resize.
233     const int current_width = label.GetPreferredSize().width();
234     label.SetFontList(cached_bold_font_list_);
235     if (label.GetPreferredSize().width() < current_width)
236       label.SetFontList(cached_normal_font_list_);
237   }
238 
239   // Calculate the required size.
240   const gfx::Size image_size(image_->GetPreferredSize());
241   gfx::Size size(label.GetPreferredSize());
242   if (image_size.width() > 0 && size.width() > 0)
243     size.Enlarge(image_label_spacing_, 0);
244   size.SetToMax(gfx::Size(0, image_size.height()));
245   const gfx::Insets insets(GetInsets());
246   size.Enlarge(image_size.width() + insets.width(), insets.height());
247 
248   // Make the size at least as large as the minimum size needed by the border.
249   size.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size());
250 
251   // Increase the minimum size monotonically with the preferred size.
252   size.SetToMax(min_size_);
253   min_size_ = size;
254 
255   // Return the largest known size clamped to the maximum size (if valid).
256   if (max_size_.width() > 0)
257     size.set_width(std::min(max_size_.width(), size.width()));
258   if (max_size_.height() > 0)
259     size.set_height(std::min(max_size_.height(), size.height()));
260 
261   // Cache this computed size, as recomputing it is an expensive operation.
262   cached_preferred_size_valid_ = true;
263   cached_preferred_size_ = size;
264   return cached_preferred_size_;
265 }
266 
GetHeightForWidth(int w) const267 int LabelButton::GetHeightForWidth(int w) const {
268   w -= GetInsets().width();
269   const gfx::Size image_size(image_->GetPreferredSize());
270   w -= image_size.width();
271   if (image_size.width() > 0 && !GetText().empty())
272     w -= image_label_spacing_;
273 
274   int height = std::max(image_size.height(), label_->GetHeightForWidth(w));
275   if (border())
276     height = std::max(height, border()->GetMinimumSize().height());
277 
278   height = std::max(height, min_size_.height());
279   if (max_size_.height() > 0)
280     height = std::min(height, max_size_.height());
281   return height;
282 }
283 
Layout()284 void LabelButton::Layout() {
285   gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
286   if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
287     adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
288         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
289 
290   gfx::Rect child_area(GetChildAreaBounds());
291   child_area.Inset(GetInsets());
292 
293   gfx::Size image_size(image_->GetPreferredSize());
294   image_size.SetToMin(child_area.size());
295 
296   // The label takes any remaining width after sizing the image, unless both
297   // views are centered. In that case, using the tighter preferred label width
298   // avoids wasted space within the label that would look like awkward padding.
299   // Labels can paint over the full button height, including the border height.
300   gfx::Size label_size(child_area.width(), height());
301   if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
302     label_size.set_width(std::max(child_area.width() -
303         image_size.width() - image_label_spacing_, 0));
304     if (adjusted_alignment == gfx::ALIGN_CENTER) {
305       // Ensure multi-line labels paired with images use their available width.
306       label_size.set_width(
307           std::min(label_size.width(), label_->GetPreferredSize().width()));
308     }
309   }
310 
311   gfx::Point image_origin(child_area.origin());
312   image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
313   if (adjusted_alignment == gfx::ALIGN_CENTER) {
314     const int spacing = (image_size.width() > 0 && label_size.width() > 0) ?
315         image_label_spacing_ : 0;
316     const int total_width = image_size.width() + label_size.width() +
317         spacing;
318     image_origin.Offset((child_area.width() - total_width) / 2, 0);
319   } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
320     image_origin.Offset(child_area.width() - image_size.width(), 0);
321   }
322 
323   gfx::Point label_origin(child_area.x(), 0);
324   if (!image_size.IsEmpty() && adjusted_alignment != gfx::ALIGN_RIGHT) {
325     label_origin.set_x(image_origin.x() + image_size.width() +
326         image_label_spacing_);
327   }
328 
329   image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
330   label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
331 }
332 
GetClassName() const333 const char* LabelButton::GetClassName() const {
334   return kViewClassName;
335 }
336 
CreateDefaultBorder() const337 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
338   return scoped_ptr<LabelButtonBorder>(new LabelButtonBorder(style_));
339 }
340 
SetBorder(scoped_ptr<Border> border)341 void LabelButton::SetBorder(scoped_ptr<Border> border) {
342   border_is_themed_border_ = false;
343   View::SetBorder(border.Pass());
344   ResetCachedPreferredSize();
345 }
346 
GetChildAreaBounds()347 gfx::Rect LabelButton::GetChildAreaBounds() {
348   return GetLocalBounds();
349 }
350 
OnPaint(gfx::Canvas * canvas)351 void LabelButton::OnPaint(gfx::Canvas* canvas) {
352   View::OnPaint(canvas);
353   Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
354 }
355 
OnFocus()356 void LabelButton::OnFocus() {
357   View::OnFocus();
358   // Typically the border renders differently when focused.
359   SchedulePaint();
360 }
361 
OnBlur()362 void LabelButton::OnBlur() {
363   View::OnBlur();
364   // Typically the border renders differently when focused.
365   SchedulePaint();
366 }
367 
GetExtraParams(ui::NativeTheme::ExtraParams * params) const368 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
369   params->button.checked = false;
370   params->button.indeterminate = false;
371   params->button.is_default = is_default_;
372   params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
373   params->button.has_border = false;
374   params->button.classic_state = 0;
375   params->button.background_color = label_->background_color();
376 }
377 
ResetColorsFromNativeTheme()378 void LabelButton::ResetColorsFromNativeTheme() {
379   const ui::NativeTheme* theme = GetNativeTheme();
380   SkColor colors[STATE_COUNT] = {
381     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
382     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
383     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
384     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
385   };
386 
387   // Certain styles do not change text color when hovered or pressed.
388   bool constant_text_color = false;
389   // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
390   if (gfx::IsInvertedColorScheme()) {
391     constant_text_color = true;
392     colors[STATE_NORMAL] = SK_ColorWHITE;
393     label_->SetBackgroundColor(SK_ColorBLACK);
394     label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK));
395     label_->SetAutoColorReadabilityEnabled(true);
396     label_->SetShadows(gfx::ShadowValues());
397   } else if (style() == STYLE_BUTTON) {
398     // TODO(erg): This is disabled on desktop linux because of the binary asset
399     // confusion. These details should either be pushed into ui::NativeThemeWin
400     // or should be obsoleted by rendering buttons with paint calls instead of
401     // with static assets. http://crbug.com/350498
402 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
403     constant_text_color = true;
404     colors[STATE_NORMAL] = kStyleButtonTextColor;
405     label_->SetBackgroundColor(theme->GetSystemColor(
406         ui::NativeTheme::kColorId_ButtonBackgroundColor));
407     label_->SetAutoColorReadabilityEnabled(false);
408     label_->SetShadows(gfx::ShadowValues(
409         1, gfx::ShadowValue(gfx::Point(0, 1), 0, kStyleButtonShadowColor)));
410 #endif
411     label_->set_background(NULL);
412   } else {
413     label_->set_background(NULL);
414   }
415 
416   if (constant_text_color)
417     colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
418 
419   for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
420     if (!explicitly_set_colors_[state]) {
421       SetTextColor(static_cast<ButtonState>(state), colors[state]);
422       explicitly_set_colors_[state] = false;
423     }
424   }
425 }
426 
UpdateImage()427 void LabelButton::UpdateImage() {
428   image_->SetImage(GetImage(state()));
429   ResetCachedPreferredSize();
430 }
431 
UpdateThemedBorder()432 void LabelButton::UpdateThemedBorder() {
433   // Don't override borders set by others.
434   if (!border_is_themed_border_)
435     return;
436 
437   scoped_ptr<LabelButtonBorder> label_button_border = CreateDefaultBorder();
438 
439 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
440   views::LinuxUI* linux_ui = views::LinuxUI::instance();
441   if (linux_ui) {
442     SetBorder(linux_ui->CreateNativeBorder(
443         this, label_button_border.Pass()));
444   } else
445 #endif
446   {
447     SetBorder(label_button_border.PassAs<Border>());
448   }
449 
450   border_is_themed_border_ = true;
451 }
452 
StateChanged()453 void LabelButton::StateChanged() {
454   const gfx::Size previous_image_size(image_->GetPreferredSize());
455   UpdateImage();
456   const SkColor color = button_state_colors_[state()];
457   if (state() != STATE_DISABLED && label_->enabled_color() != color)
458     label_->SetEnabledColor(color);
459   label_->SetEnabled(state() != STATE_DISABLED);
460   if (image_->GetPreferredSize() != previous_image_size)
461     Layout();
462 }
463 
ChildPreferredSizeChanged(View * child)464 void LabelButton::ChildPreferredSizeChanged(View* child) {
465   ResetCachedPreferredSize();
466   PreferredSizeChanged();
467 }
468 
OnNativeThemeChanged(const ui::NativeTheme * theme)469 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
470   ResetColorsFromNativeTheme();
471   UpdateThemedBorder();
472   // Invalidate the layout to pickup the new insets from the border.
473   InvalidateLayout();
474 }
475 
GetThemePart() const476 ui::NativeTheme::Part LabelButton::GetThemePart() const {
477   return ui::NativeTheme::kPushButton;
478 }
479 
GetThemePaintRect() const480 gfx::Rect LabelButton::GetThemePaintRect() const {
481   return GetLocalBounds();
482 }
483 
GetThemeState(ui::NativeTheme::ExtraParams * params) const484 ui::NativeTheme::State LabelButton::GetThemeState(
485     ui::NativeTheme::ExtraParams* params) const {
486   GetExtraParams(params);
487   switch (state()) {
488     case STATE_NORMAL:   return ui::NativeTheme::kNormal;
489     case STATE_HOVERED:  return ui::NativeTheme::kHovered;
490     case STATE_PRESSED:  return ui::NativeTheme::kPressed;
491     case STATE_DISABLED: return ui::NativeTheme::kDisabled;
492     case STATE_COUNT:    NOTREACHED() << "Unknown state: " << state();
493   }
494   return ui::NativeTheme::kNormal;
495 }
496 
GetThemeAnimation() const497 const gfx::Animation* LabelButton::GetThemeAnimation() const {
498   return hover_animation_.get();
499 }
500 
GetBackgroundThemeState(ui::NativeTheme::ExtraParams * params) const501 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
502     ui::NativeTheme::ExtraParams* params) const {
503   GetExtraParams(params);
504   return ui::NativeTheme::kNormal;
505 }
506 
GetForegroundThemeState(ui::NativeTheme::ExtraParams * params) const507 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
508     ui::NativeTheme::ExtraParams* params) const {
509   GetExtraParams(params);
510   return ui::NativeTheme::kHovered;
511 }
512 
ResetCachedPreferredSize()513 void LabelButton::ResetCachedPreferredSize() {
514   cached_preferred_size_valid_ = false;
515   cached_preferred_size_= gfx::Size();
516 }
517 
518 }  // namespace views
519