blob: 6ca6a98194017f3476d310474ed5df20cbee658a [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"
14
15#if defined(OS_WIN)
16#include "ui/gfx/color_utils.h"
[email protected]990e6222012-11-16 13:31:1817#include "ui/native_theme/native_theme_win.h"
[email protected]70e983cc2012-10-16 21:08:2218#endif
19
[email protected]70e983cc2012-10-16 21:08:2220namespace {
21
22// The spacing between the icon and text.
[email protected]c5de33662012-10-27 22:37:3023const int kSpacing = 5;
[email protected]70e983cc2012-10-16 21:08:2224
25// The length of the hover fade animation.
[email protected]c5de33662012-10-27 22:37:3026const int kHoverAnimationDurationMs = 170;
[email protected]70e983cc2012-10-16 21:08:2227
28} // namespace
29
[email protected]c5de33662012-10-27 22:37:3030namespace views {
31
[email protected]fe22ba12013-02-21 13:43:4832// static
33const char LabelButton::kViewClassName[] = "views/LabelButton";
34
[email protected]70e983cc2012-10-16 21:08:2235LabelButton::LabelButton(ButtonListener* listener, const string16& text)
36 : CustomButton(listener),
37 image_(new ImageView()),
38 label_(new Label(text)),
[email protected]379bc982012-12-19 19:56:5339 button_state_images_(),
40 button_state_colors_(),
41 explicitly_set_colors_(),
[email protected]e59fc52a2013-02-20 20:24:1142 is_default_(false),
[email protected]fe22ba12013-02-21 13:43:4843 style_(STYLE_TEXTBUTTON) {
[email protected]70e983cc2012-10-16 21:08:2244 SetAnimationDuration(kHoverAnimationDurationMs);
45
46 AddChildView(image_);
47 image_->set_interactive(false);
48
49 AddChildView(label_);
50 label_->SetAutoColorReadabilityEnabled(false);
[email protected]cc563f02012-11-07 17:37:3651 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
[email protected]70e983cc2012-10-16 21:08:2252
[email protected]fe22ba12013-02-21 13:43:4853 // Initialize the colors, border, and layout.
54 SetStyle(style_);
[email protected]70e983cc2012-10-16 21:08:2255}
56
57LabelButton::~LabelButton() {}
58
59const gfx::ImageSkia& LabelButton::GetImage(ButtonState for_state) {
[email protected]fca8dc02012-11-14 16:25:5660 if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
61 return button_state_images_[STATE_NORMAL];
[email protected]70e983cc2012-10-16 21:08:2262 return button_state_images_[for_state];
63}
64
65void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
66 button_state_images_[for_state] = image;
67 image_->SetImage(GetImage(state()));
68}
69
70const string16& LabelButton::GetText() const {
71 return label_->text();
72}
73
74void LabelButton::SetText(const string16& text) {
75 label_->SetText(text);
76}
77
78void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
79 button_state_colors_[for_state] = color;
[email protected]fca8dc02012-11-14 16:25:5680 if (for_state == STATE_DISABLED)
[email protected]70e983cc2012-10-16 21:08:2281 label_->SetDisabledColor(color);
82 else if (for_state == state())
83 label_->SetEnabledColor(color);
[email protected]204b2082012-11-07 17:53:0384 explicitly_set_colors_[for_state] = true;
[email protected]70e983cc2012-10-16 21:08:2285}
86
87bool LabelButton::GetTextMultiLine() const {
88 return label_->is_multi_line();
89}
90
91void LabelButton::SetTextMultiLine(bool text_multi_line) {
92 label_->SetMultiLine(text_multi_line);
93}
94
[email protected]cfc888a2012-11-05 18:20:5095const gfx::Font& LabelButton::GetFont() const {
96 return label_->font();
97}
98
99void LabelButton::SetFont(const gfx::Font& font) {
100 label_->SetFont(font);
101}
102
[email protected]cc563f02012-11-07 17:37:36103gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
[email protected]70e983cc2012-10-16 21:08:22104 return label_->horizontal_alignment();
105}
106
[email protected]cc563f02012-11-07 17:37:36107void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
[email protected]70e983cc2012-10-16 21:08:22108 label_->SetHorizontalAlignment(alignment);
109 InvalidateLayout();
110}
111
[email protected]e59fc52a2013-02-20 20:24:11112void LabelButton::SetIsDefault(bool is_default) {
113 if (is_default == is_default_)
[email protected]70e983cc2012-10-16 21:08:22114 return;
[email protected]e59fc52a2013-02-20 20:24:11115 is_default_ = is_default;
[email protected]70e983cc2012-10-16 21:08:22116 ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
[email protected]e59fc52a2013-02-20 20:24:11117 is_default_ ? AddAccelerator(accel) : RemoveAccelerator(accel);
[email protected]70e983cc2012-10-16 21:08:22118}
119
[email protected]fe22ba12013-02-21 13:43:48120void LabelButton::SetStyle(ButtonStyle style) {
121 style_ = style;
122 set_border(new LabelButtonBorder(style));
123 // Inset the button focus rect from the actual border; roughly match Windows.
[email protected]644f4fb2013-03-01 21:14:34124 if (style == STYLE_TEXTBUTTON || style == STYLE_NATIVE_TEXTBUTTON)
125 set_focus_border(FocusBorder::CreateDashedFocusBorder(3, 3, 3, 3));
126 if (style == STYLE_BUTTON || style_ == STYLE_NATIVE_TEXTBUTTON)
[email protected]d0abd282013-02-28 23:59:08127 label_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
[email protected]644f4fb2013-03-01 21:14:34128 if (style == STYLE_BUTTON) {
129 set_min_size(gfx::Size(70, 31));
130 const SkColor color = GetNativeTheme()->GetSystemColor(
131 ui::NativeTheme::kColorId_WindowBackground);
132 label_->SetShadowColors(color, color);
133 label_->SetShadowOffset(0, 1);
134 }
[email protected]204b2082012-11-07 17:53:03135 // Invalidate the layout to pickup the new insets from the border.
136 InvalidateLayout();
[email protected]379bc982012-12-19 19:56:53137 ResetColorsFromNativeTheme();
[email protected]204b2082012-11-07 17:53:03138}
139
[email protected]7f39bb692013-01-07 23:08:16140gfx::Size LabelButton::GetPreferredSize() {
141 // Resize multi-line labels paired with images to use their available width.
142 const gfx::Size image_size(image_->GetPreferredSize());
143 if (GetTextMultiLine() && !image_size.IsEmpty() && !GetText().empty() &&
144 GetHorizontalAlignment() == gfx::ALIGN_CENTER) {
145 label_->SizeToFit(GetLocalBounds().width() - image_size.width() - kSpacing);
146 }
147
148 // Calculate the required size.
149 gfx::Size size(label_->GetPreferredSize());
150 if (image_size.width() > 0 && size.width() > 0)
151 size.Enlarge(kSpacing, 0);
152 size.set_height(std::max(size.height(), image_size.height()));
153 size.Enlarge(image_size.width() + GetInsets().width(), GetInsets().height());
154
155 // Increase the minimum size monotonically with the preferred size.
156 size.SetSize(std::max(min_size_.width(), size.width()),
157 std::max(min_size_.height(), size.height()));
158 min_size_ = size;
159
160 // Return the largest known size clamped to the maximum size (if valid).
161 if (max_size_.width() > 0)
162 size.set_width(std::min(max_size_.width(), size.width()));
163 if (max_size_.height() > 0)
164 size.set_height(std::min(max_size_.height(), size.height()));
165 return size;
166}
167
[email protected]fe22ba12013-02-21 13:43:48168std::string LabelButton::GetClassName() const {
169 return kViewClassName;
170}
171
[email protected]379bc982012-12-19 19:56:53172void LabelButton::ResetColorsFromNativeTheme() {
[email protected]204b2082012-11-07 17:53:03173 const ui::NativeTheme* theme = GetNativeTheme();
[email protected]fca8dc02012-11-14 16:25:56174 SkColor colors[STATE_COUNT] = {
[email protected]70e983cc2012-10-16 21:08:22175 theme->GetSystemColor(ui::NativeTheme::kColorId_TextButtonEnabledColor),
176 theme->GetSystemColor(ui::NativeTheme::kColorId_TextButtonHoverColor),
177 theme->GetSystemColor(ui::NativeTheme::kColorId_TextButtonHoverColor),
178 theme->GetSystemColor(ui::NativeTheme::kColorId_TextButtonDisabledColor),
179 };
[email protected]644f4fb2013-03-01 21:14:34180
181 // Certain styles do not change text color when hovered or pressed.
182 bool constant_text_color = style() == STYLE_BUTTON;
[email protected]70e983cc2012-10-16 21:08:22183#if defined(OS_WIN)
[email protected]644f4fb2013-03-01 21:14:34184 constant_text_color |= (style() == STYLE_NATIVE_TEXTBUTTON &&
185 theme == ui::NativeThemeWin::instance());
[email protected]70e983cc2012-10-16 21:08:22186#endif
[email protected]644f4fb2013-03-01 21:14:34187 if (constant_text_color)
188 colors[STATE_HOVERED] = colors[STATE_PRESSED] = colors[STATE_NORMAL];
189
[email protected]fca8dc02012-11-14 16:25:56190 for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
[email protected]379bc982012-12-19 19:56:53191 if (!explicitly_set_colors_[state]) {
[email protected]204b2082012-11-07 17:53:03192 SetTextColor(static_cast<ButtonState>(state), colors[state]);
193 explicitly_set_colors_[state] = false;
194 }
195 }
[email protected]70e983cc2012-10-16 21:08:22196}
197
198void LabelButton::StateChanged() {
199 const gfx::Size previous_image_size(image_->GetPreferredSize());
200 image_->SetImage(GetImage(state()));
201 const SkColor color = button_state_colors_[state()];
[email protected]fca8dc02012-11-14 16:25:56202 if (state() != STATE_DISABLED && label_->enabled_color() != color)
[email protected]70e983cc2012-10-16 21:08:22203 label_->SetEnabledColor(color);
[email protected]4e88e352012-12-04 19:12:09204 label_->SetEnabled(state() != STATE_DISABLED);
[email protected]70e983cc2012-10-16 21:08:22205 if (image_->GetPreferredSize() != previous_image_size)
206 Layout();
207}
208
[email protected]70e983cc2012-10-16 21:08:22209void LabelButton::Layout() {
210 gfx::Rect child_area(GetLocalBounds());
211 child_area.Inset(GetInsets());
212
213 gfx::Size image_size(image_->GetPreferredSize());
214 image_size.set_width(std::min(image_size.width(), child_area.width()));
215 image_size.set_height(std::min(image_size.height(), child_area.height()));
216
[email protected]1c4f28f2012-10-18 23:36:06217 // The label takes any remaining width after sizing the image, unless both
218 // views are centered. In that case, using the tighter preferred label width
219 // avoids wasted space within the label that would look like awkward padding.
[email protected]70e983cc2012-10-16 21:08:22220 gfx::Size label_size(child_area.size());
221 if (!image_size.IsEmpty() && !label_size.IsEmpty()) {
[email protected]67a96b52013-01-11 05:08:56222 label_size.set_width(
223 std::max(child_area.width() - image_size.width() - kSpacing, 0));
[email protected]cc563f02012-11-07 17:37:36224 if (GetHorizontalAlignment() == gfx::ALIGN_CENTER) {
[email protected]379bc982012-12-19 19:56:53225 // Ensure multi-line labels paired with images use their available width.
226 if (GetTextMultiLine())
227 label_->SizeToFit(label_size.width());
228 label_size.set_width(
229 std::min(label_size.width(), label_->GetPreferredSize().width()));
[email protected]70e983cc2012-10-16 21:08:22230 }
231 }
232
233 gfx::Point image_origin(child_area.origin());
234 image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
[email protected]cc563f02012-11-07 17:37:36235 if (GetHorizontalAlignment() == gfx::ALIGN_CENTER) {
[email protected]70e983cc2012-10-16 21:08:22236 const int total_width = image_size.width() + label_size.width() +
237 ((image_size.width() > 0 && label_size.width() > 0) ? kSpacing : 0);
238 image_origin.Offset((child_area.width() - total_width) / 2, 0);
[email protected]cc563f02012-11-07 17:37:36239 } else if (GetHorizontalAlignment() == gfx::ALIGN_RIGHT) {
[email protected]70e983cc2012-10-16 21:08:22240 image_origin.Offset(child_area.width() - image_size.width(), 0);
241 }
242
243 gfx::Point label_origin(child_area.origin());
[email protected]cc563f02012-11-07 17:37:36244 if (!image_size.IsEmpty() && GetHorizontalAlignment() != gfx::ALIGN_RIGHT)
[email protected]70e983cc2012-10-16 21:08:22245 label_origin.set_x(image_origin.x() + image_size.width() + kSpacing);
246
247 image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
248 label_->SetBoundsRect(gfx::Rect(label_origin, label_size));
249}
250
[email protected]70e983cc2012-10-16 21:08:22251void LabelButton::ChildPreferredSizeChanged(View* child) {
252 PreferredSizeChanged();
253}
254
[email protected]204b2082012-11-07 17:53:03255void LabelButton::OnNativeThemeChanged(const ui::NativeTheme* theme) {
[email protected]379bc982012-12-19 19:56:53256 ResetColorsFromNativeTheme();
[email protected]204b2082012-11-07 17:53:03257}
258
[email protected]70e983cc2012-10-16 21:08:22259ui::NativeTheme::Part LabelButton::GetThemePart() const {
260 return ui::NativeTheme::kPushButton;
261}
262
263gfx::Rect LabelButton::GetThemePaintRect() const {
264 return GetLocalBounds();
265}
266
267ui::NativeTheme::State LabelButton::GetThemeState(
268 ui::NativeTheme::ExtraParams* params) const {
269 GetExtraParams(params);
270 switch(state()) {
[email protected]fca8dc02012-11-14 16:25:56271 case STATE_NORMAL: return ui::NativeTheme::kNormal;
272 case STATE_HOVERED: return ui::NativeTheme::kHovered;
273 case STATE_PRESSED: return ui::NativeTheme::kPressed;
274 case STATE_DISABLED: return ui::NativeTheme::kDisabled;
275 case STATE_COUNT: NOTREACHED() << "Unknown state: " << state();
[email protected]70e983cc2012-10-16 21:08:22276 }
277 return ui::NativeTheme::kNormal;
278}
279
280const ui::Animation* LabelButton::GetThemeAnimation() const {
[email protected]204b2082012-11-07 17:53:03281#if defined(OS_WIN)
[email protected]fe22ba12013-02-21 13:43:48282 if (style() == STYLE_NATIVE_TEXTBUTTON &&
283 GetNativeTheme() == ui::NativeThemeWin::instance()) {
[email protected]204b2082012-11-07 17:53:03284 return ui::NativeThemeWin::instance()->IsThemingActive() ?
285 hover_animation_.get() : NULL;
286 }
[email protected]70e983cc2012-10-16 21:08:22287#endif
288 return hover_animation_.get();
289}
290
291ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
292 ui::NativeTheme::ExtraParams* params) const {
293 GetExtraParams(params);
294 return ui::NativeTheme::kNormal;
295}
296
297ui::NativeTheme::State LabelButton::GetForegroundThemeState(
298 ui::NativeTheme::ExtraParams* params) const {
299 GetExtraParams(params);
300 return ui::NativeTheme::kHovered;
301}
302
303void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
304 params->button.checked = false;
305 params->button.indeterminate = false;
[email protected]e59fc52a2013-02-20 20:24:11306 params->button.is_default = is_default_;
[email protected]70e983cc2012-10-16 21:08:22307 params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
[email protected]fe22ba12013-02-21 13:43:48308 params->button.has_border = style() == STYLE_NATIVE_TEXTBUTTON;
[email protected]70e983cc2012-10-16 21:08:22309 params->button.classic_state = 0;
[email protected]204b2082012-11-07 17:53:03310 params->button.background_color = GetNativeTheme()->GetSystemColor(
[email protected]70e983cc2012-10-16 21:08:22311 ui::NativeTheme::kColorId_TextButtonBackgroundColor);
312}
313
314} // namespace views