blob: d35b8046959d0b7ad361f8af97e7808cf462341f [file] [log] [blame]
// 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 <algorithm>
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.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/accessibility/ax_node_data.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/event.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/native_theme/native_theme.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/controls/md_slider.h"
#include "ui/views/controls/non_md_slider.h"
#include "ui/views/resources/grit/views_resources.h"
#include "ui/views/widget/widget.h"
namespace {
const int kSlideValueChangeDurationMs = 150;
// The image chunks.
enum BorderElements {
LEFT,
CENTER_LEFT,
CENTER_RIGHT,
RIGHT,
};
} // namespace
namespace views {
// static
const char Slider::kViewClassName[] = "Slider";
// static
Slider* Slider::CreateSlider(bool is_material_design,
SliderListener* listener) {
if (is_material_design)
return new MdSlider(listener);
return new NonMdSlider(listener);
}
Slider::~Slider() {
}
void Slider::SetValue(float value) {
SetValueInternal(value, VALUE_CHANGED_BY_API);
}
void Slider::SetAccessibleName(const base::string16& name) {
accessible_name_ = name;
}
Slider::Slider(SliderListener* listener)
: listener_(listener),
value_(0.f),
keyboard_increment_(0.1f),
initial_animating_value_(0.f),
value_is_valid_(false),
accessibility_events_enabled_(true),
initial_button_offset_(0) {
EnableCanvasFlippingForRTLUI(true);
#if defined(OS_MACOSX)
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
#else
SetFocusBehavior(FocusBehavior::ALWAYS);
#endif
}
float Slider::GetAnimatingValue() const{
return move_animation_ && move_animation_->is_animating()
? move_animation_->CurrentValueBetween(initial_animating_value_,
value_)
: value_;
}
void Slider::SetHighlighted(bool is_highlighted) {}
void Slider::OnPaint(gfx::Canvas* canvas) {
View::OnPaint(canvas);
OnPaintFocus(canvas);
}
void Slider::AnimationProgressed(const gfx::Animation* animation) {
if (animation == move_animation_.get())
SchedulePaint();
}
void Slider::AnimationEnded(const gfx::Animation* animation) {
if (animation == move_animation_.get())
move_animation_.reset();
}
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.
if (!move_animation_) {
initial_animating_value_ = old_value;
move_animation_.reset(new gfx::SlideAnimation(this));
move_animation_->SetSlideDuration(kSlideValueChangeDurationMs);
move_animation_->Show();
}
} else {
SchedulePaint();
}
if (accessibility_events_enabled_ && GetWidget())
NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, true);
}
void Slider::PrepareForMove(const int new_x) {
// Try to remember the position of the mouse cursor on the button.
gfx::Insets inset = GetInsets();
gfx::Rect content = GetContentsBounds();
float value = GetAnimatingValue();
const int thumb_width = GetThumbWidth();
const int thumb_x = value * (content.width() - thumb_width);
const int candidate_x = (base::i18n::IsRTL() ?
width() - (new_x - inset.left()) :
new_x - inset.left()) - thumb_x;
if (candidate_x >= 0 && candidate_x < thumb_width)
initial_button_offset_ = candidate_x;
else
initial_button_offset_ = thumb_width / 2;
}
void Slider::MoveButtonTo(const gfx::Point& point) {
const gfx::Insets inset = GetInsets();
const int thumb_width = GetThumbWidth();
// Calculate the value.
int amount = base::i18n::IsRTL()
? width() - inset.left() - point.x() - initial_button_offset_
: point.x() - inset.left() - initial_button_offset_;
SetValueInternal(
static_cast<float>(amount) / (width() - inset.width() - thumb_width),
VALUE_CHANGED_BY_USER);
}
void Slider::OnPaintFocus(gfx::Canvas* canvas) {
if (!HasFocus())
return;
// TODO(estade): make this a glow effect instead: crbug.com/658783
gfx::Rect focus_bounds = GetLocalBounds();
focus_bounds.Inset(gfx::Insets(1));
canvas->DrawSolidFocusRect(
gfx::RectF(focus_bounds),
SkColorSetA(GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FocusedBorderColor),
0x99),
2.f);
}
void Slider::OnSliderDragStarted() {
SetHighlighted(true);
if (listener_)
listener_->SliderDragStarted(this);
}
void Slider::OnSliderDragEnded() {
SetHighlighted(false);
if (listener_)
listener_->SliderDragEnded(this);
}
const char* Slider::GetClassName() const {
return kViewClassName;
}
gfx::Size Slider::GetPreferredSize() const {
const int kSizeMajor = 200;
const int kSizeMinor = 40;
return gfx::Size(std::max(width(), kSizeMajor), kSizeMinor);
}
bool Slider::OnMousePressed(const ui::MouseEvent& event) {
if (!event.IsOnlyLeftMouseButton())
return false;
OnSliderDragStarted();
PrepareForMove(event.location().x());
MoveButtonTo(event.location());
return true;
}
bool Slider::OnMouseDragged(const ui::MouseEvent& event) {
MoveButtonTo(event.location());
return true;
}
void Slider::OnMouseReleased(const ui::MouseEvent& event) {
OnSliderDragEnded();
}
bool Slider::OnKeyPressed(const ui::KeyEvent& event) {
float new_value = value_;
if (event.key_code() == ui::VKEY_LEFT)
new_value -= keyboard_increment_;
else if (event.key_code() == ui::VKEY_RIGHT)
new_value += keyboard_increment_;
else
return false;
SetValueInternal(new_value, VALUE_CHANGED_BY_USER);
return true;
}
void Slider::GetAccessibleNodeData(ui::AXNodeData* node_data) {
node_data->role = ui::AX_ROLE_SLIDER;
node_data->SetName(accessible_name_);
node_data->SetValue(base::UTF8ToUTF16(
base::StringPrintf("%d%%", static_cast<int>(value_ * 100 + 0.5))));
}
void Slider::OnFocus() {
View::OnFocus();
SchedulePaint();
}
void Slider::OnBlur() {
View::OnBlur();
SchedulePaint();
}
void Slider::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
// In a multi point gesture only the touch point will generate
// an ET_GESTURE_TAP_DOWN event.
case ui::ET_GESTURE_TAP_DOWN:
OnSliderDragStarted();
PrepareForMove(event->location().x());
// Intentional fall through to next case.
case ui::ET_GESTURE_SCROLL_BEGIN:
case ui::ET_GESTURE_SCROLL_UPDATE:
MoveButtonTo(event->location());
event->SetHandled();
break;
case ui::ET_GESTURE_END:
MoveButtonTo(event->location());
event->SetHandled();
if (event->details().touch_points() <= 1)
OnSliderDragEnded();
break;
default:
break;
}
}
} // namespace views