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/widget/widget.h"
27 const int kSlideValueChangeDurationMS
= 150;
29 const int kBarImagesActive
[] = {
30 IDR_SLIDER_ACTIVE_LEFT
,
31 IDR_SLIDER_ACTIVE_CENTER
,
32 IDR_SLIDER_PRESSED_CENTER
,
33 IDR_SLIDER_PRESSED_RIGHT
,
36 const int kBarImagesDisabled
[] = {
37 IDR_SLIDER_DISABLED_LEFT
,
38 IDR_SLIDER_DISABLED_CENTER
,
39 IDR_SLIDER_DISABLED_CENTER
,
40 IDR_SLIDER_DISABLED_RIGHT
,
55 const char Slider::kViewClassName
[] = "Slider";
57 Slider::Slider(SliderListener
* listener
, Orientation orientation
)
58 : listener_(listener
),
59 orientation_(orientation
),
61 keyboard_increment_(0.1f
),
62 animating_value_(0.f
),
63 value_is_valid_(false),
64 accessibility_events_enabled_(true),
65 focus_border_color_(0),
66 bar_active_images_(kBarImagesActive
),
67 bar_disabled_images_(kBarImagesDisabled
) {
68 EnableCanvasFlippingForRTLUI(true);
76 void Slider::SetValue(float value
) {
77 SetValueInternal(value
, VALUE_CHANGED_BY_API
);
80 void Slider::SetKeyboardIncrement(float increment
) {
81 keyboard_increment_
= increment
;
84 void Slider::SetValueInternal(float value
, SliderChangeReason reason
) {
85 bool old_value_valid
= value_is_valid_
;
87 value_is_valid_
= true;
94 float old_value
= value_
;
97 listener_
->SliderValueChanged(this, value_
, old_value
, reason
);
99 if (old_value_valid
&& base::MessageLoop::current()) {
100 // Do not animate when setting the value of the slider for the first time.
101 // There is no message-loop when running tests. So we cannot animate then.
102 animating_value_
= old_value
;
103 move_animation_
.reset(new gfx::SlideAnimation(this));
104 move_animation_
->SetSlideDuration(kSlideValueChangeDurationMS
);
105 move_animation_
->Show();
106 AnimationProgressed(move_animation_
.get());
110 if (accessibility_events_enabled_
&& GetWidget()) {
111 NotifyAccessibilityEvent(
112 ui::AX_EVENT_VALUE_CHANGED
, true);
116 void Slider::PrepareForMove(const gfx::Point
& point
) {
117 // Try to remember the position of the mouse cursor on the button.
118 gfx::Insets inset
= GetInsets();
119 gfx::Rect content
= GetContentsBounds();
120 float value
= move_animation_
.get() && move_animation_
->is_animating() ?
121 animating_value_
: value_
;
123 // For the horizontal orientation.
124 const int thumb_x
= value
* (content
.width() - thumb_
->width());
125 const int candidate_x
= (base::i18n::IsRTL() ?
126 width() - (point
.x() - inset
.left()) :
127 point
.x() - inset
.left()) - thumb_x
;
128 if (candidate_x
>= 0 && candidate_x
< thumb_
->width())
129 initial_button_offset_
.set_x(candidate_x
);
131 initial_button_offset_
.set_x(thumb_
->width() / 2);
133 // For the vertical orientation.
134 const int thumb_y
= (1.0 - value
) * (content
.height() - thumb_
->height());
135 const int candidate_y
= point
.y() - thumb_y
;
136 if (candidate_y
>= 0 && candidate_y
< thumb_
->height())
137 initial_button_offset_
.set_y(candidate_y
);
139 initial_button_offset_
.set_y(thumb_
->height() / 2);
142 void Slider::MoveButtonTo(const gfx::Point
& point
) {
143 gfx::Insets inset
= GetInsets();
144 // Calculate the value.
145 if (orientation_
== HORIZONTAL
) {
146 int amount
= base::i18n::IsRTL() ?
147 width() - inset
.left() - point
.x() - initial_button_offset_
.x() :
148 point
.x() - inset
.left() - initial_button_offset_
.x();
149 SetValueInternal(static_cast<float>(amount
) /
150 (width() - inset
.width() - thumb_
->width()),
151 VALUE_CHANGED_BY_USER
);
154 1.0f
- static_cast<float>(point
.y() - initial_button_offset_
.y()) /
155 (height() - thumb_
->height()),
156 VALUE_CHANGED_BY_USER
);
160 void Slider::UpdateState(bool control_on
) {
161 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
163 thumb_
= rb
.GetImageNamed(IDR_SLIDER_ACTIVE_THUMB
).ToImageSkia();
164 for (int i
= 0; i
< 4; ++i
)
165 images_
[i
] = rb
.GetImageNamed(bar_active_images_
[i
]).ToImageSkia();
167 thumb_
= rb
.GetImageNamed(IDR_SLIDER_DISABLED_THUMB
).ToImageSkia();
168 for (int i
= 0; i
< 4; ++i
)
169 images_
[i
] = rb
.GetImageNamed(bar_disabled_images_
[i
]).ToImageSkia();
171 bar_height_
= images_
[LEFT
]->height();
175 void Slider::SetAccessibleName(const base::string16
& name
) {
176 accessible_name_
= name
;
179 void Slider::OnPaintFocus(gfx::Canvas
* canvas
) {
183 if (!focus_border_color_
) {
184 canvas
->DrawFocusRect(GetLocalBounds());
185 } else if (HasFocus()) {
186 canvas
->DrawSolidFocusRect(
187 gfx::Rect(1, 1, width() - 3, height() - 3),
188 focus_border_color_
);
192 const char* Slider::GetClassName() const {
193 return kViewClassName
;
196 gfx::Size
Slider::GetPreferredSize() const {
197 const int kSizeMajor
= 200;
198 const int kSizeMinor
= 40;
200 if (orientation_
== HORIZONTAL
)
201 return gfx::Size(std::max(width(), kSizeMajor
), kSizeMinor
);
202 return gfx::Size(kSizeMinor
, std::max(height(), kSizeMajor
));
205 void Slider::OnPaint(gfx::Canvas
* canvas
) {
206 View::OnPaint(canvas
);
207 gfx::Rect content
= GetContentsBounds();
208 float value
= move_animation_
.get() && move_animation_
->is_animating() ?
209 animating_value_
: value_
;
210 if (orientation_
== HORIZONTAL
) {
211 // Paint slider bar with image resources.
213 // Inset the slider bar a little bit, so that the left or the right end of
214 // the slider bar will not be exposed under the thumb button when the thumb
215 // button slides to the left most or right most position.
216 const int kBarInsetX
= 2;
217 int bar_width
= content
.width() - kBarInsetX
* 2;
218 int bar_cy
= content
.height() / 2 - bar_height_
/ 2;
220 int w
= content
.width() - thumb_
->width();
221 int full
= value
* w
;
222 int middle
= std::max(full
, images_
[LEFT
]->width());
225 canvas
->Translate(gfx::Vector2d(kBarInsetX
, bar_cy
));
226 canvas
->DrawImageInt(*images_
[LEFT
], 0, 0);
227 canvas
->DrawImageInt(*images_
[RIGHT
],
228 bar_width
- images_
[RIGHT
]->width(),
230 canvas
->TileImageInt(*images_
[CENTER_LEFT
],
231 images_
[LEFT
]->width(),
233 middle
- images_
[LEFT
]->width(),
235 canvas
->TileImageInt(*images_
[CENTER_RIGHT
],
238 bar_width
- middle
- images_
[RIGHT
]->width(),
242 // Paint slider thumb.
243 int button_cx
= content
.x() + full
;
244 int thumb_y
= content
.height() / 2 - thumb_
->height() / 2;
245 canvas
->DrawImageInt(*thumb_
, button_cx
, thumb_y
);
247 // TODO(jennyz): draw vertical slider bar with resources.
248 // TODO(sad): The painting code should use NativeTheme for various
250 const int kButtonRadius
= thumb_
->width() / 2;
251 const int kLineThickness
= bar_height_
/ 2;
252 const SkColor kFullColor
= SkColorSetARGB(125, 0, 0, 0);
253 const SkColor kEmptyColor
= SkColorSetARGB(50, 0, 0, 0);
255 int h
= content
.height() - thumb_
->height();
256 int full
= value
* h
;
257 int empty
= h
- full
;
258 int x
= content
.width() / 2 - kLineThickness
/ 2;
259 canvas
->FillRect(gfx::Rect(x
, content
.y() + kButtonRadius
,
260 kLineThickness
, empty
),
262 canvas
->FillRect(gfx::Rect(x
, content
.y() + empty
+ 2 * kButtonRadius
,
263 kLineThickness
, full
),
266 // TODO(mtomasz): We draw a thumb here because so far it is the same
267 // for horizontal and vertical orientations. If it is different, then
268 // we will need a separate resource.
269 int button_cy
= content
.y() + h
- full
;
270 int thumb_x
= content
.width() / 2 - thumb_
->width() / 2;
271 canvas
->DrawImageInt(*thumb_
, thumb_x
, button_cy
);
273 OnPaintFocus(canvas
);
276 bool Slider::OnMousePressed(const ui::MouseEvent
& event
) {
277 if (!event
.IsOnlyLeftMouseButton())
279 OnSliderDragStarted();
280 PrepareForMove(event
.location());
281 MoveButtonTo(event
.location());
285 bool Slider::OnMouseDragged(const ui::MouseEvent
& event
) {
286 MoveButtonTo(event
.location());
290 void Slider::OnMouseReleased(const ui::MouseEvent
& event
) {
294 bool Slider::OnKeyPressed(const ui::KeyEvent
& event
) {
295 if (orientation_
== HORIZONTAL
) {
296 if (event
.key_code() == ui::VKEY_LEFT
) {
297 SetValueInternal(value_
- keyboard_increment_
, VALUE_CHANGED_BY_USER
);
299 } else if (event
.key_code() == ui::VKEY_RIGHT
) {
300 SetValueInternal(value_
+ keyboard_increment_
, VALUE_CHANGED_BY_USER
);
304 if (event
.key_code() == ui::VKEY_DOWN
) {
305 SetValueInternal(value_
- keyboard_increment_
, VALUE_CHANGED_BY_USER
);
307 } else if (event
.key_code() == ui::VKEY_UP
) {
308 SetValueInternal(value_
+ keyboard_increment_
, VALUE_CHANGED_BY_USER
);
315 void Slider::OnFocus() {
320 void Slider::OnBlur() {
325 void Slider::OnGestureEvent(ui::GestureEvent
* event
) {
326 switch (event
->type()) {
327 // In a multi point gesture only the touch point will generate
328 // an ET_GESTURE_TAP_DOWN event.
329 case ui::ET_GESTURE_TAP_DOWN
:
330 OnSliderDragStarted();
331 PrepareForMove(event
->location());
332 // Intentional fall through to next case.
333 case ui::ET_GESTURE_SCROLL_BEGIN
:
334 case ui::ET_GESTURE_SCROLL_UPDATE
:
335 MoveButtonTo(event
->location());
338 case ui::ET_GESTURE_END
:
339 MoveButtonTo(event
->location());
341 if (event
->details().touch_points() <= 1)
349 void Slider::AnimationProgressed(const gfx::Animation
* animation
) {
350 animating_value_
= animation
->CurrentValueBetween(animating_value_
, value_
);
354 void Slider::GetAccessibleState(ui::AXViewState
* state
) {
355 state
->role
= ui::AX_ROLE_SLIDER
;
356 state
->name
= accessible_name_
;
357 state
->value
= base::UTF8ToUTF16(
358 base::StringPrintf("%d%%", static_cast<int>(value_
* 100 + 0.5)));
361 void Slider::OnSliderDragStarted() {
363 listener_
->SliderDragStarted(this);
366 void Slider::OnSliderDragEnded() {
368 listener_
->SliderDragEnded(this);