Add ICU message format support
[chromium-blink-merge.git] / ui / views / controls / slider.cc
blobe063456aaaf4bd716d44002a1dbe136fa029118b
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/slider.h"
7 #include <algorithm>
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "third_party/skia/include/core/SkCanvas.h"
14 #include "third_party/skia/include/core/SkColor.h"
15 #include "third_party/skia/include/core/SkPaint.h"
16 #include "ui/accessibility/ax_view_state.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/events/event.h"
19 #include "ui/gfx/animation/slide_animation.h"
20 #include "ui/gfx/canvas.h"
21 #include "ui/gfx/geometry/point.h"
22 #include "ui/gfx/geometry/rect.h"
23 #include "ui/resources/grit/ui_resources.h"
24 #include "ui/views/resources/grit/views_resources.h"
25 #include "ui/views/widget/widget.h"
27 namespace {
28 const int kSlideValueChangeDurationMS = 150;
30 const int kBarImagesActive[] = {
31 IDR_SLIDER_ACTIVE_LEFT,
32 IDR_SLIDER_ACTIVE_CENTER,
33 IDR_SLIDER_PRESSED_CENTER,
34 IDR_SLIDER_PRESSED_RIGHT,
37 const int kBarImagesDisabled[] = {
38 IDR_SLIDER_DISABLED_LEFT,
39 IDR_SLIDER_DISABLED_CENTER,
40 IDR_SLIDER_DISABLED_CENTER,
41 IDR_SLIDER_DISABLED_RIGHT,
44 // The image chunks.
45 enum BorderElements {
46 LEFT,
47 CENTER_LEFT,
48 CENTER_RIGHT,
49 RIGHT,
51 } // namespace
53 namespace views {
55 // static
56 const char Slider::kViewClassName[] = "Slider";
58 Slider::Slider(SliderListener* listener, Orientation orientation)
59 : listener_(listener),
60 orientation_(orientation),
61 value_(0.f),
62 keyboard_increment_(0.1f),
63 animating_value_(0.f),
64 value_is_valid_(false),
65 accessibility_events_enabled_(true),
66 focus_border_color_(0),
67 bar_active_images_(kBarImagesActive),
68 bar_disabled_images_(kBarImagesDisabled) {
69 EnableCanvasFlippingForRTLUI(true);
70 SetFocusable(true);
71 UpdateState(true);
74 Slider::~Slider() {
77 void Slider::SetValue(float value) {
78 SetValueInternal(value, VALUE_CHANGED_BY_API);
81 void Slider::SetKeyboardIncrement(float increment) {
82 keyboard_increment_ = increment;
85 void Slider::SetValueInternal(float value, SliderChangeReason reason) {
86 bool old_value_valid = value_is_valid_;
88 value_is_valid_ = true;
89 if (value < 0.0)
90 value = 0.0;
91 else if (value > 1.0)
92 value = 1.0;
93 if (value_ == value)
94 return;
95 float old_value = value_;
96 value_ = value;
97 if (listener_)
98 listener_->SliderValueChanged(this, value_, old_value, reason);
100 if (old_value_valid && base::MessageLoop::current()) {
101 // Do not animate when setting the value of the slider for the first time.
102 // There is no message-loop when running tests. So we cannot animate then.
103 animating_value_ = old_value;
104 move_animation_.reset(new gfx::SlideAnimation(this));
105 move_animation_->SetSlideDuration(kSlideValueChangeDurationMS);
106 move_animation_->Show();
107 AnimationProgressed(move_animation_.get());
108 } else {
109 SchedulePaint();
111 if (accessibility_events_enabled_ && GetWidget()) {
112 NotifyAccessibilityEvent(
113 ui::AX_EVENT_VALUE_CHANGED, true);
117 void Slider::PrepareForMove(const gfx::Point& point) {
118 // Try to remember the position of the mouse cursor on the button.
119 gfx::Insets inset = GetInsets();
120 gfx::Rect content = GetContentsBounds();
121 float value = move_animation_.get() && move_animation_->is_animating() ?
122 animating_value_ : value_;
124 // For the horizontal orientation.
125 const int thumb_x = value * (content.width() - thumb_->width());
126 const int candidate_x = (base::i18n::IsRTL() ?
127 width() - (point.x() - inset.left()) :
128 point.x() - inset.left()) - thumb_x;
129 if (candidate_x >= 0 && candidate_x < thumb_->width())
130 initial_button_offset_.set_x(candidate_x);
131 else
132 initial_button_offset_.set_x(thumb_->width() / 2);
134 // For the vertical orientation.
135 const int thumb_y = (1.0 - value) * (content.height() - thumb_->height());
136 const int candidate_y = point.y() - thumb_y;
137 if (candidate_y >= 0 && candidate_y < thumb_->height())
138 initial_button_offset_.set_y(candidate_y);
139 else
140 initial_button_offset_.set_y(thumb_->height() / 2);
143 void Slider::MoveButtonTo(const gfx::Point& point) {
144 gfx::Insets inset = GetInsets();
145 // Calculate the value.
146 if (orientation_ == HORIZONTAL) {
147 int amount = base::i18n::IsRTL() ?
148 width() - inset.left() - point.x() - initial_button_offset_.x() :
149 point.x() - inset.left() - initial_button_offset_.x();
150 SetValueInternal(static_cast<float>(amount) /
151 (width() - inset.width() - thumb_->width()),
152 VALUE_CHANGED_BY_USER);
153 } else {
154 SetValueInternal(
155 1.0f - static_cast<float>(point.y() - initial_button_offset_.y()) /
156 (height() - thumb_->height()),
157 VALUE_CHANGED_BY_USER);
161 void Slider::UpdateState(bool control_on) {
162 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
163 if (control_on) {
164 thumb_ = rb.GetImageNamed(IDR_SLIDER_ACTIVE_THUMB).ToImageSkia();
165 for (int i = 0; i < 4; ++i)
166 images_[i] = rb.GetImageNamed(bar_active_images_[i]).ToImageSkia();
167 } else {
168 thumb_ = rb.GetImageNamed(IDR_SLIDER_DISABLED_THUMB).ToImageSkia();
169 for (int i = 0; i < 4; ++i)
170 images_[i] = rb.GetImageNamed(bar_disabled_images_[i]).ToImageSkia();
172 bar_height_ = images_[LEFT]->height();
173 SchedulePaint();
176 void Slider::SetAccessibleName(const base::string16& name) {
177 accessible_name_ = name;
180 void Slider::OnPaintFocus(gfx::Canvas* canvas) {
181 if (!HasFocus())
182 return;
184 if (!focus_border_color_) {
185 canvas->DrawFocusRect(GetLocalBounds());
186 } else if (HasFocus()) {
187 canvas->DrawSolidFocusRect(
188 gfx::Rect(1, 1, width() - 3, height() - 3),
189 focus_border_color_);
193 const char* Slider::GetClassName() const {
194 return kViewClassName;
197 gfx::Size Slider::GetPreferredSize() const {
198 const int kSizeMajor = 200;
199 const int kSizeMinor = 40;
201 if (orientation_ == HORIZONTAL)
202 return gfx::Size(std::max(width(), kSizeMajor), kSizeMinor);
203 return gfx::Size(kSizeMinor, std::max(height(), kSizeMajor));
206 void Slider::OnPaint(gfx::Canvas* canvas) {
207 View::OnPaint(canvas);
208 gfx::Rect content = GetContentsBounds();
209 float value = move_animation_.get() && move_animation_->is_animating() ?
210 animating_value_ : value_;
211 if (orientation_ == HORIZONTAL) {
212 // Paint slider bar with image resources.
214 // Inset the slider bar a little bit, so that the left or the right end of
215 // the slider bar will not be exposed under the thumb button when the thumb
216 // button slides to the left most or right most position.
217 const int kBarInsetX = 2;
218 int bar_width = content.width() - kBarInsetX * 2;
219 int bar_cy = content.height() / 2 - bar_height_ / 2;
221 int w = content.width() - thumb_->width();
222 int full = value * w;
223 int middle = std::max(full, images_[LEFT]->width());
225 canvas->Save();
226 canvas->Translate(gfx::Vector2d(kBarInsetX, bar_cy));
227 canvas->DrawImageInt(*images_[LEFT], 0, 0);
228 canvas->DrawImageInt(*images_[RIGHT],
229 bar_width - images_[RIGHT]->width(),
231 canvas->TileImageInt(*images_[CENTER_LEFT],
232 images_[LEFT]->width(),
234 middle - images_[LEFT]->width(),
235 bar_height_);
236 canvas->TileImageInt(*images_[CENTER_RIGHT],
237 middle,
239 bar_width - middle - images_[RIGHT]->width(),
240 bar_height_);
241 canvas->Restore();
243 // Paint slider thumb.
244 int button_cx = content.x() + full;
245 int thumb_y = content.height() / 2 - thumb_->height() / 2;
246 canvas->DrawImageInt(*thumb_, button_cx, thumb_y);
247 } else {
248 // TODO(jennyz): draw vertical slider bar with resources.
249 // TODO(sad): The painting code should use NativeTheme for various
250 // platforms.
251 const int kButtonRadius = thumb_->width() / 2;
252 const int kLineThickness = bar_height_ / 2;
253 const SkColor kFullColor = SkColorSetARGB(125, 0, 0, 0);
254 const SkColor kEmptyColor = SkColorSetARGB(50, 0, 0, 0);
256 int h = content.height() - thumb_->height();
257 int full = value * h;
258 int empty = h - full;
259 int x = content.width() / 2 - kLineThickness / 2;
260 canvas->FillRect(gfx::Rect(x, content.y() + kButtonRadius,
261 kLineThickness, empty),
262 kEmptyColor);
263 canvas->FillRect(gfx::Rect(x, content.y() + empty + 2 * kButtonRadius,
264 kLineThickness, full),
265 kFullColor);
267 // TODO(mtomasz): We draw a thumb here because so far it is the same
268 // for horizontal and vertical orientations. If it is different, then
269 // we will need a separate resource.
270 int button_cy = content.y() + h - full;
271 int thumb_x = content.width() / 2 - thumb_->width() / 2;
272 canvas->DrawImageInt(*thumb_, thumb_x, button_cy);
274 OnPaintFocus(canvas);
277 bool Slider::OnMousePressed(const ui::MouseEvent& event) {
278 if (!event.IsOnlyLeftMouseButton())
279 return false;
280 OnSliderDragStarted();
281 PrepareForMove(event.location());
282 MoveButtonTo(event.location());
283 return true;
286 bool Slider::OnMouseDragged(const ui::MouseEvent& event) {
287 MoveButtonTo(event.location());
288 return true;
291 void Slider::OnMouseReleased(const ui::MouseEvent& event) {
292 OnSliderDragEnded();
295 bool Slider::OnKeyPressed(const ui::KeyEvent& event) {
296 if (orientation_ == HORIZONTAL) {
297 if (event.key_code() == ui::VKEY_LEFT) {
298 SetValueInternal(value_ - keyboard_increment_, VALUE_CHANGED_BY_USER);
299 return true;
300 } else if (event.key_code() == ui::VKEY_RIGHT) {
301 SetValueInternal(value_ + keyboard_increment_, VALUE_CHANGED_BY_USER);
302 return true;
304 } else {
305 if (event.key_code() == ui::VKEY_DOWN) {
306 SetValueInternal(value_ - keyboard_increment_, VALUE_CHANGED_BY_USER);
307 return true;
308 } else if (event.key_code() == ui::VKEY_UP) {
309 SetValueInternal(value_ + keyboard_increment_, VALUE_CHANGED_BY_USER);
310 return true;
313 return false;
316 void Slider::OnFocus() {
317 View::OnFocus();
318 SchedulePaint();
321 void Slider::OnBlur() {
322 View::OnBlur();
323 SchedulePaint();
326 void Slider::OnGestureEvent(ui::GestureEvent* event) {
327 switch (event->type()) {
328 // In a multi point gesture only the touch point will generate
329 // an ET_GESTURE_TAP_DOWN event.
330 case ui::ET_GESTURE_TAP_DOWN:
331 OnSliderDragStarted();
332 PrepareForMove(event->location());
333 // Intentional fall through to next case.
334 case ui::ET_GESTURE_SCROLL_BEGIN:
335 case ui::ET_GESTURE_SCROLL_UPDATE:
336 MoveButtonTo(event->location());
337 event->SetHandled();
338 break;
339 case ui::ET_GESTURE_END:
340 MoveButtonTo(event->location());
341 event->SetHandled();
342 if (event->details().touch_points() <= 1)
343 OnSliderDragEnded();
344 break;
345 default:
346 break;
350 void Slider::AnimationProgressed(const gfx::Animation* animation) {
351 animating_value_ = animation->CurrentValueBetween(animating_value_, value_);
352 SchedulePaint();
355 void Slider::GetAccessibleState(ui::AXViewState* state) {
356 state->role = ui::AX_ROLE_SLIDER;
357 state->name = accessible_name_;
358 state->value = base::UTF8ToUTF16(
359 base::StringPrintf("%d%%", static_cast<int>(value_ * 100 + 0.5)));
362 void Slider::OnSliderDragStarted() {
363 if (listener_)
364 listener_->SliderDragStarted(this);
367 void Slider::OnSliderDragEnded() {
368 if (listener_)
369 listener_->SliderDragEnded(this);
372 } // namespace views