ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / ui / views / controls / button / label_button.cc
blob860df6d0764b2a7cfce3c9d8881f041d2c5264ff
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/lazy_instance.h"
8 #include "base/logging.h"
9 #include "ui/gfx/animation/throb_animation.h"
10 #include "ui/gfx/canvas.h"
11 #include "ui/gfx/font_list.h"
12 #include "ui/gfx/geometry/vector2d.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 default 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 const gfx::FontList& GetDefaultNormalFontList() {
36 static base::LazyInstance<gfx::FontList>::Leaky font_list =
37 LAZY_INSTANCE_INITIALIZER;
38 return font_list.Get();
41 const gfx::FontList& GetDefaultBoldFontList() {
42 static base::LazyInstance<gfx::FontList>::Leaky font_list =
43 LAZY_INSTANCE_INITIALIZER;
44 if ((font_list.Get().GetFontStyle() & gfx::Font::BOLD) == 0) {
45 font_list.Get() = font_list.Get().
46 DeriveWithStyle(font_list.Get().GetFontStyle() | gfx::Font::BOLD);
47 DCHECK_NE(font_list.Get().GetFontStyle() & gfx::Font::BOLD, 0);
49 return font_list.Get();
52 } // namespace
54 namespace views {
56 // static
57 const int LabelButton::kHoverAnimationDurationMs = 170;
59 // static
60 const char LabelButton::kViewClassName[] = "LabelButton";
62 LabelButton::LabelButton(ButtonListener* listener, const base::string16& text)
63 : CustomButton(listener),
64 image_(new ImageView()),
65 label_(new Label()),
66 cached_normal_font_list_(GetDefaultNormalFontList()),
67 cached_bold_font_list_(GetDefaultBoldFontList()),
68 button_state_images_(),
69 button_state_colors_(),
70 explicitly_set_colors_(),
71 is_default_(false),
72 style_(STYLE_TEXTBUTTON),
73 border_is_themed_border_(true),
74 image_label_spacing_(kSpacing) {
75 SetAnimationDuration(kHoverAnimationDurationMs);
76 SetText(text);
78 AddChildView(image_);
79 image_->set_interactive(false);
81 AddChildView(label_);
82 label_->SetFontList(cached_normal_font_list_);
83 label_->SetAutoColorReadabilityEnabled(false);
84 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
86 // Initialize the colors, border, and layout.
87 SetStyle(style_);
89 SetAccessibleName(text);
92 LabelButton::~LabelButton() {}
94 const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
95 if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
96 return button_state_images_[STATE_NORMAL];
97 return button_state_images_[for_state];
100 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
101 button_state_images_[for_state] = image;
102 UpdateImage();
105 const base::string16& LabelButton::GetText() const {
106 return label_->text();
109 void LabelButton::SetText(const base::string16& text) {
110 SetAccessibleName(text);
111 label_->SetText(text);
114 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
115 button_state_colors_[for_state] = color;
116 if (for_state == STATE_DISABLED)
117 label_->SetDisabledColor(color);
118 else if (for_state == state())
119 label_->SetEnabledColor(color);
120 explicitly_set_colors_[for_state] = true;
123 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
124 label_->SetShadows(shadows);
127 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
128 label_->SetSubpixelRenderingEnabled(enabled);
131 bool LabelButton::GetTextMultiLine() const {
132 return label_->multi_line();
135 void LabelButton::SetTextMultiLine(bool text_multi_line) {
136 label_->SetMultiLine(text_multi_line);
139 const gfx::FontList& LabelButton::GetFontList() const {
140 return label_->font_list();
143 void LabelButton::SetFontList(const gfx::FontList& font_list) {
144 cached_normal_font_list_ = font_list;
145 cached_bold_font_list_ = font_list.DeriveWithStyle(
146 font_list.GetFontStyle() | gfx::Font::BOLD);
148 // STYLE_BUTTON uses bold text to indicate default buttons.
149 label_->SetFontList(
150 style_ == STYLE_BUTTON && is_default_ ?
151 cached_bold_font_list_ : cached_normal_font_list_);
154 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
155 label_->SetElideBehavior(elide_behavior);
158 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
159 return label_->GetHorizontalAlignment();
162 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
163 label_->SetHorizontalAlignment(alignment);
164 InvalidateLayout();
167 void LabelButton::SetMinSize(const gfx::Size& min_size) {
168 min_size_ = min_size;
169 ResetCachedPreferredSize();
172 void LabelButton::SetMaxSize(const gfx::Size& max_size) {
173 max_size_ = max_size;
174 ResetCachedPreferredSize();
177 void LabelButton::SetIsDefault(bool is_default) {
178 if (is_default == is_default_)
179 return;
180 is_default_ = is_default;
181 ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
182 is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
184 // STYLE_BUTTON uses bold text to indicate default buttons.
185 if (style_ == STYLE_BUTTON) {
186 label_->SetFontList(
187 is_default ? cached_bold_font_list_ : cached_normal_font_list_);
191 void LabelButton::SetStyle(ButtonStyle style) {
192 style_ = style;
193 // Inset the button focus rect from the actual border; roughly match Windows.
194 if (style == STYLE_BUTTON) {
195 SetFocusPainter(nullptr);
196 } else {
197 SetFocusPainter(Painter::CreateDashedFocusPainterWithInsets(
198 gfx::Insets(3, 3, 3, 3)));
200 if (style == STYLE_BUTTON) {
201 label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
202 SetFocusable(true);
204 if (style == STYLE_BUTTON)
205 SetMinSize(gfx::Size(70, 33));
206 OnNativeThemeChanged(GetNativeTheme());
207 ResetCachedPreferredSize();
210 void LabelButton::SetImageLabelSpacing(int spacing) {
211 if (spacing == image_label_spacing_)
212 return;
213 image_label_spacing_ = spacing;
214 ResetCachedPreferredSize();
215 InvalidateLayout();
218 void LabelButton::SetFocusPainter(scoped_ptr<Painter> focus_painter) {
219 focus_painter_ = focus_painter.Pass();
222 gfx::Size LabelButton::GetPreferredSize() const {
223 if (cached_preferred_size_valid_)
224 return cached_preferred_size_;
226 // Use a temporary label copy for sizing to avoid calculation side-effects.
227 Label label(GetText(), cached_normal_font_list_);
228 label.SetShadows(label_->shadows());
229 label.SetMultiLine(GetTextMultiLine());
231 if (style() == STYLE_BUTTON) {
232 // Some text appears wider when rendered normally than when rendered bold.
233 // Accommodate the widest, as buttons may show bold and shouldn't resize.
234 const int current_width = label.GetPreferredSize().width();
235 label.SetFontList(cached_bold_font_list_);
236 if (label.GetPreferredSize().width() < current_width)
237 label.SetFontList(cached_normal_font_list_);
240 // Calculate the required size.
241 const gfx::Size image_size(image_->GetPreferredSize());
242 gfx::Size size(label.GetPreferredSize());
243 if (image_size.width() > 0 && size.width() > 0)
244 size.Enlarge(image_label_spacing_, 0);
245 size.SetToMax(gfx::Size(0, image_size.height()));
246 const gfx::Insets insets(GetInsets());
247 size.Enlarge(image_size.width() + insets.width(), insets.height());
249 // Make the size at least as large as the minimum size needed by the border.
250 size.SetToMax(border() ? border()->GetMinimumSize() : gfx::Size());
252 // Increase the minimum size monotonically with the preferred size.
253 size.SetToMax(min_size_);
254 min_size_ = size;
256 // Return the largest known size clamped to the maximum size (if valid).
257 if (max_size_.width() > 0)
258 size.set_width(std::min(max_size_.width(), size.width()));
259 if (max_size_.height() > 0)
260 size.set_height(std::min(max_size_.height(), size.height()));
262 // Cache this computed size, as recomputing it is an expensive operation.
263 cached_preferred_size_valid_ = true;
264 cached_preferred_size_ = size;
265 return cached_preferred_size_;
268 int LabelButton::GetHeightForWidth(int w) const {
269 w -= GetInsets().width();
270 const gfx::Size image_size(image_->GetPreferredSize());
271 w -= image_size.width();
272 if (image_size.width() > 0 && !GetText().empty())
273 w -= image_label_spacing_;
275 int height = std::max(image_size.height(), label_->GetHeightForWidth(w));
276 if (border())
277 height = std::max(height, border()->GetMinimumSize().height());
279 height = std::max(height, min_size_.height());
280 if (max_size_.height() > 0)
281 height = std::min(height, max_size_.height());
282 return height;
285 void LabelButton::Layout() {
286 gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
287 if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
288 adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
289 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
291 // By default, GetChildAreaBounds() ignores the top and bottom border, but we
292 // want the image to respect it.
293 gfx::Rect child_area(GetChildAreaBounds());
294 gfx::Rect label_area(child_area);
296 gfx::Insets insets(GetInsets());
297 child_area.Inset(insets);
298 // Labels can paint over the vertical component of the border insets.
299 label_area.Inset(insets.left(), 0, insets.right(), 0);
301 gfx::Size image_size(image_->GetPreferredSize());
302 image_size.SetToMin(child_area.size());
304 // The label takes any remaining width after sizing the image, unless both
305 // views are centered. In that case, using the tighter preferred label width
306 // avoids wasted space within the label that would look like awkward padding.
307 gfx::Size label_size(label_area.size());
308 if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
309 label_size.set_width(std::max(child_area.width() -
310 image_size.width() - image_label_spacing_, 0));
311 if (adjusted_alignment == gfx::ALIGN_CENTER) {
312 // Ensure multi-line labels paired with images use their available width.
313 label_size.set_width(
314 std::min(label_size.width(), label_->GetPreferredSize().width()));
318 gfx::Point image_origin(child_area.origin());
319 image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
320 if (adjusted_alignment == gfx::ALIGN_CENTER) {
321 const int spacing = (image_size.width() > 0 && label_size.width() > 0) ?
322 image_label_spacing_ : 0;
323 const int total_width = image_size.width() + label_size.width() +
324 spacing;
325 image_origin.Offset((child_area.width() - total_width) / 2, 0);
326 } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
327 image_origin.Offset(child_area.width() - image_size.width(), 0);
330 gfx::Point label_origin(label_area.origin());
331 if (!image_size.IsEmpty() && adjusted_alignment != gfx::ALIGN_RIGHT) {
332 label_origin.set_x(image_origin.x() + image_size.width() +
333 image_label_spacing_);
336 image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
337 label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
340 const char* LabelButton::GetClassName() const {
341 return kViewClassName;
344 scoped_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
345 return make_scoped_ptr(new LabelButtonBorder(style_));
348 void LabelButton::SetBorder(scoped_ptr<Border> border) {
349 border_is_themed_border_ = false;
350 View::SetBorder(border.Pass());
351 ResetCachedPreferredSize();
354 gfx::Rect LabelButton::GetChildAreaBounds() {
355 return GetLocalBounds();
358 void LabelButton::OnPaint(gfx::Canvas* canvas) {
359 View::OnPaint(canvas);
360 Painter::PaintFocusPainter(this, canvas, focus_painter_.get());
363 void LabelButton::OnFocus() {
364 View::OnFocus();
365 // Typically the border renders differently when focused.
366 SchedulePaint();
369 void LabelButton::OnBlur() {
370 View::OnBlur();
371 // Typically the border renders differently when focused.
372 SchedulePaint();
375 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
376 params->button.checked = false;
377 params->button.indeterminate = false;
378 params->button.is_default = is_default_;
379 params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
380 params->button.has_border = false;
381 params->button.classic_state = 0;
382 params->button.background_color = label_->background_color();
385 void LabelButton::ResetColorsFromNativeTheme() {
386 const ui::NativeTheme* theme = GetNativeTheme();
387 SkColor colors[STATE_COUNT] = {
388 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
389 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
390 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
391 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
394 // Certain styles do not change text color when hovered or pressed.
395 bool constant_text_color = false;
396 // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
397 if (gfx::IsInvertedColorScheme()) {
398 constant_text_color = true;
399 colors[STATE_NORMAL] = SK_ColorWHITE;
400 label_->SetBackgroundColor(SK_ColorBLACK);
401 label_->set_background(Background::CreateSolidBackground(SK_ColorBLACK));
402 label_->SetAutoColorReadabilityEnabled(true);
403 label_->SetShadows(gfx::ShadowValues());
404 } else if (style() == STYLE_BUTTON) {
405 // TODO(erg): This is disabled on desktop linux because of the binary asset
406 // confusion. These details should either be pushed into ui::NativeThemeWin
407 // or should be obsoleted by rendering buttons with paint calls instead of
408 // with static assets. http://crbug.com/350498
409 #if !(defined(OS_LINUX) && !defined(OS_CHROMEOS))
410 constant_text_color = true;
411 colors[STATE_NORMAL] = kStyleButtonTextColor;
412 label_->SetBackgroundColor(theme->GetSystemColor(
413 ui::NativeTheme::kColorId_ButtonBackgroundColor));
414 label_->SetAutoColorReadabilityEnabled(false);
415 label_->SetShadows(gfx::ShadowValues(
416 1, gfx::ShadowValue(gfx::Vector2d(0, 1), 0, kStyleButtonShadowColor)));
417 #endif
418 label_->set_background(NULL);
419 } else {
420 label_->set_background(NULL);
423 if (constant_text_color)
424 colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
426 for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
427 if (!explicitly_set_colors_[state]) {
428 SetTextColor(static_cast<ButtonState>(state), colors[state]);
429 explicitly_set_colors_[state] = false;
434 void LabelButton::UpdateImage() {
435 image_->SetImage(GetImage(state()));
436 ResetCachedPreferredSize();
439 void LabelButton::UpdateThemedBorder() {
440 // Don't override borders set by others.
441 if (!border_is_themed_border_)
442 return;
444 scoped_ptr<LabelButtonBorder> label_button_border = CreateDefaultBorder();
446 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
447 views::LinuxUI* linux_ui = views::LinuxUI::instance();
448 if (linux_ui) {
449 SetBorder(linux_ui->CreateNativeBorder(
450 this, label_button_border.Pass()));
451 } else
452 #endif
454 SetBorder(label_button_border.Pass());
457 border_is_themed_border_ = true;
460 void LabelButton::StateChanged() {
461 const gfx::Size previous_image_size(image_->GetPreferredSize());
462 UpdateImage();
463 const SkColor color = button_state_colors_[state()];
464 if (state() != STATE_DISABLED && label_->enabled_color() != color)
465 label_->SetEnabledColor(color);
466 label_->SetEnabled(state() != STATE_DISABLED);
467 if (image_->GetPreferredSize() != previous_image_size)
468 Layout();
471 void LabelButton::ChildPreferredSizeChanged(View* child) {
472 ResetCachedPreferredSize();
473 PreferredSizeChanged();
476 void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
477 ResetColorsFromNativeTheme();
478 UpdateThemedBorder();
479 // Invalidate the layout to pickup the new insets from the border.
480 InvalidateLayout();
483 ui::NativeTheme::Part LabelButton::GetThemePart() const {
484 return ui::NativeTheme::kPushButton;
487 gfx::Rect LabelButton::GetThemePaintRect() const {
488 return GetLocalBounds();
491 ui::NativeTheme::State LabelButton::GetThemeState(
492 ui::NativeTheme::ExtraParams* params) const {
493 GetExtraParams(params);
494 switch (state()) {
495 case STATE_NORMAL: return ui::NativeTheme::kNormal;
496 case STATE_HOVERED: return ui::NativeTheme::kHovered;
497 case STATE_PRESSED: return ui::NativeTheme::kPressed;
498 case STATE_DISABLED: return ui::NativeTheme::kDisabled;
499 case STATE_COUNT: NOTREACHED() << "Unknown state: " << state();
501 return ui::NativeTheme::kNormal;
504 const gfx::Animation* LabelButton::GetThemeAnimation() const {
505 return hover_animation_.get();
508 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
509 ui::NativeTheme::ExtraParams* params) const {
510 GetExtraParams(params);
511 return ui::NativeTheme::kNormal;
514 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
515 ui::NativeTheme::ExtraParams* params) const {
516 GetExtraParams(params);
517 return ui::NativeTheme::kHovered;
520 void LabelButton::ResetCachedPreferredSize() {
521 cached_preferred_size_valid_ = false;
522 cached_preferred_size_ = gfx::Size();
525 } // namespace views