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