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/logging.h"
      8 #include "grit/ui_resources.h"
      9 #include "ui/base/animation/throb_animation.h"
     10 #include "ui/base/resource/resource_bundle.h"
     11 #include "ui/gfx/sys_color_change_listener.h"
     12 #include "ui/native_theme/native_theme.h"
     13 #include "ui/views/controls/button/label_button_border.h"
     14 #include "ui/views/focus_border.h"
     15 #include "ui/views/window/dialog_delegate.h"
     16 
     17 #if defined(OS_WIN)
     18 #include "ui/native_theme/native_theme_win.h"
     19 #endif
     20 
     21 namespace {
     22 
     23 // The spacing between the icon and text.
     24 const int kSpacing = 5;
     25 
     26 // The length of the hover fade animation.
     27 const int kHoverAnimationDurationMs = 170;
     28 
     29 // Default text and shadow colors for STYLE_BUTTON.
     30 const SkColor kStyleButtonTextColor = SK_ColorBLACK;
     31 const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
     32 
     33 }  // namespace
     34 
     35 namespace views {
     36 
     37 // static
     38 const char LabelButton::kViewClassName[] = "LabelButton";
     39 
     40 LabelButton::LabelButton(ButtonListener* listener, const string16& text)
     41     : CustomButton(listener),
     42       image_(new ImageView()),
     43       label_(new Label()),
     44       button_state_images_(),
     45       button_state_colors_(),
     46       explicitly_set_colors_(),
     47       is_default_(false),
     48       style_(STYLE_TEXTBUTTON) {
     49   SetAnimationDuration(kHoverAnimationDurationMs);
     50   SetText(text);
     51 
     52   AddChildView(image_);
     53   image_->set_interactive(false);
     54 
     55   AddChildView(label_);
     56   label_->SetAutoColorReadabilityEnabled(false);
     57   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     58 
     59   // Initialize the colors, border, and layout.
     60   SetStyle(style_);
     61 
     62   SetAccessibleName(text);
     63 }
     64 
     65 LabelButton::~LabelButton() {}
     66 
     67 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
     68   if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
     69     return button_state_images_[STATE_NORMAL];
     70   return button_state_images_[for_state];
     71 }
     72 
     73 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
     74   button_state_images_[for_state] = image;
     75   UpdateImage();
     76 }
     77 
     78 const string16& LabelButton::GetText() const {
     79   return label_->text();
     80 }
     81 
     82 void LabelButton::SetText(const string16& text) {
     83   SetAccessibleName(text);
     84   label_->SetText(text);
     85 }
     86 
     87 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
     88   button_state_colors_[for_state] = color;
     89   if (for_state == STATE_DISABLED)
     90     label_->SetDisabledColor(color);
     91   else if (for_state == state())
     92     label_->SetEnabledColor(color);
     93   explicitly_set_colors_[for_state] = true;
     94 }
     95 
     96 bool LabelButton::GetTextMultiLine() const {
     97   return label_->is_multi_line();
     98 }
     99 
    100 void LabelButton::SetTextMultiLine(bool text_multi_line) {
    101   label_->SetMultiLine(text_multi_line);
    102 }
    103 
    104 const gfx::Font& LabelButton::GetFont() const {
    105   return label_->font();
    106 }
    107 
    108 void LabelButton::SetFont(const gfx::Font& font) {
    109   label_->SetFont(font);
    110 }
    111 
    112 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
    113   return label_->horizontal_alignment();
    114 }
    115 
    116 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
    117   label_->SetHorizontalAlignment(alignment);
    118   InvalidateLayout();
    119 }
    120 
    121 void LabelButton::SetIsDefault(bool is_default) {
    122   if (is_default == is_default_)
    123     return;
    124   is_default_ = is_default;
    125   ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
    126   is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
    127 
    128   // STYLE_BUTTON uses bold text to indicate default buttons.
    129   if (style_ == STYLE_BUTTON) {
    130     int style = label_->font().GetStyle();
    131     style = is_default ? style | gfx::Font::BOLD : style & !gfx::Font::BOLD;
    132     label_->SetFont(label_->font().DeriveFont(0, style));
    133   }
    134 }
    135 
    136 void LabelButton::SetStyle(ButtonStyle style) {
    137   // Use the new button style instead of the native button style.
    138   // TODO(msw): Officialy deprecate and remove STYLE_NATIVE_TEXTBUTTON.
    139   if (DialogDelegate::UseNewStyle() && style == STYLE_NATIVE_TEXTBUTTON)
    140     style = STYLE_BUTTON;
    141 
    142   style_ = style;
    143   set_border(new LabelButtonBorder(style));
    144   // Inset the button focus rect from the actual border; roughly match Windows.
    145   set_focus_border(style == STYLE_BUTTON ?
    146       NULL : FocusBorder::CreateDashedFocusBorder(3, 3, 3, 3));
    147   if (style == STYLE_BUTTON || style == STYLE_NATIVE_TEXTBUTTON) {
    148     label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
    149     set_focusable(true);
    150   }
    151   if (style == STYLE_BUTTON)
    152     set_min_size(gfx::Size(70, 33));
    153   // Invalidate the layout to pickup the new insets from the border.
    154   InvalidateLayout();
    155   ResetColorsFromNativeTheme();
    156 }
    157 
    158 gfx::Size LabelButton::GetPreferredSize() {
    159   // Use a temporary label copy for sizing to avoid calculation side-effects.
    160   gfx::Font font = GetFont();
    161   Label label(GetText(), font);
    162   label.SetMultiLine(GetTextMultiLine());
    163 
    164   if (style() == STYLE_BUTTON) {
    165     // Some text appears wider when rendered normally than when rendered bold.
    166     // Accommodate the widest, as buttons may show bold and shouldn't resize.
    167     const int current_width = label.GetPreferredSize().width();
    168     label.SetFont(font.DeriveFont(0, font.GetStyle() ^ gfx::Font::BOLD));
    169     if (label.GetPreferredSize().width() < current_width)
    170       label.SetFont(font);
    171   }
    172 
    173   // Resize multi-line labels given the current limited available width.
    174   const gfx::Size image_size(image_->GetPreferredSize());
    175   const int image_width = image_size.width();
    176   if (GetTextMultiLine() && (width() > image_width + kSpacing))
    177     label.SizeToFit(width() - image_width - (image_width > 0 ? kSpacing : 0));
    178 
    179   // Calculate the required size.
    180   gfx::Size size(label.GetPreferredSize());
    181   if (image_width > 0 && size.width() > 0)
    182     size.Enlarge(kSpacing, 0);
    183   size.SetToMax(gfx::Size(0, image_size.height()));
    184   const gfx::Insets insets(GetInsets());
    185   size.Enlarge(image_size.width() + insets.width(), insets.height());
    186 
    187   // Increase the minimum size monotonically with the preferred size.
    188   size.SetToMax(min_size_);
    189   min_size_ = size;
    190 
    191   // Return the largest known size clamped to the maximum size (if valid).
    192   if (max_size_.width() > 0)
    193     size.set_width(std::min(max_size_.width(), size.width()));
    194   if (max_size_.height() > 0)
    195     size.set_height(std::min(max_size_.height(), size.height()));
    196   return size;
    197 }
    198 
    199 void LabelButton::Layout() {
    200   gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
    201   if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
    202     adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
    203         gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
    204 
    205   gfx::Rect child_area(GetLocalBounds());
    206   child_area.Inset(GetInsets());
    207 
    208   gfx::Size image_size(image_->GetPreferredSize());
    209   image_size.set_width(std::min(image_size.width(), child_area.width()));
    210   image_size.set_height(std::min(image_size.height(), child_area.height()));
    211 
    212   // The label takes any remaining width after sizing the image, unless both
    213   // views are centered. In that case, using the tighter preferred label width
    214   // avoids wasted space within the label that would look like awkward padding.
    215   gfx::Size label_size(child_area.size());
    216   if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
    217     label_size.set_width(
    218         std::max(child_area.width() - image_size.width() - kSpacing, 0));
    219     if (adjusted_alignment == gfx::ALIGN_CENTER) {
    220       // Ensure multi-line labels paired with images use their available width.
    221       if (GetTextMultiLine())
    222         label_->SizeToFit(label_size.width());
    223       label_size.set_width(
    224           std::min(label_size.width(), label_->GetPreferredSize().width()));
    225     }
    226   }
    227 
    228   gfx::Point image_origin(child_area.origin());
    229   image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
    230   if (adjusted_alignment == gfx::ALIGN_CENTER) {
    231     const int total_width = image_size.width() + label_size.width() +
    232         ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
    233     image_origin.Offset((child_area.width() - total_width) / 2, 0);
    234   } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
    235     image_origin.Offset(child_area.width() - image_size.width(), 0);
    236   }
    237 
    238   gfx::Point label_origin(child_area.origin());
    239   if (!image_size.IsEmpty() &&adjusted_alignment != gfx::ALIGN_RIGHT)
    240     label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
    241 
    242   image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
    243   label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
    244 }
    245 
    246 const char* LabelButton::GetClassName() const {
    247   return kViewClassName;
    248 }
    249 
    250 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
    251   params->button.checked = false;
    252   params->button.indeterminate = false;
    253   params->button.is_default = is_default_;
    254   params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
    255   params->button.has_border = style() == STYLE_NATIVE_TEXTBUTTON;
    256   params->button.classic_state = 0;
    257   params->button.background_color = label()->background_color();
    258 }
    259 
    260 void LabelButton::UpdateImage() {
    261   image_->SetImage(GetImage(state()));
    262 }
    263 
    264 void LabelButton::ResetColorsFromNativeTheme() {
    265   const ui::NativeTheme* theme = GetNativeTheme();
    266   SkColor colors[STATE_COUNT] = {
    267     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
    268     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
    269     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
    270     theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
    271   };
    272 
    273   // Certain styles do not change text color when hovered or pressed.
    274   bool constant_text_color = false;
    275 #if defined(OS_WIN)
    276   constant_text_color |= (style() == STYLE_NATIVE_TEXTBUTTON &&
    277                           theme == ui::NativeThemeWin::instance());
    278 #endif
    279 
    280   label_->SetBackgroundColor(theme->GetSystemColor(
    281       ui::NativeTheme::kColorId_ButtonBackgroundColor));
    282 
    283   // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
    284   if (gfx::IsInvertedColorScheme()) {
    285     constant_text_color = true;
    286     colors[STATE_NORMAL] = SK_ColorWHITE;
    287     label_->SetBackgroundColor(SK_ColorBLACK);
    288     label_->SetAutoColorReadabilityEnabled(true);
    289     label_->ClearEmbellishing();
    290   } else if (style() == STYLE_BUTTON) {
    291     constant_text_color = true;
    292     colors[STATE_NORMAL] = kStyleButtonTextColor;
    293     label_->SetAutoColorReadabilityEnabled(false);
    294     label_->SetShadowColors(kStyleButtonShadowColor, kStyleButtonShadowColor);
    295     label_->SetShadowOffset(0, 1);
    296   }
    297 
    298   if (constant_text_color)
    299     colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
    300 
    301   for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
    302     if (!explicitly_set_colors_[state]) {
    303       SetTextColor(static_cast<ButtonState>(state), colors[state]);
    304       explicitly_set_colors_[state] = false;
    305     }
    306   }
    307 }
    308 
    309 void LabelButton::StateChanged() {
    310   const gfx::Size previous_image_size(image_->GetPreferredSize());
    311   UpdateImage();
    312   const SkColor color = button_state_colors_[state()];
    313   if (state() != STATE_DISABLED && label_->enabled_color() != color)
    314     label_->SetEnabledColor(color);
    315   label_->SetEnabled(state() != STATE_DISABLED);
    316   if (image_->GetPreferredSize() != previous_image_size)
    317     Layout();
    318 }
    319 
    320 void LabelButton::ChildPreferredSizeChanged(View* child) {
    321   PreferredSizeChanged();
    322 }
    323 
    324 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
    325   ResetColorsFromNativeTheme();
    326 }
    327 
    328 ui::NativeTheme::Part LabelButton::GetThemePart() const {
    329   return ui::NativeTheme::kPushButton;
    330 }
    331 
    332 gfx::Rect LabelButton::GetThemePaintRect() const {
    333   return GetLocalBounds();
    334 }
    335 
    336 ui::NativeTheme::State LabelButton::GetThemeState(
    337     ui::NativeTheme::ExtraParams* params) const {
    338   GetExtraParams(params);
    339   switch (state()) {
    340     case STATE_NORMAL:   return ui::NativeTheme::kNormal;
    341     case STATE_HOVERED:  return ui::NativeTheme::kHovered;
    342     case STATE_PRESSED:  return ui::NativeTheme::kPressed;
    343     case STATE_DISABLED: return ui::NativeTheme::kDisabled;
    344     case STATE_COUNT:    NOTREACHED() << "Unknown state: " << state();
    345   }
    346   return ui::NativeTheme::kNormal;
    347 }
    348 
    349 const ui::Animation* LabelButton::GetThemeAnimation() const {
    350 #if defined(OS_WIN)
    351   if (style() == STYLE_NATIVE_TEXTBUTTON &&
    352       GetNativeTheme() == ui::NativeThemeWin::instance()) {
    353     return ui::NativeThemeWin::instance()->IsThemingActive() ?
    354         hover_animation_.get() : NULL;
    355   }
    356 #endif
    357   return hover_animation_.get();
    358 }
    359 
    360 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
    361     ui::NativeTheme::ExtraParams* params) const {
    362   GetExtraParams(params);
    363   return ui::NativeTheme::kNormal;
    364 }
    365 
    366 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
    367     ui::NativeTheme::ExtraParams* params) const {
    368   GetExtraParams(params);
    369   return ui::NativeTheme::kHovered;
    370 }
    371 
    372 }  // namespace views
    373