| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/views/controls/slider.h" |
| |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "grit/ui_resources.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "third_party/skia/include/core/SkPaint.h" |
| #include "ui/base/accessibility/accessible_view_state.h" |
| #include "ui/base/animation/slide_animation.h" |
| #include "ui/base/events/event.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/point.h" |
| #include "ui/gfx/rect.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace { |
| const int kSlideValueChangeDurationMS = 150; |
| |
| const int kBarImagesActive[] = { |
| IDR_SLIDER_ACTIVE_LEFT, |
| IDR_SLIDER_ACTIVE_CENTER, |
| IDR_SLIDER_PRESSED_CENTER, |
| IDR_SLIDER_PRESSED_RIGHT, |
| }; |
| |
| const int kBarImagesDisabled[] = { |
| IDR_SLIDER_DISABLED_LEFT, |
| IDR_SLIDER_DISABLED_CENTER, |
| IDR_SLIDER_DISABLED_CENTER, |
| IDR_SLIDER_DISABLED_RIGHT, |
| }; |
| |
| // The image chunks. |
| enum BorderElements { |
| LEFT, |
| CENTER_LEFT, |
| CENTER_RIGHT, |
| RIGHT, |
| }; |
| } |
| |
| namespace views { |
| |
| Slider::Slider(SliderListener* listener, Orientation orientation) |
| : listener_(listener), |
| orientation_(orientation), |
| value_(0.f), |
| keyboard_increment_(0.1f), |
| animating_value_(0.f), |
| value_is_valid_(false), |
| accessibility_events_enabled_(true), |
| focus_border_color_(0), |
| bar_active_images_(kBarImagesActive), |
| bar_disabled_images_(kBarImagesDisabled) { |
| EnableCanvasFlippingForRTLUI(true); |
| set_focusable(true); |
| UpdateState(true); |
| } |
| |
| Slider::~Slider() { |
| } |
| |
| void Slider::SetValue(float value) { |
| SetValueInternal(value, VALUE_CHANGED_BY_API); |
| } |
| |
| void Slider::SetKeyboardIncrement(float increment) { |
| keyboard_increment_ = increment; |
| } |
| |
| void Slider::SetValueInternal(float value, SliderChangeReason reason) { |
| bool old_value_valid = value_is_valid_; |
| |
| value_is_valid_ = true; |
| if (value < 0.0) |
| value = 0.0; |
| else if (value > 1.0) |
| value = 1.0; |
| if (value_ == value) |
| return; |
| float old_value = value_; |
| value_ = value; |
| if (listener_) |
| listener_->SliderValueChanged(this, value_, old_value, reason); |
| |
| if (old_value_valid && base::MessageLoop::current()) { |
| // Do not animate when setting the value of the slider for the first time. |
| // There is no message-loop when running tests. So we cannot animate then. |
| animating_value_ = old_value; |
| move_animation_.reset(new ui::SlideAnimation(this)); |
| move_animation_->SetSlideDuration(kSlideValueChangeDurationMS); |
| move_animation_->Show(); |
| AnimationProgressed(move_animation_.get()); |
| } else { |
| SchedulePaint(); |
| } |
| if (accessibility_events_enabled_ && GetWidget()) { |
| NotifyAccessibilityEvent( |
| ui::AccessibilityTypes::EVENT_VALUE_CHANGED, true); |
| } |
| } |
| |
| void Slider::PrepareForMove(const gfx::Point& point) { |
| // Try to remember the position of the mouse cursor on the button. |
| gfx::Insets inset = GetInsets(); |
| gfx::Rect content = GetContentsBounds(); |
| float value = move_animation_.get() && move_animation_->is_animating() ? |
| animating_value_ : value_; |
| |
| // For the horizontal orientation. |
| const int thumb_x = value * (content.width() - thumb_->width()); |
| const int candidate_x = (base::i18n::IsRTL() ? |
| width() - (point.x() - inset.left()) : |
| point.x() - inset.left()) - thumb_x; |
| if (candidate_x >= 0 && candidate_x < thumb_->width()) |
| initial_button_offset_.set_x(candidate_x); |
| else |
| initial_button_offset_.set_x(thumb_->width() / 2); |
| |
| // For the vertical orientation. |
| const int thumb_y = (1.0 - value) * (content.height() - thumb_->height()); |
| const int candidate_y = point.y() - thumb_y; |
| if (candidate_y >= 0 && candidate_y < thumb_->height()) |
| initial_button_offset_.set_y(candidate_y); |
| else |
| initial_button_offset_.set_y(thumb_->height() / 2); |
| } |
| |
| void Slider::MoveButtonTo(const gfx::Point& point) { |
| gfx::Insets inset = GetInsets(); |
| // Calculate the value. |
| if (orientation_ == HORIZONTAL) { |
| int amount = base::i18n::IsRTL() ? |
| width() - inset.left() - point.x() - initial_button_offset_.x() : |
| point.x() - inset.left() - initial_button_offset_.x(); |
| SetValueInternal(static_cast<float>(amount) / |
| (width() - inset.width() - thumb_->width()), |
| VALUE_CHANGED_BY_USER); |
| } else { |
| SetValueInternal( |
| 1.0f - static_cast<float>(point.y() - initial_button_offset_.y()) / |
| (height() - thumb_->height()), |
| VALUE_CHANGED_BY_USER); |
| } |
| } |
| |
| void Slider::UpdateState(bool control_on) { |
| ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| if (control_on) { |
| thumb_ = rb.GetImageNamed(IDR_SLIDER_ACTIVE_THUMB).ToImageSkia(); |
| for (int i = 0; i < 4; ++i) |
| images_[i] = rb.GetImageNamed(bar_active_images_[i]).ToImageSkia(); |
| } else { |
| thumb_ = rb.GetImageNamed(IDR_SLIDER_DISABLED_THUMB).ToImageSkia(); |
| for (int i = 0; i < 4; ++i) |
| images_[i] = rb.GetImageNamed(bar_disabled_images_[i]).ToImageSkia(); |
| } |
| bar_height_ = images_[LEFT]->height(); |
| SchedulePaint(); |
| } |
| |
| void Slider::SetAccessibleName(const string16& name) { |
| accessible_name_ = name; |
| } |
| |
| gfx::Size Slider::GetPreferredSize() { |
| const int kSizeMajor = 200; |
| const int kSizeMinor = 40; |
| |
| if (orientation_ == HORIZONTAL) |
| return gfx::Size(std::max(width(), kSizeMajor), kSizeMinor); |
| return gfx::Size(kSizeMinor, std::max(height(), kSizeMajor)); |
| } |
| |
| void Slider::OnPaint(gfx::Canvas* canvas) { |
| gfx::Rect content = GetContentsBounds(); |
| float value = move_animation_.get() && move_animation_->is_animating() ? |
| animating_value_ : value_; |
| if (orientation_ == HORIZONTAL) { |
| // Paint slider bar with image resources. |
| |
| // Inset the slider bar a little bit, so that the left or the right end of |
| // the slider bar will not be exposed under the thumb button when the thumb |
| // button slides to the left most or right most position. |
| const int kBarInsetX = 2; |
| int bar_width = content.width() - kBarInsetX * 2; |
| int bar_cy = content.height() / 2 - bar_height_ / 2; |
| |
| int w = content.width() - thumb_->width(); |
| int full = value * w; |
| int middle = std::max(full, images_[LEFT]->width()); |
| |
| canvas->Save(); |
| canvas->Translate(gfx::Vector2d(kBarInsetX, bar_cy)); |
| canvas->DrawImageInt(*images_[LEFT], 0, 0); |
| canvas->DrawImageInt(*images_[RIGHT], |
| bar_width - images_[RIGHT]->width(), |
| 0); |
| canvas->TileImageInt(*images_[CENTER_LEFT], |
| images_[LEFT]->width(), |
| 0, |
| middle - images_[LEFT]->width(), |
| bar_height_); |
| canvas->TileImageInt(*images_[CENTER_RIGHT], |
| middle, |
| 0, |
| bar_width - middle - images_[RIGHT]->width(), |
| bar_height_); |
| canvas->Restore(); |
| |
| // Paint slider thumb. |
| int button_cx = content.x() + full; |
| int thumb_y = content.height() / 2 - thumb_->height() / 2; |
| canvas->DrawImageInt(*thumb_, button_cx, thumb_y); |
| } else { |
| // TODO(jennyz): draw vertical slider bar with resources. |
| // TODO(sad): The painting code should use NativeTheme for various |
| // platforms. |
| const int kButtonRadius = thumb_->width() / 2; |
| const int kLineThickness = bar_height_ / 2; |
| const SkColor kFullColor = SkColorSetARGB(125, 0, 0, 0); |
| const SkColor kEmptyColor = SkColorSetARGB(50, 0, 0, 0); |
| |
| int h = content.height() - thumb_->height(); |
| int full = value * h; |
| int empty = h - full; |
| int x = content.width() / 2 - kLineThickness / 2; |
| canvas->FillRect(gfx::Rect(x, content.y() + kButtonRadius, |
| kLineThickness, empty), |
| kEmptyColor); |
| canvas->FillRect(gfx::Rect(x, content.y() + empty + 2 * kButtonRadius, |
| kLineThickness, full), |
| kFullColor); |
| |
| // TODO(mtomasz): We draw a thumb here because so far it is the same |
| // for horizontal and vertical orientations. If it is different, then |
| // we will need a separate resource. |
| int button_cy = content.y() + h - full; |
| int thumb_x = content.width() / 2 - thumb_->width() / 2; |
| canvas->DrawImageInt(*thumb_, thumb_x, button_cy); |
| } |
| View::OnPaint(canvas); |
| } |
| |
| bool Slider::OnMousePressed(const ui::MouseEvent& event) { |
| if (!event.IsOnlyLeftMouseButton()) |
| return false; |
| if (listener_) |
| listener_->SliderDragStarted(this); |
| PrepareForMove(event.location()); |
| MoveButtonTo(event.location()); |
| return true; |
| } |
| |
| bool Slider::OnMouseDragged(const ui::MouseEvent& event) { |
| MoveButtonTo(event.location()); |
| return true; |
| } |
| |
| void Slider::OnMouseReleased(const ui::MouseEvent& event) { |
| if (listener_) |
| listener_->SliderDragEnded(this); |
| } |
| |
| bool Slider::OnKeyPressed(const ui::KeyEvent& event) { |
| if (orientation_ == HORIZONTAL) { |
| if (event.key_code() == ui::VKEY_LEFT) { |
| SetValueInternal(value_ - keyboard_increment_, VALUE_CHANGED_BY_USER); |
| return true; |
| } else if (event.key_code() == ui::VKEY_RIGHT) { |
| SetValueInternal(value_ + keyboard_increment_, VALUE_CHANGED_BY_USER); |
| return true; |
| } |
| } else { |
| if (event.key_code() == ui::VKEY_DOWN) { |
| SetValueInternal(value_ - keyboard_increment_, VALUE_CHANGED_BY_USER); |
| return true; |
| } else if (event.key_code() == ui::VKEY_UP) { |
| SetValueInternal(value_ + keyboard_increment_, VALUE_CHANGED_BY_USER); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void Slider::OnGestureEvent(ui::GestureEvent* event) { |
| if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN || |
| event->type() == ui::ET_GESTURE_TAP_DOWN) { |
| PrepareForMove(event->location()); |
| MoveButtonTo(event->location()); |
| event->SetHandled(); |
| } else if (event->type() == ui::ET_GESTURE_SCROLL_UPDATE || |
| event->type() == ui::ET_GESTURE_SCROLL_END) { |
| MoveButtonTo(event->location()); |
| event->SetHandled(); |
| } |
| } |
| |
| void Slider::AnimationProgressed(const ui::Animation* animation) { |
| animating_value_ = animation->CurrentValueBetween(animating_value_, value_); |
| SchedulePaint(); |
| } |
| |
| void Slider::GetAccessibleState(ui::AccessibleViewState* state) { |
| state->role = ui::AccessibilityTypes::ROLE_SLIDER; |
| state->name = accessible_name_; |
| state->value = UTF8ToUTF16( |
| base::StringPrintf("%d%%", (int)(value_ * 100 + 0.5))); |
| } |
| |
| void Slider::OnPaintFocusBorder(gfx::Canvas* canvas) { |
| if (!focus_border_color_) { |
| View::OnPaintFocusBorder(canvas); |
| } else if (HasFocus() && (focusable() || IsAccessibilityFocusable())) { |
| canvas->DrawRect(gfx::Rect(1, 1, width() - 3, height() - 3), |
| focus_border_color_); |
| } |
| } |
| |
| } // namespace views |