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.
5 #include "ui/native_theme/native_theme_base.h"
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "third_party/skia/include/core/SkPaint.h"
13 #include "third_party/skia/include/core/SkPath.h"
14 #include "third_party/skia/include/effects/SkGradientShader.h"
15 #include "ui/base/layout.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/base/ui_base_switches.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_utils.h"
20 #include "ui/gfx/geometry/rect.h"
21 #include "ui/gfx/geometry/size.h"
22 #include "ui/gfx/image/image_skia.h"
23 #include "ui/gfx/skia_util.h"
24 #include "ui/native_theme/common_theme.h"
25 #include "ui/resources/grit/ui_resources.h"
29 // These are the default dimensions of radio buttons and checkboxes.
30 const int kCheckboxAndRadioWidth
= 13;
31 const int kCheckboxAndRadioHeight
= 13;
33 // These sizes match the sizes in Chromium Win.
34 const int kSliderThumbWidth
= 11;
35 const int kSliderThumbHeight
= 21;
37 const SkColor kSliderTrackBackgroundColor
=
38 SkColorSetRGB(0xe3, 0xdd, 0xd8);
39 const SkColor kSliderThumbLightGrey
= SkColorSetRGB(0xf4, 0xf2, 0xef);
40 const SkColor kSliderThumbDarkGrey
= SkColorSetRGB(0xea, 0xe5, 0xe0);
41 const SkColor kSliderThumbBorderDarkGrey
=
42 SkColorSetRGB(0x9d, 0x96, 0x8e);
44 const SkColor kTextBorderColor
= SkColorSetRGB(0xa9, 0xa9, 0xa9);
46 const SkColor kMenuPopupBackgroundColor
= SkColorSetRGB(210, 225, 246);
48 const unsigned int kDefaultScrollbarWidth
= 15;
49 const unsigned int kDefaultScrollbarButtonLength
= 14;
51 const SkColor kCheckboxTinyColor
= SK_ColorGRAY
;
52 const SkColor kCheckboxShadowColor
= SkColorSetARGB(0x15, 0, 0, 0);
53 const SkColor kCheckboxShadowHoveredColor
= SkColorSetARGB(0x1F, 0, 0, 0);
54 const SkColor kCheckboxShadowDisabledColor
= SkColorSetARGB(0, 0, 0, 0);
55 const SkColor kCheckboxGradientColors
[] = {
56 SkColorSetRGB(0xed, 0xed, 0xed),
57 SkColorSetRGB(0xde, 0xde, 0xde) };
58 const SkColor kCheckboxGradientPressedColors
[] = {
59 SkColorSetRGB(0xe7, 0xe7, 0xe7),
60 SkColorSetRGB(0xd7, 0xd7, 0xd7) };
61 const SkColor kCheckboxGradientHoveredColors
[] = {
62 SkColorSetRGB(0xf0, 0xf0, 0xf0),
63 SkColorSetRGB(0xe0, 0xe0, 0xe0) };
64 const SkColor kCheckboxGradientDisabledColors
[] = {
65 SkColorSetARGB(0x80, 0xed, 0xed, 0xed),
66 SkColorSetARGB(0x80, 0xde, 0xde, 0xde) };
67 const SkColor kCheckboxBorderColor
= SkColorSetARGB(0x40, 0, 0, 0);
68 const SkColor kCheckboxBorderHoveredColor
= SkColorSetARGB(0x4D, 0, 0, 0);
69 const SkColor kCheckboxBorderDisabledColor
= SkColorSetARGB(0x20, 0, 0, 0);
70 const SkColor kCheckboxStrokeColor
= SkColorSetARGB(0xB3, 0, 0, 0);
71 const SkColor kCheckboxStrokeDisabledColor
= SkColorSetARGB(0x59, 0, 0, 0);
72 const SkColor kRadioDotColor
= SkColorSetRGB(0x66, 0x66, 0x66);
73 const SkColor kRadioDotDisabledColor
= SkColorSetARGB(0x80, 0x66, 0x66, 0x66);
75 // Get lightness adjusted color.
76 SkColor
BrightenColor(const color_utils::HSL
& hsl
, SkAlpha alpha
,
77 double lightness_amount
) {
78 color_utils::HSL adjusted
= hsl
;
79 adjusted
.l
+= lightness_amount
;
85 return color_utils::HSLToSkColor(adjusted
, alpha
);
92 gfx::Size
NativeThemeBase::GetPartSize(Part part
,
94 const ExtraParams
& extra
) const {
95 gfx::Size size
= CommonThemeGetPartSize(part
, state
, extra
);
100 // Please keep these in the order of NativeTheme::Part.
102 return gfx::Size(kCheckboxAndRadioWidth
, kCheckboxAndRadioHeight
);
103 case kInnerSpinButton
:
104 return gfx::Size(scrollbar_width_
, 0);
106 return gfx::Size(); // No default size.
108 case kMenuCheckBackground
:
109 case kMenuPopupArrow
:
112 case kMenuPopupBackground
:
113 return gfx::Size(); // No default size.
114 case kMenuPopupGutter
:
115 case kMenuPopupSeparator
:
118 case kMenuItemBackground
:
121 return gfx::Size(); // No default size.
123 return gfx::Size(kCheckboxAndRadioWidth
, kCheckboxAndRadioHeight
);
124 case kScrollbarDownArrow
:
125 case kScrollbarUpArrow
:
126 return gfx::Size(scrollbar_width_
, scrollbar_button_length_
);
127 case kScrollbarLeftArrow
:
128 case kScrollbarRightArrow
:
129 return gfx::Size(scrollbar_button_length_
, scrollbar_width_
);
130 case kScrollbarHorizontalThumb
:
131 // This matches Firefox on Linux.
132 return gfx::Size(2 * scrollbar_width_
, scrollbar_width_
);
133 case kScrollbarVerticalThumb
:
134 // This matches Firefox on Linux.
135 return gfx::Size(scrollbar_width_
, 2 * scrollbar_width_
);
136 case kScrollbarHorizontalTrack
:
137 return gfx::Size(0, scrollbar_width_
);
138 case kScrollbarVerticalTrack
:
139 return gfx::Size(scrollbar_width_
, 0);
140 case kScrollbarHorizontalGripper
:
141 case kScrollbarVerticalGripper
:
145 return gfx::Size(); // No default size.
147 // These sizes match the sizes in Chromium Win.
148 return gfx::Size(kSliderThumbWidth
, kSliderThumbHeight
);
149 case kTabPanelBackground
:
153 return gfx::Size(); // No default size.
156 case kWindowResizeGripper
:
160 NOTREACHED() << "Unknown theme part: " << part
;
166 void NativeThemeBase::PaintStateTransition(SkCanvas
* canvas
,
171 const gfx::Rect
& rect
) const {
175 // Currently state transition is animation only working for overlay scrollbars
176 // on Aura platforms.
178 case kScrollbarHorizontalThumb
:
179 case kScrollbarVerticalThumb
:
180 PaintScrollbarThumbStateTransition(
181 canvas
, startState
, endState
, progress
, rect
);
184 NOTREACHED() << "Does not support state transition for this part:"
191 void NativeThemeBase::Paint(SkCanvas
* canvas
,
194 const gfx::Rect
& rect
,
195 const ExtraParams
& extra
) const {
200 // Please keep these in the order of NativeTheme::Part.
202 CommonThemePaintComboboxArrow(canvas
, rect
);
205 PaintCheckbox(canvas
, state
, rect
, extra
.button
);
207 case kInnerSpinButton
:
208 PaintInnerSpinButton(canvas
, state
, rect
, extra
.inner_spin
);
211 PaintMenuList(canvas
, state
, rect
, extra
.menu_list
);
214 case kMenuCheckBackground
:
215 case kMenuPopupArrow
:
218 case kMenuPopupBackground
:
219 PaintMenuPopupBackground(canvas
, rect
.size(), extra
.menu_background
);
221 case kMenuPopupGutter
:
222 case kMenuPopupSeparator
:
225 case kMenuItemBackground
:
226 PaintMenuItemBackground(canvas
, state
, rect
, extra
.menu_list
);
229 PaintProgressBar(canvas
, state
, rect
, extra
.progress_bar
);
232 PaintButton(canvas
, state
, rect
, extra
.button
);
235 PaintRadio(canvas
, state
, rect
, extra
.button
);
237 case kScrollbarDownArrow
:
238 case kScrollbarUpArrow
:
239 case kScrollbarLeftArrow
:
240 case kScrollbarRightArrow
:
241 if (scrollbar_button_length_
> 0)
242 PaintArrowButton(canvas
, rect
, part
, state
);
244 case kScrollbarHorizontalThumb
:
245 case kScrollbarVerticalThumb
:
246 PaintScrollbarThumb(canvas
, part
, state
, rect
);
248 case kScrollbarHorizontalTrack
:
249 case kScrollbarVerticalTrack
:
250 PaintScrollbarTrack(canvas
, part
, state
, extra
.scrollbar_track
, rect
);
252 case kScrollbarHorizontalGripper
:
253 case kScrollbarVerticalGripper
:
254 // Invoked by views scrollbar code, don't care about for non-win
255 // implementations, so no NOTIMPLEMENTED.
257 case kScrollbarCorner
:
258 PaintScrollbarCorner(canvas
, state
, rect
);
261 PaintSliderTrack(canvas
, state
, rect
, extra
.slider
);
264 PaintSliderThumb(canvas
, state
, rect
, extra
.slider
);
266 case kTabPanelBackground
:
270 PaintTextField(canvas
, state
, rect
, extra
.text_field
);
274 case kWindowResizeGripper
:
278 NOTREACHED() << "Unknown theme part: " << part
;
283 NativeThemeBase::NativeThemeBase()
284 : scrollbar_width_(kDefaultScrollbarWidth
),
285 scrollbar_button_length_(kDefaultScrollbarButtonLength
) {
288 NativeThemeBase::~NativeThemeBase() {
291 void NativeThemeBase::PaintArrowButton(
293 const gfx::Rect
& rect
, Part direction
, State state
) const {
296 // Calculate button color.
297 SkScalar trackHSV
[3];
298 SkColorToHSV(track_color_
, trackHSV
);
299 SkColor buttonColor
= SaturateAndBrighten(trackHSV
, 0, 0.2f
);
300 SkColor backgroundColor
= buttonColor
;
301 if (state
== kPressed
) {
302 SkScalar buttonHSV
[3];
303 SkColorToHSV(buttonColor
, buttonHSV
);
304 buttonColor
= SaturateAndBrighten(buttonHSV
, 0, -0.1f
);
305 } else if (state
== kHovered
) {
306 SkScalar buttonHSV
[3];
307 SkColorToHSV(buttonColor
, buttonHSV
);
308 buttonColor
= SaturateAndBrighten(buttonHSV
, 0, 0.05f
);
312 skrect
.set(rect
.x(), rect
.y(), rect
.x() + rect
.width(), rect
.y()
314 // Paint the background (the area visible behind the rounded corners).
315 paint
.setColor(backgroundColor
);
316 canvas
->drawIRect(skrect
, paint
);
318 // Paint the button's outline and fill the middle
321 case kScrollbarUpArrow
:
322 outline
.moveTo(rect
.x() + 0.5, rect
.y() + rect
.height() + 0.5);
323 outline
.rLineTo(0, -(rect
.height() - 2));
324 outline
.rLineTo(2, -2);
325 outline
.rLineTo(rect
.width() - 5, 0);
326 outline
.rLineTo(2, 2);
327 outline
.rLineTo(0, rect
.height() - 2);
329 case kScrollbarDownArrow
:
330 outline
.moveTo(rect
.x() + 0.5, rect
.y() - 0.5);
331 outline
.rLineTo(0, rect
.height() - 2);
332 outline
.rLineTo(2, 2);
333 outline
.rLineTo(rect
.width() - 5, 0);
334 outline
.rLineTo(2, -2);
335 outline
.rLineTo(0, -(rect
.height() - 2));
337 case kScrollbarRightArrow
:
338 outline
.moveTo(rect
.x() - 0.5, rect
.y() + 0.5);
339 outline
.rLineTo(rect
.width() - 2, 0);
340 outline
.rLineTo(2, 2);
341 outline
.rLineTo(0, rect
.height() - 5);
342 outline
.rLineTo(-2, 2);
343 outline
.rLineTo(-(rect
.width() - 2), 0);
345 case kScrollbarLeftArrow
:
346 outline
.moveTo(rect
.x() + rect
.width() + 0.5, rect
.y() + 0.5);
347 outline
.rLineTo(-(rect
.width() - 2), 0);
348 outline
.rLineTo(-2, 2);
349 outline
.rLineTo(0, rect
.height() - 5);
350 outline
.rLineTo(2, 2);
351 outline
.rLineTo(rect
.width() - 2, 0);
358 paint
.setStyle(SkPaint::kFill_Style
);
359 paint
.setColor(buttonColor
);
360 canvas
->drawPath(outline
, paint
);
362 paint
.setAntiAlias(true);
363 paint
.setStyle(SkPaint::kStroke_Style
);
364 SkScalar thumbHSV
[3];
365 SkColorToHSV(thumb_inactive_color_
, thumbHSV
);
366 paint
.setColor(OutlineColor(trackHSV
, thumbHSV
));
367 canvas
->drawPath(outline
, paint
);
369 PaintArrow(canvas
, rect
, direction
, GetArrowColor(state
));
372 void NativeThemeBase::PaintArrow(SkCanvas
* gc
,
373 const gfx::Rect
& rect
,
375 SkColor color
) const {
376 int width_middle
, length_middle
;
377 if (direction
== kScrollbarUpArrow
|| direction
== kScrollbarDownArrow
) {
378 width_middle
= rect
.width() / 2 + 1;
379 length_middle
= rect
.height() / 2 + 1;
381 length_middle
= rect
.width() / 2 + 1;
382 width_middle
= rect
.height() / 2 + 1;
386 paint
.setColor(color
);
387 paint
.setAntiAlias(false);
388 paint
.setStyle(SkPaint::kFill_Style
);
391 // The constants in this block of code are hand-tailored to produce good
392 // looking arrows without anti-aliasing.
394 case kScrollbarUpArrow
:
395 path
.moveTo(rect
.x() + width_middle
- 4, rect
.y() + length_middle
+ 2);
397 path
.rLineTo(-4, -4);
399 case kScrollbarDownArrow
:
400 path
.moveTo(rect
.x() + width_middle
- 4, rect
.y() + length_middle
- 3);
404 case kScrollbarRightArrow
:
405 path
.moveTo(rect
.x() + length_middle
- 3, rect
.y() + width_middle
- 4);
409 case kScrollbarLeftArrow
:
410 path
.moveTo(rect
.x() + length_middle
+ 1, rect
.y() + width_middle
- 5);
412 path
.rLineTo(-4, -4);
419 gc
->drawPath(path
, paint
);
422 void NativeThemeBase::PaintScrollbarTrack(SkCanvas
* canvas
,
425 const ScrollbarTrackExtraParams
& extra_params
,
426 const gfx::Rect
& rect
) const {
430 skrect
.set(rect
.x(), rect
.y(), rect
.right(), rect
.bottom());
431 SkScalar track_hsv
[3];
432 SkColorToHSV(track_color_
, track_hsv
);
433 paint
.setColor(SaturateAndBrighten(track_hsv
, 0, 0));
434 canvas
->drawIRect(skrect
, paint
);
436 SkScalar thumb_hsv
[3];
437 SkColorToHSV(thumb_inactive_color_
, thumb_hsv
);
439 paint
.setColor(OutlineColor(track_hsv
, thumb_hsv
));
440 DrawBox(canvas
, rect
, paint
);
443 void NativeThemeBase::PaintScrollbarThumb(SkCanvas
* canvas
,
446 const gfx::Rect
& rect
) const {
447 const bool hovered
= state
== kHovered
;
448 const int midx
= rect
.x() + rect
.width() / 2;
449 const int midy
= rect
.y() + rect
.height() / 2;
450 const bool vertical
= part
== kScrollbarVerticalThumb
;
453 SkColorToHSV(hovered
? thumb_active_color_
: thumb_inactive_color_
, thumb
);
456 paint
.setColor(SaturateAndBrighten(thumb
, 0, 0.02f
));
460 skrect
.set(rect
.x(), rect
.y(), midx
+ 1, rect
.y() + rect
.height());
462 skrect
.set(rect
.x(), rect
.y(), rect
.x() + rect
.width(), midy
+ 1);
464 canvas
->drawIRect(skrect
, paint
);
466 paint
.setColor(SaturateAndBrighten(thumb
, 0, -0.02f
));
470 midx
+ 1, rect
.y(), rect
.x() + rect
.width(), rect
.y() + rect
.height());
473 rect
.x(), midy
+ 1, rect
.x() + rect
.width(), rect
.y() + rect
.height());
476 canvas
->drawIRect(skrect
, paint
);
479 SkColorToHSV(track_color_
, track
);
480 paint
.setColor(OutlineColor(track
, thumb
));
481 DrawBox(canvas
, rect
, paint
);
483 if (rect
.height() > 10 && rect
.width() > 10) {
484 const int grippy_half_width
= 2;
485 const int inter_grippy_offset
= 3;
487 DrawHorizLine(canvas
,
488 midx
- grippy_half_width
,
489 midx
+ grippy_half_width
,
490 midy
- inter_grippy_offset
,
492 DrawHorizLine(canvas
,
493 midx
- grippy_half_width
,
494 midx
+ grippy_half_width
,
497 DrawHorizLine(canvas
,
498 midx
- grippy_half_width
,
499 midx
+ grippy_half_width
,
500 midy
+ inter_grippy_offset
,
504 midx
- inter_grippy_offset
,
505 midy
- grippy_half_width
,
506 midy
+ grippy_half_width
,
510 midy
- grippy_half_width
,
511 midy
+ grippy_half_width
,
514 midx
+ inter_grippy_offset
,
515 midy
- grippy_half_width
,
516 midy
+ grippy_half_width
,
522 void NativeThemeBase::PaintScrollbarCorner(SkCanvas
* canvas
,
524 const gfx::Rect
& rect
) const {
527 void NativeThemeBase::PaintCheckbox(SkCanvas
* canvas
,
529 const gfx::Rect
& rect
,
530 const ButtonExtraParams
& button
) const {
531 SkRect skrect
= PaintCheckboxRadioCommon(canvas
, state
, rect
,
533 if (!skrect
.isEmpty()) {
534 // Draw the checkmark / dash.
536 paint
.setAntiAlias(true);
537 paint
.setStyle(SkPaint::kStroke_Style
);
538 if (state
== kDisabled
)
539 paint
.setColor(kCheckboxStrokeDisabledColor
);
541 paint
.setColor(kCheckboxStrokeColor
);
542 if (button
.indeterminate
) {
544 dash
.moveTo(skrect
.x() + skrect
.width() * 0.16,
545 (skrect
.y() + skrect
.bottom()) / 2);
546 dash
.rLineTo(skrect
.width() * 0.68, 0);
547 paint
.setStrokeWidth(SkFloatToScalar(skrect
.height() * 0.2));
548 canvas
->drawPath(dash
, paint
);
549 } else if (button
.checked
) {
551 check
.moveTo(skrect
.x() + skrect
.width() * 0.2,
552 skrect
.y() + skrect
.height() * 0.5);
553 check
.rLineTo(skrect
.width() * 0.2, skrect
.height() * 0.2);
554 paint
.setStrokeWidth(SkFloatToScalar(skrect
.height() * 0.23));
555 check
.lineTo(skrect
.right() - skrect
.width() * 0.2,
556 skrect
.y() + skrect
.height() * 0.2);
557 canvas
->drawPath(check
, paint
);
562 // Draws the common elements of checkboxes and radio buttons.
563 // Returns the rectangle within which any additional decorations should be
564 // drawn, or empty if none.
565 SkRect
NativeThemeBase::PaintCheckboxRadioCommon(
568 const gfx::Rect
& rect
,
569 const SkScalar borderRadius
) const {
571 SkRect skrect
= gfx::RectToSkRect(rect
);
573 // Use the largest square that fits inside the provided rectangle.
574 // No other browser seems to support non-square widget, so accidentally
575 // having non-square sizes is common (eg. amazon and webkit dev tools).
576 if (skrect
.width() != skrect
.height()) {
577 SkScalar size
= SkMinScalar(skrect
.width(), skrect
.height());
578 skrect
.inset((skrect
.width() - size
) / 2, (skrect
.height() - size
) / 2);
581 // If the rectangle is too small then paint only a rectangle. We don't want
582 // to have to worry about '- 1' and '+ 1' calculations below having overflow
584 if (skrect
.width() <= 2) {
586 paint
.setColor(kCheckboxTinyColor
);
587 paint
.setStyle(SkPaint::kFill_Style
);
588 canvas
->drawRect(skrect
, paint
);
589 // Too small to draw anything more.
590 return SkRect::MakeEmpty();
593 // Make room for the drop shadow.
594 skrect
.iset(skrect
.x(), skrect
.y(), skrect
.right() - 1, skrect
.bottom() - 1);
596 // Draw the drop shadow below the widget.
597 if (state
!= kPressed
) {
599 paint
.setAntiAlias(true);
600 SkRect shadowRect
= skrect
;
601 shadowRect
.offset(0, 1);
602 if (state
== kDisabled
)
603 paint
.setColor(kCheckboxShadowDisabledColor
);
604 else if (state
== kHovered
)
605 paint
.setColor(kCheckboxShadowHoveredColor
);
607 paint
.setColor(kCheckboxShadowColor
);
608 paint
.setStyle(SkPaint::kFill_Style
);
609 canvas
->drawRoundRect(shadowRect
, borderRadius
, borderRadius
, paint
);
612 // Draw the gradient-filled rectangle
613 SkPoint gradient_bounds
[3];
614 gradient_bounds
[0].set(skrect
.x(), skrect
.y());
615 gradient_bounds
[1].set(skrect
.x(), skrect
.y() + skrect
.height() * 0.38);
616 gradient_bounds
[2].set(skrect
.x(), skrect
.bottom());
617 const SkColor
* startEndColors
;
618 if (state
== kPressed
)
619 startEndColors
= kCheckboxGradientPressedColors
;
620 else if (state
== kHovered
)
621 startEndColors
= kCheckboxGradientHoveredColors
;
622 else if (state
== kDisabled
)
623 startEndColors
= kCheckboxGradientDisabledColors
;
625 startEndColors
= kCheckboxGradientColors
;
626 SkColor colors
[3] = {startEndColors
[0], startEndColors
[0], startEndColors
[1]};
627 skia::RefPtr
<SkShader
> shader
= skia::AdoptRef(
628 SkGradientShader::CreateLinear(
629 gradient_bounds
, colors
, NULL
, 3, SkShader::kClamp_TileMode
));
631 paint
.setAntiAlias(true);
632 paint
.setShader(shader
.get());
633 paint
.setStyle(SkPaint::kFill_Style
);
634 canvas
->drawRoundRect(skrect
, borderRadius
, borderRadius
, paint
);
635 paint
.setShader(NULL
);
638 if (state
== kHovered
)
639 paint
.setColor(kCheckboxBorderHoveredColor
);
640 else if (state
== kDisabled
)
641 paint
.setColor(kCheckboxBorderDisabledColor
);
643 paint
.setColor(kCheckboxBorderColor
);
644 paint
.setStyle(SkPaint::kStroke_Style
);
645 paint
.setStrokeWidth(SkIntToScalar(1));
646 skrect
.inset(SkFloatToScalar(.5f
), SkFloatToScalar(.5f
));
647 canvas
->drawRoundRect(skrect
, borderRadius
, borderRadius
, paint
);
649 // Return the rectangle excluding the drop shadow for drawing any additional
654 void NativeThemeBase::PaintRadio(SkCanvas
* canvas
,
656 const gfx::Rect
& rect
,
657 const ButtonExtraParams
& button
) const {
659 // Most of a radio button is the same as a checkbox, except the the rounded
660 // square is a circle (i.e. border radius >= 100%).
661 const SkScalar radius
= SkFloatToScalar(
662 static_cast<float>(std::max(rect
.width(), rect
.height())) / 2);
663 SkRect skrect
= PaintCheckboxRadioCommon(canvas
, state
, rect
, radius
);
664 if (!skrect
.isEmpty() && button
.checked
) {
667 paint
.setAntiAlias(true);
668 paint
.setStyle(SkPaint::kFill_Style
);
669 if (state
== kDisabled
)
670 paint
.setColor(kRadioDotDisabledColor
);
672 paint
.setColor(kRadioDotColor
);
673 skrect
.inset(skrect
.width() * 0.25, skrect
.height() * 0.25);
674 // Use drawRoundedRect instead of drawOval to be completely consistent
675 // with the border in PaintCheckboxRadioNewCommon.
676 canvas
->drawRoundRect(skrect
, radius
, radius
, paint
);
680 void NativeThemeBase::PaintButton(SkCanvas
* canvas
,
682 const gfx::Rect
& rect
,
683 const ButtonExtraParams
& button
) const {
685 const int kRight
= rect
.right();
686 const int kBottom
= rect
.bottom();
687 SkRect skrect
= SkRect::MakeLTRB(rect
.x(), rect
.y(), kRight
, kBottom
);
688 SkColor base_color
= button
.background_color
;
690 color_utils::HSL base_hsl
;
691 color_utils::SkColorToHSL(base_color
, &base_hsl
);
693 // Our standard gradient is from 0xdd to 0xf8. This is the amount of
694 // increased luminance between those values.
695 SkColor
light_color(BrightenColor(base_hsl
, SkColorGetA(base_color
), 0.105));
697 // If the button is too small, fallback to drawing a single, solid color
698 if (rect
.width() < 5 || rect
.height() < 5) {
699 paint
.setColor(base_color
);
700 canvas
->drawRect(skrect
, paint
);
704 paint
.setColor(SK_ColorBLACK
);
705 const int kLightEnd
= state
== kPressed
? 1 : 0;
706 const int kDarkEnd
= !kLightEnd
;
707 SkPoint gradient_bounds
[2];
708 gradient_bounds
[kLightEnd
].iset(rect
.x(), rect
.y());
709 gradient_bounds
[kDarkEnd
].iset(rect
.x(), kBottom
- 1);
711 colors
[0] = light_color
;
712 colors
[1] = base_color
;
714 skia::RefPtr
<SkShader
> shader
= skia::AdoptRef(
715 SkGradientShader::CreateLinear(
716 gradient_bounds
, colors
, NULL
, 2, SkShader::kClamp_TileMode
));
717 paint
.setStyle(SkPaint::kFill_Style
);
718 paint
.setAntiAlias(true);
719 paint
.setShader(shader
.get());
721 canvas
->drawRoundRect(skrect
, SkIntToScalar(1), SkIntToScalar(1), paint
);
722 paint
.setShader(NULL
);
724 if (button
.has_border
) {
725 int border_alpha
= state
== kHovered
? 0x80 : 0x55;
726 if (button
.is_focused
) {
728 paint
.setColor(GetSystemColor(kColorId_FocusedBorderColor
));
730 paint
.setStyle(SkPaint::kStroke_Style
);
731 paint
.setStrokeWidth(SkIntToScalar(1));
732 paint
.setAlpha(border_alpha
);
733 skrect
.inset(SkFloatToScalar(.5f
), SkFloatToScalar(.5f
));
734 canvas
->drawRoundRect(skrect
, SkIntToScalar(1), SkIntToScalar(1), paint
);
738 void NativeThemeBase::PaintTextField(SkCanvas
* canvas
,
740 const gfx::Rect
& rect
,
741 const TextFieldExtraParams
& text
) const {
743 bounds
.set(rect
.x(), rect
.y(), rect
.right() - 1, rect
.bottom() - 1);
746 fill_paint
.setStyle(SkPaint::kFill_Style
);
747 fill_paint
.setColor(text
.background_color
);
748 canvas
->drawRect(bounds
, fill_paint
);
750 // Text INPUT, listbox SELECT, and TEXTAREA have consistent borders.
751 // border: 1px solid #a9a9a9
752 SkPaint stroke_paint
;
753 stroke_paint
.setStyle(SkPaint::kStroke_Style
);
754 stroke_paint
.setColor(kTextBorderColor
);
755 canvas
->drawRect(bounds
, stroke_paint
);
758 void NativeThemeBase::PaintMenuList(
761 const gfx::Rect
& rect
,
762 const MenuListExtraParams
& menu_list
) const {
763 // If a border radius is specified, we let the WebCore paint the background
764 // and the border of the control.
765 if (!menu_list
.has_border_radius
) {
766 ButtonExtraParams button
= { 0 };
767 button
.background_color
= menu_list
.background_color
;
768 button
.has_border
= menu_list
.has_border
;
769 PaintButton(canvas
, state
, rect
, button
);
773 paint
.setColor(SK_ColorBLACK
);
774 paint
.setAntiAlias(true);
775 paint
.setStyle(SkPaint::kFill_Style
);
777 static const int kArrowWidth
= 6;
778 static const int kArrowHeight
= 6;
782 menu_list
.arrow_y
- (kArrowHeight
/ 2),
786 // Constrain to the paint rect.
787 arrow
.Intersect(rect
);
790 path
.moveTo(arrow
.x(), arrow
.y());
791 path
.lineTo(arrow
.right(), arrow
.y());
792 path
.lineTo(arrow
.x() + arrow
.width() / 2, arrow
.bottom());
794 canvas
->drawPath(path
, paint
);
797 void NativeThemeBase::PaintMenuPopupBackground(
799 const gfx::Size
& size
,
800 const MenuBackgroundExtraParams
& menu_background
) const {
801 canvas
->drawColor(kMenuPopupBackgroundColor
, SkXfermode::kSrc_Mode
);
804 void NativeThemeBase::PaintMenuItemBackground(
807 const gfx::Rect
& rect
,
808 const MenuListExtraParams
& menu_list
) const {
809 // By default don't draw anything over the normal background.
812 void NativeThemeBase::PaintSliderTrack(SkCanvas
* canvas
,
814 const gfx::Rect
& rect
,
815 const SliderExtraParams
& slider
) const {
816 const int kMidX
= rect
.x() + rect
.width() / 2;
817 const int kMidY
= rect
.y() + rect
.height() / 2;
820 paint
.setColor(kSliderTrackBackgroundColor
);
823 if (slider
.vertical
) {
824 skrect
.set(std::max(rect
.x(), kMidX
- 2),
826 std::min(rect
.right(), kMidX
+ 2),
830 std::max(rect
.y(), kMidY
- 2),
832 std::min(rect
.bottom(), kMidY
+ 2));
834 canvas
->drawRect(skrect
, paint
);
837 void NativeThemeBase::PaintSliderThumb(SkCanvas
* canvas
,
839 const gfx::Rect
& rect
,
840 const SliderExtraParams
& slider
) const {
841 const bool hovered
= (state
== kHovered
) || slider
.in_drag
;
842 const int kMidX
= rect
.x() + rect
.width() / 2;
843 const int kMidY
= rect
.y() + rect
.height() / 2;
846 paint
.setColor(hovered
? SK_ColorWHITE
: kSliderThumbLightGrey
);
850 skrect
.set(rect
.x(), rect
.y(), kMidX
+ 1, rect
.bottom());
852 skrect
.set(rect
.x(), rect
.y(), rect
.right(), kMidY
+ 1);
854 canvas
->drawIRect(skrect
, paint
);
856 paint
.setColor(hovered
? kSliderThumbLightGrey
: kSliderThumbDarkGrey
);
859 skrect
.set(kMidX
+ 1, rect
.y(), rect
.right(), rect
.bottom());
861 skrect
.set(rect
.x(), kMidY
+ 1, rect
.right(), rect
.bottom());
863 canvas
->drawIRect(skrect
, paint
);
865 paint
.setColor(kSliderThumbBorderDarkGrey
);
866 DrawBox(canvas
, rect
, paint
);
868 if (rect
.height() > 10 && rect
.width() > 10) {
869 DrawHorizLine(canvas
, kMidX
- 2, kMidX
+ 2, kMidY
, paint
);
870 DrawHorizLine(canvas
, kMidX
- 2, kMidX
+ 2, kMidY
- 3, paint
);
871 DrawHorizLine(canvas
, kMidX
- 2, kMidX
+ 2, kMidY
+ 3, paint
);
875 void NativeThemeBase::PaintInnerSpinButton(SkCanvas
* canvas
,
877 const gfx::Rect
& rect
,
878 const InnerSpinButtonExtraParams
& spin_button
) const {
879 if (spin_button
.read_only
)
882 State north_state
= state
;
883 State south_state
= state
;
884 if (spin_button
.spin_up
)
885 south_state
= south_state
!= kDisabled
? kNormal
: kDisabled
;
887 north_state
= north_state
!= kDisabled
? kNormal
: kDisabled
;
889 gfx::Rect half
= rect
;
890 half
.set_height(rect
.height() / 2);
891 PaintArrowButton(canvas
, half
, kScrollbarUpArrow
, north_state
);
893 half
.set_y(rect
.y() + rect
.height() / 2);
894 PaintArrowButton(canvas
, half
, kScrollbarDownArrow
, south_state
);
897 void NativeThemeBase::PaintProgressBar(SkCanvas
* canvas
,
899 const gfx::Rect
& rect
,
900 const ProgressBarExtraParams
& progress_bar
) const {
901 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
902 gfx::ImageSkia
* bar_image
= rb
.GetImageSkiaNamed(IDR_PROGRESS_BAR
);
903 gfx::ImageSkia
* left_border_image
= rb
.GetImageSkiaNamed(
904 IDR_PROGRESS_BORDER_LEFT
);
905 gfx::ImageSkia
* right_border_image
= rb
.GetImageSkiaNamed(
906 IDR_PROGRESS_BORDER_RIGHT
);
908 DCHECK(bar_image
->width() > 0);
909 DCHECK(rect
.width() > 0);
911 float tile_scale_y
= static_cast<float>(rect
.height()) / bar_image
->height();
913 int dest_left_border_width
= left_border_image
->width();
914 int dest_right_border_width
= right_border_image
->width();
916 // Since an implicit float -> int conversion will truncate, we want to make
917 // sure that if a border is desired, it gets at least one pixel.
918 if (dest_left_border_width
> 0) {
919 dest_left_border_width
= dest_left_border_width
* tile_scale_y
;
920 dest_left_border_width
= std::max(dest_left_border_width
, 1);
922 if (dest_right_border_width
> 0) {
923 dest_right_border_width
= dest_right_border_width
* tile_scale_y
;
924 dest_right_border_width
= std::max(dest_right_border_width
, 1);
927 // Since the width of the progress bar may not be evenly divisible by the
928 // tile size, in order to make it look right we may need to draw some of the
929 // with a width of 1 pixel smaller than the rest of the tiles.
930 int new_tile_width
= static_cast<int>(bar_image
->width() * tile_scale_y
);
931 new_tile_width
= std::max(new_tile_width
, 1);
933 float tile_scale_x
= static_cast<float>(new_tile_width
) / bar_image
->width();
934 if (rect
.width() % new_tile_width
== 0) {
935 DrawTiledImage(canvas
, *bar_image
, 0, 0, tile_scale_x
, tile_scale_y
,
937 rect
.width(), rect
.height());
939 int num_tiles
= 1 + rect
.width() / new_tile_width
;
940 int overshoot
= num_tiles
* new_tile_width
- rect
.width();
941 // Since |overshoot| represents the number of tiles that were too big, draw
942 // |overshoot| tiles with their width reduced by 1.
943 int num_big_tiles
= num_tiles
- overshoot
;
944 int num_small_tiles
= overshoot
;
945 int small_width
= new_tile_width
- 1;
946 float small_scale_x
= static_cast<float>(small_width
) / bar_image
->width();
947 float big_scale_x
= tile_scale_x
;
949 gfx::Rect big_rect
= rect
;
950 gfx::Rect small_rect
= rect
;
951 big_rect
.Inset(0, 0, num_small_tiles
*small_width
, 0);
952 small_rect
.Inset(num_big_tiles
*new_tile_width
, 0, 0, 0);
954 DrawTiledImage(canvas
, *bar_image
, 0, 0, big_scale_x
, tile_scale_y
,
955 big_rect
.x(), big_rect
.y(), big_rect
.width(), big_rect
.height());
956 DrawTiledImage(canvas
, *bar_image
, 0, 0, small_scale_x
, tile_scale_y
,
957 small_rect
.x(), small_rect
.y(), small_rect
.width(), small_rect
.height());
959 if (progress_bar
.value_rect_width
) {
960 gfx::ImageSkia
* value_image
= rb
.GetImageSkiaNamed(IDR_PROGRESS_VALUE
);
962 new_tile_width
= static_cast<int>(value_image
->width() * tile_scale_y
);
963 tile_scale_x
= static_cast<float>(new_tile_width
) /
964 value_image
->width();
966 DrawTiledImage(canvas
, *value_image
, 0, 0, tile_scale_x
, tile_scale_y
,
967 progress_bar
.value_rect_x
,
968 progress_bar
.value_rect_y
,
969 progress_bar
.value_rect_width
,
970 progress_bar
.value_rect_height
);
973 DrawImageInt(canvas
, *left_border_image
, 0, 0, left_border_image
->width(),
974 left_border_image
->height(), rect
.x(), rect
.y(), dest_left_border_width
,
977 int dest_x
= rect
.right() - dest_right_border_width
;
978 DrawImageInt(canvas
, *right_border_image
, 0, 0, right_border_image
->width(),
979 right_border_image
->height(), dest_x
, rect
.y(),
980 dest_right_border_width
, rect
.height());
983 bool NativeThemeBase::IntersectsClipRectInt(SkCanvas
* canvas
,
984 int x
, int y
, int w
, int h
) const {
986 return canvas
->getClipBounds(&clip
) &&
987 clip
.intersect(SkIntToScalar(x
), SkIntToScalar(y
), SkIntToScalar(x
+ w
),
988 SkIntToScalar(y
+ h
));
991 void NativeThemeBase::DrawImageInt(
992 SkCanvas
* sk_canvas
, const gfx::ImageSkia
& image
,
993 int src_x
, int src_y
, int src_w
, int src_h
,
994 int dest_x
, int dest_y
, int dest_w
, int dest_h
) const {
995 scoped_ptr
<gfx::Canvas
> canvas(CommonThemeCreateCanvas(sk_canvas
));
996 canvas
->DrawImageInt(image
, src_x
, src_y
, src_w
, src_h
,
997 dest_x
, dest_y
, dest_w
, dest_h
, true);
1000 void NativeThemeBase::DrawTiledImage(SkCanvas
* sk_canvas
,
1001 const gfx::ImageSkia
& image
,
1002 int src_x
, int src_y
, float tile_scale_x
, float tile_scale_y
,
1003 int dest_x
, int dest_y
, int w
, int h
) const {
1004 scoped_ptr
<gfx::Canvas
> canvas(CommonThemeCreateCanvas(sk_canvas
));
1005 canvas
->TileImageInt(image
, src_x
, src_y
, tile_scale_x
,
1006 tile_scale_y
, dest_x
, dest_y
, w
, h
);
1009 SkColor
NativeThemeBase::SaturateAndBrighten(SkScalar
* hsv
,
1010 SkScalar saturate_amount
,
1011 SkScalar brighten_amount
) const {
1014 color
[1] = Clamp(hsv
[1] + saturate_amount
, 0.0, 1.0);
1015 color
[2] = Clamp(hsv
[2] + brighten_amount
, 0.0, 1.0);
1016 return SkHSVToColor(color
);
1019 SkColor
NativeThemeBase::GetArrowColor(State state
) const {
1020 if (state
!= kDisabled
)
1021 return SK_ColorBLACK
;
1023 SkScalar track_hsv
[3];
1024 SkColorToHSV(track_color_
, track_hsv
);
1025 SkScalar thumb_hsv
[3];
1026 SkColorToHSV(thumb_inactive_color_
, thumb_hsv
);
1027 return OutlineColor(track_hsv
, thumb_hsv
);
1030 void NativeThemeBase::DrawVertLine(SkCanvas
* canvas
,
1034 const SkPaint
& paint
) const {
1036 skrect
.set(x
, y1
, x
+ 1, y2
+ 1);
1037 canvas
->drawIRect(skrect
, paint
);
1040 void NativeThemeBase::DrawHorizLine(SkCanvas
* canvas
,
1044 const SkPaint
& paint
) const {
1046 skrect
.set(x1
, y
, x2
+ 1, y
+ 1);
1047 canvas
->drawIRect(skrect
, paint
);
1050 void NativeThemeBase::DrawBox(SkCanvas
* canvas
,
1051 const gfx::Rect
& rect
,
1052 const SkPaint
& paint
) const {
1053 const int right
= rect
.x() + rect
.width() - 1;
1054 const int bottom
= rect
.y() + rect
.height() - 1;
1055 DrawHorizLine(canvas
, rect
.x(), right
, rect
.y(), paint
);
1056 DrawVertLine(canvas
, right
, rect
.y(), bottom
, paint
);
1057 DrawHorizLine(canvas
, rect
.x(), right
, bottom
, paint
);
1058 DrawVertLine(canvas
, rect
.x(), rect
.y(), bottom
, paint
);
1061 SkScalar
NativeThemeBase::Clamp(SkScalar value
,
1063 SkScalar max
) const {
1064 return std::min(std::max(value
, min
), max
);
1067 SkColor
NativeThemeBase::OutlineColor(SkScalar
* hsv1
, SkScalar
* hsv2
) const {
1068 // GTK Theme engines have way too much control over the layout of
1069 // the scrollbar. We might be able to more closely approximate its
1070 // look-and-feel, if we sent whole images instead of just colors
1071 // from the browser to the renderer. But even then, some themes
1072 // would just break.
1074 // So, instead, we don't even try to 100% replicate the look of
1075 // the native scrollbar. We render our own version, but we make
1076 // sure to pick colors that blend in nicely with the system GTK
1077 // theme. In most cases, we can just sample a couple of pixels
1078 // from the system scrollbar and use those colors to draw our
1081 // This works fine for the track color and the overall thumb
1082 // color. But it fails spectacularly for the outline color used
1083 // around the thumb piece. Not all themes have a clearly defined
1084 // outline. For some of them it is partially transparent, and for
1085 // others the thickness is very unpredictable.
1087 // So, instead of trying to approximate the system theme, we
1088 // instead try to compute a reasonable looking choice based on the
1089 // known color of the track and the thumb piece. This is difficult
1090 // when trying to deal both with high- and low-contrast themes,
1091 // and both with positive and inverted themes.
1093 // The following code has been tested to look OK with all of the
1094 // default GTK themes.
1095 SkScalar min_diff
= Clamp((hsv1
[1] + hsv2
[1]) * 1.2f
, 0.28f
, 0.5f
);
1096 SkScalar diff
= Clamp(fabs(hsv1
[2] - hsv2
[2]) / 2, min_diff
, 0.5f
);
1098 if (hsv1
[2] + hsv2
[2] > 1.0)
1101 return SaturateAndBrighten(hsv2
, -0.2f
, diff
);