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 "grit/ui_resources.h"
13 #include "third_party/skia/include/effects/SkGradientShader.h"
14 #include "ui/base/layout.h"
15 #include "ui/base/resource/resource_bundle.h"
16 #include "ui/base/ui_base_switches.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/color_utils.h"
19 #include "ui/gfx/image/image_skia.h"
20 #include "ui/gfx/rect.h"
21 #include "ui/gfx/size.h"
22 #include "ui/gfx/skia_util.h"
26 // These are the default dimensions of radio buttons and checkboxes.
27 const int kCheckboxAndRadioWidth
= 13;
28 const int kCheckboxAndRadioHeight
= 13;
30 // These sizes match the sizes in Chromium Win.
31 const int kSliderThumbWidth
= 11;
32 const int kSliderThumbHeight
= 21;
34 const SkColor kSliderTrackBackgroundColor
=
35 SkColorSetRGB(0xe3, 0xdd, 0xd8);
36 const SkColor kSliderThumbLightGrey
= SkColorSetRGB(0xf4, 0xf2, 0xef);
37 const SkColor kSliderThumbDarkGrey
= SkColorSetRGB(0xea, 0xe5, 0xe0);
38 const SkColor kSliderThumbBorderDarkGrey
=
39 SkColorSetRGB(0x9d, 0x96, 0x8e);
41 const SkColor kMenuPopupBackgroundColor
= SkColorSetRGB(210, 225, 246);
43 const unsigned int kDefaultScrollbarWidth
= 15;
44 const unsigned int kDefaultScrollbarButtonLength
= 14;
46 const SkColor kCheckboxTinyColor
= SK_ColorGRAY
;
47 const SkColor kCheckboxShadowColor
= SkColorSetARGB(0x15, 0, 0, 0);
48 const SkColor kCheckboxShadowHoveredColor
= SkColorSetARGB(0x1F, 0, 0, 0);
49 const SkColor kCheckboxShadowDisabledColor
= SkColorSetARGB(0, 0, 0, 0);
50 const SkColor kCheckboxGradientColors
[] = {
51 SkColorSetRGB(0xed, 0xed, 0xed),
52 SkColorSetRGB(0xde, 0xde, 0xde) };
53 const SkColor kCheckboxGradientPressedColors
[] = {
54 SkColorSetRGB(0xe7, 0xe7, 0xe7),
55 SkColorSetRGB(0xd7, 0xd7, 0xd7) };
56 const SkColor kCheckboxGradientHoveredColors
[] = {
57 SkColorSetRGB(0xf0, 0xf0, 0xf0),
58 SkColorSetRGB(0xe0, 0xe0, 0xe0) };
59 const SkColor kCheckboxGradientDisabledColors
[] = {
60 SkColorSetARGB(0x80, 0xed, 0xed, 0xed),
61 SkColorSetARGB(0x80, 0xde, 0xde, 0xde) };
62 const SkColor kCheckboxBorderColor
= SkColorSetARGB(0x40, 0, 0, 0);
63 const SkColor kCheckboxBorderHoveredColor
= SkColorSetARGB(0x4D, 0, 0, 0);
64 const SkColor kCheckboxBorderDisabledColor
= SkColorSetARGB(0x20, 0, 0, 0);
65 const SkColor kCheckboxStrokeColor
= SkColorSetARGB(0xB3, 0, 0, 0);
66 const SkColor kCheckboxStrokeDisabledColor
= SkColorSetARGB(0x59, 0, 0, 0);
67 const SkColor kRadioDotColor
= SkColorSetRGB(0x66, 0x66, 0x66);
68 const SkColor kRadioDotDisabledColor
= SkColorSetARGB(0x80, 0x66, 0x66, 0x66);
70 // Get lightness adjusted color.
71 SkColor
BrightenColor(const color_utils::HSL
& hsl
, SkAlpha alpha
,
72 double lightness_amount
) {
73 color_utils::HSL adjusted
= hsl
;
74 adjusted
.l
+= lightness_amount
;
80 return color_utils::HSLToSkColor(adjusted
, alpha
);
87 gfx::Size
NativeThemeBase::GetPartSize(Part part
,
89 const ExtraParams
& extra
) const {
91 // Please keep these in the order of NativeTheme::Part.
93 return gfx::Size(kCheckboxAndRadioWidth
, kCheckboxAndRadioHeight
);
94 case kInnerSpinButton
:
95 return gfx::Size(scrollbar_width_
, 0);
97 return gfx::Size(); // No default size.
99 case kMenuCheckBackground
:
100 case kMenuPopupArrow
:
103 case kMenuPopupBackground
:
104 return gfx::Size(); // No default size.
105 case kMenuPopupGutter
:
106 case kMenuPopupSeparator
:
109 case kMenuItemBackground
:
112 return gfx::Size(); // No default size.
114 return gfx::Size(kCheckboxAndRadioWidth
, kCheckboxAndRadioHeight
);
115 case kScrollbarDownArrow
:
116 case kScrollbarUpArrow
:
117 return gfx::Size(scrollbar_width_
, scrollbar_button_length_
);
118 case kScrollbarLeftArrow
:
119 case kScrollbarRightArrow
:
120 return gfx::Size(scrollbar_button_length_
, scrollbar_width_
);
121 case kScrollbarHorizontalThumb
:
122 // This matches Firefox on Linux.
123 return gfx::Size(2 * scrollbar_width_
, scrollbar_width_
);
124 case kScrollbarVerticalThumb
:
125 // This matches Firefox on Linux.
126 return gfx::Size(scrollbar_width_
, 2 * scrollbar_width_
);
127 case kScrollbarHorizontalTrack
:
128 return gfx::Size(0, scrollbar_width_
);
129 case kScrollbarVerticalTrack
:
130 return gfx::Size(scrollbar_width_
, 0);
131 case kScrollbarHorizontalGripper
:
132 case kScrollbarVerticalGripper
:
136 return gfx::Size(); // No default size.
138 // These sizes match the sizes in Chromium Win.
139 return gfx::Size(kSliderThumbWidth
, kSliderThumbHeight
);
140 case kTabPanelBackground
:
144 return gfx::Size(); // No default size.
147 case kWindowResizeGripper
:
151 NOTREACHED() << "Unknown theme part: " << part
;
157 void NativeThemeBase::Paint(SkCanvas
* canvas
,
160 const gfx::Rect
& rect
,
161 const ExtraParams
& extra
) const {
166 // Please keep these in the order of NativeTheme::Part.
168 PaintCheckbox(canvas
, state
, rect
, extra
.button
);
170 case kInnerSpinButton
:
171 PaintInnerSpinButton(canvas
, state
, rect
, extra
.inner_spin
);
174 PaintMenuList(canvas
, state
, rect
, extra
.menu_list
);
177 case kMenuCheckBackground
:
178 case kMenuPopupArrow
:
181 case kMenuPopupBackground
:
182 PaintMenuPopupBackground(canvas
, rect
.size(), extra
.menu_background
);
184 case kMenuPopupGutter
:
185 case kMenuPopupSeparator
:
188 case kMenuItemBackground
:
189 PaintMenuItemBackground(canvas
, state
, rect
, extra
.menu_list
);
192 PaintProgressBar(canvas
, state
, rect
, extra
.progress_bar
);
195 PaintButton(canvas
, state
, rect
, extra
.button
);
198 PaintRadio(canvas
, state
, rect
, extra
.button
);
200 case kScrollbarDownArrow
:
201 case kScrollbarUpArrow
:
202 case kScrollbarLeftArrow
:
203 case kScrollbarRightArrow
:
204 PaintArrowButton(canvas
, rect
, part
, state
);
206 case kScrollbarHorizontalThumb
:
207 case kScrollbarVerticalThumb
:
208 PaintScrollbarThumb(canvas
, part
, state
, rect
);
210 case kScrollbarHorizontalTrack
:
211 case kScrollbarVerticalTrack
:
212 PaintScrollbarTrack(canvas
, part
, state
, extra
.scrollbar_track
, rect
);
214 case kScrollbarHorizontalGripper
:
215 case kScrollbarVerticalGripper
:
216 // Invoked by views scrollbar code, don't care about for non-win
217 // implementations, so no NOTIMPLEMENTED.
220 PaintSliderTrack(canvas
, state
, rect
, extra
.slider
);
223 PaintSliderThumb(canvas
, state
, rect
, extra
.slider
);
225 case kTabPanelBackground
:
229 PaintTextField(canvas
, state
, rect
, extra
.text_field
);
233 case kWindowResizeGripper
:
237 NOTREACHED() << "Unknown theme part: " << part
;
242 NativeThemeBase::NativeThemeBase()
243 : scrollbar_width_(kDefaultScrollbarWidth
),
244 scrollbar_button_length_(kDefaultScrollbarButtonLength
) {
247 NativeThemeBase::~NativeThemeBase() {
250 void NativeThemeBase::PaintArrowButton(
252 const gfx::Rect
& rect
, Part direction
, State state
) const {
253 int widthMiddle
, lengthMiddle
;
255 if (direction
== kScrollbarUpArrow
|| direction
== kScrollbarDownArrow
) {
256 widthMiddle
= rect
.width() / 2 + 1;
257 lengthMiddle
= rect
.height() / 2 + 1;
259 lengthMiddle
= rect
.width() / 2 + 1;
260 widthMiddle
= rect
.height() / 2 + 1;
263 // Calculate button color.
264 SkScalar trackHSV
[3];
265 SkColorToHSV(track_color_
, trackHSV
);
266 SkColor buttonColor
= SaturateAndBrighten(trackHSV
, 0, 0.2f
);
267 SkColor backgroundColor
= buttonColor
;
268 if (state
== kPressed
) {
269 SkScalar buttonHSV
[3];
270 SkColorToHSV(buttonColor
, buttonHSV
);
271 buttonColor
= SaturateAndBrighten(buttonHSV
, 0, -0.1f
);
272 } else if (state
== kHovered
) {
273 SkScalar buttonHSV
[3];
274 SkColorToHSV(buttonColor
, buttonHSV
);
275 buttonColor
= SaturateAndBrighten(buttonHSV
, 0, 0.05f
);
279 skrect
.set(rect
.x(), rect
.y(), rect
.x() + rect
.width(), rect
.y()
281 // Paint the background (the area visible behind the rounded corners).
282 paint
.setColor(backgroundColor
);
283 canvas
->drawIRect(skrect
, paint
);
285 // Paint the button's outline and fill the middle
288 case kScrollbarUpArrow
:
289 outline
.moveTo(rect
.x() + 0.5, rect
.y() + rect
.height() + 0.5);
290 outline
.rLineTo(0, -(rect
.height() - 2));
291 outline
.rLineTo(2, -2);
292 outline
.rLineTo(rect
.width() - 5, 0);
293 outline
.rLineTo(2, 2);
294 outline
.rLineTo(0, rect
.height() - 2);
296 case kScrollbarDownArrow
:
297 outline
.moveTo(rect
.x() + 0.5, rect
.y() - 0.5);
298 outline
.rLineTo(0, rect
.height() - 2);
299 outline
.rLineTo(2, 2);
300 outline
.rLineTo(rect
.width() - 5, 0);
301 outline
.rLineTo(2, -2);
302 outline
.rLineTo(0, -(rect
.height() - 2));
304 case kScrollbarRightArrow
:
305 outline
.moveTo(rect
.x() - 0.5, rect
.y() + 0.5);
306 outline
.rLineTo(rect
.width() - 2, 0);
307 outline
.rLineTo(2, 2);
308 outline
.rLineTo(0, rect
.height() - 5);
309 outline
.rLineTo(-2, 2);
310 outline
.rLineTo(-(rect
.width() - 2), 0);
312 case kScrollbarLeftArrow
:
313 outline
.moveTo(rect
.x() + rect
.width() + 0.5, rect
.y() + 0.5);
314 outline
.rLineTo(-(rect
.width() - 2), 0);
315 outline
.rLineTo(-2, 2);
316 outline
.rLineTo(0, rect
.height() - 5);
317 outline
.rLineTo(2, 2);
318 outline
.rLineTo(rect
.width() - 2, 0);
325 paint
.setStyle(SkPaint::kFill_Style
);
326 paint
.setColor(buttonColor
);
327 canvas
->drawPath(outline
, paint
);
329 paint
.setAntiAlias(true);
330 paint
.setStyle(SkPaint::kStroke_Style
);
331 SkScalar thumbHSV
[3];
332 SkColorToHSV(thumb_inactive_color_
, thumbHSV
);
333 paint
.setColor(OutlineColor(trackHSV
, thumbHSV
));
334 canvas
->drawPath(outline
, paint
);
336 // If the button is disabled or read-only, the arrow is drawn with the
338 if (state
!= kDisabled
)
339 paint
.setColor(SK_ColorBLACK
);
341 paint
.setAntiAlias(false);
342 paint
.setStyle(SkPaint::kFill_Style
);
345 // The constants in this block of code are hand-tailored to produce good
346 // looking arrows without anti-aliasing.
348 case kScrollbarUpArrow
:
349 path
.moveTo(rect
.x() + widthMiddle
- 4, rect
.y() + lengthMiddle
+ 2);
351 path
.rLineTo(-4, -4);
353 case kScrollbarDownArrow
:
354 path
.moveTo(rect
.x() + widthMiddle
- 4, rect
.y() + lengthMiddle
- 3);
358 case kScrollbarRightArrow
:
359 path
.moveTo(rect
.x() + lengthMiddle
- 3, rect
.y() + widthMiddle
- 4);
363 case kScrollbarLeftArrow
:
364 path
.moveTo(rect
.x() + lengthMiddle
+ 1, rect
.y() + widthMiddle
- 5);
366 path
.rLineTo(-4, -4);
373 canvas
->drawPath(path
, paint
);
376 void NativeThemeBase::PaintScrollbarTrack(SkCanvas
* canvas
,
379 const ScrollbarTrackExtraParams
& extra_params
,
380 const gfx::Rect
& rect
) const {
384 skrect
.set(rect
.x(), rect
.y(), rect
.right(), rect
.bottom());
385 SkScalar track_hsv
[3];
386 SkColorToHSV(track_color_
, track_hsv
);
387 paint
.setColor(SaturateAndBrighten(track_hsv
, 0, 0));
388 canvas
->drawIRect(skrect
, paint
);
390 SkScalar thumb_hsv
[3];
391 SkColorToHSV(thumb_inactive_color_
, thumb_hsv
);
393 paint
.setColor(OutlineColor(track_hsv
, thumb_hsv
));
394 DrawBox(canvas
, rect
, paint
);
397 void NativeThemeBase::PaintScrollbarThumb(SkCanvas
* canvas
,
400 const gfx::Rect
& rect
) const {
401 const bool hovered
= state
== kHovered
;
402 const int midx
= rect
.x() + rect
.width() / 2;
403 const int midy
= rect
.y() + rect
.height() / 2;
404 const bool vertical
= part
== kScrollbarVerticalThumb
;
407 SkColorToHSV(hovered
? thumb_active_color_
: thumb_inactive_color_
, thumb
);
410 paint
.setColor(SaturateAndBrighten(thumb
, 0, 0.02f
));
414 skrect
.set(rect
.x(), rect
.y(), midx
+ 1, rect
.y() + rect
.height());
416 skrect
.set(rect
.x(), rect
.y(), rect
.x() + rect
.width(), midy
+ 1);
418 canvas
->drawIRect(skrect
, paint
);
420 paint
.setColor(SaturateAndBrighten(thumb
, 0, -0.02f
));
424 midx
+ 1, rect
.y(), rect
.x() + rect
.width(), rect
.y() + rect
.height());
427 rect
.x(), midy
+ 1, rect
.x() + rect
.width(), rect
.y() + rect
.height());
430 canvas
->drawIRect(skrect
, paint
);
433 SkColorToHSV(track_color_
, track
);
434 paint
.setColor(OutlineColor(track
, thumb
));
435 DrawBox(canvas
, rect
, paint
);
437 if (rect
.height() > 10 && rect
.width() > 10) {
438 const int grippy_half_width
= 2;
439 const int inter_grippy_offset
= 3;
441 DrawHorizLine(canvas
,
442 midx
- grippy_half_width
,
443 midx
+ grippy_half_width
,
444 midy
- inter_grippy_offset
,
446 DrawHorizLine(canvas
,
447 midx
- grippy_half_width
,
448 midx
+ grippy_half_width
,
451 DrawHorizLine(canvas
,
452 midx
- grippy_half_width
,
453 midx
+ grippy_half_width
,
454 midy
+ inter_grippy_offset
,
458 midx
- inter_grippy_offset
,
459 midy
- grippy_half_width
,
460 midy
+ grippy_half_width
,
464 midy
- grippy_half_width
,
465 midy
+ grippy_half_width
,
468 midx
+ inter_grippy_offset
,
469 midy
- grippy_half_width
,
470 midy
+ grippy_half_width
,
476 void NativeThemeBase::PaintCheckbox(SkCanvas
* canvas
,
478 const gfx::Rect
& rect
,
479 const ButtonExtraParams
& button
) const {
480 SkRect skrect
= PaintCheckboxRadioCommon(canvas
, state
, rect
,
482 if (!skrect
.isEmpty()) {
483 // Draw the checkmark / dash.
485 paint
.setAntiAlias(true);
486 paint
.setStyle(SkPaint::kStroke_Style
);
487 if (state
== kDisabled
)
488 paint
.setColor(kCheckboxStrokeDisabledColor
);
490 paint
.setColor(kCheckboxStrokeColor
);
491 if (button
.indeterminate
) {
493 dash
.moveTo(skrect
.x() + skrect
.width() * 0.16,
494 (skrect
.y() + skrect
.bottom()) / 2);
495 dash
.rLineTo(skrect
.width() * 0.68, 0);
496 paint
.setStrokeWidth(SkFloatToScalar(skrect
.height() * 0.2));
497 canvas
->drawPath(dash
, paint
);
498 } else if (button
.checked
) {
500 check
.moveTo(skrect
.x() + skrect
.width() * 0.2,
501 skrect
.y() + skrect
.height() * 0.5);
502 check
.rLineTo(skrect
.width() * 0.2, skrect
.height() * 0.2);
503 paint
.setStrokeWidth(SkFloatToScalar(skrect
.height() * 0.23));
504 check
.lineTo(skrect
.right() - skrect
.width() * 0.2,
505 skrect
.y() + skrect
.height() * 0.2);
506 canvas
->drawPath(check
, paint
);
511 // Draws the common elements of checkboxes and radio buttons.
512 // Returns the rectangle within which any additional decorations should be
513 // drawn, or empty if none.
514 SkRect
NativeThemeBase::PaintCheckboxRadioCommon(
517 const gfx::Rect
& rect
,
518 const SkScalar borderRadius
) const {
520 SkRect skrect
= gfx::RectToSkRect(rect
);
522 // Use the largest square that fits inside the provided rectangle.
523 // No other browser seems to support non-square widget, so accidentally
524 // having non-square sizes is common (eg. amazon and webkit dev tools).
525 if (skrect
.width() != skrect
.height()) {
526 SkScalar size
= SkMinScalar(skrect
.width(), skrect
.height());
527 skrect
.inset((skrect
.width() - size
) / 2, (skrect
.height() - size
) / 2);
530 // If the rectangle is too small then paint only a rectangle. We don't want
531 // to have to worry about '- 1' and '+ 1' calculations below having overflow
533 if (skrect
.width() <= 2) {
535 paint
.setColor(kCheckboxTinyColor
);
536 paint
.setStyle(SkPaint::kFill_Style
);
537 canvas
->drawRect(skrect
, paint
);
538 // Too small to draw anything more.
539 return SkRect::MakeEmpty();
542 // Make room for the drop shadow.
543 skrect
.iset(skrect
.x(), skrect
.y(), skrect
.right() - 1, skrect
.bottom() - 1);
545 // Draw the drop shadow below the widget.
546 if (state
!= kPressed
) {
548 paint
.setAntiAlias(true);
549 SkRect shadowRect
= skrect
;
550 shadowRect
.offset(0, 1);
551 if (state
== kDisabled
)
552 paint
.setColor(kCheckboxShadowDisabledColor
);
553 else if (state
== kHovered
)
554 paint
.setColor(kCheckboxShadowHoveredColor
);
556 paint
.setColor(kCheckboxShadowColor
);
557 paint
.setStyle(SkPaint::kFill_Style
);
558 canvas
->drawRoundRect(shadowRect
, borderRadius
, borderRadius
, paint
);
561 // Draw the gradient-filled rectangle
562 SkPoint gradient_bounds
[3];
563 gradient_bounds
[0].set(skrect
.x(), skrect
.y());
564 gradient_bounds
[1].set(skrect
.x(), skrect
.y() + skrect
.height() * 0.38);
565 gradient_bounds
[2].set(skrect
.x(), skrect
.bottom());
566 const SkColor
* startEndColors
;
567 if (state
== kPressed
)
568 startEndColors
= kCheckboxGradientPressedColors
;
569 else if (state
== kHovered
)
570 startEndColors
= kCheckboxGradientHoveredColors
;
571 else if (state
== kDisabled
)
572 startEndColors
= kCheckboxGradientDisabledColors
;
574 startEndColors
= kCheckboxGradientColors
;
575 SkColor colors
[3] = {startEndColors
[0], startEndColors
[0], startEndColors
[1]};
576 skia::RefPtr
<SkShader
> shader
= skia::AdoptRef(
577 SkGradientShader::CreateLinear(
578 gradient_bounds
, colors
, NULL
, 3, SkShader::kClamp_TileMode
, NULL
));
580 paint
.setAntiAlias(true);
581 paint
.setShader(shader
.get());
582 paint
.setStyle(SkPaint::kFill_Style
);
583 canvas
->drawRoundRect(skrect
, borderRadius
, borderRadius
, paint
);
584 paint
.setShader(NULL
);
587 if (state
== kHovered
)
588 paint
.setColor(kCheckboxBorderHoveredColor
);
589 else if (state
== kDisabled
)
590 paint
.setColor(kCheckboxBorderDisabledColor
);
592 paint
.setColor(kCheckboxBorderColor
);
593 paint
.setStyle(SkPaint::kStroke_Style
);
594 paint
.setStrokeWidth(SkIntToScalar(1));
595 skrect
.inset(SkFloatToScalar(.5f
), SkFloatToScalar(.5f
));
596 canvas
->drawRoundRect(skrect
, borderRadius
, borderRadius
, paint
);
598 // Return the rectangle excluding the drop shadow for drawing any additional
603 void NativeThemeBase::PaintRadio(SkCanvas
* canvas
,
605 const gfx::Rect
& rect
,
606 const ButtonExtraParams
& button
) const {
608 // Most of a radio button is the same as a checkbox, except the the rounded
609 // square is a circle (i.e. border radius >= 100%).
610 const SkScalar radius
= SkFloatToScalar(
611 static_cast<float>(std::max(rect
.width(), rect
.height())) / 2);
612 SkRect skrect
= PaintCheckboxRadioCommon(canvas
, state
, rect
, radius
);
613 if (!skrect
.isEmpty() && button
.checked
) {
616 paint
.setAntiAlias(true);
617 paint
.setStyle(SkPaint::kFill_Style
);
618 if (state
== kDisabled
)
619 paint
.setColor(kRadioDotDisabledColor
);
621 paint
.setColor(kRadioDotColor
);
622 skrect
.inset(skrect
.width() * 0.25, skrect
.height() * 0.25);
623 // Use drawRoundedRect instead of drawOval to be completely consistent
624 // with the border in PaintCheckboxRadioNewCommon.
625 canvas
->drawRoundRect(skrect
, radius
, radius
, paint
);
629 void NativeThemeBase::PaintButton(SkCanvas
* canvas
,
631 const gfx::Rect
& rect
,
632 const ButtonExtraParams
& button
) const {
634 const int kRight
= rect
.right();
635 const int kBottom
= rect
.bottom();
636 SkRect skrect
= SkRect::MakeLTRB(rect
.x(), rect
.y(), kRight
, kBottom
);
637 SkColor base_color
= button
.background_color
;
639 color_utils::HSL base_hsl
;
640 color_utils::SkColorToHSL(base_color
, &base_hsl
);
642 // Our standard gradient is from 0xdd to 0xf8. This is the amount of
643 // increased luminance between those values.
644 SkColor
light_color(BrightenColor(base_hsl
, SkColorGetA(base_color
), 0.105));
646 // If the button is too small, fallback to drawing a single, solid color
647 if (rect
.width() < 5 || rect
.height() < 5) {
648 paint
.setColor(base_color
);
649 canvas
->drawRect(skrect
, paint
);
653 paint
.setColor(SK_ColorBLACK
);
654 const int kLightEnd
= state
== kPressed
? 1 : 0;
655 const int kDarkEnd
= !kLightEnd
;
656 SkPoint gradient_bounds
[2];
657 gradient_bounds
[kLightEnd
].iset(rect
.x(), rect
.y());
658 gradient_bounds
[kDarkEnd
].iset(rect
.x(), kBottom
- 1);
660 colors
[0] = light_color
;
661 colors
[1] = base_color
;
663 skia::RefPtr
<SkShader
> shader
= skia::AdoptRef(
664 SkGradientShader::CreateLinear(
665 gradient_bounds
, colors
, NULL
, 2, SkShader::kClamp_TileMode
, NULL
));
666 paint
.setStyle(SkPaint::kFill_Style
);
667 paint
.setAntiAlias(true);
668 paint
.setShader(shader
.get());
670 canvas
->drawRoundRect(skrect
, SkIntToScalar(1), SkIntToScalar(1), paint
);
671 paint
.setShader(NULL
);
673 if (button
.has_border
) {
674 int border_alpha
= state
== kHovered
? 0x80 : 0x55;
675 if (button
.is_focused
) {
677 paint
.setColor(GetSystemColor(kColorId_FocusedBorderColor
));
679 paint
.setStyle(SkPaint::kStroke_Style
);
680 paint
.setStrokeWidth(SkIntToScalar(1));
681 paint
.setAlpha(border_alpha
);
682 skrect
.inset(SkFloatToScalar(.5f
), SkFloatToScalar(.5f
));
683 canvas
->drawRoundRect(skrect
, SkIntToScalar(1), SkIntToScalar(1), paint
);
687 void NativeThemeBase::PaintTextField(SkCanvas
* canvas
,
689 const gfx::Rect
& rect
,
690 const TextFieldExtraParams
& text
) const {
691 // The following drawing code simulates the user-agent css border for
692 // text area and text input so that we do not break layout tests. Once we
693 // have decided the desired looks, we should update the code here and
694 // the layout test expectations.
696 bounds
.set(rect
.x(), rect
.y(), rect
.right() - 1, rect
.bottom() - 1);
699 fill_paint
.setStyle(SkPaint::kFill_Style
);
700 fill_paint
.setColor(text
.background_color
);
701 canvas
->drawRect(bounds
, fill_paint
);
703 if (text
.is_text_area
) {
704 // Draw text area border: 1px solid black
705 SkPaint stroke_paint
;
706 fill_paint
.setStyle(SkPaint::kStroke_Style
);
707 fill_paint
.setColor(SK_ColorBLACK
);
708 canvas
->drawRect(bounds
, fill_paint
);
710 // Draw text input and listbox inset border
711 // Text Input: 2px inset #eee
712 // Listbox: 1px inset #808080
713 const SkColor kLightColor
= text
.is_listbox
?
714 SkColorSetRGB(0x80, 0x80, 0x80) : SkColorSetRGB(0xee, 0xee, 0xee);
715 const SkColor kDarkColor
= text
.is_listbox
?
716 SkColorSetRGB(0x2c, 0x2c, 0x2c) : SkColorSetRGB(0x9a, 0x9a, 0x9a);
717 const int kBorderWidth
= text
.is_listbox
? 1 : 2;
720 dark_paint
.setAntiAlias(true);
721 dark_paint
.setStyle(SkPaint::kFill_Style
);
722 dark_paint
.setColor(kDarkColor
);
725 light_paint
.setAntiAlias(true);
726 light_paint
.setStyle(SkPaint::kFill_Style
);
727 light_paint
.setColor(kLightColor
);
731 int right
= rect
.right();
732 int bottom
= rect
.bottom();
738 path
.moveTo(SkIntToScalar(left
), SkIntToScalar(top
));
739 path
.lineTo(SkIntToScalar(left
+ kBorderWidth
),
740 SkIntToScalar(top
+ kBorderWidth
));
741 path
.lineTo(SkIntToScalar(right
- kBorderWidth
),
742 SkIntToScalar(top
+ kBorderWidth
));
743 path
.lineTo(SkIntToScalar(right
), SkIntToScalar(top
));
744 canvas
->drawPath(path
, dark_paint
);
748 path
.moveTo(SkIntToScalar(left
+ kBorderWidth
),
749 SkIntToScalar(bottom
- kBorderWidth
));
750 path
.lineTo(SkIntToScalar(left
), SkIntToScalar(bottom
));
751 path
.lineTo(SkIntToScalar(right
), SkIntToScalar(bottom
));
752 path
.lineTo(SkIntToScalar(right
- kBorderWidth
),
753 SkIntToScalar(bottom
- kBorderWidth
));
754 canvas
->drawPath(path
, light_paint
);
758 path
.moveTo(SkIntToScalar(left
), SkIntToScalar(top
));
759 path
.lineTo(SkIntToScalar(left
), SkIntToScalar(bottom
));
760 path
.lineTo(SkIntToScalar(left
+ kBorderWidth
),
761 SkIntToScalar(bottom
- kBorderWidth
));
762 path
.lineTo(SkIntToScalar(left
+ kBorderWidth
),
763 SkIntToScalar(top
+ kBorderWidth
));
764 canvas
->drawPath(path
, dark_paint
);
768 path
.moveTo(SkIntToScalar(right
- kBorderWidth
),
769 SkIntToScalar(top
+ kBorderWidth
));
770 path
.lineTo(SkIntToScalar(right
- kBorderWidth
), SkIntToScalar(bottom
));
771 path
.lineTo(SkIntToScalar(right
), SkIntToScalar(bottom
));
772 path
.lineTo(SkIntToScalar(right
), SkIntToScalar(top
));
773 canvas
->drawPath(path
, light_paint
);
777 void NativeThemeBase::PaintMenuList(
780 const gfx::Rect
& rect
,
781 const MenuListExtraParams
& menu_list
) const {
782 // If a border radius is specified, we let the WebCore paint the background
783 // and the border of the control.
784 if (!menu_list
.has_border_radius
) {
785 ButtonExtraParams button
= { 0 };
786 button
.background_color
= menu_list
.background_color
;
787 button
.has_border
= menu_list
.has_border
;
788 PaintButton(canvas
, state
, rect
, button
);
792 paint
.setColor(SK_ColorBLACK
);
793 paint
.setAntiAlias(true);
794 paint
.setStyle(SkPaint::kFill_Style
);
797 path
.moveTo(menu_list
.arrow_x
, menu_list
.arrow_y
- 3);
801 canvas
->drawPath(path
, paint
);
804 void NativeThemeBase::PaintMenuPopupBackground(
806 const gfx::Size
& size
,
807 const MenuBackgroundExtraParams
& menu_background
) const {
808 canvas
->drawColor(kMenuPopupBackgroundColor
, SkXfermode::kSrc_Mode
);
811 void NativeThemeBase::PaintMenuItemBackground(
814 const gfx::Rect
& rect
,
815 const MenuListExtraParams
& menu_list
) const {
816 // By default don't draw anything over the normal background.
819 void NativeThemeBase::PaintSliderTrack(SkCanvas
* canvas
,
821 const gfx::Rect
& rect
,
822 const SliderExtraParams
& slider
) const {
823 const int kMidX
= rect
.x() + rect
.width() / 2;
824 const int kMidY
= rect
.y() + rect
.height() / 2;
827 paint
.setColor(kSliderTrackBackgroundColor
);
830 if (slider
.vertical
) {
831 skrect
.set(std::max(rect
.x(), kMidX
- 2),
833 std::min(rect
.right(), kMidX
+ 2),
837 std::max(rect
.y(), kMidY
- 2),
839 std::min(rect
.bottom(), kMidY
+ 2));
841 canvas
->drawRect(skrect
, paint
);
844 void NativeThemeBase::PaintSliderThumb(SkCanvas
* canvas
,
846 const gfx::Rect
& rect
,
847 const SliderExtraParams
& slider
) const {
848 const bool hovered
= (state
== kHovered
) || slider
.in_drag
;
849 const int kMidX
= rect
.x() + rect
.width() / 2;
850 const int kMidY
= rect
.y() + rect
.height() / 2;
853 paint
.setColor(hovered
? SK_ColorWHITE
: kSliderThumbLightGrey
);
857 skrect
.set(rect
.x(), rect
.y(), kMidX
+ 1, rect
.bottom());
859 skrect
.set(rect
.x(), rect
.y(), rect
.right(), kMidY
+ 1);
861 canvas
->drawIRect(skrect
, paint
);
863 paint
.setColor(hovered
? kSliderThumbLightGrey
: kSliderThumbDarkGrey
);
866 skrect
.set(kMidX
+ 1, rect
.y(), rect
.right(), rect
.bottom());
868 skrect
.set(rect
.x(), kMidY
+ 1, rect
.right(), rect
.bottom());
870 canvas
->drawIRect(skrect
, paint
);
872 paint
.setColor(kSliderThumbBorderDarkGrey
);
873 DrawBox(canvas
, rect
, paint
);
875 if (rect
.height() > 10 && rect
.width() > 10) {
876 DrawHorizLine(canvas
, kMidX
- 2, kMidX
+ 2, kMidY
, paint
);
877 DrawHorizLine(canvas
, kMidX
- 2, kMidX
+ 2, kMidY
- 3, paint
);
878 DrawHorizLine(canvas
, kMidX
- 2, kMidX
+ 2, kMidY
+ 3, paint
);
882 void NativeThemeBase::PaintInnerSpinButton(SkCanvas
* canvas
,
884 const gfx::Rect
& rect
,
885 const InnerSpinButtonExtraParams
& spin_button
) const {
886 if (spin_button
.read_only
)
889 State north_state
= state
;
890 State south_state
= state
;
891 if (spin_button
.spin_up
)
892 south_state
= south_state
!= kDisabled
? kNormal
: kDisabled
;
894 north_state
= north_state
!= kDisabled
? kNormal
: kDisabled
;
896 gfx::Rect half
= rect
;
897 half
.set_height(rect
.height() / 2);
898 PaintArrowButton(canvas
, half
, kScrollbarUpArrow
, north_state
);
900 half
.set_y(rect
.y() + rect
.height() / 2);
901 PaintArrowButton(canvas
, half
, kScrollbarDownArrow
, south_state
);
904 void NativeThemeBase::PaintProgressBar(SkCanvas
* canvas
,
906 const gfx::Rect
& rect
,
907 const ProgressBarExtraParams
& progress_bar
) const {
908 ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
909 gfx::ImageSkia
* bar_image
= rb
.GetImageSkiaNamed(IDR_PROGRESS_BAR
);
910 gfx::ImageSkia
* left_border_image
= rb
.GetImageSkiaNamed(
911 IDR_PROGRESS_BORDER_LEFT
);
912 gfx::ImageSkia
* right_border_image
= rb
.GetImageSkiaNamed(
913 IDR_PROGRESS_BORDER_RIGHT
);
915 DCHECK(bar_image
->width() > 0);
916 DCHECK(rect
.width() > 0);
918 float tile_scale_y
= static_cast<float>(rect
.height()) / bar_image
->height();
920 int dest_left_border_width
= left_border_image
->width();
921 int dest_right_border_width
= right_border_image
->width();
923 // Since an implicit float -> int conversion will truncate, we want to make
924 // sure that if a border is desired, it gets at least one pixel.
925 if (dest_left_border_width
> 0) {
926 dest_left_border_width
= dest_left_border_width
* tile_scale_y
;
927 dest_left_border_width
= std::max(dest_left_border_width
, 1);
929 if (dest_right_border_width
> 0) {
930 dest_right_border_width
= dest_right_border_width
* tile_scale_y
;
931 dest_right_border_width
= std::max(dest_right_border_width
, 1);
934 // Since the width of the progress bar may not be evenly divisible by the
935 // tile size, in order to make it look right we may need to draw some of the
936 // with a width of 1 pixel smaller than the rest of the tiles.
937 int new_tile_width
= static_cast<int>(bar_image
->width() * tile_scale_y
);
938 new_tile_width
= std::max(new_tile_width
, 1);
940 float tile_scale_x
= static_cast<float>(new_tile_width
) / bar_image
->width();
941 if (rect
.width() % new_tile_width
== 0) {
942 DrawTiledImage(canvas
, *bar_image
, 0, 0, tile_scale_x
, tile_scale_y
,
944 rect
.width(), rect
.height());
946 int num_tiles
= 1 + rect
.width() / new_tile_width
;
947 int overshoot
= num_tiles
* new_tile_width
- rect
.width();
948 // Since |overshoot| represents the number of tiles that were too big, draw
949 // |overshoot| tiles with their width reduced by 1.
950 int num_big_tiles
= num_tiles
- overshoot
;
951 int num_small_tiles
= overshoot
;
952 int small_width
= new_tile_width
- 1;
953 float small_scale_x
= static_cast<float>(small_width
) / bar_image
->width();
954 float big_scale_x
= tile_scale_x
;
956 gfx::Rect big_rect
= rect
;
957 gfx::Rect small_rect
= rect
;
958 big_rect
.Inset(0, 0, num_small_tiles
*small_width
, 0);
959 small_rect
.Inset(num_big_tiles
*new_tile_width
, 0, 0, 0);
961 DrawTiledImage(canvas
, *bar_image
, 0, 0, big_scale_x
, tile_scale_y
,
962 big_rect
.x(), big_rect
.y(), big_rect
.width(), big_rect
.height());
963 DrawTiledImage(canvas
, *bar_image
, 0, 0, small_scale_x
, tile_scale_y
,
964 small_rect
.x(), small_rect
.y(), small_rect
.width(), small_rect
.height());
966 if (progress_bar
.value_rect_width
) {
967 gfx::ImageSkia
* value_image
= rb
.GetImageSkiaNamed(IDR_PROGRESS_VALUE
);
969 new_tile_width
= static_cast<int>(value_image
->width() * tile_scale_y
);
970 tile_scale_x
= static_cast<float>(new_tile_width
) /
971 value_image
->width();
973 DrawTiledImage(canvas
, *value_image
, 0, 0, tile_scale_x
, tile_scale_y
,
974 progress_bar
.value_rect_x
,
975 progress_bar
.value_rect_y
,
976 progress_bar
.value_rect_width
,
977 progress_bar
.value_rect_height
);
980 DrawImageInt(canvas
, *left_border_image
, 0, 0, left_border_image
->width(),
981 left_border_image
->height(), rect
.x(), rect
.y(), dest_left_border_width
,
984 int dest_x
= rect
.right() - dest_right_border_width
;
985 DrawImageInt(canvas
, *right_border_image
, 0, 0, right_border_image
->width(),
986 right_border_image
->height(), dest_x
, rect
.y(),
987 dest_right_border_width
, rect
.height());
990 bool NativeThemeBase::IntersectsClipRectInt(SkCanvas
* canvas
,
991 int x
, int y
, int w
, int h
) const {
993 return canvas
->getClipBounds(&clip
) &&
994 clip
.intersect(SkIntToScalar(x
), SkIntToScalar(y
), SkIntToScalar(x
+ w
),
995 SkIntToScalar(y
+ h
));
998 void NativeThemeBase::DrawImageInt(
999 SkCanvas
* sk_canvas
, const gfx::ImageSkia
& image
,
1000 int src_x
, int src_y
, int src_w
, int src_h
,
1001 int dest_x
, int dest_y
, int dest_w
, int dest_h
) const {
1002 // TODO(pkotwicz): Do something better and don't infer device
1003 // scale factor from canvas scale.
1004 SkMatrix m
= sk_canvas
->getTotalMatrix();
1005 float device_scale
= static_cast<float>(SkScalarAbs(m
.getScaleX()));
1006 scoped_ptr
<gfx::Canvas
> canvas(gfx::Canvas::CreateCanvasWithoutScaling(
1007 sk_canvas
, device_scale
));
1008 canvas
->DrawImageInt(image
, src_x
, src_y
, src_w
, src_h
,
1009 dest_x
, dest_y
, dest_w
, dest_h
, true);
1012 void NativeThemeBase::DrawTiledImage(SkCanvas
* sk_canvas
,
1013 const gfx::ImageSkia
& image
,
1014 int src_x
, int src_y
, float tile_scale_x
, float tile_scale_y
,
1015 int dest_x
, int dest_y
, int w
, int h
) const {
1016 // TODO(pkotwicz): Do something better and don't infer device
1017 // scale factor from canvas scale.
1018 SkMatrix m
= sk_canvas
->getTotalMatrix();
1019 float device_scale
= static_cast<float>(SkScalarAbs(m
.getScaleX()));
1020 scoped_ptr
<gfx::Canvas
> canvas(gfx::Canvas::CreateCanvasWithoutScaling(
1021 sk_canvas
, device_scale
));
1022 canvas
->TileImageInt(image
, src_x
, src_y
, tile_scale_x
,
1023 tile_scale_y
, dest_x
, dest_y
, w
, h
);
1026 SkColor
NativeThemeBase::SaturateAndBrighten(SkScalar
* hsv
,
1027 SkScalar saturate_amount
,
1028 SkScalar brighten_amount
) const {
1031 color
[1] = Clamp(hsv
[1] + saturate_amount
, 0.0, 1.0);
1032 color
[2] = Clamp(hsv
[2] + brighten_amount
, 0.0, 1.0);
1033 return SkHSVToColor(color
);
1036 void NativeThemeBase::DrawVertLine(SkCanvas
* canvas
,
1040 const SkPaint
& paint
) const {
1042 skrect
.set(x
, y1
, x
+ 1, y2
+ 1);
1043 canvas
->drawIRect(skrect
, paint
);
1046 void NativeThemeBase::DrawHorizLine(SkCanvas
* canvas
,
1050 const SkPaint
& paint
) const {
1052 skrect
.set(x1
, y
, x2
+ 1, y
+ 1);
1053 canvas
->drawIRect(skrect
, paint
);
1056 void NativeThemeBase::DrawBox(SkCanvas
* canvas
,
1057 const gfx::Rect
& rect
,
1058 const SkPaint
& paint
) const {
1059 const int right
= rect
.x() + rect
.width() - 1;
1060 const int bottom
= rect
.y() + rect
.height() - 1;
1061 DrawHorizLine(canvas
, rect
.x(), right
, rect
.y(), paint
);
1062 DrawVertLine(canvas
, right
, rect
.y(), bottom
, paint
);
1063 DrawHorizLine(canvas
, rect
.x(), right
, bottom
, paint
);
1064 DrawVertLine(canvas
, rect
.x(), rect
.y(), bottom
, paint
);
1067 SkScalar
NativeThemeBase::Clamp(SkScalar value
,
1069 SkScalar max
) const {
1070 return std::min(std::max(value
, min
), max
);
1073 SkColor
NativeThemeBase::OutlineColor(SkScalar
* hsv1
, SkScalar
* hsv2
) const {
1074 // GTK Theme engines have way too much control over the layout of
1075 // the scrollbar. We might be able to more closely approximate its
1076 // look-and-feel, if we sent whole images instead of just colors
1077 // from the browser to the renderer. But even then, some themes
1078 // would just break.
1080 // So, instead, we don't even try to 100% replicate the look of
1081 // the native scrollbar. We render our own version, but we make
1082 // sure to pick colors that blend in nicely with the system GTK
1083 // theme. In most cases, we can just sample a couple of pixels
1084 // from the system scrollbar and use those colors to draw our
1087 // This works fine for the track color and the overall thumb
1088 // color. But it fails spectacularly for the outline color used
1089 // around the thumb piece. Not all themes have a clearly defined
1090 // outline. For some of them it is partially transparent, and for
1091 // others the thickness is very unpredictable.
1093 // So, instead of trying to approximate the system theme, we
1094 // instead try to compute a reasonable looking choice based on the
1095 // known color of the track and the thumb piece. This is difficult
1096 // when trying to deal both with high- and low-contrast themes,
1097 // and both with positive and inverted themes.
1099 // The following code has been tested to look OK with all of the
1100 // default GTK themes.
1101 SkScalar min_diff
= Clamp((hsv1
[1] + hsv2
[1]) * 1.2f
, 0.28f
, 0.5f
);
1102 SkScalar diff
= Clamp(fabs(hsv1
[2] - hsv2
[2]) / 2, min_diff
, 0.5f
);
1104 if (hsv1
[2] + hsv2
[2] > 1.0)
1107 return SaturateAndBrighten(hsv2
, -0.2f
, diff
);