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 25namespace views { 26 27namespace { 28 29// Default space between the icon and text. 30const int kDefaultIconTextSpacing = 5; 31 32// Preferred padding between text and edge. 33const int kPreferredPaddingHorizontal = 6; 34const int kPreferredPaddingVertical = 5; 35 36// Preferred padding between text and edge for NativeTheme border. 37const int kPreferredNativeThemePaddingHorizontal = 12; 38const 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. 45const int kFocusRectInset = 3; 46 47// How long the hover fade animation should last. 48const int kHoverAnimationDurationMs = 170; 49 50#if defined(OS_WIN) 51// These sizes are from http://msdn.microsoft.com/en-us/library/aa511279.aspx 52const int kMinWidthDLUs = 50; 53const int kMinHeightDLUs = 14; 54#endif 55 56// The default hot and pushed button image IDs; normal has none by default. 57const int kHotImages[] = IMAGE_GRID(IDR_TEXTBUTTON_HOVER); 58const int kPushedImages[] = IMAGE_GRID(IDR_TEXTBUTTON_PRESSED); 59 60} // namespace 61 62// static 63const char TextButtonBase::kViewClassName[] = "TextButtonBase"; 64// static 65const char TextButton::kViewClassName[] = "TextButton"; 66 67 68// TextButtonBorder ----------------------------------------------------------- 69 70TextButtonBorder::TextButtonBorder() { 71} 72 73TextButtonBorder::~TextButtonBorder() { 74} 75 76void TextButtonBorder::Paint(const View& view, gfx::Canvas* canvas) { 77} 78 79gfx::Insets TextButtonBorder::GetInsets() const { 80 return insets_; 81} 82 83gfx::Size TextButtonBorder::GetMinimumSize() const { 84 return gfx::Size(); 85} 86 87void TextButtonBorder::SetInsets(const gfx::Insets& insets) { 88 insets_ = insets; 89} 90 91TextButtonBorder* TextButtonBorder::AsTextButtonBorder() { 92 return this; 93} 94 95const TextButtonBorder* TextButtonBorder::AsTextButtonBorder() const { 96 return this; 97} 98 99 100// TextButtonDefaultBorder ---------------------------------------------------- 101 102TextButtonDefaultBorder::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 110TextButtonDefaultBorder::~TextButtonDefaultBorder() { 111} 112 113void 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 144gfx::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 158TextButtonNativeThemeBorder::TextButtonNativeThemeBorder( 159 NativeThemeDelegate* delegate) 160 : delegate_(delegate) { 161 SetInsets(gfx::Insets(kPreferredNativeThemePaddingVertical, 162 kPreferredNativeThemePaddingHorizontal, 163 kPreferredNativeThemePaddingVertical, 164 kPreferredNativeThemePaddingHorizontal)); 165} 166 167TextButtonNativeThemeBorder::~TextButtonNativeThemeBorder() { 168} 169 170void 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 202TextButtonBase::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 228TextButtonBase::~TextButtonBase() { 229} 230 231void 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 242void TextButtonBase::SetText(const string16& text) { 243 if (text == text_) 244 return; 245 text_ = text; 246 SetAccessibleName(text); 247 UpdateTextSize(); 248} 249 250void TextButtonBase::SetFont(const gfx::Font& font) { 251 font_ = font; 252 UpdateTextSize(); 253} 254 255void TextButtonBase::SetEnabledColor(SkColor color) { 256 color_enabled_ = color; 257 use_enabled_color_from_theme_ = false; 258 UpdateColor(); 259} 260 261void TextButtonBase::SetDisabledColor(SkColor color) { 262 color_disabled_ = color; 263 use_disabled_color_from_theme_ = false; 264 UpdateColor(); 265} 266 267void TextButtonBase::SetHighlightColor(SkColor color) { 268 color_highlight_ = color; 269 use_highlight_color_from_theme_ = false; 270} 271 272void TextButtonBase::SetHoverColor(SkColor color) { 273 color_hover_ = color; 274 use_hover_color_from_theme_ = false; 275} 276 277void 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 284void TextButtonBase::SetTextShadowOffset(int x, int y) { 285 text_shadow_offset_.SetPoint(x, y); 286} 287 288void TextButtonBase::ClearEmbellishing() { 289 has_text_shadow_ = false; 290} 291 292void TextButtonBase::ClearMaxTextSize() { 293 max_text_size_ = text_size_; 294} 295 296void TextButtonBase::SetShowMultipleIconStates(bool show_multiple_icon_states) { 297 show_multiple_icon_states_ = show_multiple_icon_states; 298} 299 300void 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 309gfx::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 329int 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 343void TextButtonBase::OnPaint(gfx::Canvas* canvas) { 344 PaintButton(canvas, PB_NORMAL); 345} 346 347void TextButtonBase::OnBoundsChanged(const gfx::Rect& previous_bounds) { 348 if (multi_line_) 349 UpdateTextSize(); 350} 351 352const gfx::Animation* TextButtonBase::GetAnimation() const { 353 return hover_animation_.get(); 354} 355 356void TextButtonBase::UpdateColor() { 357 color_ = enabled() ? color_enabled_ : color_disabled_; 358} 359 360void 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 380void 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 391int 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 410void TextButtonBase::OnFocus() { 411 View::OnFocus(); 412 if (focus_painter_) 413 SchedulePaint(); 414} 415 416void TextButtonBase::OnBlur() { 417 View::OnBlur(); 418 if (focus_painter_) 419 SchedulePaint(); 420} 421 422void 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 435gfx::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 463gfx::Rect TextButtonBase::GetTextBounds() const { 464 return GetContentBounds(0); 465} 466 467void TextButtonBase::SetFocusPainter(scoped_ptr<Painter> focus_painter) { 468 focus_painter_ = focus_painter.Pass(); 469} 470 471void 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 530gfx::Size TextButtonBase::GetMinimumSize() { 531 return max_text_size_; 532} 533 534void 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 541const char* TextButtonBase::GetClassName() const { 542 return kViewClassName; 543} 544 545void 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 565gfx::Rect TextButtonBase::GetThemePaintRect() const { 566 return GetLocalBounds(); 567} 568 569ui::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 587const 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 597ui::NativeTheme::State TextButtonBase::GetBackgroundThemeState( 598 ui::NativeTheme::ExtraParams* params) const { 599 GetExtraParams(params); 600 return ui::NativeTheme::kNormal; 601} 602 603ui::NativeTheme::State TextButtonBase::GetForegroundThemeState( 604 ui::NativeTheme::ExtraParams* params) const { 605 GetExtraParams(params); 606 return ui::NativeTheme::kHovered; 607} 608 609 610// TextButton ----------------------------------------------------------------- 611 612TextButton::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 625TextButton::~TextButton() { 626} 627 628void TextButton::SetIcon(const gfx::ImageSkia& icon) { 629 icon_ = icon; 630 SchedulePaint(); 631} 632 633void TextButton::SetHoverIcon(const gfx::ImageSkia& icon) { 634 icon_hover_ = icon; 635 has_hover_icon_ = true; 636 SchedulePaint(); 637} 638 639void TextButton::SetPushedIcon(const gfx::ImageSkia& icon) { 640 icon_pushed_ = icon; 641 has_pushed_icon_ = true; 642 SchedulePaint(); 643} 644 645gfx::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 677void 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 706void TextButton::set_ignore_minimum_size(bool ignore_minimum_size) { 707 ignore_minimum_size_ = ignore_minimum_size; 708} 709 710const char* TextButton::GetClassName() const { 711 return kViewClassName; 712} 713 714ui::NativeTheme::Part TextButton::GetThemePart() const { 715 return ui::NativeTheme::kPushButton; 716} 717 718void TextButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const { 719 TextButtonBase::GetExtraParams(params); 720 params->button.is_default = is_default_; 721} 722 723gfx::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 744const 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