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/scrollbar/native_scroll_bar_views.h"
6
7#include "base/logging.h"
8#include "ui/events/keycodes/keyboard_codes.h"
9#include "ui/gfx/canvas.h"
10#include "ui/gfx/path.h"
11#include "ui/views/controls/button/custom_button.h"
12#include "ui/views/controls/focusable_border.h"
13#include "ui/views/controls/scrollbar/base_scroll_bar_button.h"
14#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
15#include "ui/views/controls/scrollbar/native_scroll_bar.h"
16#include "ui/views/controls/scrollbar/scroll_bar.h"
17
18namespace views {
19
20namespace {
21
22// Wrapper for the scroll buttons.
23class ScrollBarButton : public BaseScrollBarButton {
24 public:
25  enum Type {
26    UP,
27    DOWN,
28    LEFT,
29    RIGHT,
30  };
31
32  ScrollBarButton(ButtonListener* listener, Type type);
33  virtual ~ScrollBarButton();
34
35  virtual gfx::Size GetPreferredSize() const OVERRIDE;
36  virtual const char* GetClassName() const OVERRIDE {
37    return "ScrollBarButton";
38  }
39
40 protected:
41  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
42
43 private:
44  ui::NativeTheme::ExtraParams GetNativeThemeParams() const;
45  ui::NativeTheme::Part GetNativeThemePart() const;
46  ui::NativeTheme::State GetNativeThemeState() const;
47
48  Type type_;
49};
50
51// Wrapper for the scroll thumb
52class ScrollBarThumb : public BaseScrollBarThumb {
53 public:
54  explicit ScrollBarThumb(BaseScrollBar* scroll_bar);
55  virtual ~ScrollBarThumb();
56
57  virtual gfx::Size GetPreferredSize() const OVERRIDE;
58  virtual const char* GetClassName() const OVERRIDE {
59    return "ScrollBarThumb";
60  }
61
62 protected:
63  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
64
65 private:
66  ui::NativeTheme::ExtraParams GetNativeThemeParams() const;
67  ui::NativeTheme::Part GetNativeThemePart() const;
68  ui::NativeTheme::State GetNativeThemeState() const;
69
70  ScrollBar* scroll_bar_;
71};
72
73/////////////////////////////////////////////////////////////////////////////
74// ScrollBarButton
75
76ScrollBarButton::ScrollBarButton(ButtonListener* listener, Type type)
77    : BaseScrollBarButton(listener),
78      type_(type) {
79  SetFocusable(false);
80  SetAccessibilityFocusable(false);
81}
82
83ScrollBarButton::~ScrollBarButton() {
84}
85
86gfx::Size ScrollBarButton::GetPreferredSize() const {
87  return GetNativeTheme()->GetPartSize(GetNativeThemePart(),
88                                       GetNativeThemeState(),
89                                       GetNativeThemeParams());
90}
91
92void ScrollBarButton::OnPaint(gfx::Canvas* canvas) {
93  gfx::Rect bounds(GetPreferredSize());
94  GetNativeTheme()->Paint(canvas->sk_canvas(), GetNativeThemePart(),
95                          GetNativeThemeState(), bounds,
96                          GetNativeThemeParams());
97}
98
99ui::NativeTheme::ExtraParams
100    ScrollBarButton::GetNativeThemeParams() const {
101  ui::NativeTheme::ExtraParams params;
102
103  switch (state_) {
104    case CustomButton::STATE_HOVERED:
105      params.scrollbar_arrow.is_hovering = true;
106      break;
107    default:
108      params.scrollbar_arrow.is_hovering = false;
109      break;
110  }
111
112  return params;
113}
114
115ui::NativeTheme::Part
116    ScrollBarButton::GetNativeThemePart() const {
117  switch (type_) {
118    case UP:
119      return ui::NativeTheme::kScrollbarUpArrow;
120    case DOWN:
121      return ui::NativeTheme::kScrollbarDownArrow;
122    case LEFT:
123      return ui::NativeTheme::kScrollbarLeftArrow;
124    case RIGHT:
125      return ui::NativeTheme::kScrollbarRightArrow;
126    default:
127      return ui::NativeTheme::kScrollbarUpArrow;
128  }
129}
130
131ui::NativeTheme::State
132    ScrollBarButton::GetNativeThemeState() const {
133  ui::NativeTheme::State state;
134
135  switch (state_) {
136    case CustomButton::STATE_HOVERED:
137      state = ui::NativeTheme::kHovered;
138      break;
139    case CustomButton::STATE_PRESSED:
140      state = ui::NativeTheme::kPressed;
141      break;
142    case CustomButton::STATE_DISABLED:
143      state = ui::NativeTheme::kDisabled;
144      break;
145    case CustomButton::STATE_NORMAL:
146    default:
147      state = ui::NativeTheme::kNormal;
148      break;
149  }
150
151  return state;
152}
153
154/////////////////////////////////////////////////////////////////////////////
155// ScrollBarThumb
156
157ScrollBarThumb::ScrollBarThumb(BaseScrollBar* scroll_bar)
158    : BaseScrollBarThumb(scroll_bar),
159      scroll_bar_(scroll_bar) {
160  SetFocusable(false);
161  SetAccessibilityFocusable(false);
162}
163
164ScrollBarThumb::~ScrollBarThumb() {
165}
166
167gfx::Size ScrollBarThumb::GetPreferredSize() const {
168  return GetNativeTheme()->GetPartSize(GetNativeThemePart(),
169                                       GetNativeThemeState(),
170                                       GetNativeThemeParams());
171}
172
173void ScrollBarThumb::OnPaint(gfx::Canvas* canvas) {
174  const gfx::Rect local_bounds(GetLocalBounds());
175  const ui::NativeTheme::State theme_state = GetNativeThemeState();
176  const ui::NativeTheme::ExtraParams extra_params(GetNativeThemeParams());
177  GetNativeTheme()->Paint(canvas->sk_canvas(),
178                          GetNativeThemePart(),
179                          theme_state,
180                          local_bounds,
181                          extra_params);
182  const ui::NativeTheme::Part gripper_part = scroll_bar_->IsHorizontal() ?
183      ui::NativeTheme::kScrollbarHorizontalGripper :
184      ui::NativeTheme::kScrollbarVerticalGripper;
185  GetNativeTheme()->Paint(canvas->sk_canvas(), gripper_part, theme_state,
186                          local_bounds, extra_params);
187}
188
189ui::NativeTheme::ExtraParams ScrollBarThumb::GetNativeThemeParams() const {
190  // This gives the behavior we want.
191  ui::NativeTheme::ExtraParams params;
192  params.scrollbar_thumb.is_hovering =
193      (GetState() != CustomButton::STATE_HOVERED);
194  return params;
195}
196
197ui::NativeTheme::Part ScrollBarThumb::GetNativeThemePart() const {
198  if (scroll_bar_->IsHorizontal())
199    return ui::NativeTheme::kScrollbarHorizontalThumb;
200  return ui::NativeTheme::kScrollbarVerticalThumb;
201}
202
203ui::NativeTheme::State ScrollBarThumb::GetNativeThemeState() const {
204  ui::NativeTheme::State state;
205
206  switch (GetState()) {
207    case CustomButton::STATE_HOVERED:
208      state = ui::NativeTheme::kHovered;
209      break;
210    case CustomButton::STATE_PRESSED:
211      state = ui::NativeTheme::kPressed;
212      break;
213    case CustomButton::STATE_DISABLED:
214      state = ui::NativeTheme::kDisabled;
215      break;
216    case CustomButton::STATE_NORMAL:
217    default:
218      state = ui::NativeTheme::kNormal;
219      break;
220  }
221
222  return state;
223}
224
225}  // namespace
226
227////////////////////////////////////////////////////////////////////////////////
228// NativeScrollBarViews, public:
229
230const char NativeScrollBarViews::kViewClassName[] = "NativeScrollBarViews";
231
232NativeScrollBarViews::NativeScrollBarViews(NativeScrollBar* scroll_bar)
233    : BaseScrollBar(scroll_bar->IsHorizontal(),
234                    new ScrollBarThumb(this)),
235      native_scroll_bar_(scroll_bar) {
236  set_controller(native_scroll_bar_->controller());
237
238  if (native_scroll_bar_->IsHorizontal()) {
239    prev_button_ = new ScrollBarButton(this, ScrollBarButton::LEFT);
240    next_button_ = new ScrollBarButton(this, ScrollBarButton::RIGHT);
241
242    part_ = ui::NativeTheme::kScrollbarHorizontalTrack;
243  } else {
244    prev_button_ = new ScrollBarButton(this, ScrollBarButton::UP);
245    next_button_ = new ScrollBarButton(this, ScrollBarButton::DOWN);
246
247    part_ = ui::NativeTheme::kScrollbarVerticalTrack;
248  }
249
250  state_ = ui::NativeTheme::kNormal;
251
252  AddChildView(prev_button_);
253  AddChildView(next_button_);
254
255  prev_button_->set_context_menu_controller(this);
256  next_button_->set_context_menu_controller(this);
257}
258
259NativeScrollBarViews::~NativeScrollBarViews() {
260}
261
262////////////////////////////////////////////////////////////////////////////////
263// NativeScrollBarViews, View overrides:
264
265void NativeScrollBarViews::Layout() {
266  gfx::Size size = prev_button_->GetPreferredSize();
267  prev_button_->SetBounds(0, 0, size.width(), size.height());
268
269  if (native_scroll_bar_->IsHorizontal()) {
270    next_button_->SetBounds(width() - size.width(), 0,
271                            size.width(), size.height());
272  } else {
273    next_button_->SetBounds(0, height() - size.height(),
274                            size.width(), size.height());
275  }
276
277  GetThumb()->SetBoundsRect(GetTrackBounds());
278}
279
280void NativeScrollBarViews::OnPaint(gfx::Canvas* canvas) {
281  gfx::Rect bounds = GetTrackBounds();
282
283  if (bounds.IsEmpty())
284    return;
285
286  params_.scrollbar_track.track_x = bounds.x();
287  params_.scrollbar_track.track_y = bounds.y();
288  params_.scrollbar_track.track_width = bounds.width();
289  params_.scrollbar_track.track_height = bounds.height();
290  params_.scrollbar_track.classic_state = 0;
291
292  GetNativeTheme()->Paint(canvas->sk_canvas(), part_, state_, bounds, params_);
293}
294
295gfx::Size NativeScrollBarViews::GetPreferredSize() const {
296  const ui::NativeTheme* theme = native_scroll_bar_->GetNativeTheme();
297  if (native_scroll_bar_->IsHorizontal())
298    return gfx::Size(0, GetHorizontalScrollBarHeight(theme));
299  return gfx::Size(GetVerticalScrollBarWidth(theme), 0);
300}
301
302const char* NativeScrollBarViews::GetClassName() const {
303  return kViewClassName;
304}
305
306int NativeScrollBarViews::GetLayoutSize() const {
307  gfx::Size size = prev_button_->GetPreferredSize();
308  return IsHorizontal() ? size.height() : size.width();
309}
310
311void NativeScrollBarViews::ScrollToPosition(int position) {
312  controller()->ScrollToPosition(native_scroll_bar_, position);
313}
314
315int NativeScrollBarViews::GetScrollIncrement(bool is_page, bool is_positive) {
316  return controller()->GetScrollIncrement(native_scroll_bar_,
317                                          is_page,
318                                          is_positive);
319}
320
321//////////////////////////////////////////////////////////////////////////////
322// BaseButton::ButtonListener overrides:
323
324void NativeScrollBarViews::ButtonPressed(Button* sender,
325                                         const ui::Event& event) {
326  if (sender == prev_button_) {
327    ScrollByAmount(SCROLL_PREV_LINE);
328  } else if (sender == next_button_) {
329    ScrollByAmount(SCROLL_NEXT_LINE);
330  }
331}
332
333////////////////////////////////////////////////////////////////////////////////
334// NativeScrollBarViews, NativeScrollBarWrapper overrides:
335
336int NativeScrollBarViews::GetPosition() const {
337  return BaseScrollBar::GetPosition();
338}
339
340View* NativeScrollBarViews::GetView() {
341  return this;
342}
343
344void NativeScrollBarViews::Update(int viewport_size,
345                                  int content_size,
346                                  int current_pos) {
347  BaseScrollBar::Update(viewport_size, content_size, current_pos);
348}
349
350////////////////////////////////////////////////////////////////////////////////
351// NativeScrollBarViews, private:
352
353gfx::Rect NativeScrollBarViews::GetTrackBounds() const {
354  gfx::Rect bounds = GetLocalBounds();
355  gfx::Size size = prev_button_->GetPreferredSize();
356  BaseScrollBarThumb* thumb = GetThumb();
357
358  if (native_scroll_bar_->IsHorizontal()) {
359    bounds.set_x(bounds.x() + size.width());
360    bounds.set_width(std::max(0, bounds.width() - 2 * size.width()));
361    bounds.set_height(thumb->GetPreferredSize().height());
362  } else {
363    bounds.set_y(bounds.y() + size.height());
364    bounds.set_height(std::max(0, bounds.height() - 2 * size.height()));
365    bounds.set_width(thumb->GetPreferredSize().width());
366  }
367
368  return bounds;
369}
370
371////////////////////////////////////////////////////////////////////////////////
372// NativewScrollBarWrapper, public:
373
374// static
375NativeScrollBarWrapper* NativeScrollBarWrapper::CreateWrapper(
376    NativeScrollBar* scroll_bar) {
377  return new NativeScrollBarViews(scroll_bar);
378}
379
380// static
381int NativeScrollBarWrapper::GetHorizontalScrollBarHeight(
382    const ui::NativeTheme* theme) {
383  if (!theme)
384    theme = ui::NativeTheme::instance();
385  ui::NativeTheme::ExtraParams button_params;
386  button_params.scrollbar_arrow.is_hovering = false;
387  gfx::Size button_size = theme->GetPartSize(
388      ui::NativeTheme::kScrollbarLeftArrow,
389      ui::NativeTheme::kNormal,
390      button_params);
391
392  ui::NativeTheme::ExtraParams thumb_params;
393  thumb_params.scrollbar_thumb.is_hovering = false;
394  gfx::Size track_size = theme->GetPartSize(
395      ui::NativeTheme::kScrollbarHorizontalThumb,
396      ui::NativeTheme::kNormal,
397      thumb_params);
398
399  return std::max(track_size.height(), button_size.height());
400}
401
402// static
403int NativeScrollBarWrapper::GetVerticalScrollBarWidth(
404    const ui::NativeTheme* theme) {
405  if (!theme)
406    theme = ui::NativeTheme::instance();
407  ui::NativeTheme::ExtraParams button_params;
408  button_params.scrollbar_arrow.is_hovering = false;
409  gfx::Size button_size = theme->GetPartSize(
410      ui::NativeTheme::kScrollbarUpArrow,
411      ui::NativeTheme::kNormal,
412      button_params);
413
414  ui::NativeTheme::ExtraParams thumb_params;
415  thumb_params.scrollbar_thumb.is_hovering = false;
416  gfx::Size track_size = theme->GetPartSize(
417      ui::NativeTheme::kScrollbarVerticalThumb,
418      ui::NativeTheme::kNormal,
419      thumb_params);
420
421  return std::max(track_size.width(), button_size.width());
422}
423
424}  // namespace views
425