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
AsTextButtonBorder()91 TextButtonBorder* TextButtonBorder::AsTextButtonBorder() {
92 return this;
93 }
94
AsTextButtonBorder() const95 const TextButtonBorder* TextButtonBorder::AsTextButtonBorder() const {
96 return this;
97 }
98
99
100 // TextButtonDefaultBorder ----------------------------------------------------
101
TextButtonDefaultBorder()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
~TextButtonDefaultBorder()110 TextButtonDefaultBorder::~TextButtonDefaultBorder() {
111 }
112
Paint(const View & view,gfx::Canvas * canvas)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
GetMinimumSize() const144 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
TextButtonNativeThemeBorder(NativeThemeDelegate * delegate)158 TextButtonNativeThemeBorder::TextButtonNativeThemeBorder(
159 NativeThemeDelegate* delegate)
160 : delegate_(delegate) {
161 SetInsets(gfx::Insets(kPreferredNativeThemePaddingVertical,
162 kPreferredNativeThemePaddingHorizontal,
163 kPreferredNativeThemePaddingVertical,
164 kPreferredNativeThemePaddingHorizontal));
165 }
166
~TextButtonNativeThemeBorder()167 TextButtonNativeThemeBorder::~TextButtonNativeThemeBorder() {
168 }
169
Paint(const View & view,gfx::Canvas * canvas)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
TextButtonBase(ButtonListener * listener,const string16 & text)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
~TextButtonBase()228 TextButtonBase::~TextButtonBase() {
229 }
230
SetIsDefault(bool is_default)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
SetText(const string16 & text)242 void TextButtonBase::SetText(const string16& text) {
243 if (text == text_)
244 return;
245 text_ = text;
246 SetAccessibleName(text);
247 UpdateTextSize();
248 }
249
SetFont(const gfx::Font & font)250 void TextButtonBase::SetFont(const gfx::Font& font) {
251 font_ = font;
252 UpdateTextSize();
253 }
254
SetEnabledColor(SkColor color)255 void TextButtonBase::SetEnabledColor(SkColor color) {
256 color_enabled_ = color;
257 use_enabled_color_from_theme_ = false;
258 UpdateColor();
259 }
260
SetDisabledColor(SkColor color)261 void TextButtonBase::SetDisabledColor(SkColor color) {
262 color_disabled_ = color;
263 use_disabled_color_from_theme_ = false;
264 UpdateColor();
265 }
266
SetHighlightColor(SkColor color)267 void TextButtonBase::SetHighlightColor(SkColor color) {
268 color_highlight_ = color;
269 use_highlight_color_from_theme_ = false;
270 }
271
SetHoverColor(SkColor color)272 void TextButtonBase::SetHoverColor(SkColor color) {
273 color_hover_ = color;
274 use_hover_color_from_theme_ = false;
275 }
276
SetTextShadowColors(SkColor active_color,SkColor inactive_color)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
SetTextShadowOffset(int x,int y)284 void TextButtonBase::SetTextShadowOffset(int x, int y) {
285 text_shadow_offset_.SetPoint(x, y);
286 }
287
ClearEmbellishing()288 void TextButtonBase::ClearEmbellishing() {
289 has_text_shadow_ = false;
290 }
291
ClearMaxTextSize()292 void TextButtonBase::ClearMaxTextSize() {
293 max_text_size_ = text_size_;
294 }
295
SetShowMultipleIconStates(bool show_multiple_icon_states)296 void TextButtonBase::SetShowMultipleIconStates(bool show_multiple_icon_states) {
297 show_multiple_icon_states_ = show_multiple_icon_states;
298 }
299
SetMultiLine(bool multi_line)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
GetPreferredSize()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
GetHeightForWidth(int w)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
OnPaint(gfx::Canvas * canvas)343 void TextButtonBase::OnPaint(gfx::Canvas* canvas) {
344 PaintButton(canvas, PB_NORMAL);
345 }
346
OnBoundsChanged(const gfx::Rect & previous_bounds)347 void TextButtonBase::OnBoundsChanged(const gfx::Rect& previous_bounds) {
348 if (multi_line_)
349 UpdateTextSize();
350 }
351
GetAnimation() const352 const gfx::Animation* TextButtonBase::GetAnimation() const {
353 return hover_animation_.get();
354 }
355
UpdateColor()356 void TextButtonBase::UpdateColor() {
357 color_ = enabled() ? color_enabled_ : color_disabled_;
358 }
359
UpdateTextSize()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
CalculateTextSize(gfx::Size * text_size,int max_width)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
ComputeCanvasStringFlags() const391 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
OnFocus()410 void TextButtonBase::OnFocus() {
411 View::OnFocus();
412 if (focus_painter_)
413 SchedulePaint();
414 }
415
OnBlur()416 void TextButtonBase::OnBlur() {
417 View::OnBlur();
418 if (focus_painter_)
419 SchedulePaint();
420 }
421
GetExtraParams(ui::NativeTheme::ExtraParams * params) const422 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
GetContentBounds(int extra_width) const435 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
GetTextBounds() const463 gfx::Rect TextButtonBase::GetTextBounds() const {
464 return GetContentBounds(0);
465 }
466
SetFocusPainter(scoped_ptr<Painter> focus_painter)467 void TextButtonBase::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
468 focus_painter_ = focus_painter.Pass();
469 }
470
PaintButton(gfx::Canvas * canvas,PaintButtonMode mode)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
GetMinimumSize()530 gfx::Size TextButtonBase::GetMinimumSize() {
531 return max_text_size_;
532 }
533
OnEnabledChanged()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
GetClassName() const541 const char* TextButtonBase::GetClassName() const {
542 return kViewClassName;
543 }
544
OnNativeThemeChanged(const ui::NativeTheme * theme)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
GetThemePaintRect() const565 gfx::Rect TextButtonBase::GetThemePaintRect() const {
566 return GetLocalBounds();
567 }
568
GetThemeState(ui::NativeTheme::ExtraParams * params) const569 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
GetThemeAnimation() const587 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
GetBackgroundThemeState(ui::NativeTheme::ExtraParams * params) const597 ui::NativeTheme::State TextButtonBase::GetBackgroundThemeState(
598 ui::NativeTheme::ExtraParams* params) const {
599 GetExtraParams(params);
600 return ui::NativeTheme::kNormal;
601 }
602
GetForegroundThemeState(ui::NativeTheme::ExtraParams * params) const603 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
TextButton(ButtonListener * listener,const string16 & text)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
~TextButton()625 TextButton::~TextButton() {
626 }
627
SetIcon(const gfx::ImageSkia & icon)628 void TextButton::SetIcon(const gfx::ImageSkia& icon) {
629 icon_ = icon;
630 SchedulePaint();
631 }
632
SetHoverIcon(const gfx::ImageSkia & icon)633 void TextButton::SetHoverIcon(const gfx::ImageSkia& icon) {
634 icon_hover_ = icon;
635 has_hover_icon_ = true;
636 SchedulePaint();
637 }
638
SetPushedIcon(const gfx::ImageSkia & icon)639 void TextButton::SetPushedIcon(const gfx::ImageSkia& icon) {
640 icon_pushed_ = icon;
641 has_pushed_icon_ = true;
642 SchedulePaint();
643 }
644
GetPreferredSize()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
PaintButton(gfx::Canvas * canvas,PaintButtonMode mode)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
set_ignore_minimum_size(bool ignore_minimum_size)706 void TextButton::set_ignore_minimum_size(bool ignore_minimum_size) {
707 ignore_minimum_size_ = ignore_minimum_size;
708 }
709
GetClassName() const710 const char* TextButton::GetClassName() const {
711 return kViewClassName;
712 }
713
GetThemePart() const714 ui::NativeTheme::Part TextButton::GetThemePart() const {
715 return ui::NativeTheme::kPushButton;
716 }
717
GetExtraParams(ui::NativeTheme::ExtraParams * params) const718 void TextButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
719 TextButtonBase::GetExtraParams(params);
720 params->button.is_default = is_default_;
721 }
722
GetTextBounds() const723 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
GetImageToPaint() const744 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