• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ui/views/controls/slider.h"
6 
7 #include <algorithm>
8 
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"
25 
26 namespace {
27 const int kSlideValueChangeDurationMS = 150;
28 
29 const int kBarImagesActive[] = {
30     IDR_SLIDER_ACTIVE_LEFT,
31     IDR_SLIDER_ACTIVE_CENTER,
32     IDR_SLIDER_PRESSED_CENTER,
33     IDR_SLIDER_PRESSED_RIGHT,
34 };
35 
36 const int kBarImagesDisabled[] = {
37     IDR_SLIDER_DISABLED_LEFT,
38     IDR_SLIDER_DISABLED_CENTER,
39     IDR_SLIDER_DISABLED_CENTER,
40     IDR_SLIDER_DISABLED_RIGHT,
41 };
42 
43 // The image chunks.
44 enum BorderElements {
45   LEFT,
46   CENTER_LEFT,
47   CENTER_RIGHT,
48   RIGHT,
49 };
50 }  // namespace
51 
52 namespace views {
53 
Slider(SliderListener * listener,Orientation orientation)54 Slider::Slider(SliderListener* listener, Orientation orientation)
55     : listener_(listener),
56       orientation_(orientation),
57       value_(0.f),
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);
66   SetFocusable(true);
67   UpdateState(true);
68 }
69 
~Slider()70 Slider::~Slider() {
71 }
72 
SetValue(float value)73 void Slider::SetValue(float value) {
74   SetValueInternal(value, VALUE_CHANGED_BY_API);
75 }
76 
SetKeyboardIncrement(float increment)77 void Slider::SetKeyboardIncrement(float increment) {
78   keyboard_increment_ = increment;
79 }
80 
SetValueInternal(float value,SliderChangeReason reason)81 void Slider::SetValueInternal(float value, SliderChangeReason reason) {
82   bool old_value_valid = value_is_valid_;
83 
84   value_is_valid_ = true;
85   if (value < 0.0)
86     value = 0.0;
87   else if (value > 1.0)
88     value = 1.0;
89   if (value_ == value)
90     return;
91   float old_value = value_;
92   value_ = value;
93   if (listener_)
94     listener_->SliderValueChanged(this, value_, old_value, reason);
95 
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());
104   } else {
105     SchedulePaint();
106   }
107   if (accessibility_events_enabled_ && GetWidget()) {
108     NotifyAccessibilityEvent(
109         ui::AX_EVENT_VALUE_CHANGED, true);
110   }
111 }
112 
PrepareForMove(const gfx::Point & point)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_;
119 
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);
127   else
128     initial_button_offset_.set_x(thumb_->width() / 2);
129 
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);
135   else
136     initial_button_offset_.set_y(thumb_->height() / 2);
137 }
138 
MoveButtonTo(const gfx::Point & point)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);
149   } else {
150     SetValueInternal(
151         1.0f - static_cast<float>(point.y() - initial_button_offset_.y()) /
152             (height() - thumb_->height()),
153         VALUE_CHANGED_BY_USER);
154   }
155 }
156 
UpdateState(bool control_on)157 void Slider::UpdateState(bool control_on) {
158   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
159   if (control_on) {
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();
163   } else {
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();
167   }
168   bar_height_ = images_[LEFT]->height();
169   SchedulePaint();
170 }
171 
SetAccessibleName(const base::string16 & name)172 void Slider::SetAccessibleName(const base::string16& name) {
173   accessible_name_ = name;
174 }
175 
OnPaintFocus(gfx::Canvas * canvas)176 void Slider::OnPaintFocus(gfx::Canvas* canvas) {
177   if (!HasFocus())
178     return;
179 
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_);
186   }
187 }
188 
GetPreferredSize() const189 gfx::Size Slider::GetPreferredSize() const {
190   const int kSizeMajor = 200;
191   const int kSizeMinor = 40;
192 
193   if (orientation_ == HORIZONTAL)
194     return gfx::Size(std::max(width(), kSizeMajor), kSizeMinor);
195   return gfx::Size(kSizeMinor, std::max(height(), kSizeMajor));
196 }
197 
OnPaint(gfx::Canvas * canvas)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.
204 
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;
211 
212     int w = content.width() - thumb_->width();
213     int full = value * w;
214     int middle = std::max(full, images_[LEFT]->width());
215 
216     canvas->Save();
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(),
221                          0);
222     canvas->TileImageInt(*images_[CENTER_LEFT],
223                          images_[LEFT]->width(),
224                          0,
225                          middle - images_[LEFT]->width(),
226                          bar_height_);
227     canvas->TileImageInt(*images_[CENTER_RIGHT],
228                          middle,
229                          0,
230                          bar_width - middle - images_[RIGHT]->width(),
231                          bar_height_);
232     canvas->Restore();
233 
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);
238   } else {
239     // TODO(jennyz): draw vertical slider bar with resources.
240     // TODO(sad): The painting code should use NativeTheme for various
241     // platforms.
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);
246 
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),
253                      kEmptyColor);
254     canvas->FillRect(gfx::Rect(x, content.y() + empty + 2 * kButtonRadius,
255                                kLineThickness, full),
256                      kFullColor);
257 
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);
264   }
265   View::OnPaint(canvas);
266   OnPaintFocus(canvas);
267 }
268 
OnMousePressed(const ui::MouseEvent & event)269 bool Slider::OnMousePressed(const ui::MouseEvent& event) {
270   if (!event.IsOnlyLeftMouseButton())
271     return false;
272   OnSliderDragStarted();
273   PrepareForMove(event.location());
274   MoveButtonTo(event.location());
275   return true;
276 }
277 
OnMouseDragged(const ui::MouseEvent & event)278 bool Slider::OnMouseDragged(const ui::MouseEvent& event) {
279   MoveButtonTo(event.location());
280   return true;
281 }
282 
OnMouseReleased(const ui::MouseEvent & event)283 void Slider::OnMouseReleased(const ui::MouseEvent& event) {
284   OnSliderDragEnded();
285 }
286 
OnKeyPressed(const ui::KeyEvent & 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);
291       return true;
292     } else if (event.key_code() == ui::VKEY_RIGHT) {
293       SetValueInternal(value_ + keyboard_increment_, VALUE_CHANGED_BY_USER);
294       return true;
295     }
296   } else {
297     if (event.key_code() == ui::VKEY_DOWN) {
298       SetValueInternal(value_ - keyboard_increment_, VALUE_CHANGED_BY_USER);
299       return true;
300     } else if (event.key_code() == ui::VKEY_UP) {
301       SetValueInternal(value_ + keyboard_increment_, VALUE_CHANGED_BY_USER);
302       return true;
303     }
304   }
305   return false;
306 }
307 
OnFocus()308 void Slider::OnFocus() {
309   View::OnFocus();
310   SchedulePaint();
311 }
312 
OnBlur()313 void Slider::OnBlur() {
314   View::OnBlur();
315   SchedulePaint();
316 }
317 
OnGestureEvent(ui::GestureEvent * event)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());
329       event->SetHandled();
330       break;
331     case ui::ET_GESTURE_END:
332       MoveButtonTo(event->location());
333       event->SetHandled();
334       if (event->details().touch_points() <= 1)
335         OnSliderDragEnded();
336       break;
337     default:
338       break;
339   }
340 }
341 
AnimationProgressed(const gfx::Animation * animation)342 void Slider::AnimationProgressed(const gfx::Animation* animation) {
343   animating_value_ = animation->CurrentValueBetween(animating_value_, value_);
344   SchedulePaint();
345 }
346 
GetAccessibleState(ui::AXViewState * state)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)));
352 }
353 
OnSliderDragStarted()354 void Slider::OnSliderDragStarted() {
355   if (listener_)
356     listener_->SliderDragStarted(this);
357 }
358 
OnSliderDragEnded()359 void Slider::OnSliderDragEnded() {
360   if (listener_)
361     listener_->SliderDragEnded(this);
362 }
363 
364 }  // namespace views
365