• 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 "base/logging.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "grit/ui_resources.h"
12 #include "third_party/skia/include/core/SkCanvas.h"
13 #include "third_party/skia/include/core/SkColor.h"
14 #include "third_party/skia/include/core/SkPaint.h"
15 #include "ui/base/accessibility/accessible_view_state.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/events/event.h"
18 #include "ui/gfx/animation/slide_animation.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/point.h"
21 #include "ui/gfx/rect.h"
22 #include "ui/views/widget/widget.h"
23 
24 namespace {
25 const int kSlideValueChangeDurationMS = 150;
26 
27 const int kBarImagesActive[] = {
28     IDR_SLIDER_ACTIVE_LEFT,
29     IDR_SLIDER_ACTIVE_CENTER,
30     IDR_SLIDER_PRESSED_CENTER,
31     IDR_SLIDER_PRESSED_RIGHT,
32 };
33 
34 const int kBarImagesDisabled[] = {
35     IDR_SLIDER_DISABLED_LEFT,
36     IDR_SLIDER_DISABLED_CENTER,
37     IDR_SLIDER_DISABLED_CENTER,
38     IDR_SLIDER_DISABLED_RIGHT,
39 };
40 
41 // The image chunks.
42 enum BorderElements {
43   LEFT,
44   CENTER_LEFT,
45   CENTER_RIGHT,
46   RIGHT,
47 };
48 }
49 
50 namespace views {
51 
Slider(SliderListener * listener,Orientation orientation)52 Slider::Slider(SliderListener* listener, Orientation orientation)
53     : listener_(listener),
54       orientation_(orientation),
55       value_(0.f),
56       keyboard_increment_(0.1f),
57       animating_value_(0.f),
58       value_is_valid_(false),
59       accessibility_events_enabled_(true),
60       focus_border_color_(0),
61       bar_active_images_(kBarImagesActive),
62       bar_disabled_images_(kBarImagesDisabled) {
63   EnableCanvasFlippingForRTLUI(true);
64   SetFocusable(true);
65   UpdateState(true);
66 }
67 
~Slider()68 Slider::~Slider() {
69 }
70 
SetValue(float value)71 void Slider::SetValue(float value) {
72   SetValueInternal(value, VALUE_CHANGED_BY_API);
73 }
74 
SetKeyboardIncrement(float increment)75 void Slider::SetKeyboardIncrement(float increment) {
76   keyboard_increment_ = increment;
77 }
78 
SetValueInternal(float value,SliderChangeReason reason)79 void Slider::SetValueInternal(float value, SliderChangeReason reason) {
80   bool old_value_valid = value_is_valid_;
81 
82   value_is_valid_ = true;
83   if (value < 0.0)
84    value = 0.0;
85   else if (value > 1.0)
86     value = 1.0;
87   if (value_ == value)
88     return;
89   float old_value = value_;
90   value_ = value;
91   if (listener_)
92     listener_->SliderValueChanged(this, value_, old_value, reason);
93 
94   if (old_value_valid && base::MessageLoop::current()) {
95     // Do not animate when setting the value of the slider for the first time.
96     // There is no message-loop when running tests. So we cannot animate then.
97     animating_value_ = old_value;
98     move_animation_.reset(new gfx::SlideAnimation(this));
99     move_animation_->SetSlideDuration(kSlideValueChangeDurationMS);
100     move_animation_->Show();
101     AnimationProgressed(move_animation_.get());
102   } else {
103     SchedulePaint();
104   }
105   if (accessibility_events_enabled_ && GetWidget()) {
106     NotifyAccessibilityEvent(
107         ui::AccessibilityTypes::EVENT_VALUE_CHANGED, true);
108   }
109 }
110 
PrepareForMove(const gfx::Point & point)111 void Slider::PrepareForMove(const gfx::Point& point) {
112   // Try to remember the position of the mouse cursor on the button.
113   gfx::Insets inset = GetInsets();
114   gfx::Rect content = GetContentsBounds();
115   float value = move_animation_.get() && move_animation_->is_animating() ?
116         animating_value_ : value_;
117 
118   // For the horizontal orientation.
119   const int thumb_x = value * (content.width() - thumb_->width());
120   const int candidate_x = (base::i18n::IsRTL() ?
121       width() - (point.x() - inset.left()) :
122       point.x() - inset.left()) - thumb_x;
123   if (candidate_x >= 0 && candidate_x < thumb_->width())
124     initial_button_offset_.set_x(candidate_x);
125   else
126     initial_button_offset_.set_x(thumb_->width() / 2);
127 
128   // For the vertical orientation.
129   const int thumb_y = (1.0 - value) * (content.height() - thumb_->height());
130   const int candidate_y = point.y() - thumb_y;
131   if (candidate_y >= 0 && candidate_y < thumb_->height())
132     initial_button_offset_.set_y(candidate_y);
133   else
134     initial_button_offset_.set_y(thumb_->height() / 2);
135 }
136 
MoveButtonTo(const gfx::Point & point)137 void Slider::MoveButtonTo(const gfx::Point& point) {
138   gfx::Insets inset = GetInsets();
139   // Calculate the value.
140   if (orientation_ == HORIZONTAL) {
141     int amount = base::i18n::IsRTL() ?
142         width() - inset.left() - point.x() - initial_button_offset_.x() :
143         point.x() - inset.left() - initial_button_offset_.x();
144     SetValueInternal(static_cast<float>(amount) /
145                          (width() - inset.width() - thumb_->width()),
146                      VALUE_CHANGED_BY_USER);
147   } else {
148     SetValueInternal(
149         1.0f - static_cast<float>(point.y() - initial_button_offset_.y()) /
150             (height() - thumb_->height()),
151         VALUE_CHANGED_BY_USER);
152   }
153 }
154 
UpdateState(bool control_on)155 void Slider::UpdateState(bool control_on) {
156   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
157   if (control_on) {
158     thumb_ = rb.GetImageNamed(IDR_SLIDER_ACTIVE_THUMB).ToImageSkia();
159     for (int i = 0; i < 4; ++i)
160       images_[i] = rb.GetImageNamed(bar_active_images_[i]).ToImageSkia();
161   } else {
162     thumb_ = rb.GetImageNamed(IDR_SLIDER_DISABLED_THUMB).ToImageSkia();
163     for (int i = 0; i < 4; ++i)
164       images_[i] = rb.GetImageNamed(bar_disabled_images_[i]).ToImageSkia();
165   }
166   bar_height_ = images_[LEFT]->height();
167   SchedulePaint();
168 }
169 
SetAccessibleName(const string16 & name)170 void Slider::SetAccessibleName(const string16& name) {
171   accessible_name_ = name;
172 }
173 
OnPaintFocus(gfx::Canvas * canvas)174 void Slider::OnPaintFocus(gfx::Canvas* canvas) {
175   if (!HasFocus())
176     return;
177 
178   if (!focus_border_color_) {
179     canvas->DrawFocusRect(GetLocalBounds());
180   } else if (HasFocus()) {
181     canvas->DrawSolidFocusRect(
182         gfx::Rect(1, 1, width() - 3, height() - 3),
183         focus_border_color_);
184   }
185 }
186 
GetPreferredSize()187 gfx::Size Slider::GetPreferredSize() {
188   const int kSizeMajor = 200;
189   const int kSizeMinor = 40;
190 
191   if (orientation_ == HORIZONTAL)
192     return gfx::Size(std::max(width(), kSizeMajor), kSizeMinor);
193   return gfx::Size(kSizeMinor, std::max(height(), kSizeMajor));
194 }
195 
OnPaint(gfx::Canvas * canvas)196 void Slider::OnPaint(gfx::Canvas* canvas) {
197   gfx::Rect content = GetContentsBounds();
198   float value = move_animation_.get() && move_animation_->is_animating() ?
199       animating_value_ : value_;
200   if (orientation_ == HORIZONTAL) {
201     // Paint slider bar with image resources.
202 
203     // Inset the slider bar a little bit, so that the left or the right end of
204     // the slider bar will not be exposed under the thumb button when the thumb
205     // button slides to the left most or right most position.
206     const int kBarInsetX = 2;
207     int bar_width = content.width() - kBarInsetX * 2;
208     int bar_cy = content.height() / 2 - bar_height_ / 2;
209 
210     int w = content.width() - thumb_->width();
211     int full = value * w;
212     int middle = std::max(full, images_[LEFT]->width());
213 
214     canvas->Save();
215     canvas->Translate(gfx::Vector2d(kBarInsetX, bar_cy));
216     canvas->DrawImageInt(*images_[LEFT], 0, 0);
217     canvas->DrawImageInt(*images_[RIGHT],
218                          bar_width - images_[RIGHT]->width(),
219                          0);
220     canvas->TileImageInt(*images_[CENTER_LEFT],
221                          images_[LEFT]->width(),
222                          0,
223                          middle - images_[LEFT]->width(),
224                          bar_height_);
225     canvas->TileImageInt(*images_[CENTER_RIGHT],
226                          middle,
227                          0,
228                          bar_width - middle - images_[RIGHT]->width(),
229                          bar_height_);
230     canvas->Restore();
231 
232     // Paint slider thumb.
233     int button_cx = content.x() + full;
234     int thumb_y = content.height() / 2 - thumb_->height() / 2;
235     canvas->DrawImageInt(*thumb_, button_cx, thumb_y);
236   } else {
237     // TODO(jennyz): draw vertical slider bar with resources.
238     // TODO(sad): The painting code should use NativeTheme for various
239     // platforms.
240     const int kButtonRadius = thumb_->width() / 2;
241     const int kLineThickness = bar_height_ / 2;
242     const SkColor kFullColor = SkColorSetARGB(125, 0, 0, 0);
243     const SkColor kEmptyColor = SkColorSetARGB(50, 0, 0, 0);
244 
245     int h = content.height() - thumb_->height();
246     int full = value * h;
247     int empty = h - full;
248     int x = content.width() / 2 - kLineThickness / 2;
249     canvas->FillRect(gfx::Rect(x, content.y() + kButtonRadius,
250                                kLineThickness, empty),
251                      kEmptyColor);
252     canvas->FillRect(gfx::Rect(x, content.y() + empty + 2 * kButtonRadius,
253                                kLineThickness, full),
254                      kFullColor);
255 
256     // TODO(mtomasz): We draw a thumb here because so far it is the same
257     // for horizontal and vertical orientations. If it is different, then
258     // we will need a separate resource.
259     int button_cy = content.y() + h - full;
260     int thumb_x = content.width() / 2 - thumb_->width() / 2;
261     canvas->DrawImageInt(*thumb_, thumb_x, button_cy);
262   }
263   View::OnPaint(canvas);
264   OnPaintFocus(canvas);
265 }
266 
OnMousePressed(const ui::MouseEvent & event)267 bool Slider::OnMousePressed(const ui::MouseEvent& event) {
268   if (!event.IsOnlyLeftMouseButton())
269     return false;
270   if (listener_)
271     listener_->SliderDragStarted(this);
272   PrepareForMove(event.location());
273   MoveButtonTo(event.location());
274   return true;
275 }
276 
OnMouseDragged(const ui::MouseEvent & event)277 bool Slider::OnMouseDragged(const ui::MouseEvent& event) {
278   MoveButtonTo(event.location());
279   return true;
280 }
281 
OnMouseReleased(const ui::MouseEvent & event)282 void Slider::OnMouseReleased(const ui::MouseEvent& event) {
283   if (listener_)
284     listener_->SliderDragEnded(this);
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 
OnGestureEvent(ui::GestureEvent * event)308 void Slider::OnGestureEvent(ui::GestureEvent* event) {
309   if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN ||
310       event->type() == ui::ET_GESTURE_TAP_DOWN) {
311     PrepareForMove(event->location());
312     MoveButtonTo(event->location());
313     event->SetHandled();
314   } else if (event->type() == ui::ET_GESTURE_SCROLL_UPDATE ||
315              event->type() == ui::ET_GESTURE_SCROLL_END) {
316     MoveButtonTo(event->location());
317     event->SetHandled();
318   }
319 }
320 
AnimationProgressed(const gfx::Animation * animation)321 void Slider::AnimationProgressed(const gfx::Animation* animation) {
322   animating_value_ = animation->CurrentValueBetween(animating_value_, value_);
323   SchedulePaint();
324 }
325 
GetAccessibleState(ui::AccessibleViewState * state)326 void Slider::GetAccessibleState(ui::AccessibleViewState* state) {
327   state->role = ui::AccessibilityTypes::ROLE_SLIDER;
328   state->name = accessible_name_;
329   state->value = UTF8ToUTF16(
330       base::StringPrintf("%d%%", (int)(value_ * 100 + 0.5)));
331 }
332 
333 }  // namespace views
334