Home | History | Annotate | Download | only in controls
      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 
     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 
     68 Slider::~Slider() {
     69 }
     70 
     71 void Slider::SetValue(float value) {
     72   SetValueInternal(value, VALUE_CHANGED_BY_API);
     73 }
     74 
     75 void Slider::SetKeyboardIncrement(float increment) {
     76   keyboard_increment_ = increment;
     77 }
     78 
     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 
    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 
    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 
    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 
    170 void Slider::SetAccessibleName(const string16& name) {
    171   accessible_name_ = name;
    172 }
    173 
    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 
    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 
    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 
    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 
    277 bool Slider::OnMouseDragged(const ui::MouseEvent& event) {
    278   MoveButtonTo(event.location());
    279   return true;
    280 }
    281 
    282 void Slider::OnMouseReleased(const ui::MouseEvent& event) {
    283   if (listener_)
    284     listener_->SliderDragEnded(this);
    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::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 
    321 void Slider::AnimationProgressed(const gfx::Animation* animation) {
    322   animating_value_ = animation->CurrentValueBetween(animating_value_, value_);
    323   SchedulePaint();
    324 }
    325 
    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