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/text_button.h" 6 7 #include <algorithm> 8 9 #include "base/logging.h" 10 #include "grit/ui_resources.h" 11 #include "ui/base/animation/throb_animation.h" 12 #include "ui/base/resource/resource_bundle.h" 13 #include "ui/gfx/canvas.h" 14 #include "ui/gfx/image/image.h" 15 #include "ui/views/controls/button/button.h" 16 #include "ui/views/focus_border.h" 17 #include "ui/views/widget/widget.h" 18 19 #if defined(OS_WIN) 20 #include "skia/ext/skia_utils_win.h" 21 #include "ui/gfx/platform_font_win.h" 22 #include "ui/native_theme/native_theme_win.h" 23 #endif 24 25 namespace views { 26 27 namespace { 28 29 // Default space between the icon and text. 30 const int kDefaultIconTextSpacing = 5; 31 32 // Preferred padding between text and edge. 33 const int kPreferredPaddingHorizontal = 6; 34 const int kPreferredPaddingVertical = 5; 35 36 // Preferred padding between text and edge for NativeTheme border. 37 const int kPreferredNativeThemePaddingHorizontal = 12; 38 const int kPreferredNativeThemePaddingVertical = 5; 39 40 // By default the focus rect is drawn at the border of the view. 41 // For a button, we inset the focus rect by 3 pixels so that it 42 // doesn't draw on top of the button's border. This roughly matches 43 // how the Windows native focus rect for buttons looks. A subclass 44 // that draws a button with different padding may need to 45 // override OnPaintFocusBorder and do something different. 46 const int kFocusRectInset = 3; 47 48 // How long the hover fade animation should last. 49 const int kHoverAnimationDurationMs = 170; 50 51 #if defined(OS_WIN) 52 // These sizes are from http://msdn.microsoft.com/en-us/library/aa511279.aspx 53 const int kMinWidthDLUs = 50; 54 const int kMinHeightDLUs = 14; 55 #endif 56 57 // The default hot and pushed button image IDs; normal has none by default. 58 const int kHotImages[] = IMAGE_GRID(IDR_TEXTBUTTON_HOVER); 59 const int kPushedImages[] = IMAGE_GRID(IDR_TEXTBUTTON_PRESSED); 60 61 } // namespace 62 63 // static 64 const char TextButtonBase::kViewClassName[] = "TextButtonBase"; 65 // static 66 const char TextButton::kViewClassName[] = "TextButton"; 67 68 //////////////////////////////////////////////////////////////////////////////// 69 // 70 // TextButtonBorder 71 // 72 //////////////////////////////////////////////////////////////////////////////// 73 74 TextButtonBorder::TextButtonBorder() { 75 } 76 77 TextButtonBorder::~TextButtonBorder() { 78 } 79 80 gfx::Insets TextButtonBorder::GetInsets() const { 81 return insets_; 82 } 83 84 void TextButtonBorder::SetInsets(const gfx::Insets& insets) { 85 insets_ = insets; 86 } 87 88 TextButtonBorder* TextButtonBorder::AsTextButtonBorder() { 89 return this; 90 } 91 92 const TextButtonBorder* TextButtonBorder::AsTextButtonBorder() const { 93 return this; 94 } 95 96 //////////////////////////////////////////////////////////////////////////////// 97 // 98 // TextButtonDefaultBorder - constructors, destructors, initialization 99 // 100 //////////////////////////////////////////////////////////////////////////////// 101 102 TextButtonDefaultBorder::TextButtonDefaultBorder() 103 : vertical_padding_(kPreferredPaddingVertical) { 104 set_hot_painter(Painter::CreateImageGridPainter(kHotImages)); 105 set_pushed_painter(Painter::CreateImageGridPainter(kPushedImages)); 106 SetInsets(gfx::Insets(vertical_padding_, kPreferredPaddingHorizontal, 107 vertical_padding_, kPreferredPaddingHorizontal)); 108 } 109 110 TextButtonDefaultBorder::~TextButtonDefaultBorder() { 111 } 112 113 //////////////////////////////////////////////////////////////////////////////// 114 // 115 // TextButtonDefaultBorder - painting 116 // 117 //////////////////////////////////////////////////////////////////////////////// 118 void TextButtonDefaultBorder::Paint(const View& view, gfx::Canvas* canvas) { 119 const TextButton* button = static_cast<const TextButton*>(&view); 120 int state = button->state(); 121 bool animating = button->GetAnimation()->is_animating(); 122 123 Painter* painter = normal_painter_.get(); 124 // Use the hot painter when we're hovered. Also use the hot painter when we're 125 // STATE_NORMAL and |animating| so that we show throb animations started from 126 // CustomButton::StartThrobbing which should start throbbing the button 127 // regardless of whether it is hovered. 128 if (button->show_multiple_icon_states() && 129 ((state == TextButton::STATE_HOVERED) || 130 (state == TextButton::STATE_PRESSED) || 131 ((state == TextButton::STATE_NORMAL) && animating))) { 132 painter = (state == TextButton::STATE_PRESSED) ? 133 pushed_painter_.get() : hot_painter_.get(); 134 } 135 if (painter) { 136 if (animating) { 137 // TODO(pkasting): Really this should crossfade between states so it could 138 // handle the case of having a non-NULL |normal_painter_|. 139 canvas->SaveLayerAlpha(static_cast<uint8>( 140 button->GetAnimation()->CurrentValueBetween(0, 255))); 141 painter->Paint(canvas, view.size()); 142 canvas->Restore(); 143 } else { 144 painter->Paint(canvas, view.size()); 145 } 146 } 147 } 148 149 //////////////////////////////////////////////////////////////////////////////// 150 // 151 // TextButtonNativeThemeBorder 152 // 153 //////////////////////////////////////////////////////////////////////////////// 154 155 TextButtonNativeThemeBorder::TextButtonNativeThemeBorder( 156 NativeThemeDelegate* delegate) 157 : delegate_(delegate) { 158 SetInsets(gfx::Insets(kPreferredNativeThemePaddingVertical, 159 kPreferredNativeThemePaddingHorizontal, 160 kPreferredNativeThemePaddingVertical, 161 kPreferredNativeThemePaddingHorizontal)); 162 } 163 164 TextButtonNativeThemeBorder::~TextButtonNativeThemeBorder() { 165 } 166 167 void TextButtonNativeThemeBorder::Paint(const View& view, gfx::Canvas* canvas) { 168 const ui::NativeTheme* theme = view.GetNativeTheme(); 169 const TextButtonBase* tb = static_cast<const TextButton*>(&view); 170 ui::NativeTheme::Part part = delegate_->GetThemePart(); 171 gfx::Rect rect(delegate_->GetThemePaintRect()); 172 173 if (tb->show_multiple_icon_states() && 174 delegate_->GetThemeAnimation() != NULL && 175 delegate_->GetThemeAnimation()->is_animating()) { 176 // Paint background state. 177 ui::NativeTheme::ExtraParams prev_extra; 178 ui::NativeTheme::State prev_state = 179 delegate_->GetBackgroundThemeState(&prev_extra); 180 theme->Paint(canvas->sk_canvas(), part, prev_state, rect, prev_extra); 181 182 // Composite foreground state above it. 183 ui::NativeTheme::ExtraParams extra; 184 ui::NativeTheme::State state = delegate_->GetForegroundThemeState(&extra); 185 int alpha = delegate_->GetThemeAnimation()->CurrentValueBetween(0, 255); 186 canvas->SaveLayerAlpha(static_cast<uint8>(alpha)); 187 theme->Paint(canvas->sk_canvas(), part, state, rect, extra); 188 canvas->Restore(); 189 } else { 190 ui::NativeTheme::ExtraParams extra; 191 ui::NativeTheme::State state = delegate_->GetThemeState(&extra); 192 theme->Paint(canvas->sk_canvas(), part, state, rect, extra); 193 } 194 } 195 196 //////////////////////////////////////////////////////////////////////////////// 197 // TextButtonBase, public: 198 199 TextButtonBase::TextButtonBase(ButtonListener* listener, const string16& text) 200 : CustomButton(listener), 201 alignment_(ALIGN_LEFT), 202 font_(ResourceBundle::GetSharedInstance().GetFont( 203 ResourceBundle::BaseFont)), 204 has_text_shadow_(false), 205 active_text_shadow_color_(0), 206 inactive_text_shadow_color_(0), 207 text_shadow_offset_(gfx::Point(1, 1)), 208 min_width_(0), 209 min_height_(0), 210 max_width_(0), 211 show_multiple_icon_states_(true), 212 is_default_(false), 213 multi_line_(false), 214 use_enabled_color_from_theme_(true), 215 use_disabled_color_from_theme_(true), 216 use_highlight_color_from_theme_(true), 217 use_hover_color_from_theme_(true) { 218 SetText(text); 219 // OnNativeThemeChanged sets the color member variables. 220 TextButtonBase::OnNativeThemeChanged(GetNativeTheme()); 221 SetAnimationDuration(kHoverAnimationDurationMs); 222 } 223 224 TextButtonBase::~TextButtonBase() { 225 } 226 227 void TextButtonBase::SetIsDefault(bool is_default) { 228 if (is_default == is_default_) 229 return; 230 is_default_ = is_default; 231 if (is_default_) 232 AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); 233 else 234 RemoveAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)); 235 SchedulePaint(); 236 } 237 238 void TextButtonBase::SetText(const string16& text) { 239 if (text == text_) 240 return; 241 text_ = text; 242 SetAccessibleName(text); 243 UpdateTextSize(); 244 } 245 246 void TextButtonBase::SetFont(const gfx::Font& font) { 247 font_ = font; 248 UpdateTextSize(); 249 } 250 251 void TextButtonBase::SetEnabledColor(SkColor color) { 252 color_enabled_ = color; 253 use_enabled_color_from_theme_ = false; 254 UpdateColor(); 255 } 256 257 void TextButtonBase::SetDisabledColor(SkColor color) { 258 color_disabled_ = color; 259 use_disabled_color_from_theme_ = false; 260 UpdateColor(); 261 } 262 263 void TextButtonBase::SetHighlightColor(SkColor color) { 264 color_highlight_ = color; 265 use_highlight_color_from_theme_ = false; 266 } 267 268 void TextButtonBase::SetHoverColor(SkColor color) { 269 color_hover_ = color; 270 use_hover_color_from_theme_ = false; 271 } 272 273 void TextButtonBase::SetTextShadowColors(SkColor active_color, 274 SkColor inactive_color) { 275 active_text_shadow_color_ = active_color; 276 inactive_text_shadow_color_ = inactive_color; 277 has_text_shadow_ = true; 278 } 279 280 void TextButtonBase::SetTextShadowOffset(int x, int y) { 281 text_shadow_offset_.SetPoint(x, y); 282 } 283 284 void TextButtonBase::ClearEmbellishing() { 285 has_text_shadow_ = false; 286 } 287 288 void TextButtonBase::ClearMaxTextSize() { 289 max_text_size_ = text_size_; 290 } 291 292 void TextButtonBase::SetShowMultipleIconStates(bool show_multiple_icon_states) { 293 show_multiple_icon_states_ = show_multiple_icon_states; 294 } 295 296 void TextButtonBase::SetMultiLine(bool multi_line) { 297 if (multi_line != multi_line_) { 298 multi_line_ = multi_line; 299 max_text_size_.SetSize(0, 0); 300 UpdateTextSize(); 301 SchedulePaint(); 302 } 303 } 304 305 gfx::Size TextButtonBase::GetPreferredSize() { 306 gfx::Insets insets = GetInsets(); 307 308 // Use the max size to set the button boundaries. 309 // In multiline mode max size can be undefined while 310 // width() is 0, so max it out with current text size. 311 gfx::Size prefsize(std::max(max_text_size_.width(), 312 text_size_.width()) + insets.width(), 313 std::max(max_text_size_.height(), 314 text_size_.height()) + insets.height()); 315 316 if (max_width_ > 0) 317 prefsize.set_width(std::min(max_width_, prefsize.width())); 318 319 prefsize.set_width(std::max(prefsize.width(), min_width_)); 320 prefsize.set_height(std::max(prefsize.height(), min_height_)); 321 322 return prefsize; 323 } 324 325 int TextButtonBase::GetHeightForWidth(int w) { 326 if (!multi_line_) 327 return View::GetHeightForWidth(w); 328 329 if (max_width_ > 0) 330 w = std::min(max_width_, w); 331 332 gfx::Size text_size; 333 CalculateTextSize(&text_size, w); 334 int height = text_size.height() + GetInsets().height(); 335 336 return std::max(height, min_height_); 337 } 338 339 void TextButtonBase::OnPaint(gfx::Canvas* canvas) { 340 PaintButton(canvas, PB_NORMAL); 341 } 342 343 void TextButtonBase::OnBoundsChanged(const gfx::Rect& previous_bounds) { 344 if (multi_line_) 345 UpdateTextSize(); 346 } 347 348 const ui::Animation* TextButtonBase::GetAnimation() const { 349 return hover_animation_.get(); 350 } 351 352 void TextButtonBase::UpdateColor() { 353 color_ = enabled() ? color_enabled_ : color_disabled_; 354 } 355 356 void TextButtonBase::UpdateTextSize() { 357 int text_width = width(); 358 // If width is defined, use GetTextBounds.width() for maximum text width, 359 // as it will take size of checkbox/radiobutton into account. 360 if (text_width != 0) { 361 gfx::Rect text_bounds = GetTextBounds(); 362 text_width = text_bounds.width(); 363 } 364 CalculateTextSize(&text_size_, text_width); 365 // Before layout width() is 0, and multiline text will be treated as one line. 366 // Do not store max_text_size in this case. UpdateTextSize will be called 367 // again once width() changes. 368 if (!multi_line_ || text_width != 0) { 369 max_text_size_.SetSize(std::max(max_text_size_.width(), text_size_.width()), 370 std::max(max_text_size_.height(), 371 text_size_.height())); 372 PreferredSizeChanged(); 373 } 374 } 375 376 void TextButtonBase::CalculateTextSize(gfx::Size* text_size, int max_width) { 377 int h = font_.GetHeight(); 378 int w = multi_line_ ? max_width : 0; 379 int flags = ComputeCanvasStringFlags(); 380 if (!multi_line_) 381 flags |= gfx::Canvas::NO_ELLIPSIS; 382 383 gfx::Canvas::SizeStringInt(text_, font_, &w, &h, 0, flags); 384 text_size->SetSize(w, h); 385 } 386 387 int TextButtonBase::ComputeCanvasStringFlags() const { 388 if (!multi_line_) 389 return 0; 390 391 int flags = gfx::Canvas::MULTI_LINE; 392 switch (alignment_) { 393 case ALIGN_LEFT: 394 flags |= gfx::Canvas::TEXT_ALIGN_LEFT; 395 break; 396 case ALIGN_RIGHT: 397 flags |= gfx::Canvas::TEXT_ALIGN_RIGHT; 398 break; 399 case ALIGN_CENTER: 400 flags |= gfx::Canvas::TEXT_ALIGN_CENTER; 401 break; 402 } 403 return flags; 404 } 405 406 void TextButtonBase::GetExtraParams( 407 ui::NativeTheme::ExtraParams* params) const { 408 params->button.checked = false; 409 params->button.indeterminate = false; 410 params->button.is_default = false; 411 params->button.is_focused = false; 412 params->button.has_border = false; 413 params->button.classic_state = 0; 414 params->button.background_color = 415 GetNativeTheme()->GetSystemColor( 416 ui::NativeTheme::kColorId_ButtonBackgroundColor); 417 } 418 419 gfx::Rect TextButtonBase::GetContentBounds(int extra_width) const { 420 gfx::Insets insets = GetInsets(); 421 int available_width = width() - insets.width(); 422 int content_width = text_size_.width() + extra_width; 423 int content_x = 0; 424 switch(alignment_) { 425 case ALIGN_LEFT: 426 content_x = insets.left(); 427 break; 428 case ALIGN_RIGHT: 429 content_x = width() - insets.right() - content_width; 430 if (content_x < insets.left()) 431 content_x = insets.left(); 432 break; 433 case ALIGN_CENTER: 434 content_x = insets.left() + std::max(0, 435 (available_width - content_width) / 2); 436 break; 437 } 438 content_width = std::min(content_width, 439 width() - insets.right() - content_x); 440 int available_height = height() - insets.height(); 441 int content_y = (available_height - text_size_.height()) / 2 + insets.top(); 442 443 gfx::Rect bounds(content_x, content_y, content_width, text_size_.height()); 444 return bounds; 445 } 446 447 gfx::Rect TextButtonBase::GetTextBounds() const { 448 return GetContentBounds(0); 449 } 450 451 void TextButtonBase::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { 452 if (mode == PB_NORMAL) { 453 OnPaintBackground(canvas); 454 OnPaintBorder(canvas); 455 OnPaintFocusBorder(canvas); 456 } 457 458 gfx::Rect text_bounds(GetTextBounds()); 459 if (text_bounds.width() > 0) { 460 // Because the text button can (at times) draw multiple elements on the 461 // canvas, we can not mirror the button by simply flipping the canvas as 462 // doing this will mirror the text itself. Flipping the canvas will also 463 // make the icons look wrong because icons are almost always represented as 464 // direction-insensitive images and such images should never be flipped 465 // horizontally. 466 // 467 // Due to the above, we must perform the flipping manually for RTL UIs. 468 text_bounds.set_x(GetMirroredXForRect(text_bounds)); 469 470 SkColor text_color = (show_multiple_icon_states_ && 471 (state() == STATE_HOVERED || state() == STATE_PRESSED)) ? 472 color_hover_ : color_; 473 474 int draw_string_flags = gfx::Canvas::DefaultCanvasTextAlignment() | 475 ComputeCanvasStringFlags(); 476 477 if (mode == PB_FOR_DRAG) { 478 // Disable sub-pixel rendering as background is transparent. 479 draw_string_flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING; 480 481 #if defined(OS_WIN) 482 // TODO(erg): Either port DrawStringWithHalo to linux or find an 483 // alternative here. 484 canvas->DrawStringWithHalo(text_, font_, SK_ColorBLACK, SK_ColorWHITE, 485 text_bounds.x(), text_bounds.y(), text_bounds.width(), 486 text_bounds.height(), draw_string_flags); 487 #else 488 canvas->DrawStringInt(text_, 489 font_, 490 text_color, 491 text_bounds.x(), 492 text_bounds.y(), 493 text_bounds.width(), 494 text_bounds.height(), 495 draw_string_flags); 496 #endif 497 } else { 498 gfx::ShadowValues shadows; 499 if (has_text_shadow_) { 500 SkColor color = GetWidget()->IsActive() ? active_text_shadow_color_ : 501 inactive_text_shadow_color_; 502 shadows.push_back(gfx::ShadowValue(text_shadow_offset_, 0, color)); 503 } 504 canvas->DrawStringWithShadows(text_, font_, text_color, text_bounds, 505 0, draw_string_flags, shadows); 506 } 507 } 508 } 509 510 //////////////////////////////////////////////////////////////////////////////// 511 // TextButtonBase, View overrides: 512 513 gfx::Size TextButtonBase::GetMinimumSize() { 514 return max_text_size_; 515 } 516 517 void TextButtonBase::OnEnabledChanged() { 518 // We should always call UpdateColor() since the state of the button might be 519 // changed by other functions like CustomButton::SetState(). 520 UpdateColor(); 521 CustomButton::OnEnabledChanged(); 522 } 523 524 const char* TextButtonBase::GetClassName() const { 525 return kViewClassName; 526 } 527 528 void TextButtonBase::OnNativeThemeChanged(const ui::NativeTheme* theme) { 529 if (use_enabled_color_from_theme_) { 530 color_enabled_ = theme->GetSystemColor( 531 ui::NativeTheme::kColorId_ButtonEnabledColor); 532 } 533 if (use_disabled_color_from_theme_) { 534 color_disabled_ = theme->GetSystemColor( 535 ui::NativeTheme::kColorId_ButtonDisabledColor); 536 } 537 if (use_highlight_color_from_theme_) { 538 color_highlight_ = theme->GetSystemColor( 539 ui::NativeTheme::kColorId_ButtonHighlightColor); 540 } 541 if (use_hover_color_from_theme_) { 542 color_hover_ = theme->GetSystemColor( 543 ui::NativeTheme::kColorId_ButtonHoverColor); 544 } 545 UpdateColor(); 546 } 547 548 //////////////////////////////////////////////////////////////////////////////// 549 // TextButtonBase, NativeThemeDelegate overrides: 550 551 gfx::Rect TextButtonBase::GetThemePaintRect() const { 552 return GetLocalBounds(); 553 } 554 555 ui::NativeTheme::State TextButtonBase::GetThemeState( 556 ui::NativeTheme::ExtraParams* params) const { 557 GetExtraParams(params); 558 switch(state()) { 559 case STATE_DISABLED: 560 return ui::NativeTheme::kDisabled; 561 case STATE_NORMAL: 562 return ui::NativeTheme::kNormal; 563 case STATE_HOVERED: 564 return ui::NativeTheme::kHovered; 565 case STATE_PRESSED: 566 return ui::NativeTheme::kPressed; 567 default: 568 NOTREACHED() << "Unknown state: " << state(); 569 return ui::NativeTheme::kNormal; 570 } 571 } 572 573 const ui::Animation* TextButtonBase::GetThemeAnimation() const { 574 #if defined(OS_WIN) 575 if (GetNativeTheme() == ui::NativeThemeWin::instance()) { 576 return ui::NativeThemeWin::instance()->IsThemingActive() ? 577 hover_animation_.get() : NULL; 578 } 579 #endif 580 return hover_animation_.get(); 581 } 582 583 ui::NativeTheme::State TextButtonBase::GetBackgroundThemeState( 584 ui::NativeTheme::ExtraParams* params) const { 585 GetExtraParams(params); 586 return ui::NativeTheme::kNormal; 587 } 588 589 ui::NativeTheme::State TextButtonBase::GetForegroundThemeState( 590 ui::NativeTheme::ExtraParams* params) const { 591 GetExtraParams(params); 592 return ui::NativeTheme::kHovered; 593 } 594 595 //////////////////////////////////////////////////////////////////////////////// 596 // 597 // TextButton 598 // 599 //////////////////////////////////////////////////////////////////////////////// 600 601 TextButton::TextButton(ButtonListener* listener, const string16& text) 602 : TextButtonBase(listener, text), 603 icon_placement_(ICON_ON_LEFT), 604 has_hover_icon_(false), 605 has_pushed_icon_(false), 606 icon_text_spacing_(kDefaultIconTextSpacing), 607 ignore_minimum_size_(true) { 608 set_border(new TextButtonDefaultBorder); 609 set_focus_border(FocusBorder::CreateDashedFocusBorder(kFocusRectInset, 610 kFocusRectInset, 611 kFocusRectInset, 612 kFocusRectInset)); 613 } 614 615 TextButton::~TextButton() { 616 } 617 618 void TextButton::SetIcon(const gfx::ImageSkia& icon) { 619 icon_ = icon; 620 SchedulePaint(); 621 } 622 623 void TextButton::SetHoverIcon(const gfx::ImageSkia& icon) { 624 icon_hover_ = icon; 625 has_hover_icon_ = true; 626 SchedulePaint(); 627 } 628 629 void TextButton::SetPushedIcon(const gfx::ImageSkia& icon) { 630 icon_pushed_ = icon; 631 has_pushed_icon_ = true; 632 SchedulePaint(); 633 } 634 635 gfx::Size TextButton::GetPreferredSize() { 636 gfx::Size prefsize(TextButtonBase::GetPreferredSize()); 637 prefsize.Enlarge(icon_.width(), 0); 638 prefsize.set_height(std::max(prefsize.height(), icon_.height())); 639 640 // Use the max size to set the button boundaries. 641 if (icon_.width() > 0 && !text_.empty()) 642 prefsize.Enlarge(icon_text_spacing_, 0); 643 644 if (max_width_ > 0) 645 prefsize.set_width(std::min(max_width_, prefsize.width())); 646 647 #if defined(OS_WIN) 648 // Clamp the size returned to at least the minimum size. 649 if (!ignore_minimum_size_) { 650 gfx::PlatformFontWin* platform_font = 651 static_cast<gfx::PlatformFontWin*>(font_.platform_font()); 652 prefsize.set_width(std::max( 653 prefsize.width(), 654 platform_font->horizontal_dlus_to_pixels(kMinWidthDLUs))); 655 prefsize.set_height(std::max( 656 prefsize.height(), 657 platform_font->vertical_dlus_to_pixels(kMinHeightDLUs))); 658 } 659 #endif 660 661 prefsize.set_width(std::max(prefsize.width(), min_width_)); 662 prefsize.set_height(std::max(prefsize.height(), min_height_)); 663 664 return prefsize; 665 } 666 667 void TextButton::PaintButton(gfx::Canvas* canvas, PaintButtonMode mode) { 668 TextButtonBase::PaintButton(canvas, mode); 669 670 const gfx::ImageSkia& icon = GetImageToPaint(); 671 672 if (icon.width() > 0) { 673 gfx::Rect text_bounds = GetTextBounds(); 674 int icon_x; 675 int spacing = text_.empty() ? 0 : icon_text_spacing_; 676 gfx::Insets insets = GetInsets(); 677 if (icon_placement_ == ICON_ON_LEFT) { 678 icon_x = text_bounds.x() - icon.width() - spacing; 679 } else if (icon_placement_ == ICON_ON_RIGHT) { 680 icon_x = text_bounds.right() + spacing; 681 } else { // ICON_CENTERED 682 DCHECK(text_.empty()); 683 icon_x = (width() - insets.width() - icon.width()) / 2 + insets.left(); 684 } 685 686 int available_height = height() - insets.height(); 687 int icon_y = (available_height - icon.height()) / 2 + insets.top(); 688 689 // Mirroring the icon position if necessary. 690 gfx::Rect icon_bounds(icon_x, icon_y, icon.width(), icon.height()); 691 icon_bounds.set_x(GetMirroredXForRect(icon_bounds)); 692 canvas->DrawImageInt(icon, icon_bounds.x(), icon_bounds.y()); 693 } 694 } 695 696 void TextButton::set_ignore_minimum_size(bool ignore_minimum_size) { 697 ignore_minimum_size_ = ignore_minimum_size; 698 } 699 700 const char* TextButton::GetClassName() const { 701 return kViewClassName; 702 } 703 704 ui::NativeTheme::Part TextButton::GetThemePart() const { 705 return ui::NativeTheme::kPushButton; 706 } 707 708 void TextButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const { 709 TextButtonBase::GetExtraParams(params); 710 params->button.is_default = is_default_; 711 } 712 713 gfx::Rect TextButton::GetTextBounds() const { 714 int extra_width = 0; 715 716 const gfx::ImageSkia& icon = GetImageToPaint(); 717 if (icon.width() > 0) 718 extra_width = icon.width() + (text_.empty() ? 0 : icon_text_spacing_); 719 720 gfx::Rect bounds(GetContentBounds(extra_width)); 721 722 if (extra_width > 0) { 723 // Make sure the icon is always fully visible. 724 if (icon_placement_ == ICON_ON_LEFT) { 725 bounds.Inset(extra_width, 0, 0, 0); 726 } else if (icon_placement_ == ICON_ON_RIGHT) { 727 bounds.Inset(0, 0, extra_width, 0); 728 } 729 } 730 731 return bounds; 732 } 733 734 const gfx::ImageSkia& TextButton::GetImageToPaint() const { 735 if (show_multiple_icon_states_) { 736 if (has_hover_icon_ && (state() == STATE_HOVERED)) 737 return icon_hover_; 738 if (has_pushed_icon_ && (state() == STATE_PRESSED)) 739 return icon_pushed_; 740 } 741 return icon_; 742 } 743 744 } // namespace views 745