blob: 39140ed5def3f561c10d57bf56efcae47c1bdc8e [file] [log] [blame]
[email protected]70e983cc2012-10-16 21:08:221// 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/label_button.h"
6
7#include "base/logging.h"
8#include "grit/ui_resources.h"
[email protected]70e983cc2012-10-16 21:08:229#include "ui/base/resource/resource_bundle.h"
[email protected]ffb15d12013-09-15 17:29:3010#include "ui/gfx/animation/throb_animation.h"
[email protected]8858a832013-06-19 14:46:3011#include "ui/gfx/sys_color_change_listener.h"
[email protected]990e6222012-11-16 13:31:1812#include "ui/native_theme/native_theme.h"
[email protected]70e983cc2012-10-16 21:08:2213#include "ui/views/controls/button/label_button_border.h"
14#include "ui/views/focus_border.h"
[email protected]6d114932013-04-24 02:38:2015#include "ui/views/window/dialog_delegate.h"
[email protected]70e983cc2012-10-16 21:08:2216
17#if defined(OS_WIN)
[email protected]990e6222012-11-16 13:31:1818#include "ui/native_theme/native_theme_win.h"
[email protected]70e983cc2012-10-16 21:08:2219#endif
20
[email protected]70e983cc2012-10-16 21:08:2221namespace {
22
23// The spacing between the icon and text.
[email protected]c5de33662012-10-27 22:37:3024const int kSpacing = 5;
[email protected]70e983cc2012-10-16 21:08:2225
26// The length of the hover fade animation.
[email protected]c5de33662012-10-27 22:37:3027const int kHoverAnimationDurationMs = 170;
[email protected]70e983cc2012-10-16 21:08:2228
[email protected]8858a832013-06-19 14:46:3029// Default text and shadow colors for STYLE_BUTTON.
30const SkColor kStyleButtonTextColor = SK_ColorBLACK;
31const SkColor kStyleButtonShadowColor = SK_ColorWHITE;
32
[email protected]70e983cc2012-10-16 21:08:2233} // namespace
34
[email protected]c5de33662012-10-27 22:37:3035namespace views {
36
[email protected]fe22ba12013-02-21 13:43:4837// static
[email protected]f4167cf2013-05-26 23:36:1538const char LabelButton::kViewClassName[] = "LabelButton";
[email protected]fe22ba12013-02-21 13:43:4839
[email protected]70e983cc2012-10-16 21:08:2240LabelButton::LabelButton(ButtonListener* listener, const string16& text)
41 : CustomButton(listener),
42 image_(new ImageView()),
[email protected]3e4efae2013-04-05 13:17:3643 label_(new Label()),
[email protected]379bc982012-12-19 19:56:5344 button_state_images_(),
45 button_state_colors_(),
46 explicitly_set_colors_(),
[email protected]e59fc52a2013-02-20 20:24:1147 is_default_(false),
[email protected]fe22ba12013-02-21 13:43:4848 style_(STYLE_TEXTBUTTON) {
[email protected]70e983cc2012-10-16 21:08:2249 SetAnimationDuration(kHoverAnimationDurationMs);
[email protected]3e4efae2013-04-05 13:17:3650 SetText(text);
[email protected]70e983cc2012-10-16 21:08:2251
52 AddChildView(image_);
53 image_->set_interactive(false);
54
55 AddChildView(label_);
56 label_->SetAutoColorReadabilityEnabled(false);
[email protected]cc563f02012-11-07 17:37:3657 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
[email protected]70e983cc2012-10-16 21:08:2258
[email protected]fe22ba12013-02-21 13:43:4859 // Initialize the colors, border, and layout.
60 SetStyle(style_);
[email protected]cd70b3e2013-04-03 20:01:3761
62 SetAccessibleName(text);
[email protected]70e983cc2012-10-16 21:08:2263}
64
65LabelButton::~LabelButton() {}
66
67const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
[email protected]fca8dc02012-11-14 16:25:5668 if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
69 return button_state_images_[STATE_NORMAL];
[email protected]70e983cc2012-10-16 21:08:2270 return button_state_images_[for_state];
71}
72
73void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
74 button_state_images_[for_state] = image;
[email protected]987b7662013-05-23 10:45:4975 UpdateImage();
[email protected]70e983cc2012-10-16 21:08:2276}
77
78const string16& LabelButton::GetText() const {
79 return label_->text();
80}
81
82void LabelButton::SetText(const string16& text) {
[email protected]3e4efae2013-04-05 13:17:3683 SetAccessibleName(text);
[email protected]70e983cc2012-10-16 21:08:2284 label_->SetText(text);
85}
86
87void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
88 button_state_colors_[for_state] = color;
[email protected]fca8dc02012-11-14 16:25:5689 if (for_state == STATE_DISABLED)
[email protected]70e983cc2012-10-16 21:08:2290 label_->SetDisabledColor(color);
91 else if (for_state == state())
92 label_->SetEnabledColor(color);
[email protected]204b2082012-11-07 17:53:0393 explicitly_set_colors_[for_state] = true;
[email protected]70e983cc2012-10-16 21:08:2294}
95
96bool LabelButton::GetTextMultiLine() const {
97 return label_->is_multi_line();
98}
99
100void LabelButton::SetTextMultiLine(bool text_multi_line) {
101 label_->SetMultiLine(text_multi_line);
102}
103
[email protected]cfc888a2012-11-05 18:20:50104const gfx::Font& LabelButton::GetFont() const {
105 return label_->font();
106}
107
108void LabelButton::SetFont(const gfx::Font& font) {
109 label_->SetFont(font);
110}
111
[email protected]252cec32013-09-24 03:17:52112void LabelButton::SetElideBehavior(Label::ElideBehavior elide_behavior) {
113 label_->SetElideBehavior(elide_behavior);
114}
115
[email protected]cc563f02012-11-07 17:37:36116gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
[email protected]70e983cc2012-10-16 21:08:22117 return label_->horizontal_alignment();
118}
119
[email protected]cc563f02012-11-07 17:37:36120void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
[email protected]70e983cc2012-10-16 21:08:22121 label_->SetHorizontalAlignment(alignment);
122 InvalidateLayout();
123}
124
[email protected]e59fc52a2013-02-20 20:24:11125void LabelButton::SetIsDefault(bool is_default) {
126 if (is_default == is_default_)
[email protected]70e983cc2012-10-16 21:08:22127 return;
[email protected]e59fc52a2013-02-20 20:24:11128 is_default_ = is_default;
[email protected]70e983cc2012-10-16 21:08:22129 ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
[email protected]e59fc52a2013-02-20 20:24:11130 is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
[email protected]640bff12013-04-17 21:08:14131
132 // STYLE_BUTTON uses bold text to indicate default buttons.
133 if (style_ == STYLE_BUTTON) {
134 int style = label_->font().GetStyle();
[email protected]72dd2a52013-08-13 01:15:15135 style = is_default ? style | gfx::Font::BOLD : style & ~gfx::Font::BOLD;
[email protected]640bff12013-04-17 21:08:14136 label_->SetFont(label_->font().DeriveFont(0, style));
137 }
[email protected]70e983cc2012-10-16 21:08:22138}
139
[email protected]fe22ba12013-02-21 13:43:48140void LabelButton::SetStyle(ButtonStyle style) {
[email protected]640bff12013-04-17 21:08:14141 // Use the new button style instead of the native button style.
142 // TODO(msw): Officialy deprecate and remove STYLE_NATIVE_TEXTBUTTON.
[email protected]6d114932013-04-24 02:38:20143 if (DialogDelegate::UseNewStyle() && style == STYLE_NATIVE_TEXTBUTTON)
[email protected]640bff12013-04-17 21:08:14144 style = STYLE_BUTTON;
145
[email protected]fe22ba12013-02-21 13:43:48146 style_ = style;
147 set_border(new LabelButtonBorder(style));
148 // Inset the button focus rect from the actual border; roughly match Windows.
[email protected]682189b2013-05-10 02:42:26149 set_focus_border(style == STYLE_BUTTON ?
150 NULL : FocusBorder::CreateDashedFocusBorder(3, 3, 3, 3));
[email protected]640bff12013-04-17 21:08:14151 if (style == STYLE_BUTTON || style == STYLE_NATIVE_TEXTBUTTON) {
[email protected]d0abd282013-02-28 23:59:08152 label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
[email protected]df798cb2013-04-05 06:27:52153 set_focusable(true);
[email protected]640bff12013-04-17 21:08:14154 }
[email protected]8858a832013-06-19 14:46:30155 if (style == STYLE_BUTTON)
[email protected]31012722013-06-12 22:00:10156 set_min_size(gfx::Size(70, 33));
[email protected]204b2082012-11-07 17:53:03157 // Invalidate the layout to pickup the new insets from the border.
158 InvalidateLayout();
[email protected]379bc982012-12-19 19:56:53159 ResetColorsFromNativeTheme();
[email protected]204b2082012-11-07 17:53:03160}
161
[email protected]7f39bb692013-01-07 23:08:16162gfx::Size LabelButton::GetPreferredSize() {
[email protected]9baef622013-05-14 05:30:46163 // Use a temporary label copy for sizing to avoid calculation side-effects.
[email protected]5b7355b2013-07-17 04:44:54164 gfx::Font font = GetFont();
165 Label label(GetText(), font);
[email protected]9baef622013-05-14 05:30:46166 label.SetMultiLine(GetTextMultiLine());
[email protected]640bff12013-04-17 21:08:14167
[email protected]5b7355b2013-07-17 04:44:54168 if (style() == STYLE_BUTTON) {
169 // Some text appears wider when rendered normally than when rendered bold.
170 // Accommodate the widest, as buttons may show bold and shouldn't resize.
171 const int current_width = label.GetPreferredSize().width();
172 label.SetFont(font.DeriveFont(0, font.GetStyle() ^ gfx::Font::BOLD));
173 if (label.GetPreferredSize().width() < current_width)
174 label.SetFont(font);
175 }
176
[email protected]9baef622013-05-14 05:30:46177 // Resize multi-line labels given the current limited available width.
[email protected]7f39bb692013-01-07 23:08:16178 const gfx::Size image_size(image_->GetPreferredSize());
[email protected]9baef622013-05-14 05:30:46179 const int image_width = image_size.width();
180 if (GetTextMultiLine() && (width() > image_width + kSpacing))
181 label.SizeToFit(width() - image_width - (image_width > 0 ? kSpacing : 0));
[email protected]7f39bb692013-01-07 23:08:16182
183 // Calculate the required size.
[email protected]9baef622013-05-14 05:30:46184 gfx::Size size(label.GetPreferredSize());
185 if (image_width > 0 && size.width() > 0)
[email protected]7f39bb692013-01-07 23:08:16186 size.Enlarge(kSpacing, 0);
[email protected]a4a08d02013-05-30 00:18:00187 size.SetToMax(gfx::Size(0, image_size.height()));
[email protected]9baef622013-05-14 05:30:46188 const gfx::Insets insets(GetInsets());
189 size.Enlarge(image_size.width() + insets.width(), insets.height());
[email protected]640bff12013-04-17 21:08:14190
[email protected]7f39bb692013-01-07 23:08:16191 // Increase the minimum size monotonically with the preferred size.
[email protected]a4a08d02013-05-30 00:18:00192 size.SetToMax(min_size_);
[email protected]7f39bb692013-01-07 23:08:16193 min_size_ = size;
194
195 // Return the largest known size clamped to the maximum size (if valid).
196 if (max_size_.width() > 0)
197 size.set_width(std::min(max_size_.width(), size.width()));
198 if (max_size_.height() > 0)
199 size.set_height(std::min(max_size_.height(), size.height()));
200 return size;
201}
202
[email protected]987b7662013-05-23 10:45:49203void LabelButton::Layout() {
204 gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
205 if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
206 adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
207 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
208
209 gfx::Rect child_area(GetLocalBounds());
210 child_area.Inset(GetInsets());
211
212 gfx::Size image_size(image_->GetPreferredSize());
213 image_size.set_width(std::min(image_size.width(), child_area.width()));
214 image_size.set_height(std::min(image_size.height(), child_area.height()));
215
216 // The label takes any remaining width after sizing the image, unless both
217 // views are centered. In that case, using the tighter preferred label width
218 // avoids wasted space within the label that would look like awkward padding.
219 gfx::Size label_size(child_area.size());
220 if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
221 label_size.set_width(
222 std::max(child_area.width() - image_size.width() - kSpacing, 0));
223 if (adjusted_alignment == gfx::ALIGN_CENTER) {
224 // Ensure multi-line labels paired with images use their available width.
225 if (GetTextMultiLine())
226 label_->SizeToFit(label_size.width());
227 label_size.set_width(
228 std::min(label_size.width(), label_->GetPreferredSize().width()));
229 }
230 }
231
232 gfx::Point image_origin(child_area.origin());
233 image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
234 if (adjusted_alignment == gfx::ALIGN_CENTER) {
235 const int total_width = image_size.width() + label_size.width() +
236 ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
237 image_origin.Offset((child_area.width() - total_width) / 2, 0);
238 } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
239 image_origin.Offset(child_area.width() - image_size.width(), 0);
240 }
241
242 gfx::Point label_origin(child_area.origin());
243 if (!image_size.IsEmpty() &&adjusted_alignment != gfx::ALIGN_RIGHT)
244 label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
245
246 image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
247 label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
248}
249
[email protected]707bd372013-05-09 08:32:27250const char* LabelButton::GetClassName() const {
[email protected]fe22ba12013-02-21 13:43:48251 return kViewClassName;
252}
253
[email protected]987b7662013-05-23 10:45:49254void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
255 params->button.checked = false;
256 params->button.indeterminate = false;
257 params->button.is_default = is_default_;
258 params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
259 params->button.has_border = style() == STYLE_NATIVE_TEXTBUTTON;
260 params->button.classic_state = 0;
[email protected]8858a832013-06-19 14:46:30261 params->button.background_color = label()->background_color();
[email protected]987b7662013-05-23 10:45:49262}
263
[email protected]379bc982012-12-19 19:56:53264void LabelButton::ResetColorsFromNativeTheme() {
[email protected]204b2082012-11-07 17:53:03265 const ui::NativeTheme* theme = GetNativeTheme();
[email protected]fca8dc02012-11-14 16:25:56266 SkColor colors[STATE_COUNT] = {
[email protected]1b768f8f2013-05-17 01:16:52267 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
268 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
269 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
270 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
[email protected]70e983cc2012-10-16 21:08:22271 };
[email protected]644f4fb2013-03-01 21:14:34272
273 // Certain styles do not change text color when hovered or pressed.
[email protected]8858a832013-06-19 14:46:30274 bool constant_text_color = false;
[email protected]70e983cc2012-10-16 21:08:22275#if defined(OS_WIN)
[email protected]644f4fb2013-03-01 21:14:34276 constant_text_color |= (style() == STYLE_NATIVE_TEXTBUTTON &&
277 theme == ui::NativeThemeWin::instance());
[email protected]70e983cc2012-10-16 21:08:22278#endif
[email protected]8858a832013-06-19 14:46:30279
[email protected]8858a832013-06-19 14:46:30280 // Use hardcoded colors for inverted color scheme support and STYLE_BUTTON.
281 if (gfx::IsInvertedColorScheme()) {
282 constant_text_color = true;
283 colors[STATE_NORMAL] = SK_ColorWHITE;
284 label_->SetBackgroundColor(SK_ColorBLACK);
285 label_->SetAutoColorReadabilityEnabled(true);
286 label_->ClearEmbellishing();
287 } else if (style() == STYLE_BUTTON) {
288 constant_text_color = true;
289 colors[STATE_NORMAL] = kStyleButtonTextColor;
[email protected]763f83b2013-09-17 18:26:32290 label_->SetBackgroundColor(theme->GetSystemColor(
291 ui::NativeTheme::kColorId_ButtonBackgroundColor));
[email protected]8858a832013-06-19 14:46:30292 label_->SetAutoColorReadabilityEnabled(false);
293 label_->SetShadowColors(kStyleButtonShadowColor, kStyleButtonShadowColor);
294 label_->SetShadowOffset(0, 1);
295 }
296
[email protected]644f4fb2013-03-01 21:14:34297 if (constant_text_color)
298 colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
299
[email protected]fca8dc02012-11-14 16:25:56300 for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
[email protected]379bc982012-12-19 19:56:53301 if (!explicitly_set_colors_[state]) {
[email protected]204b2082012-11-07 17:53:03302 SetTextColor(static_cast<ButtonState>(state), colors[state]);
303 explicitly_set_colors_[state] = false;
304 }
305 }
[email protected]70e983cc2012-10-16 21:08:22306}
307
[email protected]763f83b2013-09-17 18:26:32308void LabelButton::UpdateImage() {
309 image_->SetImage(GetImage(state()));
310}
311
[email protected]70e983cc2012-10-16 21:08:22312void LabelButton::StateChanged() {
313 const gfx::Size previous_image_size(image_->GetPreferredSize());
[email protected]987b7662013-05-23 10:45:49314 UpdateImage();
[email protected]70e983cc2012-10-16 21:08:22315 const SkColor color = button_state_colors_[state()];
[email protected]fca8dc02012-11-14 16:25:56316 if (state() != STATE_DISABLED && label_->enabled_color() != color)
[email protected]70e983cc2012-10-16 21:08:22317 label_->SetEnabledColor(color);
[email protected]4e88e352012-12-04 19:12:09318 label_->SetEnabled(state() != STATE_DISABLED);
[email protected]70e983cc2012-10-16 21:08:22319 if (image_->GetPreferredSize() != previous_image_size)
320 Layout();
321}
322
[email protected]70e983cc2012-10-16 21:08:22323void LabelButton::ChildPreferredSizeChanged(View* child) {
324 PreferredSizeChanged();
325}
326
[email protected]204b2082012-11-07 17:53:03327void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
[email protected]379bc982012-12-19 19:56:53328 ResetColorsFromNativeTheme();
[email protected]204b2082012-11-07 17:53:03329}
330
[email protected]70e983cc2012-10-16 21:08:22331ui::NativeTheme::Part LabelButton::GetThemePart() const {
332 return ui::NativeTheme::kPushButton;
333}
334
335gfx::Rect LabelButton::GetThemePaintRect() const {
336 return GetLocalBounds();
337}
338
339ui::NativeTheme::State LabelButton::GetThemeState(
340 ui::NativeTheme::ExtraParams* params) const {
341 GetExtraParams(params);
[email protected]987b7662013-05-23 10:45:49342 switch (state()) {
[email protected]fca8dc02012-11-14 16:25:56343 case STATE_NORMAL: return ui::NativeTheme::kNormal;
344 case STATE_HOVERED: return ui::NativeTheme::kHovered;
345 case STATE_PRESSED: return ui::NativeTheme::kPressed;
346 case STATE_DISABLED: return ui::NativeTheme::kDisabled;
347 case STATE_COUNT: NOTREACHED() << "Unknown state: " << state();
[email protected]70e983cc2012-10-16 21:08:22348 }
349 return ui::NativeTheme::kNormal;
350}
351
[email protected]ffb15d12013-09-15 17:29:30352const gfx::Animation* LabelButton::GetThemeAnimation() const {
[email protected]204b2082012-11-07 17:53:03353#if defined(OS_WIN)
[email protected]fe22ba12013-02-21 13:43:48354 if (style() == STYLE_NATIVE_TEXTBUTTON &&
355 GetNativeTheme() == ui::NativeThemeWin::instance()) {
[email protected]204b2082012-11-07 17:53:03356 return ui::NativeThemeWin::instance()->IsThemingActive() ?
357 hover_animation_.get() : NULL;
358 }
[email protected]70e983cc2012-10-16 21:08:22359#endif
360 return hover_animation_.get();
361}
362
363ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
364 ui::NativeTheme::ExtraParams* params) const {
365 GetExtraParams(params);
366 return ui::NativeTheme::kNormal;
367}
368
369ui::NativeTheme::State LabelButton::GetForegroundThemeState(
370 ui::NativeTheme::ExtraParams* params) const {
371 GetExtraParams(params);
372 return ui::NativeTheme::kHovered;
373}
374
[email protected]70e983cc2012-10-16 21:08:22375} // namespace views