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
18 namespace views {
19
20 namespace {
21
22 // Wrapper for the scroll buttons.
23 class 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() OVERRIDE;
GetClassName() const36 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
52 class ScrollBarThumb : public BaseScrollBarThumb {
53 public:
54 explicit ScrollBarThumb(BaseScrollBar* scroll_bar);
55 virtual ~ScrollBarThumb();
56
57 virtual gfx::Size GetPreferredSize() OVERRIDE;
GetClassName() const58 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
ScrollBarButton(ButtonListener * listener,Type type)76 ScrollBarButton::ScrollBarButton(ButtonListener* listener, Type type)
77 : BaseScrollBarButton(listener),
78 type_(type) {
79 SetFocusable(false);
80 SetAccessibilityFocusable(false);
81 }
82
~ScrollBarButton()83 ScrollBarButton::~ScrollBarButton() {
84 }
85
GetPreferredSize()86 gfx::Size ScrollBarButton::GetPreferredSize() {
87 return GetNativeTheme()->GetPartSize(GetNativeThemePart(),
88 GetNativeThemeState(),
89 GetNativeThemeParams());
90 }
91
OnPaint(gfx::Canvas * canvas)92 void ScrollBarButton::OnPaint(gfx::Canvas* canvas) {
93 gfx::Rect bounds(GetPreferredSize());
94 GetNativeTheme()->Paint(canvas->sk_canvas(), GetNativeThemePart(),
95 GetNativeThemeState(), bounds,
96 GetNativeThemeParams());
97 }
98
99 ui::NativeTheme::ExtraParams
GetNativeThemeParams() const100 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
115 ui::NativeTheme::Part
GetNativeThemePart() const116 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
131 ui::NativeTheme::State
GetNativeThemeState() const132 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
ScrollBarThumb(BaseScrollBar * scroll_bar)157 ScrollBarThumb::ScrollBarThumb(BaseScrollBar* scroll_bar)
158 : BaseScrollBarThumb(scroll_bar),
159 scroll_bar_(scroll_bar) {
160 SetFocusable(false);
161 SetAccessibilityFocusable(false);
162 }
163
~ScrollBarThumb()164 ScrollBarThumb::~ScrollBarThumb() {
165 }
166
GetPreferredSize()167 gfx::Size ScrollBarThumb::GetPreferredSize() {
168 return GetNativeTheme()->GetPartSize(GetNativeThemePart(),
169 GetNativeThemeState(),
170 GetNativeThemeParams());
171 }
172
OnPaint(gfx::Canvas * canvas)173 void 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
GetNativeThemeParams() const189 ui::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
GetNativeThemePart() const197 ui::NativeTheme::Part ScrollBarThumb::GetNativeThemePart() const {
198 if (scroll_bar_->IsHorizontal())
199 return ui::NativeTheme::kScrollbarHorizontalThumb;
200 return ui::NativeTheme::kScrollbarVerticalThumb;
201 }
202
GetNativeThemeState() const203 ui::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
230 const char NativeScrollBarViews::kViewClassName[] = "NativeScrollBarViews";
231
NativeScrollBarViews(NativeScrollBar * scroll_bar)232 NativeScrollBarViews::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
~NativeScrollBarViews()259 NativeScrollBarViews::~NativeScrollBarViews() {
260 }
261
262 ////////////////////////////////////////////////////////////////////////////////
263 // NativeScrollBarViews, View overrides:
264
Layout()265 void 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
OnPaint(gfx::Canvas * canvas)280 void 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
GetPreferredSize()295 gfx::Size NativeScrollBarViews::GetPreferredSize() {
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
GetClassName() const302 const char* NativeScrollBarViews::GetClassName() const {
303 return kViewClassName;
304 }
305
GetLayoutSize() const306 int NativeScrollBarViews::GetLayoutSize() const {
307 gfx::Size size = prev_button_->GetPreferredSize();
308 return IsHorizontal() ? size.height() : size.width();
309 }
310
ScrollToPosition(int position)311 void NativeScrollBarViews::ScrollToPosition(int position) {
312 controller()->ScrollToPosition(native_scroll_bar_, position);
313 }
314
GetScrollIncrement(bool is_page,bool is_positive)315 int 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
ButtonPressed(Button * sender,const ui::Event & event)324 void 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
GetPosition() const336 int NativeScrollBarViews::GetPosition() const {
337 return BaseScrollBar::GetPosition();
338 }
339
GetView()340 View* NativeScrollBarViews::GetView() {
341 return this;
342 }
343
Update(int viewport_size,int content_size,int current_pos)344 void 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
GetTrackBounds() const353 gfx::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
CreateWrapper(NativeScrollBar * scroll_bar)375 NativeScrollBarWrapper* NativeScrollBarWrapper::CreateWrapper(
376 NativeScrollBar* scroll_bar) {
377 return new NativeScrollBarViews(scroll_bar);
378 }
379
380 // static
GetHorizontalScrollBarHeight(const ui::NativeTheme * theme)381 int 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
GetVerticalScrollBarWidth(const ui::NativeTheme * theme)403 int 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