Prepare to use gfx::RenderText in views::Label.
[chromium-blink-merge.git] / ui / views / controls / button / label_button.cc
blob5ee97918dc9f794c1d1189ffa3e570449e6c16f7
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.
5 #include "ui/views/controls/button/label_button.h"
7 #include "base/logging.h"
8 #include "grit/ui_resources.h"
9 #include "ui/base/resource/resource_bundle.h"
10 #include "ui/gfx/animation/throb_animation.h"
11 #include "ui/gfx/canvas.h"
12 #include "ui/gfx/font_list.h"
13 #include "ui/gfx/sys_color_change_listener.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/views/background.h"
16 #include "ui/views/controls/button/label_button_border.h"
17 #include "ui/views/painter.h"
18 #include "ui/views/window/dialog_delegate.h"
20 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
21 #include "ui/views/linux_ui/linux_ui.h"
22 #endif
24 namespace {
26 // The spacing between the icon and text.
27 const int kSpacing = 5;
29 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
30 // Default text and shadow colors for STYLE_BUTTON.
31 const SkColor kStyleButtonTextColor = SK_ColorBLACK;
32 const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
33 #endif
35 } // namespace
37 namespace views {
39 // static
40 const int LabelButton::kHoverAnimationDurationMs = 170;
42 // static
43 const char LabelButton::kViewClassName[] = "LabelButton";
45 LabelButton::LabelButton(ButtonListener* listener, const base::string16& text)
46 : CustomButton(listener),
47 image_(new ImageView()),
48 label_(new Label()),
49 button_state_images_(),
50 button_state_colors_(),
51 explicitly_set_colors_(),
52 is_default_(false),
53 style_(STYLE_TEXTBUTTON),
54 border_is_themed_border_(true) {
55 SetAnimationDuration(kHoverAnimationDurationMs);
56 SetText(text);
57 SetFontList(gfx::FontList());
59 AddChildView(image_);
60 image_->set_interactive(false);
62 AddChildView(label_);
63 label_->SetAutoColorReadabilityEnabled(false);
64 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
66 // Initialize the colors, border, and layout.
67 SetStyle(style_);
69 SetAccessibleName(text);
72 LabelButton::~LabelButton() {}
74 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
75 if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
76 return button_state_images_[STATE_NORMAL];
77 return button_state_images_[for_state];
80 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
81 button_state_images_[for_state] = image;
82 UpdateImage();
85 const base::string16& LabelButton::GetText() const {
86 return label_->text();
89 void LabelButton::SetText(const base::string16& text) {
90 SetAccessibleName(text);
91 label_->SetText(text);
94 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
95 button_state_colors_[for_state] = color;
96 if (for_state == STATE_DISABLED)
97 label_->SetDisabledColor(color);
98 else if (for_state == state())
99 label_->SetEnabledColor(color);
100 explicitly_set_colors_[for_state] = true;
103 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
104 label_->SetShadows(shadows);
107 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
108 label_->SetSubpixelRenderingEnabled(enabled);
111 bool LabelButton::GetTextMultiLine() const {
112 return label_->multi_line();
115 void LabelButton::SetTextMultiLine(bool text_multi_line) {
116 label_->SetMultiLine(text_multi_line);
119 const gfx::FontList& LabelButton::GetFontList() const {
120 return label_->font_list();
123 void LabelButton::SetFontList(const gfx::FontList& font_list) {
124 cached_normal_font_list_ = font_list;
125 cached_bold_font_list_ = font_list.DeriveWithStyle(
126 font_list.GetFontStyle() | gfx::Font::BOLD);
128 // STYLE_BUTTON uses bold text to indicate default buttons.
129 label_->SetFontList(
130 style_ == STYLE_BUTTON && is_default_ ?
131 cached_bold_font_list_ : cached_normal_font_list_);
134 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
135 label_->SetElideBehavior(elide_behavior);
138 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
139 return label_->GetHorizontalAlignment();
142 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
143 label_->SetHorizontalAlignment(alignment);
144 InvalidateLayout();
147 void LabelButton::SetIsDefault(bool is_default) {
148 if (is_default == is_default_)
149 return;
150 is_default_ = is_default;
151 ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
152 is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
154 // STYLE_BUTTON uses bold text to indicate default buttons.
155 if (style_ == STYLE_BUTTON) {
156 label_->SetFontList(
157 is_default ? cached_bold_font_list_ : cached_normal_font_list_);
161 void LabelButton::SetStyle(ButtonStyle style) {
162 style_ = style;
163 // Inset the button focus rect from the actual border; roughly match Windows.
164 if (style == STYLE_BUTTON) {
165 SetFocusPainter(scoped_ptr<Painter>());
166 } else {
167 SetFocusPainter(Painter::CreateDashedFocusPainterWithInsets(
168 gfx::Insets(3, 3, 3, 3)));
170 if (style == STYLE_BUTTON) {
171 label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
172 SetFocusable(true);
174 if (style == STYLE_BUTTON)
175 set_min_size(gfx::Size(70, 33));
177 OnNativeThemeChanged(GetNativeTheme());
180 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
181 focus_painter_ = focus_painter.Pass();
184 gfx::Size LabelButton::GetPreferredSize() const {
185 // Use a temporary label copy for sizing to avoid calculation side-effects.
186 Label label(GetText(), cached_normal_font_list_);
187 label.SetShadows(label_->shadows());
188 label.SetMultiLine(GetTextMultiLine());
190 if (style() == STYLE_BUTTON) {
191 // Some text appears wider when rendered normally than when rendered bold.
192 // Accommodate the widest, as buttons may show bold and shouldn't resize.
193 const int current_width = label.GetPreferredSize().width();
194 label.SetFontList(cached_bold_font_list_);
195 if (label.GetPreferredSize().width() < current_width)
196 label.SetFontList(cached_normal_font_list_);
199 // Calculate the required size.
200 const gfx::Size image_size(image_->GetPreferredSize());
201 gfx::Size size(label.GetPreferredSize());
202 if (image_size.width() > 0 && size.width() > 0)
203 size.Enlarge(kSpacing, 0);
204 size.SetToMax(gfx::Size(0, image_size.height()));
205 const gfx::Insets insets(GetInsets());
206 size.Enlarge(image_size.width() + insets.width(), insets.height());
208 // Make the size at least as large as the minimum size needed by the border.
209 size.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size());
211 // Increase the minimum size monotonically with the preferred size.
212 size.SetToMax(min_size_);
213 min_size_ = size;
215 // Return the largest known size clamped to the maximum size (if valid).
216 if (max_size_.width() > 0)
217 size.set_width(std::min(max_size_.width(), size.width()));
218 if (max_size_.height() > 0)
219 size.set_height(std::min(max_size_.height(), size.height()));
220 return size;
223 int LabelButton::GetHeightForWidth(int w) const {
224 w -= GetInsets().width();
225 const gfx::Size image_size(image_->GetPreferredSize());
226 w -= image_size.width();
227 if (image_size.width() > 0 && !GetText().empty())
228 w -= kSpacing;
230 int height = std::max(image_size.height(), label_->GetHeightForWidth(w));
231 if (border())
232 height = std::max(height, border()->GetMinimumSize().height());
234 height = std::max(height, min_size_.height());
235 if (max_size_.height() > 0)
236 height = std::min(height, max_size_.height());
237 return height;
240 void LabelButton::Layout() {
241 gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
242 if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
243 adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
244 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
246 gfx::Rect child_area(GetChildAreaBounds());
247 child_area.Inset(GetInsets());
249 gfx::Size image_size(image_->GetPreferredSize());
250 image_size.SetToMin(child_area.size());
252 // The label takes any remaining width after sizing the image, unless both
253 // views are centered. In that case, using the tighter preferred label width
254 // avoids wasted space within the label that would look like awkward padding.
255 gfx::Size label_size(child_area.size());
256 if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
257 label_size.set_width(
258 std::max(child_area.width() - image_size.width() - kSpacing, 0));
259 if (adjusted_alignment == gfx::ALIGN_CENTER) {
260 // Ensure multi-line labels paired with images use their available width.
261 label_size.set_width(
262 std::min(label_size.width(), label_->GetPreferredSize().width()));
266 gfx::Point image_origin(child_area.origin());
267 image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
268 if (adjusted_alignment == gfx::ALIGN_CENTER) {
269 const int total_width = image_size.width() + label_size.width() +
270 ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
271 image_origin.Offset((child_area.width() - total_width) / 2, 0);
272 } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
273 image_origin.Offset(child_area.width() - image_size.width(), 0);
276 gfx::Point label_origin(child_area.origin());
277 if (!image_size.IsEmpty() && adjusted_alignment != gfx::ALIGN_RIGHT)
278 label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
280 image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
281 label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
284 const char* LabelButton::GetClassName() const {
285 return kViewClassName;
288 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
289 return scoped_ptr<LabelButtonBorder>(new LabelButtonBorder(style_));
292 void LabelButton::SetBorder(scoped_ptr<Border> border) {
293 border_is_themed_border_ = false;
294 View::SetBorder(border.Pass());
297 gfx::Rect LabelButton::GetChildAreaBounds() {
298 return GetLocalBounds();
301 void LabelButton::OnPaint(gfx::Canvas* canvas) {
302 View::OnPaint(canvas);
303 Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
306 void LabelButton::OnFocus() {
307 View::OnFocus();
308 // Typically the border renders differently when focused.
309 SchedulePaint();
312 void LabelButton::OnBlur() {
313 View::OnBlur();
314 // Typically the border renders differently when focused.
315 SchedulePaint();
318 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
319 params->button.checked = false;
320 params->button.indeterminate = false;
321 params->button.is_default = is_default_;
322 params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
323 params->button.has_border = false;
324 params->button.classic_state = 0;
325 params->button.background_color = label_->background_color();
328 void LabelButton::ResetColorsFromNativeTheme() {
329 const ui::NativeTheme* theme = GetNativeTheme();
330 SkColor colors[STATE_COUNT] = {
331 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
332 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
333 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
334 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
337 // Certain styles do not change text color when hovered or pressed.
338 bool constant_text_color = false;
339 // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
340 if (gfx::IsInvertedColorScheme()) {
341 constant_text_color = true;
342 colors[STATE_NORMAL] = SK_ColorWHITE;
343 label_->SetBackgroundColor(SK_ColorBLACK);
344 label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK));
345 label_->SetAutoColorReadabilityEnabled(true);
346 label_->SetShadows(gfx::ShadowValues());
347 } else if (style() == STYLE_BUTTON) {
348 // TODO(erg): This is disabled on desktop linux because of the binary asset
349 // confusion. These details should either be pushed into ui::NativeThemeWin
350 // or should be obsoleted by rendering buttons with paint calls instead of
351 // with static assets. http://crbug.com/350498
352 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
353 constant_text_color = true;
354 colors[STATE_NORMAL] = kStyleButtonTextColor;
355 label_->SetBackgroundColor(theme->GetSystemColor(
356 ui::NativeTheme::kColorId_ButtonBackgroundColor));
357 label_->SetAutoColorReadabilityEnabled(false);
358 label_->SetShadows(gfx::ShadowValues(
359 1, gfx::ShadowValue(gfx::Point(0, 1), 0, kStyleButtonShadowColor)));
360 #endif
361 label_->set_background(NULL);
362 } else {
363 label_->set_background(NULL);
366 if (constant_text_color)
367 colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
369 for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
370 if (!explicitly_set_colors_[state]) {
371 SetTextColor(static_cast<ButtonState>(state), colors[state]);
372 explicitly_set_colors_[state] = false;
377 void LabelButton::UpdateImage() {
378 image_->SetImage(GetImage(state()));
381 void LabelButton::UpdateThemedBorder() {
382 // Don't override borders set by others.
383 if (!border_is_themed_border_)
384 return;
386 scoped_ptr<LabelButtonBorder> label_button_border = CreateDefaultBorder();
388 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
389 views::LinuxUI* linux_ui = views::LinuxUI::instance();
390 if (linux_ui) {
391 SetBorder(linux_ui->CreateNativeBorder(
392 this, label_button_border.Pass()));
393 } else
394 #endif
396 SetBorder(label_button_border.PassAs<Border>());
399 border_is_themed_border_ = true;
402 void LabelButton::StateChanged() {
403 const gfx::Size previous_image_size(image_->GetPreferredSize());
404 UpdateImage();
405 const SkColor color = button_state_colors_[state()];
406 if (state() != STATE_DISABLED && label_->enabled_color() != color)
407 label_->SetEnabledColor(color);
408 label_->SetEnabled(state() != STATE_DISABLED);
409 if (image_->GetPreferredSize() != previous_image_size)
410 Layout();
413 void LabelButton::ChildPreferredSizeChanged(View* child) {
414 PreferredSizeChanged();
417 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
418 ResetColorsFromNativeTheme();
419 UpdateThemedBorder();
420 // Invalidate the layout to pickup the new insets from the border.
421 InvalidateLayout();
424 ui::NativeTheme::Part LabelButton::GetThemePart() const {
425 return ui::NativeTheme::kPushButton;
428 gfx::Rect LabelButton::GetThemePaintRect() const {
429 return GetLocalBounds();
432 ui::NativeTheme::State LabelButton::GetThemeState(
433 ui::NativeTheme::ExtraParams* params) const {
434 GetExtraParams(params);
435 switch (state()) {
436 case STATE_NORMAL: return ui::NativeTheme::kNormal;
437 case STATE_HOVERED: return ui::NativeTheme::kHovered;
438 case STATE_PRESSED: return ui::NativeTheme::kPressed;
439 case STATE_DISABLED: return ui::NativeTheme::kDisabled;
440 case STATE_COUNT: NOTREACHED() << "Unknown state: " << state();
442 return ui::NativeTheme::kNormal;
445 const gfx::Animation* LabelButton::GetThemeAnimation() const {
446 return hover_animation_.get();
449 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
450 ui::NativeTheme::ExtraParams* params) const {
451 GetExtraParams(params);
452 return ui::NativeTheme::kNormal;
455 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
456 ui::NativeTheme::ExtraParams* params) const {
457 GetExtraParams(params);
458 return ui::NativeTheme::kHovered;
461 } // namespace views