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