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/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" 19 20 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) 21 #include "ui/views/linux_ui/linux_ui.h" 22 #endif 23 24 namespace { 25 26 // The spacing between the icon and text. 27 const int kSpacing = 5; 28 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; 33 #endif 34 35 } // namespace 36 37 namespace views { 38 39 // static 40 const int LabelButton::kHoverAnimationDurationMs = 170; 41 42 // static 43 const char LabelButton::kViewClassName[] = "LabelButton"; 44 45 LabelButton::LabelButton(ButtonListener* listener, const base::string16& text) 46 : CustomButton(listener), 47 image_(new ImageView()), 48 label_(new Label()), 49 button_state_images_(), 50 button_state_colors_(), 51 explicitly_set_colors_(), 52 is_default_(false), 53 style_(STYLE_TEXTBUTTON), 54 border_is_themed_border_(true) { 55 SetAnimationDuration(kHoverAnimationDurationMs); 56 SetText(text); 57 SetFontList(gfx::FontList()); 58 59 AddChildView(image_); 60 image_->set_interactive(false); 61 62 AddChildView(label_); 63 label_->SetAutoColorReadabilityEnabled(false); 64 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 65 66 // Initialize the colors, border, and layout. 67 SetStyle(style_); 68 69 SetAccessibleName(text); 70 } 71 72 LabelButton::~LabelButton() {} 73 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]; 78 } 79 80 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) { 81 button_state_images_[for_state] = image; 82 UpdateImage(); 83 } 84 85 const base::string16& LabelButton::GetText() const { 86 return label_->text(); 87 } 88 89 void LabelButton::SetText(const base::string16& text) { 90 SetAccessibleName(text); 91 label_->SetText(text); 92 } 93 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; 101 } 102 103 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) { 104 label_->set_shadows(shadows); 105 } 106 107 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) { 108 label_->set_subpixel_rendering_enabled(enabled); 109 } 110 111 bool LabelButton::GetTextMultiLine() const { 112 return label_->is_multi_line(); 113 } 114 115 void LabelButton::SetTextMultiLine(bool text_multi_line) { 116 label_->SetMultiLine(text_multi_line); 117 } 118 119 const gfx::FontList& LabelButton::GetFontList() const { 120 return label_->font_list(); 121 } 122 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); 127 128 // STYLE_BUTTON uses bold text to indicate default buttons. 129 label_->SetFontList( 130 style_ == STYLE_BUTTON && is_default_ ? 131 cached_bold_font_list_ : cached_normal_font_list_); 132 } 133 134 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) { 135 label_->SetElideBehavior(elide_behavior); 136 } 137 138 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const { 139 return label_->GetHorizontalAlignment(); 140 } 141 142 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { 143 label_->SetHorizontalAlignment(alignment); 144 InvalidateLayout(); 145 } 146 147 void LabelButton::SetDirectionalityMode(gfx::DirectionalityMode mode) { 148 label_->set_directionality_mode(mode); 149 } 150 151 void LabelButton::SetIsDefault(bool is_default) { 152 if (is_default == is_default_) 153 return; 154 is_default_ = is_default; 155 ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE); 156 is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel); 157 158 // STYLE_BUTTON uses bold text to indicate default buttons. 159 if (style_ == STYLE_BUTTON) { 160 label_->SetFontList( 161 is_default ? cached_bold_font_list_ : cached_normal_font_list_); 162 } 163 } 164 165 void LabelButton::SetStyle(ButtonStyle style) { 166 style_ = style; 167 // Inset the button focus rect from the actual border; roughly match Windows. 168 if (style == STYLE_BUTTON) { 169 SetFocusPainter(scoped_ptr<Painter>()); 170 } else { 171 SetFocusPainter(Painter::CreateDashedFocusPainterWithInsets( 172 gfx::Insets(3, 3, 3, 3))); 173 } 174 if (style == STYLE_BUTTON) { 175 label_->SetHorizontalAlignment(gfx::ALIGN_CENTER); 176 SetFocusable(true); 177 } 178 if (style == STYLE_BUTTON) 179 set_min_size(gfx::Size(70, 33)); 180 181 OnNativeThemeChanged(GetNativeTheme()); 182 } 183 184 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) { 185 focus_painter_ = focus_painter.Pass(); 186 } 187 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()); 193 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_); 201 } 202 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)); 208 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()); 216 217 // Make the size at least as large as the minimum size needed by the border. 218 size.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size()); 219 220 // Increase the minimum size monotonically with the preferred size. 221 size.SetToMax(min_size_); 222 min_size_ = size; 223 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())); 229 return size; 230 } 231 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; 237 238 gfx::Rect child_area(GetChildAreaBounds()); 239 child_area.Inset(GetInsets()); 240 241 gfx::Size image_size(image_->GetPreferredSize()); 242 image_size.SetToMin(child_area.size()); 243 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 // Labels can paint over the full button height, including the border height. 248 gfx::Size label_size(child_area.width(), height()); 249 if (!image_size.IsEmpty() && !label_size.IsEmpty()) { 250 label_size.set_width( 251 std::max(child_area.width() - image_size.width() - kSpacing, 0)); 252 if (adjusted_alignment == gfx::ALIGN_CENTER) { 253 // Ensure multi-line labels paired with images use their available width. 254 if (GetTextMultiLine()) 255 label_->SizeToFit(label_size.width()); 256 label_size.set_width( 257 std::min(label_size.width(), label_->GetPreferredSize().width())); 258 } 259 } 260 261 gfx::Point image_origin(child_area.origin()); 262 image_origin.Offset(0, (child_area.height() - image_size.height()) / 2); 263 if (adjusted_alignment == gfx::ALIGN_CENTER) { 264 const int total_width = image_size.width() + label_size.width() + 265 ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0); 266 image_origin.Offset((child_area.width() - total_width) / 2, 0); 267 } else if (adjusted_alignment == gfx::ALIGN_RIGHT) { 268 image_origin.Offset(child_area.width() - image_size.width(), 0); 269 } 270 271 gfx::Point label_origin(child_area.x(), 0); 272 if (!image_size.IsEmpty() && adjusted_alignment != gfx::ALIGN_RIGHT) 273 label_origin.set_x(image_origin.x() + image_size.width() + kSpacing); 274 275 image_->SetBoundsRect(gfx::Rect(image_origin, image_size)); 276 label_->SetBoundsRect(gfx::Rect(label_origin, label_size)); 277 } 278 279 const char* LabelButton::GetClassName() const { 280 return kViewClassName; 281 } 282 283 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const { 284 return scoped_ptr<LabelButtonBorder>(new LabelButtonBorder(style_)); 285 } 286 287 void LabelButton::SetBorder(scoped_ptr<Border> border) { 288 border_is_themed_border_ = false; 289 View::SetBorder(border.Pass()); 290 } 291 292 gfx::Rect LabelButton::GetChildAreaBounds() { 293 return GetLocalBounds(); 294 } 295 296 void LabelButton::OnPaint(gfx::Canvas* canvas) { 297 View::OnPaint(canvas); 298 Painter::PaintFocusPainter(this, canvas, focus_painter_.get()); 299 } 300 301 void LabelButton::OnFocus() { 302 View::OnFocus(); 303 // Typically the border renders differently when focused. 304 SchedulePaint(); 305 } 306 307 void LabelButton::OnBlur() { 308 View::OnBlur(); 309 // Typically the border renders differently when focused. 310 SchedulePaint(); 311 } 312 313 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const { 314 params->button.checked = false; 315 params->button.indeterminate = false; 316 params->button.is_default = is_default_; 317 params->button.is_focused = HasFocus() && IsAccessibilityFocusable(); 318 params->button.has_border = false; 319 params->button.classic_state = 0; 320 params->button.background_color = label_->background_color(); 321 } 322 323 void LabelButton::ResetColorsFromNativeTheme() { 324 const ui::NativeTheme* theme = GetNativeTheme(); 325 SkColor colors[STATE_COUNT] = { 326 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor), 327 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor), 328 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor), 329 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor), 330 }; 331 332 // Certain styles do not change text color when hovered or pressed. 333 bool constant_text_color = false; 334 // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON. 335 if (gfx::IsInvertedColorScheme()) { 336 constant_text_color = true; 337 colors[STATE_NORMAL] = SK_ColorWHITE; 338 label_->SetBackgroundColor(SK_ColorBLACK); 339 label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK)); 340 label_->SetAutoColorReadabilityEnabled(true); 341 label_->set_shadows(gfx::ShadowValues()); 342 } else if (style() == STYLE_BUTTON) { 343 // TODO(erg): This is disabled on desktop linux because of the binary asset 344 // confusion. These details should either be pushed into ui::NativeThemeWin 345 // or should be obsoleted by rendering buttons with paint calls instead of 346 // with static assets. http://crbug.com/350498 347 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS)) 348 constant_text_color = true; 349 colors[STATE_NORMAL] = kStyleButtonTextColor; 350 label_->SetBackgroundColor(theme->GetSystemColor( 351 ui::NativeTheme::kColorId_ButtonBackgroundColor)); 352 label_->SetAutoColorReadabilityEnabled(false); 353 label_->set_shadows(gfx::ShadowValues(1, 354 gfx::ShadowValue(gfx::Point(0, 1), 0, kStyleButtonShadowColor))); 355 #endif 356 label_->set_background(NULL); 357 } else { 358 label_->set_background(NULL); 359 } 360 361 if (constant_text_color) 362 colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL]; 363 364 for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) { 365 if (!explicitly_set_colors_[state]) { 366 SetTextColor(static_cast<ButtonState>(state), colors[state]); 367 explicitly_set_colors_[state] = false; 368 } 369 } 370 } 371 372 void LabelButton::UpdateImage() { 373 image_->SetImage(GetImage(state())); 374 } 375 376 void LabelButton::UpdateThemedBorder() { 377 // Don't override borders set by others. 378 if (!border_is_themed_border_) 379 return; 380 381 scoped_ptr<LabelButtonBorder> label_button_border = CreateDefaultBorder(); 382 383 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) 384 views::LinuxUI* linux_ui = views::LinuxUI::instance(); 385 if (linux_ui) { 386 SetBorder(linux_ui->CreateNativeBorder( 387 this, label_button_border.Pass())); 388 } else 389 #endif 390 { 391 SetBorder(label_button_border.PassAs<Border>()); 392 } 393 394 border_is_themed_border_ = true; 395 } 396 397 void LabelButton::StateChanged() { 398 const gfx::Size previous_image_size(image_->GetPreferredSize()); 399 UpdateImage(); 400 const SkColor color = button_state_colors_[state()]; 401 if (state() != STATE_DISABLED && label_->enabled_color() != color) 402 label_->SetEnabledColor(color); 403 label_->SetEnabled(state() != STATE_DISABLED); 404 if (image_->GetPreferredSize() != previous_image_size) 405 Layout(); 406 } 407 408 void LabelButton::ChildPreferredSizeChanged(View* child) { 409 PreferredSizeChanged(); 410 } 411 412 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) { 413 ResetColorsFromNativeTheme(); 414 UpdateThemedBorder(); 415 // Invalidate the layout to pickup the new insets from the border. 416 InvalidateLayout(); 417 } 418 419 ui::NativeTheme::Part LabelButton::GetThemePart() const { 420 return ui::NativeTheme::kPushButton; 421 } 422 423 gfx::Rect LabelButton::GetThemePaintRect() const { 424 return GetLocalBounds(); 425 } 426 427 ui::NativeTheme::State LabelButton::GetThemeState( 428 ui::NativeTheme::ExtraParams* params) const { 429 GetExtraParams(params); 430 switch (state()) { 431 case STATE_NORMAL: return ui::NativeTheme::kNormal; 432 case STATE_HOVERED: return ui::NativeTheme::kHovered; 433 case STATE_PRESSED: return ui::NativeTheme::kPressed; 434 case STATE_DISABLED: return ui::NativeTheme::kDisabled; 435 case STATE_COUNT: NOTREACHED() << "Unknown state: " << state(); 436 } 437 return ui::NativeTheme::kNormal; 438 } 439 440 const gfx::Animation* LabelButton::GetThemeAnimation() const { 441 return hover_animation_.get(); 442 } 443 444 ui::NativeTheme::State LabelButton::GetBackgroundThemeState( 445 ui::NativeTheme::ExtraParams* params) const { 446 GetExtraParams(params); 447 return ui::NativeTheme::kNormal; 448 } 449 450 ui::NativeTheme::State LabelButton::GetForegroundThemeState( 451 ui::NativeTheme::ExtraParams* params) const { 452 GetExtraParams(params); 453 return ui::NativeTheme::kHovered; 454 } 455 456 } // namespace views 457