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/point.h"
22 #include "ui/gfx/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
,
54 Slider::Slider(SliderListener
* listener
, Orientation orientation
)
55 : listener_(listener
),
56 orientation_(orientation
),
58 keyboard_increment_(0.1f
),
59 animating_value_(0.f
),
60 value_is_valid_(false),
61 accessibility_events_enabled_(true),
62 focus_border_color_(0),
63 bar_active_images_(kBarImagesActive
),
64 bar_disabled_images_(kBarImagesDisabled
) {
65 EnableCanvasFlippingForRTLUI(true);
73 void Slider::SetValue(float value
) {
74 SetValueInternal(value
, VALUE_CHANGED_BY_API
);
77 void Slider::SetKeyboardIncrement(float increment
) {
78 keyboard_increment_
= increment
;
81 void Slider::SetValueInternal(float value
, SliderChangeReason reason
) {
82 bool old_value_valid
= value_is_valid_
;
84 value_is_valid_
= true;
91 float old_value
= value_
;
94 listener_
->SliderValueChanged(this, value_
, old_value
, reason
);
96 if (old_value_valid
&& base::MessageLoop::current()) {
97 // Do not animate when setting the value of the slider for the first time.
98 // There is no message-loop when running tests. So we cannot animate then.
99 animating_value_
= old_value
;
100 move_animation_
.reset(new gfx::SlideAnimation(this));
101 move_animation_
->SetSlideDuration(kSlideValueChangeDurationMS
);
102 move_animation_
->Show();
103 AnimationProgressed(move_animation_
.get());
107 if (accessibility_events_enabled_
&& GetWidget()) {
108 NotifyAccessibilityEvent(
109 ui::AX_EVENT_VALUE_CHANGED
, true);
113 void Slider::PrepareForMove(const gfx::Point
& point
) {
114 // Try to remember the position of the mouse cursor on the button.
115 gfx::Insets inset
= GetInsets();
116 gfx::Rect content
= GetContentsBounds();
117 float value
= move_animation_
.get() && move_animation_
->is_animating() ?
118 animating_value_
: value_
;
120 // For the horizontal orientation.
121 const int thumb_x
= value
* (content
.width() - thumb_
->width());
122 const int candidate_x
= (base::i18n::IsRTL() ?
123 width() - (point
.x() - inset
.left()) :
124 point
.x() - inset
.left()) - thumb_x
;
125 if (candidate_x
>= 0 && candidate_x
< thumb_
->width())
126 initial_button_offset_
.set_x(candidate_x
);
128 initial_button_offset_
.set_x(thumb_
->width() / 2);
130 // For the vertical orientation.
131 const int thumb_y
= (1.0 - value
) * (content
.height() - thumb_
->height());
132 const int candidate_y
= point
.y() - thumb_y
;
133 if (candidate_y
>= 0 && candidate_y
< thumb_
->height())
134 initial_button_offset_
.set_y(candidate_y
);
136 initial_button_offset_
.set_y(thumb_
->height() / 2);
139 void Slider::MoveButtonTo(const gfx::Point
& point
) {
140 gfx::Insets inset
= GetInsets();
141 // Calculate the value.
142 if (orientation_
== HORIZONTAL
) {
143 int amount
= base::i18n::IsRTL() ?
144 width() - inset
.left() - point
.x() - initial_button_offset_
.x() :
145 point
.x() - inset
.left() - initial_button_offset_
.x();
146 SetValueInternal(static_cast<float>(amount
) /
147 (width() - inset
.width() - thumb_
->width()),
148 VALUE_CHANGED_BY_USER
);
151 1.0f
- static_cast<float>(point
.y() - initial_button_offset_
.y()) /
152 (height() - thumb_
->height()),
153 VALUE_CHANGED_BY_USER
);
157 void Slider::UpdateState(bool control_on
) {
158 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
160 thumb_
= rb
.GetImageNamed(IDR_SLIDER_ACTIVE_THUMB
).ToImageSkia();
161 for (int i
= 0; i
< 4; ++i
)
162 images_
[i
] = rb
.GetImageNamed(bar_active_images_
[i
]).ToImageSkia();
164 thumb_
= rb
.GetImageNamed(IDR_SLIDER_DISABLED_THUMB
).ToImageSkia();
165 for (int i
= 0; i
< 4; ++i
)
166 images_
[i
] = rb
.GetImageNamed(bar_disabled_images_
[i
]).ToImageSkia();
168 bar_height_
= images_
[LEFT
]->height();
172 void Slider::SetAccessibleName(const base::string16
& name
) {
173 accessible_name_
= name
;
176 void Slider::OnPaintFocus(gfx::Canvas
* canvas
) {
180 if (!focus_border_color_
) {
181 canvas
->DrawFocusRect(GetLocalBounds());
182 } else if (HasFocus()) {
183 canvas
->DrawSolidFocusRect(
184 gfx::Rect(1, 1, width() - 3, height() - 3),
185 focus_border_color_
);
189 gfx::Size
Slider::GetPreferredSize() const {
190 const int kSizeMajor
= 200;
191 const int kSizeMinor
= 40;
193 if (orientation_
== HORIZONTAL
)
194 return gfx::Size(std::max(width(), kSizeMajor
), kSizeMinor
);
195 return gfx::Size(kSizeMinor
, std::max(height(), kSizeMajor
));
198 void Slider::OnPaint(gfx::Canvas
* canvas
) {
199 gfx::Rect content
= GetContentsBounds();
200 float value
= move_animation_
.get() && move_animation_
->is_animating() ?
201 animating_value_
: value_
;
202 if (orientation_
== HORIZONTAL
) {
203 // Paint slider bar with image resources.
205 // Inset the slider bar a little bit, so that the left or the right end of
206 // the slider bar will not be exposed under the thumb button when the thumb
207 // button slides to the left most or right most position.
208 const int kBarInsetX
= 2;
209 int bar_width
= content
.width() - kBarInsetX
* 2;
210 int bar_cy
= content
.height() / 2 - bar_height_
/ 2;
212 int w
= content
.width() - thumb_
->width();
213 int full
= value
* w
;
214 int middle
= std::max(full
, images_
[LEFT
]->width());
217 canvas
->Translate(gfx::Vector2d(kBarInsetX
, bar_cy
));
218 canvas
->DrawImageInt(*images_
[LEFT
], 0, 0);
219 canvas
->DrawImageInt(*images_
[RIGHT
],
220 bar_width
- images_
[RIGHT
]->width(),
222 canvas
->TileImageInt(*images_
[CENTER_LEFT
],
223 images_
[LEFT
]->width(),
225 middle
- images_
[LEFT
]->width(),
227 canvas
->TileImageInt(*images_
[CENTER_RIGHT
],
230 bar_width
- middle
- images_
[RIGHT
]->width(),
234 // Paint slider thumb.
235 int button_cx
= content
.x() + full
;
236 int thumb_y
= content
.height() / 2 - thumb_
->height() / 2;
237 canvas
->DrawImageInt(*thumb_
, button_cx
, thumb_y
);
239 // TODO(jennyz): draw vertical slider bar with resources.
240 // TODO(sad): The painting code should use NativeTheme for various
242 const int kButtonRadius
= thumb_
->width() / 2;
243 const int kLineThickness
= bar_height_
/ 2;
244 const SkColor kFullColor
= SkColorSetARGB(125, 0, 0, 0);
245 const SkColor kEmptyColor
= SkColorSetARGB(50, 0, 0, 0);
247 int h
= content
.height() - thumb_
->height();
248 int full
= value
* h
;
249 int empty
= h
- full
;
250 int x
= content
.width() / 2 - kLineThickness
/ 2;
251 canvas
->FillRect(gfx::Rect(x
, content
.y() + kButtonRadius
,
252 kLineThickness
, empty
),
254 canvas
->FillRect(gfx::Rect(x
, content
.y() + empty
+ 2 * kButtonRadius
,
255 kLineThickness
, full
),
258 // TODO(mtomasz): We draw a thumb here because so far it is the same
259 // for horizontal and vertical orientations. If it is different, then
260 // we will need a separate resource.
261 int button_cy
= content
.y() + h
- full
;
262 int thumb_x
= content
.width() / 2 - thumb_
->width() / 2;
263 canvas
->DrawImageInt(*thumb_
, thumb_x
, button_cy
);
265 View::OnPaint(canvas
);
266 OnPaintFocus(canvas
);
269 bool Slider::OnMousePressed(const ui::MouseEvent
& event
) {
270 if (!event
.IsOnlyLeftMouseButton())
272 OnSliderDragStarted();
273 PrepareForMove(event
.location());
274 MoveButtonTo(event
.location());
278 bool Slider::OnMouseDragged(const ui::MouseEvent
& event
) {
279 MoveButtonTo(event
.location());
283 void Slider::OnMouseReleased(const ui::MouseEvent
& event
) {
287 bool Slider::OnKeyPressed(const ui::KeyEvent
& event
) {
288 if (orientation_
== HORIZONTAL
) {
289 if (event
.key_code() == ui::VKEY_LEFT
) {
290 SetValueInternal(value_
- keyboard_increment_
, VALUE_CHANGED_BY_USER
);
292 } else if (event
.key_code() == ui::VKEY_RIGHT
) {
293 SetValueInternal(value_
+ keyboard_increment_
, VALUE_CHANGED_BY_USER
);
297 if (event
.key_code() == ui::VKEY_DOWN
) {
298 SetValueInternal(value_
- keyboard_increment_
, VALUE_CHANGED_BY_USER
);
300 } else if (event
.key_code() == ui::VKEY_UP
) {
301 SetValueInternal(value_
+ keyboard_increment_
, VALUE_CHANGED_BY_USER
);
308 void Slider::OnFocus() {
313 void Slider::OnBlur() {
318 void Slider::OnGestureEvent(ui::GestureEvent
* event
) {
319 switch (event
->type()) {
320 // In a multi point gesture only the touch point will generate
321 // an ET_GESTURE_TAP_DOWN event.
322 case ui::ET_GESTURE_TAP_DOWN
:
323 OnSliderDragStarted();
324 PrepareForMove(event
->location());
325 // Intentional fall through to next case.
326 case ui::ET_GESTURE_SCROLL_BEGIN
:
327 case ui::ET_GESTURE_SCROLL_UPDATE
:
328 MoveButtonTo(event
->location());
331 case ui::ET_GESTURE_END
:
332 MoveButtonTo(event
->location());
334 if (event
->details().touch_points() <= 1)
342 void Slider::AnimationProgressed(const gfx::Animation
* animation
) {
343 animating_value_
= animation
->CurrentValueBetween(animating_value_
, value_
);
347 void Slider::GetAccessibleState(ui::AXViewState
* state
) {
348 state
->role
= ui::AX_ROLE_SLIDER
;
349 state
->name
= accessible_name_
;
350 state
->value
= base::UTF8ToUTF16(
351 base::StringPrintf("%d%%", static_cast<int>(value_
* 100 + 0.5)));
354 void Slider::OnSliderDragStarted() {
356 listener_
->SliderDragStarted(this);
359 void Slider::OnSliderDragEnded() {
361 listener_
->SliderDragEnded(this);