| OLD | NEW |
| (Empty) |
| 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 | |
| 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 | |
| 70 Slider::~Slider() { | |
| 71 } | |
| 72 | |
| 73 void Slider::SetValue(float value) { | |
| 74 SetValueInternal(value, VALUE_CHANGED_BY_API); | |
| 75 } | |
| 76 | |
| 77 void Slider::SetKeyboardIncrement(float increment) { | |
| 78 keyboard_increment_ = increment; | |
| 79 } | |
| 80 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 172 void Slider::SetAccessibleName(const base::string16& name) { | |
| 173 accessible_name_ = name; | |
| 174 } | |
| 175 | |
| 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 | |
| 189 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 | |
| 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 | |
| 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 | |
| 278 bool Slider::OnMouseDragged(const ui::MouseEvent& event) { | |
| 279 MoveButtonTo(event.location()); | |
| 280 return true; | |
| 281 } | |
| 282 | |
| 283 void Slider::OnMouseReleased(const ui::MouseEvent& event) { | |
| 284 OnSliderDragEnded(); | |
| 285 } | |
| 286 | |
| 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 | |
| 308 void Slider::OnFocus() { | |
| 309 View::OnFocus(); | |
| 310 SchedulePaint(); | |
| 311 } | |
| 312 | |
| 313 void Slider::OnBlur() { | |
| 314 View::OnBlur(); | |
| 315 SchedulePaint(); | |
| 316 } | |
| 317 | |
| 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 | |
| 342 void Slider::AnimationProgressed(const gfx::Animation* animation) { | |
| 343 animating_value_ = animation->CurrentValueBetween(animating_value_, value_); | |
| 344 SchedulePaint(); | |
| 345 } | |
| 346 | |
| 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 | |
| 354 void Slider::OnSliderDragStarted() { | |
| 355 if (listener_) | |
| 356 listener_->SliderDragStarted(this); | |
| 357 } | |
| 358 | |
| 359 void Slider::OnSliderDragEnded() { | |
| 360 if (listener_) | |
| 361 listener_->SliderDragEnded(this); | |
| 362 } | |
| 363 | |
| 364 } // namespace views | |
| OLD | NEW |