blob: 8a08d245405e5f50f667134abc67dd06f3fe477b [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"
9#include "ui/base/animation/throb_animation.h"
10#include "ui/base/resource/resource_bundle.h"
[email protected]990e6222012-11-16 13:31:1811#include "ui/native_theme/native_theme.h"
[email protected]70e983cc2012-10-16 21:08:2212#include "ui/views/controls/button/label_button_border.h"
13#include "ui/views/focus_border.h"
[email protected]6d114932013-04-24 02:38:2014#include "ui/views/window/dialog_delegate.h"
[email protected]70e983cc2012-10-16 21:08:2215
16#if defined(OS_WIN)
17#include "ui/gfx/color_utils.h"
[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
29} // namespace
30
[email protected]c5de33662012-10-27 22:37:3031namespace views {
32
[email protected]fe22ba12013-02-21 13:43:4833// static
34const char LabelButton::kViewClassName[] = "views/LabelButton";
35
[email protected]70e983cc2012-10-16 21:08:2236LabelButton::LabelButton(ButtonListener* listener, const string16& text)
37 : CustomButton(listener),
38 image_(new ImageView()),
[email protected]3e4efae2013-04-05 13:17:3639 label_(new Label()),
[email protected]379bc982012-12-19 19:56:5340 button_state_images_(),
41 button_state_colors_(),
42 explicitly_set_colors_(),
[email protected]e59fc52a2013-02-20 20:24:1143 is_default_(false),
[email protected]fe22ba12013-02-21 13:43:4844 style_(STYLE_TEXTBUTTON) {
[email protected]70e983cc2012-10-16 21:08:2245 SetAnimationDuration(kHoverAnimationDurationMs);
[email protected]3e4efae2013-04-05 13:17:3646 SetText(text);
[email protected]70e983cc2012-10-16 21:08:2247
48 AddChildView(image_);
49 image_->set_interactive(false);
50
51 AddChildView(label_);
52 label_->SetAutoColorReadabilityEnabled(false);
[email protected]cc563f02012-11-07 17:37:3653 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
[email protected]70e983cc2012-10-16 21:08:2254
[email protected]fe22ba12013-02-21 13:43:4855 // Initialize the colors, border, and layout.
56 SetStyle(style_);
[email protected]cd70b3e2013-04-03 20:01:3757
58 SetAccessibleName(text);
[email protected]70e983cc2012-10-16 21:08:2259}
60
61LabelButton::~LabelButton() {}
62
63const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
[email protected]fca8dc02012-11-14 16:25:5664 if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
65 return button_state_images_[STATE_NORMAL];
[email protected]70e983cc2012-10-16 21:08:2266 return button_state_images_[for_state];
67}
68
69void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
70 button_state_images_[for_state] = image;
[email protected]987b7662013-05-23 10:45:4971 UpdateImage();
[email protected]70e983cc2012-10-16 21:08:2272}
73
74const string16& LabelButton::GetText() const {
75 return label_->text();
76}
77
78void LabelButton::SetText(const string16& text) {
[email protected]3e4efae2013-04-05 13:17:3679 SetAccessibleName(text);
[email protected]70e983cc2012-10-16 21:08:2280 label_->SetText(text);
81}
82
83void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
84 button_state_colors_[for_state] = color;
[email protected]fca8dc02012-11-14 16:25:5685 if (for_state == STATE_DISABLED)
[email protected]70e983cc2012-10-16 21:08:2286 label_->SetDisabledColor(color);
87 else if (for_state == state())
88 label_->SetEnabledColor(color);
[email protected]204b2082012-11-07 17:53:0389 explicitly_set_colors_[for_state] = true;
[email protected]70e983cc2012-10-16 21:08:2290}
91
92bool LabelButton::GetTextMultiLine() const {
93 return label_->is_multi_line();
94}
95
96void LabelButton::SetTextMultiLine(bool text_multi_line) {
97 label_->SetMultiLine(text_multi_line);
98}
99
[email protected]cfc888a2012-11-05 18:20:50100const gfx::Font& LabelButton::GetFont() const {
101 return label_->font();
102}
103
104void LabelButton::SetFont(const gfx::Font& font) {
105 label_->SetFont(font);
106}
107
[email protected]cc563f02012-11-07 17:37:36108gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
[email protected]70e983cc2012-10-16 21:08:22109 return label_->horizontal_alignment();
110}
111
[email protected]cc563f02012-11-07 17:37:36112void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
[email protected]70e983cc2012-10-16 21:08:22113 label_->SetHorizontalAlignment(alignment);
114 InvalidateLayout();
115}
116
[email protected]e59fc52a2013-02-20 20:24:11117void LabelButton::SetIsDefault(bool is_default) {
118 if (is_default == is_default_)
[email protected]70e983cc2012-10-16 21:08:22119 return;
[email protected]e59fc52a2013-02-20 20:24:11120 is_default_ = is_default;
[email protected]70e983cc2012-10-16 21:08:22121 ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
[email protected]e59fc52a2013-02-20 20:24:11122 is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
[email protected]640bff12013-04-17 21:08:14123
124 // STYLE_BUTTON uses bold text to indicate default buttons.
125 if (style_ == STYLE_BUTTON) {
126 int style = label_->font().GetStyle();
127 style = is_default ? style | gfx::Font::BOLD : style & !gfx::Font::BOLD;
128 label_->SetFont(label_->font().DeriveFont(0, style));
129 }
[email protected]70e983cc2012-10-16 21:08:22130}
131
[email protected]fe22ba12013-02-21 13:43:48132void LabelButton::SetStyle(ButtonStyle style) {
[email protected]640bff12013-04-17 21:08:14133 // Use the new button style instead of the native button style.
134 // TODO(msw): Officialy deprecate and remove STYLE_NATIVE_TEXTBUTTON.
[email protected]6d114932013-04-24 02:38:20135 if (DialogDelegate::UseNewStyle() && style == STYLE_NATIVE_TEXTBUTTON)
[email protected]640bff12013-04-17 21:08:14136 style = STYLE_BUTTON;
137
[email protected]fe22ba12013-02-21 13:43:48138 style_ = style;
139 set_border(new LabelButtonBorder(style));
140 // Inset the button focus rect from the actual border; roughly match Windows.
[email protected]682189b2013-05-10 02:42:26141 set_focus_border(style == STYLE_BUTTON ?
142 NULL : FocusBorder::CreateDashedFocusBorder(3, 3, 3, 3));
[email protected]640bff12013-04-17 21:08:14143 if (style == STYLE_BUTTON || style == STYLE_NATIVE_TEXTBUTTON) {
[email protected]d0abd282013-02-28 23:59:08144 label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
[email protected]df798cb2013-04-05 06:27:52145 set_focusable(true);
[email protected]640bff12013-04-17 21:08:14146 }
[email protected]644f4fb2013-03-01 21:14:34147 if (style == STYLE_BUTTON) {
148 set_min_size(gfx::Size(70, 31));
149 const SkColor color = GetNativeTheme()->GetSystemColor(
150 ui::NativeTheme::kColorId_WindowBackground);
151 label_->SetShadowColors(color, color);
152 label_->SetShadowOffset(0, 1);
153 }
[email protected]204b2082012-11-07 17:53:03154 // Invalidate the layout to pickup the new insets from the border.
155 InvalidateLayout();
[email protected]379bc982012-12-19 19:56:53156 ResetColorsFromNativeTheme();
[email protected]204b2082012-11-07 17:53:03157}
158
[email protected]7f39bb692013-01-07 23:08:16159gfx::Size LabelButton::GetPreferredSize() {
[email protected]640bff12013-04-17 21:08:14160 const gfx::Font font = label_->font();
[email protected]9baef622013-05-14 05:30:46161 // Use a temporary label copy for sizing to avoid calculation side-effects.
162 // Use bold text for STYLE_BUTTON to avoid changing size when made default.
163 Label label(label_->text(), style_ == STYLE_BUTTON ?
164 font.DeriveFont(0, font.GetStyle() | gfx::Font::BOLD) : font);
165 label.SetMultiLine(GetTextMultiLine());
[email protected]640bff12013-04-17 21:08:14166
[email protected]9baef622013-05-14 05:30:46167 // Resize multi-line labels given the current limited available width.
[email protected]7f39bb692013-01-07 23:08:16168 const gfx::Size image_size(image_->GetPreferredSize());
[email protected]9baef622013-05-14 05:30:46169 const int image_width = image_size.width();
170 if (GetTextMultiLine() && (width() > image_width + kSpacing))
171 label.SizeToFit(width() - image_width - (image_width > 0 ? kSpacing : 0));
[email protected]7f39bb692013-01-07 23:08:16172
173 // Calculate the required size.
[email protected]9baef622013-05-14 05:30:46174 gfx::Size size(label.GetPreferredSize());
175 if (image_width > 0 && size.width() > 0)
[email protected]7f39bb692013-01-07 23:08:16176 size.Enlarge(kSpacing, 0);
[email protected]9baef622013-05-14 05:30:46177 size.ClampToMin(gfx::Size(0, image_size.height()));
178 const gfx::Insets insets(GetInsets());
179 size.Enlarge(image_size.width() + insets.width(), insets.height());
[email protected]640bff12013-04-17 21:08:14180
[email protected]7f39bb692013-01-07 23:08:16181 // Increase the minimum size monotonically with the preferred size.
[email protected]9baef622013-05-14 05:30:46182 size.ClampToMin(min_size_);
[email protected]7f39bb692013-01-07 23:08:16183 min_size_ = size;
184
185 // Return the largest known size clamped to the maximum size (if valid).
186 if (max_size_.width() > 0)
187 size.set_width(std::min(max_size_.width(), size.width()));
188 if (max_size_.height() > 0)
189 size.set_height(std::min(max_size_.height(), size.height()));
190 return size;
191}
192
[email protected]987b7662013-05-23 10:45:49193void LabelButton::Layout() {
194 gfx::HorizontalAlignment adjusted_alignment = GetHorizontalAlignment();
195 if (base::i18n::IsRTL() && adjusted_alignment != gfx::ALIGN_CENTER)
196 adjusted_alignment = (adjusted_alignment == gfx::ALIGN_LEFT) ?
197 gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
198
199 gfx::Rect child_area(GetLocalBounds());
200 child_area.Inset(GetInsets());
201
202 gfx::Size image_size(image_->GetPreferredSize());
203 image_size.set_width(std::min(image_size.width(), child_area.width()));
204 image_size.set_height(std::min(image_size.height(), child_area.height()));
205
206 // The label takes any remaining width after sizing the image, unless both
207 // views are centered. In that case, using the tighter preferred label width
208 // avoids wasted space within the label that would look like awkward padding.
209 gfx::Size label_size(child_area.size());
210 if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
211 label_size.set_width(
212 std::max(child_area.width() - image_size.width() - kSpacing, 0));
213 if (adjusted_alignment == gfx::ALIGN_CENTER) {
214 // Ensure multi-line labels paired with images use their available width.
215 if (GetTextMultiLine())
216 label_->SizeToFit(label_size.width());
217 label_size.set_width(
218 std::min(label_size.width(), label_->GetPreferredSize().width()));
219 }
220 }
221
222 gfx::Point image_origin(child_area.origin());
223 image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
224 if (adjusted_alignment == gfx::ALIGN_CENTER) {
225 const int total_width = image_size.width() + label_size.width() +
226 ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
227 image_origin.Offset((child_area.width() - total_width) / 2, 0);
228 } else if (adjusted_alignment == gfx::ALIGN_RIGHT) {
229 image_origin.Offset(child_area.width() - image_size.width(), 0);
230 }
231
232 gfx::Point label_origin(child_area.origin());
233 if (!image_size.IsEmpty() &&adjusted_alignment != gfx::ALIGN_RIGHT)
234 label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
235
236 image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
237 label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
238}
239
[email protected]707bd372013-05-09 08:32:27240const char* LabelButton::GetClassName() const {
[email protected]fe22ba12013-02-21 13:43:48241 return kViewClassName;
242}
243
[email protected]987b7662013-05-23 10:45:49244void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
245 params->button.checked = false;
246 params->button.indeterminate = false;
247 params->button.is_default = is_default_;
248 params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
249 params->button.has_border = style() == STYLE_NATIVE_TEXTBUTTON;
250 params->button.classic_state = 0;
251 params->button.background_color = GetNativeTheme()->GetSystemColor(
252 ui::NativeTheme::kColorId_ButtonBackgroundColor);
253}
254
255void LabelButton::UpdateImage() {
256 image_->SetImage(GetImage(state()));
257}
258
[email protected]379bc982012-12-19 19:56:53259void LabelButton::ResetColorsFromNativeTheme() {
[email protected]204b2082012-11-07 17:53:03260 const ui::NativeTheme* theme = GetNativeTheme();
[email protected]fca8dc02012-11-14 16:25:56261 SkColor colors[STATE_COUNT] = {
[email protected]1b768f8f2013-05-17 01:16:52262 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonEnabledColor),
263 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
264 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonHoverColor),
265 theme->GetSystemColor(ui::NativeTheme::kColorId_ButtonDisabledColor),
[email protected]70e983cc2012-10-16 21:08:22266 };
[email protected]644f4fb2013-03-01 21:14:34267
268 // Certain styles do not change text color when hovered or pressed.
269 bool constant_text_color = style() == STYLE_BUTTON;
[email protected]70e983cc2012-10-16 21:08:22270#if defined(OS_WIN)
[email protected]644f4fb2013-03-01 21:14:34271 constant_text_color |= (style() == STYLE_NATIVE_TEXTBUTTON &&
272 theme == ui::NativeThemeWin::instance());
[email protected]70e983cc2012-10-16 21:08:22273#endif
[email protected]644f4fb2013-03-01 21:14:34274 if (constant_text_color)
275 colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
276
[email protected]fca8dc02012-11-14 16:25:56277 for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
[email protected]379bc982012-12-19 19:56:53278 if (!explicitly_set_colors_[state]) {
[email protected]204b2082012-11-07 17:53:03279 SetTextColor(static_cast<ButtonState>(state), colors[state]);
280 explicitly_set_colors_[state] = false;
281 }
282 }
[email protected]70e983cc2012-10-16 21:08:22283}
284
285void LabelButton::StateChanged() {
286 const gfx::Size previous_image_size(image_->GetPreferredSize());
[email protected]987b7662013-05-23 10:45:49287 UpdateImage();
[email protected]70e983cc2012-10-16 21:08:22288 const SkColor color = button_state_colors_[state()];
[email protected]fca8dc02012-11-14 16:25:56289 if (state() != STATE_DISABLED && label_->enabled_color() != color)
[email protected]70e983cc2012-10-16 21:08:22290 label_->SetEnabledColor(color);
[email protected]4e88e352012-12-04 19:12:09291 label_->SetEnabled(state() != STATE_DISABLED);
[email protected]70e983cc2012-10-16 21:08:22292 if (image_->GetPreferredSize() != previous_image_size)
293 Layout();
294}
295
[email protected]70e983cc2012-10-16 21:08:22296void LabelButton::ChildPreferredSizeChanged(View* child) {
297 PreferredSizeChanged();
298}
299
[email protected]204b2082012-11-07 17:53:03300void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
[email protected]379bc982012-12-19 19:56:53301 ResetColorsFromNativeTheme();
[email protected]204b2082012-11-07 17:53:03302}
303
[email protected]70e983cc2012-10-16 21:08:22304ui::NativeTheme::Part LabelButton::GetThemePart() const {
305 return ui::NativeTheme::kPushButton;
306}
307
308gfx::Rect LabelButton::GetThemePaintRect() const {
309 return GetLocalBounds();
310}
311
312ui::NativeTheme::State LabelButton::GetThemeState(
313 ui::NativeTheme::ExtraParams* params) const {
314 GetExtraParams(params);
[email protected]987b7662013-05-23 10:45:49315 switch (state()) {
[email protected]fca8dc02012-11-14 16:25:56316 case STATE_NORMAL: return ui::NativeTheme::kNormal;
317 case STATE_HOVERED: return ui::NativeTheme::kHovered;
318 case STATE_PRESSED: return ui::NativeTheme::kPressed;
319 case STATE_DISABLED: return ui::NativeTheme::kDisabled;
320 case STATE_COUNT: NOTREACHED() << "Unknown state: " << state();
[email protected]70e983cc2012-10-16 21:08:22321 }
322 return ui::NativeTheme::kNormal;
323}
324
325const ui::Animation* LabelButton::GetThemeAnimation() const {
[email protected]204b2082012-11-07 17:53:03326#if defined(OS_WIN)
[email protected]fe22ba12013-02-21 13:43:48327 if (style() == STYLE_NATIVE_TEXTBUTTON &&
328 GetNativeTheme() == ui::NativeThemeWin::instance()) {
[email protected]204b2082012-11-07 17:53:03329 return ui::NativeThemeWin::instance()->IsThemingActive() ?
330 hover_animation_.get() : NULL;
331 }
[email protected]70e983cc2012-10-16 21:08:22332#endif
333 return hover_animation_.get();
334}
335
336ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
337 ui::NativeTheme::ExtraParams* params) const {
338 GetExtraParams(params);
339 return ui::NativeTheme::kNormal;
340}
341
342ui::NativeTheme::State LabelButton::GetForegroundThemeState(
343 ui::NativeTheme::ExtraParams* params) const {
344 GetExtraParams(params);
345 return ui::NativeTheme::kHovered;
346}
347
[email protected]70e983cc2012-10-16 21:08:22348} // namespace views