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"
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"
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
,
56 const char Slider::kViewClassName
[] = "Slider";
58 Slider::Slider(SliderListener
* listener
, Orientation orientation
)
59 : listener_(listener
),
60 orientation_(orientation
),
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);
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;
95 float old_value
= value_
;
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());
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
);
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
);
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
);
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();
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();
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();
176 void Slider::SetAccessibleName(const base::string16
& name
) {
177 accessible_name_
= name
;
180 void Slider::OnPaintFocus(gfx::Canvas
* canvas
) {
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());
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(),
236 canvas
->TileImageInt(*images_
[CENTER_RIGHT
],
239 bar_width
- middle
- images_
[RIGHT
]->width(),
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
);
248 // TODO(jennyz): draw vertical slider bar with resources.
249 // TODO(sad): The painting code should use NativeTheme for various
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
),
263 canvas
->FillRect(gfx::Rect(x
, content
.y() + empty
+ 2 * kButtonRadius
,
264 kLineThickness
, full
),
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())
280 OnSliderDragStarted();
281 PrepareForMove(event
.location());
282 MoveButtonTo(event
.location());
286 bool Slider::OnMouseDragged(const ui::MouseEvent
& event
) {
287 MoveButtonTo(event
.location());
291 void Slider::OnMouseReleased(const ui::MouseEvent
& event
) {
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
);
300 } else if (event
.key_code() == ui::VKEY_RIGHT
) {
301 SetValueInternal(value_
+ keyboard_increment_
, VALUE_CHANGED_BY_USER
);
305 if (event
.key_code() == ui::VKEY_DOWN
) {
306 SetValueInternal(value_
- keyboard_increment_
, VALUE_CHANGED_BY_USER
);
308 } else if (event
.key_code() == ui::VKEY_UP
) {
309 SetValueInternal(value_
+ keyboard_increment_
, VALUE_CHANGED_BY_USER
);
316 void Slider::OnFocus() {
321 void Slider::OnBlur() {
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());
339 case ui::ET_GESTURE_END
:
340 MoveButtonTo(event
->location());
342 if (event
->details().touch_points() <= 1)
350 void Slider::AnimationProgressed(const gfx::Animation
* animation
) {
351 animating_value_
= animation
->CurrentValueBetween(animating_value_
, value_
);
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() {
364 listener_
->SliderDragStarted(this);
367 void Slider::OnSliderDragEnded() {
369 listener_
->SliderDragEnded(this);