Home | History | Annotate | Download | only in button
      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 
     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 
     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 
     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 
     91 LabelButton::~LabelButton() {}
     92 
     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 
     99 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
    100   button_state_images_[for_state] = image;
    101   UpdateImage();
    102 }
    103 
    104 const base::string16& LabelButton::GetText() const {
    105   return label_->text();
    106 }
    107 
    108 void LabelButton::SetText(const base::string16& text) {
    109   SetAccessibleName(text);
    110   label_->SetText(text);
    111 }
    112 
    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 
    122 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
    123   label_->SetShadows(shadows);
    124 }
    125 
    126 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
    127   label_->SetSubpixelRenderingEnabled(enabled);
    128 }
    129 
    130 bool LabelButton::GetTextMultiLine() const {
    131   return label_->multi_line();
    132 }
    133 
    134 void LabelButton::SetTextMultiLine(bool text_multi_line) {
    135   label_->SetMultiLine(text_multi_line);
    136 }
    137 
    138 const gfx::FontList& LabelButton::GetFontList() const {
    139   return label_->font_list();
    140 }
    141 
    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 
    153 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
    154   label_->SetElideBehavior(elide_behavior);
    155 }
    156 
    157 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
    158   return label_->GetHorizontalAlignment();
    159 }
    160 
    161 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
    162   label_->SetHorizontalAlignment(alignment);
    163   InvalidateLayout();
    164 }
    165 
    166 void LabelButton::SetMinSize(const gfx::Size& min_size) {
    167   min_size_ = min_size;
    168   ResetCachedPreferredSize();
    169 }
    170 
    171 void LabelButton::SetMaxSize(const gfx::Size& max_size) {
    172   max_size_ = max_size;
    173   ResetCachedPreferredSize();
    174 }
    175 
    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 
    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 
    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 
    217 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
    218   focus_painter_ = focus_painter.Pass();
    219 }
    220 
    221 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 
    267 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 
    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 
    333 const char* LabelButton::GetClassName() const {
    334   return kViewClassName;
    335 }
    336 
    337 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
    338   return scoped_ptr<LabelButtonBorder>(new LabelButtonBorder(style_));
    339 }
    340 
    341 void LabelButton::SetBorder(scoped_ptr<Border> border) {
    342   border_is_themed_border_ = false;
    343   View::SetBorder(border.Pass());
    344   ResetCachedPreferredSize();
    345 }
    346 
    347 gfx::Rect LabelButton::GetChildAreaBounds() {
    348   return GetLocalBounds();
    349 }
    350 
    351 void LabelButton::OnPaint(gfx::Canvas* canvas) {
    352   View::OnPaint(canvas);
    353   Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
    354 }
    355 
    356 void LabelButton::OnFocus() {
    357   View::OnFocus();
    358   // Typically the border renders differently when focused.
    359   SchedulePaint();
    360 }
    361 
    362 void LabelButton::OnBlur() {
    363   View::OnBlur();
    364   // Typically the border renders differently when focused.
    365   SchedulePaint();
    366 }
    367 
    368 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 
    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 
    427 void LabelButton::UpdateImage() {
    428   image_->SetImage(GetImage(state()));
    429   ResetCachedPreferredSize();
    430 }
    431 
    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 
    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 
    464 void LabelButton::ChildPreferredSizeChanged(View* child) {
    465   ResetCachedPreferredSize();
    466   PreferredSizeChanged();
    467 }
    468 
    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 
    476 ui::NativeTheme::Part LabelButton::GetThemePart() const {
    477   return ui::NativeTheme::kPushButton;
    478 }
    479 
    480 gfx::Rect LabelButton::GetThemePaintRect() const {
    481   return GetLocalBounds();
    482 }
    483 
    484 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 
    497 const gfx::Animation* LabelButton::GetThemeAnimation() const {
    498   return hover_animation_.get();
    499 }
    500 
    501 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
    502     ui::NativeTheme::ExtraParams* params) const {
    503   GetExtraParams(params);
    504   return ui::NativeTheme::kNormal;
    505 }
    506 
    507 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
    508     ui::NativeTheme::ExtraParams* params) const {
    509   GetExtraParams(params);
    510   return ui::NativeTheme::kHovered;
    511 }
    512 
    513 void LabelButton::ResetCachedPreferredSize() {
    514   cached_preferred_size_valid_ = false;
    515   cached_preferred_size_= gfx::Size();
    516 }
    517 
    518 }  // namespace views
    519