• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/native_theme/native_theme_base.h"
6 
7 #include <limits>
8 
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"
23 #include "ui/native_theme/common_theme.h"
24 
25 namespace {
26 
27 // These are the default dimensions of radio buttons and checkboxes.
28 const int kCheckboxAndRadioWidth = 13;
29 const int kCheckboxAndRadioHeight = 13;
30 
31 // These sizes match the sizes in Chromium Win.
32 const int kSliderThumbWidth = 11;
33 const int kSliderThumbHeight = 21;
34 
35 const SkColor kSliderTrackBackgroundColor =
36     SkColorSetRGB(0xe3, 0xdd, 0xd8);
37 const SkColor kSliderThumbLightGrey = SkColorSetRGB(0xf4, 0xf2, 0xef);
38 const SkColor kSliderThumbDarkGrey = SkColorSetRGB(0xea, 0xe5, 0xe0);
39 const SkColor kSliderThumbBorderDarkGrey =
40     SkColorSetRGB(0x9d, 0x96, 0x8e);
41 
42 const SkColor kTextBorderColor = SkColorSetRGB(0xa9, 0xa9, 0xa9);
43 
44 const SkColor kMenuPopupBackgroundColor = SkColorSetRGB(210, 225, 246);
45 
46 const unsigned int kDefaultScrollbarWidth = 15;
47 const unsigned int kDefaultScrollbarButtonLength = 14;
48 
49 const SkColor kCheckboxTinyColor = SK_ColorGRAY;
50 const SkColor kCheckboxShadowColor = SkColorSetARGB(0x15, 0, 0, 0);
51 const SkColor kCheckboxShadowHoveredColor = SkColorSetARGB(0x1F, 0, 0, 0);
52 const SkColor kCheckboxShadowDisabledColor = SkColorSetARGB(0, 0, 0, 0);
53 const SkColor kCheckboxGradientColors[] = {
54     SkColorSetRGB(0xed, 0xed, 0xed),
55     SkColorSetRGB(0xde, 0xde, 0xde) };
56 const SkColor kCheckboxGradientPressedColors[] = {
57     SkColorSetRGB(0xe7, 0xe7, 0xe7),
58     SkColorSetRGB(0xd7, 0xd7, 0xd7) };
59 const SkColor kCheckboxGradientHoveredColors[] = {
60     SkColorSetRGB(0xf0, 0xf0, 0xf0),
61     SkColorSetRGB(0xe0, 0xe0, 0xe0) };
62 const SkColor kCheckboxGradientDisabledColors[] = {
63     SkColorSetARGB(0x80, 0xed, 0xed, 0xed),
64     SkColorSetARGB(0x80, 0xde, 0xde, 0xde) };
65 const SkColor kCheckboxBorderColor = SkColorSetARGB(0x40, 0, 0, 0);
66 const SkColor kCheckboxBorderHoveredColor = SkColorSetARGB(0x4D, 0, 0, 0);
67 const SkColor kCheckboxBorderDisabledColor = SkColorSetARGB(0x20, 0, 0, 0);
68 const SkColor kCheckboxStrokeColor = SkColorSetARGB(0xB3, 0, 0, 0);
69 const SkColor kCheckboxStrokeDisabledColor = SkColorSetARGB(0x59, 0, 0, 0);
70 const SkColor kRadioDotColor = SkColorSetRGB(0x66, 0x66, 0x66);
71 const SkColor kRadioDotDisabledColor = SkColorSetARGB(0x80, 0x66, 0x66, 0x66);
72 
73 // Get lightness adjusted color.
BrightenColor(const color_utils::HSL & hsl,SkAlpha alpha,double lightness_amount)74 SkColor BrightenColor(const color_utils::HSL& hsl, SkAlpha alpha,
75     double lightness_amount) {
76   color_utils::HSL adjusted = hsl;
77   adjusted.l += lightness_amount;
78   if (adjusted.l > 1.0)
79     adjusted.l = 1.0;
80   if (adjusted.l < 0.0)
81     adjusted.l = 0.0;
82 
83   return color_utils::HSLToSkColor(adjusted, alpha);
84 }
85 
86 }  // namespace
87 
88 namespace ui {
89 
GetPartSize(Part part,State state,const ExtraParams & extra) const90 gfx::Size NativeThemeBase::GetPartSize(Part part,
91                                        State state,
92                                        const ExtraParams& extra) const {
93   gfx::Size size = CommonThemeGetPartSize(part, state, extra);
94   if (!size.IsEmpty())
95     return size;
96 
97   switch (part) {
98     // Please keep these in the order of NativeTheme::Part.
99     case kCheckbox:
100       return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight);
101     case kInnerSpinButton:
102       return gfx::Size(scrollbar_width_, 0);
103     case kMenuList:
104       return gfx::Size();  // No default size.
105     case kMenuCheck:
106     case kMenuCheckBackground:
107     case kMenuPopupArrow:
108       NOTIMPLEMENTED();
109       break;
110     case kMenuPopupBackground:
111       return gfx::Size();  // No default size.
112     case kMenuPopupGutter:
113     case kMenuPopupSeparator:
114       NOTIMPLEMENTED();
115       break;
116     case kMenuItemBackground:
117     case kProgressBar:
118     case kPushButton:
119       return gfx::Size();  // No default size.
120     case kRadio:
121       return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight);
122     case kScrollbarDownArrow:
123     case kScrollbarUpArrow:
124       return gfx::Size(scrollbar_width_, scrollbar_button_length_);
125     case kScrollbarLeftArrow:
126     case kScrollbarRightArrow:
127       return gfx::Size(scrollbar_button_length_, scrollbar_width_);
128     case kScrollbarHorizontalThumb:
129       // This matches Firefox on Linux.
130       return gfx::Size(2 * scrollbar_width_, scrollbar_width_);
131     case kScrollbarVerticalThumb:
132       // This matches Firefox on Linux.
133       return gfx::Size(scrollbar_width_, 2 * scrollbar_width_);
134     case kScrollbarHorizontalTrack:
135       return gfx::Size(0, scrollbar_width_);
136     case kScrollbarVerticalTrack:
137       return gfx::Size(scrollbar_width_, 0);
138     case kScrollbarHorizontalGripper:
139     case kScrollbarVerticalGripper:
140       NOTIMPLEMENTED();
141       break;
142     case kSliderTrack:
143       return gfx::Size();  // No default size.
144     case kSliderThumb:
145       // These sizes match the sizes in Chromium Win.
146       return gfx::Size(kSliderThumbWidth, kSliderThumbHeight);
147     case kTabPanelBackground:
148       NOTIMPLEMENTED();
149       break;
150     case kTextField:
151       return gfx::Size();  // No default size.
152     case kTrackbarThumb:
153     case kTrackbarTrack:
154     case kWindowResizeGripper:
155       NOTIMPLEMENTED();
156       break;
157     default:
158       NOTREACHED() << "Unknown theme part: " << part;
159       break;
160   }
161   return gfx::Size();
162 }
163 
PaintStateTransition(SkCanvas * canvas,Part part,State startState,State endState,double progress,const gfx::Rect & rect) const164 void NativeThemeBase::PaintStateTransition(SkCanvas* canvas,
165                                            Part part,
166                                            State startState,
167                                            State endState,
168                                            double progress,
169                                            const gfx::Rect& rect) const {
170   if (rect.IsEmpty())
171     return;
172 
173   // Currently state transition is animation only working for overlay scrollbars
174   // on Aura platforms.
175   switch (part) {
176     case kScrollbarHorizontalThumb:
177     case kScrollbarVerticalThumb:
178       PaintScrollbarThumbStateTransition(
179           canvas, startState, endState, progress, rect);
180       break;
181     default:
182       NOTREACHED() << "Does not support state transition for this part:"
183                    << part;
184       break;
185   }
186   return;
187 }
188 
Paint(SkCanvas * canvas,Part part,State state,const gfx::Rect & rect,const ExtraParams & extra) const189 void NativeThemeBase::Paint(SkCanvas* canvas,
190                             Part part,
191                             State state,
192                             const gfx::Rect& rect,
193                             const ExtraParams& extra) const {
194   if (rect.IsEmpty())
195     return;
196 
197   switch (part) {
198     // Please keep these in the order of NativeTheme::Part.
199     case kComboboxArrow:
200       CommonThemePaintComboboxArrow(canvas, rect);
201       break;
202     case kCheckbox:
203       PaintCheckbox(canvas, state, rect, extra.button);
204       break;
205     case kInnerSpinButton:
206       PaintInnerSpinButton(canvas, state, rect, extra.inner_spin);
207       break;
208     case kMenuList:
209       PaintMenuList(canvas, state, rect, extra.menu_list);
210       break;
211     case kMenuCheck:
212     case kMenuCheckBackground:
213     case kMenuPopupArrow:
214       NOTIMPLEMENTED();
215       break;
216     case kMenuPopupBackground:
217       PaintMenuPopupBackground(canvas, rect.size(), extra.menu_background);
218       break;
219     case kMenuPopupGutter:
220     case kMenuPopupSeparator:
221       NOTIMPLEMENTED();
222       break;
223     case kMenuItemBackground:
224       PaintMenuItemBackground(canvas, state, rect, extra.menu_list);
225       break;
226     case kProgressBar:
227       PaintProgressBar(canvas, state, rect, extra.progress_bar);
228       break;
229     case kPushButton:
230       PaintButton(canvas, state, rect, extra.button);
231       break;
232     case kRadio:
233       PaintRadio(canvas, state, rect, extra.button);
234       break;
235     case kScrollbarDownArrow:
236     case kScrollbarUpArrow:
237     case kScrollbarLeftArrow:
238     case kScrollbarRightArrow:
239       PaintArrowButton(canvas, rect, part, state);
240       break;
241     case kScrollbarHorizontalThumb:
242     case kScrollbarVerticalThumb:
243       PaintScrollbarThumb(canvas, part, state, rect);
244       break;
245     case kScrollbarHorizontalTrack:
246     case kScrollbarVerticalTrack:
247       PaintScrollbarTrack(canvas, part, state, extra.scrollbar_track, rect);
248       break;
249     case kScrollbarHorizontalGripper:
250     case kScrollbarVerticalGripper:
251       // Invoked by views scrollbar code, don't care about for non-win
252       // implementations, so no NOTIMPLEMENTED.
253       break;
254     case kScrollbarCorner:
255       PaintScrollbarCorner(canvas, state, rect);
256       break;
257     case kSliderTrack:
258       PaintSliderTrack(canvas, state, rect, extra.slider);
259       break;
260     case kSliderThumb:
261       PaintSliderThumb(canvas, state, rect, extra.slider);
262       break;
263     case kTabPanelBackground:
264       NOTIMPLEMENTED();
265       break;
266     case kTextField:
267       PaintTextField(canvas, state, rect, extra.text_field);
268       break;
269     case kTrackbarThumb:
270     case kTrackbarTrack:
271     case kWindowResizeGripper:
272       NOTIMPLEMENTED();
273       break;
274     default:
275       NOTREACHED() << "Unknown theme part: " << part;
276       break;
277   }
278 }
279 
NativeThemeBase()280 NativeThemeBase::NativeThemeBase()
281     : scrollbar_width_(kDefaultScrollbarWidth),
282       scrollbar_button_length_(kDefaultScrollbarButtonLength) {
283 }
284 
~NativeThemeBase()285 NativeThemeBase::~NativeThemeBase() {
286 }
287 
PaintArrowButton(SkCanvas * canvas,const gfx::Rect & rect,Part direction,State state) const288 void NativeThemeBase::PaintArrowButton(
289     SkCanvas* canvas,
290     const gfx::Rect& rect, Part direction, State state) const {
291   SkPaint paint;
292 
293   // Calculate button color.
294   SkScalar trackHSV[3];
295   SkColorToHSV(track_color_, trackHSV);
296   SkColor buttonColor = SaturateAndBrighten(trackHSV, 0, 0.2f);
297   SkColor backgroundColor = buttonColor;
298   if (state == kPressed) {
299     SkScalar buttonHSV[3];
300     SkColorToHSV(buttonColor, buttonHSV);
301     buttonColor = SaturateAndBrighten(buttonHSV, 0, -0.1f);
302   } else if (state == kHovered) {
303     SkScalar buttonHSV[3];
304     SkColorToHSV(buttonColor, buttonHSV);
305     buttonColor = SaturateAndBrighten(buttonHSV, 0, 0.05f);
306   }
307 
308   SkIRect skrect;
309   skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), rect.y()
310       + rect.height());
311   // Paint the background (the area visible behind the rounded corners).
312   paint.setColor(backgroundColor);
313   canvas->drawIRect(skrect, paint);
314 
315   // Paint the button's outline and fill the middle
316   SkPath outline;
317   switch (direction) {
318     case kScrollbarUpArrow:
319       outline.moveTo(rect.x() + 0.5, rect.y() + rect.height() + 0.5);
320       outline.rLineTo(0, -(rect.height() - 2));
321       outline.rLineTo(2, -2);
322       outline.rLineTo(rect.width() - 5, 0);
323       outline.rLineTo(2, 2);
324       outline.rLineTo(0, rect.height() - 2);
325       break;
326     case kScrollbarDownArrow:
327       outline.moveTo(rect.x() + 0.5, rect.y() - 0.5);
328       outline.rLineTo(0, rect.height() - 2);
329       outline.rLineTo(2, 2);
330       outline.rLineTo(rect.width() - 5, 0);
331       outline.rLineTo(2, -2);
332       outline.rLineTo(0, -(rect.height() - 2));
333       break;
334     case kScrollbarRightArrow:
335       outline.moveTo(rect.x() - 0.5, rect.y() + 0.5);
336       outline.rLineTo(rect.width() - 2, 0);
337       outline.rLineTo(2, 2);
338       outline.rLineTo(0, rect.height() - 5);
339       outline.rLineTo(-2, 2);
340       outline.rLineTo(-(rect.width() - 2), 0);
341       break;
342     case kScrollbarLeftArrow:
343       outline.moveTo(rect.x() + rect.width() + 0.5, rect.y() + 0.5);
344       outline.rLineTo(-(rect.width() - 2), 0);
345       outline.rLineTo(-2, 2);
346       outline.rLineTo(0, rect.height() - 5);
347       outline.rLineTo(2, 2);
348       outline.rLineTo(rect.width() - 2, 0);
349       break;
350     default:
351       break;
352   }
353   outline.close();
354 
355   paint.setStyle(SkPaint::kFill_Style);
356   paint.setColor(buttonColor);
357   canvas->drawPath(outline, paint);
358 
359   paint.setAntiAlias(true);
360   paint.setStyle(SkPaint::kStroke_Style);
361   SkScalar thumbHSV[3];
362   SkColorToHSV(thumb_inactive_color_, thumbHSV);
363   paint.setColor(OutlineColor(trackHSV, thumbHSV));
364   canvas->drawPath(outline, paint);
365 
366   PaintArrow(canvas, rect, direction, GetArrowColor(state));
367 }
368 
PaintArrow(SkCanvas * gc,const gfx::Rect & rect,Part direction,SkColor color) const369 void NativeThemeBase::PaintArrow(SkCanvas* gc,
370                                  const gfx::Rect& rect,
371                                  Part direction,
372                                  SkColor color) const {
373   int width_middle, length_middle;
374   if (direction == kScrollbarUpArrow || direction == kScrollbarDownArrow) {
375     width_middle = rect.width() / 2 + 1;
376     length_middle = rect.height() / 2 + 1;
377   } else {
378     length_middle = rect.width() / 2 + 1;
379     width_middle = rect.height() / 2 + 1;
380   }
381 
382   SkPaint paint;
383   paint.setColor(color);
384   paint.setAntiAlias(false);
385   paint.setStyle(SkPaint::kFill_Style);
386 
387   SkPath path;
388   // The constants in this block of code are hand-tailored to produce good
389   // looking arrows without anti-aliasing.
390   switch (direction) {
391     case kScrollbarUpArrow:
392       path.moveTo(rect.x() + width_middle - 4, rect.y() + length_middle + 2);
393       path.rLineTo(7, 0);
394       path.rLineTo(-4, -4);
395       break;
396     case kScrollbarDownArrow:
397       path.moveTo(rect.x() + width_middle - 4, rect.y() + length_middle - 3);
398       path.rLineTo(7, 0);
399       path.rLineTo(-4, 4);
400       break;
401     case kScrollbarRightArrow:
402       path.moveTo(rect.x() + length_middle - 3, rect.y() + width_middle - 4);
403       path.rLineTo(0, 7);
404       path.rLineTo(4, -4);
405       break;
406     case kScrollbarLeftArrow:
407       path.moveTo(rect.x() + length_middle + 1, rect.y() + width_middle - 5);
408       path.rLineTo(0, 9);
409       path.rLineTo(-4, -4);
410       break;
411     default:
412       break;
413   }
414   path.close();
415 
416   gc->drawPath(path, paint);
417 }
418 
PaintScrollbarTrack(SkCanvas * canvas,Part part,State state,const ScrollbarTrackExtraParams & extra_params,const gfx::Rect & rect) const419 void NativeThemeBase::PaintScrollbarTrack(SkCanvas* canvas,
420     Part part,
421     State state,
422     const ScrollbarTrackExtraParams& extra_params,
423     const gfx::Rect& rect) const {
424   SkPaint paint;
425   SkIRect skrect;
426 
427   skrect.set(rect.x(), rect.y(), rect.right(), rect.bottom());
428   SkScalar track_hsv[3];
429   SkColorToHSV(track_color_, track_hsv);
430   paint.setColor(SaturateAndBrighten(track_hsv, 0, 0));
431   canvas->drawIRect(skrect, paint);
432 
433   SkScalar thumb_hsv[3];
434   SkColorToHSV(thumb_inactive_color_, thumb_hsv);
435 
436   paint.setColor(OutlineColor(track_hsv, thumb_hsv));
437   DrawBox(canvas, rect, paint);
438 }
439 
PaintScrollbarThumb(SkCanvas * canvas,Part part,State state,const gfx::Rect & rect) const440 void NativeThemeBase::PaintScrollbarThumb(SkCanvas* canvas,
441                                            Part part,
442                                            State state,
443                                            const gfx::Rect& rect) const {
444   const bool hovered = state == kHovered;
445   const int midx = rect.x() + rect.width() / 2;
446   const int midy = rect.y() + rect.height() / 2;
447   const bool vertical = part == kScrollbarVerticalThumb;
448 
449   SkScalar thumb[3];
450   SkColorToHSV(hovered ? thumb_active_color_ : thumb_inactive_color_, thumb);
451 
452   SkPaint paint;
453   paint.setColor(SaturateAndBrighten(thumb, 0, 0.02f));
454 
455   SkIRect skrect;
456   if (vertical)
457     skrect.set(rect.x(), rect.y(), midx + 1, rect.y() + rect.height());
458   else
459     skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), midy + 1);
460 
461   canvas->drawIRect(skrect, paint);
462 
463   paint.setColor(SaturateAndBrighten(thumb, 0, -0.02f));
464 
465   if (vertical) {
466     skrect.set(
467         midx + 1, rect.y(), rect.x() + rect.width(), rect.y() + rect.height());
468   } else {
469     skrect.set(
470         rect.x(), midy + 1, rect.x() + rect.width(), rect.y() + rect.height());
471   }
472 
473   canvas->drawIRect(skrect, paint);
474 
475   SkScalar track[3];
476   SkColorToHSV(track_color_, track);
477   paint.setColor(OutlineColor(track, thumb));
478   DrawBox(canvas, rect, paint);
479 
480   if (rect.height() > 10 && rect.width() > 10) {
481     const int grippy_half_width = 2;
482     const int inter_grippy_offset = 3;
483     if (vertical) {
484       DrawHorizLine(canvas,
485                     midx - grippy_half_width,
486                     midx + grippy_half_width,
487                     midy - inter_grippy_offset,
488                     paint);
489       DrawHorizLine(canvas,
490                     midx - grippy_half_width,
491                     midx + grippy_half_width,
492                     midy,
493                     paint);
494       DrawHorizLine(canvas,
495                     midx - grippy_half_width,
496                     midx + grippy_half_width,
497                     midy + inter_grippy_offset,
498                     paint);
499     } else {
500       DrawVertLine(canvas,
501                    midx - inter_grippy_offset,
502                    midy - grippy_half_width,
503                    midy + grippy_half_width,
504                    paint);
505       DrawVertLine(canvas,
506                    midx,
507                    midy - grippy_half_width,
508                    midy + grippy_half_width,
509                    paint);
510       DrawVertLine(canvas,
511                    midx + inter_grippy_offset,
512                    midy - grippy_half_width,
513                    midy + grippy_half_width,
514                    paint);
515     }
516   }
517 }
518 
PaintScrollbarCorner(SkCanvas * canvas,State state,const gfx::Rect & rect) const519 void NativeThemeBase::PaintScrollbarCorner(SkCanvas* canvas,
520                                            State state,
521                                            const gfx::Rect& rect) const {
522   SkPaint paint;
523   paint.setColor(SK_ColorWHITE);
524   paint.setStyle(SkPaint::kFill_Style);
525   paint.setXfermodeMode(SkXfermode::kSrc_Mode);
526   canvas->drawIRect(RectToSkIRect(rect), paint);
527 }
528 
PaintCheckbox(SkCanvas * canvas,State state,const gfx::Rect & rect,const ButtonExtraParams & button) const529 void NativeThemeBase::PaintCheckbox(SkCanvas* canvas,
530                                     State state,
531                                     const gfx::Rect& rect,
532                                     const ButtonExtraParams& button) const {
533   SkRect skrect = PaintCheckboxRadioCommon(canvas, state, rect,
534                                            SkIntToScalar(2));
535   if (!skrect.isEmpty()) {
536     // Draw the checkmark / dash.
537     SkPaint paint;
538     paint.setAntiAlias(true);
539     paint.setStyle(SkPaint::kStroke_Style);
540     if (state == kDisabled)
541       paint.setColor(kCheckboxStrokeDisabledColor);
542     else
543       paint.setColor(kCheckboxStrokeColor);
544     if (button.indeterminate) {
545       SkPath dash;
546       dash.moveTo(skrect.x() + skrect.width() * 0.16,
547                   (skrect.y() + skrect.bottom()) / 2);
548       dash.rLineTo(skrect.width() * 0.68, 0);
549       paint.setStrokeWidth(SkFloatToScalar(skrect.height() * 0.2));
550       canvas->drawPath(dash, paint);
551     } else if (button.checked) {
552       SkPath check;
553       check.moveTo(skrect.x() + skrect.width() * 0.2,
554                    skrect.y() + skrect.height() * 0.5);
555       check.rLineTo(skrect.width() * 0.2, skrect.height() * 0.2);
556       paint.setStrokeWidth(SkFloatToScalar(skrect.height() * 0.23));
557       check.lineTo(skrect.right() - skrect.width() * 0.2,
558                    skrect.y() + skrect.height() * 0.2);
559       canvas->drawPath(check, paint);
560     }
561   }
562 }
563 
564 // Draws the common elements of checkboxes and radio buttons.
565 // Returns the rectangle within which any additional decorations should be
566 // drawn, or empty if none.
PaintCheckboxRadioCommon(SkCanvas * canvas,State state,const gfx::Rect & rect,const SkScalar borderRadius) const567 SkRect NativeThemeBase::PaintCheckboxRadioCommon(
568     SkCanvas* canvas,
569     State state,
570     const gfx::Rect& rect,
571     const SkScalar borderRadius) const {
572 
573   SkRect skrect = gfx::RectToSkRect(rect);
574 
575   // Use the largest square that fits inside the provided rectangle.
576   // No other browser seems to support non-square widget, so accidentally
577   // having non-square sizes is common (eg. amazon and webkit dev tools).
578   if (skrect.width() != skrect.height()) {
579     SkScalar size = SkMinScalar(skrect.width(), skrect.height());
580     skrect.inset((skrect.width() - size) / 2, (skrect.height() - size) / 2);
581   }
582 
583   // If the rectangle is too small then paint only a rectangle.  We don't want
584   // to have to worry about '- 1' and '+ 1' calculations below having overflow
585   // or underflow.
586   if (skrect.width() <= 2) {
587     SkPaint paint;
588     paint.setColor(kCheckboxTinyColor);
589     paint.setStyle(SkPaint::kFill_Style);
590     canvas->drawRect(skrect, paint);
591     // Too small to draw anything more.
592     return SkRect::MakeEmpty();
593   }
594 
595   // Make room for the drop shadow.
596   skrect.iset(skrect.x(), skrect.y(), skrect.right() - 1, skrect.bottom() - 1);
597 
598   // Draw the drop shadow below the widget.
599   if (state != kPressed) {
600     SkPaint paint;
601     paint.setAntiAlias(true);
602     SkRect shadowRect = skrect;
603     shadowRect.offset(0, 1);
604     if (state == kDisabled)
605      paint.setColor(kCheckboxShadowDisabledColor);
606     else if (state == kHovered)
607       paint.setColor(kCheckboxShadowHoveredColor);
608     else
609       paint.setColor(kCheckboxShadowColor);
610     paint.setStyle(SkPaint::kFill_Style);
611     canvas->drawRoundRect(shadowRect, borderRadius, borderRadius, paint);
612   }
613 
614   // Draw the gradient-filled rectangle
615   SkPoint gradient_bounds[3];
616   gradient_bounds[0].set(skrect.x(), skrect.y());
617   gradient_bounds[1].set(skrect.x(), skrect.y() + skrect.height() * 0.38);
618   gradient_bounds[2].set(skrect.x(), skrect.bottom());
619   const SkColor* startEndColors;
620   if (state == kPressed)
621     startEndColors = kCheckboxGradientPressedColors;
622   else if (state == kHovered)
623     startEndColors = kCheckboxGradientHoveredColors;
624   else if (state == kDisabled)
625     startEndColors = kCheckboxGradientDisabledColors;
626   else /* kNormal */
627     startEndColors = kCheckboxGradientColors;
628   SkColor colors[3] = {startEndColors[0], startEndColors[0], startEndColors[1]};
629   skia::RefPtr<SkShader> shader = skia::AdoptRef(
630       SkGradientShader::CreateLinear(
631           gradient_bounds, colors, NULL, 3, SkShader::kClamp_TileMode));
632   SkPaint paint;
633   paint.setAntiAlias(true);
634   paint.setShader(shader.get());
635   paint.setStyle(SkPaint::kFill_Style);
636   canvas->drawRoundRect(skrect, borderRadius, borderRadius, paint);
637   paint.setShader(NULL);
638 
639   // Draw the border.
640   if (state == kHovered)
641     paint.setColor(kCheckboxBorderHoveredColor);
642   else if (state == kDisabled)
643     paint.setColor(kCheckboxBorderDisabledColor);
644   else
645     paint.setColor(kCheckboxBorderColor);
646   paint.setStyle(SkPaint::kStroke_Style);
647   paint.setStrokeWidth(SkIntToScalar(1));
648   skrect.inset(SkFloatToScalar(.5f), SkFloatToScalar(.5f));
649   canvas->drawRoundRect(skrect, borderRadius, borderRadius, paint);
650 
651   // Return the rectangle excluding the drop shadow for drawing any additional
652   // decorations.
653   return skrect;
654 }
655 
PaintRadio(SkCanvas * canvas,State state,const gfx::Rect & rect,const ButtonExtraParams & button) const656 void NativeThemeBase::PaintRadio(SkCanvas* canvas,
657                                   State state,
658                                   const gfx::Rect& rect,
659                                   const ButtonExtraParams& button) const {
660 
661   // Most of a radio button is the same as a checkbox, except the the rounded
662   // square is a circle (i.e. border radius >= 100%).
663   const SkScalar radius = SkFloatToScalar(
664       static_cast<float>(std::max(rect.width(), rect.height())) / 2);
665   SkRect skrect = PaintCheckboxRadioCommon(canvas, state, rect, radius);
666   if (!skrect.isEmpty() && button.checked) {
667     // Draw the dot.
668     SkPaint paint;
669     paint.setAntiAlias(true);
670     paint.setStyle(SkPaint::kFill_Style);
671     if (state == kDisabled)
672       paint.setColor(kRadioDotDisabledColor);
673     else
674       paint.setColor(kRadioDotColor);
675     skrect.inset(skrect.width() * 0.25, skrect.height() * 0.25);
676     // Use drawRoundedRect instead of drawOval to be completely consistent
677     // with the border in PaintCheckboxRadioNewCommon.
678     canvas->drawRoundRect(skrect, radius, radius, paint);
679   }
680 }
681 
PaintButton(SkCanvas * canvas,State state,const gfx::Rect & rect,const ButtonExtraParams & button) const682 void NativeThemeBase::PaintButton(SkCanvas* canvas,
683                                   State state,
684                                   const gfx::Rect& rect,
685                                   const ButtonExtraParams& button) const {
686   SkPaint paint;
687   const int kRight = rect.right();
688   const int kBottom = rect.bottom();
689   SkRect skrect = SkRect::MakeLTRB(rect.x(), rect.y(), kRight, kBottom);
690   SkColor base_color = button.background_color;
691 
692   color_utils::HSL base_hsl;
693   color_utils::SkColorToHSL(base_color, &base_hsl);
694 
695   // Our standard gradient is from 0xdd to 0xf8. This is the amount of
696   // increased luminance between those values.
697   SkColor light_color(BrightenColor(base_hsl, SkColorGetA(base_color), 0.105));
698 
699   // If the button is too small, fallback to drawing a single, solid color
700   if (rect.width() < 5 || rect.height() < 5) {
701     paint.setColor(base_color);
702     canvas->drawRect(skrect, paint);
703     return;
704   }
705 
706   paint.setColor(SK_ColorBLACK);
707   const int kLightEnd = state == kPressed ? 1 : 0;
708   const int kDarkEnd = !kLightEnd;
709   SkPoint gradient_bounds[2];
710   gradient_bounds[kLightEnd].iset(rect.x(), rect.y());
711   gradient_bounds[kDarkEnd].iset(rect.x(), kBottom - 1);
712   SkColor colors[2];
713   colors[0] = light_color;
714   colors[1] = base_color;
715 
716   skia::RefPtr<SkShader> shader = skia::AdoptRef(
717       SkGradientShader::CreateLinear(
718           gradient_bounds, colors, NULL, 2, SkShader::kClamp_TileMode));
719   paint.setStyle(SkPaint::kFill_Style);
720   paint.setAntiAlias(true);
721   paint.setShader(shader.get());
722 
723   canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint);
724   paint.setShader(NULL);
725 
726   if (button.has_border) {
727     int border_alpha = state == kHovered ? 0x80 : 0x55;
728     if (button.is_focused) {
729       border_alpha = 0xff;
730       paint.setColor(GetSystemColor(kColorId_FocusedBorderColor));
731     }
732     paint.setStyle(SkPaint::kStroke_Style);
733     paint.setStrokeWidth(SkIntToScalar(1));
734     paint.setAlpha(border_alpha);
735     skrect.inset(SkFloatToScalar(.5f), SkFloatToScalar(.5f));
736     canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint);
737   }
738 }
739 
PaintTextField(SkCanvas * canvas,State state,const gfx::Rect & rect,const TextFieldExtraParams & text) const740 void NativeThemeBase::PaintTextField(SkCanvas* canvas,
741                                      State state,
742                                      const gfx::Rect& rect,
743                                      const TextFieldExtraParams& text) const {
744   SkRect bounds;
745   bounds.set(rect.x(), rect.y(), rect.right() - 1, rect.bottom() - 1);
746 
747   SkPaint fill_paint;
748   fill_paint.setStyle(SkPaint::kFill_Style);
749   fill_paint.setColor(text.background_color);
750   canvas->drawRect(bounds, fill_paint);
751 
752   // Text INPUT, listbox SELECT, and TEXTAREA have consistent borders.
753   // border: 1px solid #a9a9a9
754   SkPaint stroke_paint;
755   stroke_paint.setStyle(SkPaint::kStroke_Style);
756   stroke_paint.setColor(kTextBorderColor);
757   canvas->drawRect(bounds, stroke_paint);
758 }
759 
PaintMenuList(SkCanvas * canvas,State state,const gfx::Rect & rect,const MenuListExtraParams & menu_list) const760 void NativeThemeBase::PaintMenuList(
761     SkCanvas* canvas,
762     State state,
763     const gfx::Rect& rect,
764     const MenuListExtraParams& menu_list) const {
765   // If a border radius is specified, we let the WebCore paint the background
766   // and the border of the control.
767   if (!menu_list.has_border_radius) {
768     ButtonExtraParams button = { 0 };
769     button.background_color = menu_list.background_color;
770     button.has_border = menu_list.has_border;
771     PaintButton(canvas, state, rect, button);
772   }
773 
774   SkPaint paint;
775   paint.setColor(SK_ColorBLACK);
776   paint.setAntiAlias(true);
777   paint.setStyle(SkPaint::kFill_Style);
778 
779   SkPath path;
780   path.moveTo(menu_list.arrow_x, menu_list.arrow_y - 3);
781   path.rLineTo(6, 0);
782   path.rLineTo(-3, 6);
783   path.close();
784   canvas->drawPath(path, paint);
785 }
786 
PaintMenuPopupBackground(SkCanvas * canvas,const gfx::Size & size,const MenuBackgroundExtraParams & menu_background) const787 void NativeThemeBase::PaintMenuPopupBackground(
788     SkCanvas* canvas,
789     const gfx::Size& size,
790     const MenuBackgroundExtraParams& menu_background) const {
791   canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode);
792 }
793 
PaintMenuItemBackground(SkCanvas * canvas,State state,const gfx::Rect & rect,const MenuListExtraParams & menu_list) const794 void NativeThemeBase::PaintMenuItemBackground(
795     SkCanvas* canvas,
796     State state,
797     const gfx::Rect& rect,
798     const MenuListExtraParams& menu_list) const {
799   // By default don't draw anything over the normal background.
800 }
801 
PaintSliderTrack(SkCanvas * canvas,State state,const gfx::Rect & rect,const SliderExtraParams & slider) const802 void NativeThemeBase::PaintSliderTrack(SkCanvas* canvas,
803                                        State state,
804                                        const gfx::Rect& rect,
805                                        const SliderExtraParams& slider) const {
806   const int kMidX = rect.x() + rect.width() / 2;
807   const int kMidY = rect.y() + rect.height() / 2;
808 
809   SkPaint paint;
810   paint.setColor(kSliderTrackBackgroundColor);
811 
812   SkRect skrect;
813   if (slider.vertical) {
814     skrect.set(std::max(rect.x(), kMidX - 2),
815                rect.y(),
816                std::min(rect.right(), kMidX + 2),
817                rect.bottom());
818   } else {
819     skrect.set(rect.x(),
820                std::max(rect.y(), kMidY - 2),
821                rect.right(),
822                std::min(rect.bottom(), kMidY + 2));
823   }
824   canvas->drawRect(skrect, paint);
825 }
826 
PaintSliderThumb(SkCanvas * canvas,State state,const gfx::Rect & rect,const SliderExtraParams & slider) const827 void NativeThemeBase::PaintSliderThumb(SkCanvas* canvas,
828                                        State state,
829                                        const gfx::Rect& rect,
830                                        const SliderExtraParams& slider) const {
831   const bool hovered = (state == kHovered) || slider.in_drag;
832   const int kMidX = rect.x() + rect.width() / 2;
833   const int kMidY = rect.y() + rect.height() / 2;
834 
835   SkPaint paint;
836   paint.setColor(hovered ? SK_ColorWHITE : kSliderThumbLightGrey);
837 
838   SkIRect skrect;
839   if (slider.vertical)
840     skrect.set(rect.x(), rect.y(), kMidX + 1, rect.bottom());
841   else
842     skrect.set(rect.x(), rect.y(), rect.right(), kMidY + 1);
843 
844   canvas->drawIRect(skrect, paint);
845 
846   paint.setColor(hovered ? kSliderThumbLightGrey : kSliderThumbDarkGrey);
847 
848   if (slider.vertical)
849     skrect.set(kMidX + 1, rect.y(), rect.right(), rect.bottom());
850   else
851     skrect.set(rect.x(), kMidY + 1, rect.right(), rect.bottom());
852 
853   canvas->drawIRect(skrect, paint);
854 
855   paint.setColor(kSliderThumbBorderDarkGrey);
856   DrawBox(canvas, rect, paint);
857 
858   if (rect.height() > 10 && rect.width() > 10) {
859     DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY, paint);
860     DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY - 3, paint);
861     DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY + 3, paint);
862   }
863 }
864 
PaintInnerSpinButton(SkCanvas * canvas,State state,const gfx::Rect & rect,const InnerSpinButtonExtraParams & spin_button) const865 void NativeThemeBase::PaintInnerSpinButton(SkCanvas* canvas,
866     State state,
867     const gfx::Rect& rect,
868     const InnerSpinButtonExtraParams& spin_button) const {
869   if (spin_button.read_only)
870     state = kDisabled;
871 
872   State north_state = state;
873   State south_state = state;
874   if (spin_button.spin_up)
875     south_state = south_state != kDisabled ? kNormal : kDisabled;
876   else
877     north_state = north_state != kDisabled ? kNormal : kDisabled;
878 
879   gfx::Rect half = rect;
880   half.set_height(rect.height() / 2);
881   PaintArrowButton(canvas, half, kScrollbarUpArrow, north_state);
882 
883   half.set_y(rect.y() + rect.height() / 2);
884   PaintArrowButton(canvas, half, kScrollbarDownArrow, south_state);
885 }
886 
PaintProgressBar(SkCanvas * canvas,State state,const gfx::Rect & rect,const ProgressBarExtraParams & progress_bar) const887 void NativeThemeBase::PaintProgressBar(SkCanvas* canvas,
888     State state,
889     const gfx::Rect& rect,
890     const ProgressBarExtraParams& progress_bar) const {
891   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
892   gfx::ImageSkia* bar_image = rb.GetImageSkiaNamed(IDR_PROGRESS_BAR);
893   gfx::ImageSkia* left_border_image = rb.GetImageSkiaNamed(
894       IDR_PROGRESS_BORDER_LEFT);
895   gfx::ImageSkia* right_border_image = rb.GetImageSkiaNamed(
896       IDR_PROGRESS_BORDER_RIGHT);
897 
898   DCHECK(bar_image->width() > 0);
899   DCHECK(rect.width() > 0);
900 
901   float tile_scale_y = static_cast<float>(rect.height()) / bar_image->height();
902 
903   int dest_left_border_width = left_border_image->width();
904   int dest_right_border_width = right_border_image->width();
905 
906   // Since an implicit float -> int conversion will truncate, we want to make
907   // sure that if a border is desired, it gets at least one pixel.
908   if (dest_left_border_width > 0) {
909     dest_left_border_width = dest_left_border_width * tile_scale_y;
910     dest_left_border_width = std::max(dest_left_border_width, 1);
911   }
912   if (dest_right_border_width > 0) {
913     dest_right_border_width = dest_right_border_width * tile_scale_y;
914     dest_right_border_width = std::max(dest_right_border_width, 1);
915   }
916 
917   // Since the width of the progress bar may not be evenly divisible by the
918   // tile size, in order to make it look right we may need to draw some of the
919   // with a width of 1 pixel smaller than the rest of the tiles.
920   int new_tile_width = static_cast<int>(bar_image->width() * tile_scale_y);
921   new_tile_width = std::max(new_tile_width, 1);
922 
923   float tile_scale_x = static_cast<float>(new_tile_width) / bar_image->width();
924   if (rect.width() % new_tile_width == 0) {
925     DrawTiledImage(canvas, *bar_image, 0, 0, tile_scale_x, tile_scale_y,
926         rect.x(), rect.y(),
927         rect.width(), rect.height());
928   } else {
929     int num_tiles = 1 + rect.width() / new_tile_width;
930     int overshoot = num_tiles * new_tile_width - rect.width();
931     // Since |overshoot| represents the number of tiles that were too big, draw
932     // |overshoot| tiles with their width reduced by 1.
933     int num_big_tiles = num_tiles - overshoot;
934     int num_small_tiles = overshoot;
935     int small_width = new_tile_width - 1;
936     float small_scale_x = static_cast<float>(small_width) / bar_image->width();
937     float big_scale_x = tile_scale_x;
938 
939     gfx::Rect big_rect = rect;
940     gfx::Rect small_rect = rect;
941     big_rect.Inset(0, 0, num_small_tiles*small_width, 0);
942     small_rect.Inset(num_big_tiles*new_tile_width, 0, 0, 0);
943 
944     DrawTiledImage(canvas, *bar_image, 0, 0, big_scale_x, tile_scale_y,
945       big_rect.x(), big_rect.y(), big_rect.width(), big_rect.height());
946     DrawTiledImage(canvas, *bar_image, 0, 0, small_scale_x, tile_scale_y,
947       small_rect.x(), small_rect.y(), small_rect.width(), small_rect.height());
948   }
949   if (progress_bar.value_rect_width) {
950     gfx::ImageSkia* value_image = rb.GetImageSkiaNamed(IDR_PROGRESS_VALUE);
951 
952     new_tile_width = static_cast<int>(value_image->width() * tile_scale_y);
953     tile_scale_x = static_cast<float>(new_tile_width) /
954         value_image->width();
955 
956     DrawTiledImage(canvas, *value_image, 0, 0, tile_scale_x, tile_scale_y,
957         progress_bar.value_rect_x,
958         progress_bar.value_rect_y,
959         progress_bar.value_rect_width,
960         progress_bar.value_rect_height);
961   }
962 
963   DrawImageInt(canvas, *left_border_image, 0, 0, left_border_image->width(),
964       left_border_image->height(), rect.x(), rect.y(), dest_left_border_width,
965       rect.height());
966 
967   int dest_x = rect.right() - dest_right_border_width;
968   DrawImageInt(canvas, *right_border_image, 0, 0, right_border_image->width(),
969                right_border_image->height(), dest_x, rect.y(),
970                dest_right_border_width, rect.height());
971 }
972 
IntersectsClipRectInt(SkCanvas * canvas,int x,int y,int w,int h) const973 bool NativeThemeBase::IntersectsClipRectInt(SkCanvas* canvas,
974                                             int x, int y, int w, int h) const {
975   SkRect clip;
976   return canvas->getClipBounds(&clip) &&
977       clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w),
978                      SkIntToScalar(y + h));
979 }
980 
DrawImageInt(SkCanvas * sk_canvas,const gfx::ImageSkia & image,int src_x,int src_y,int src_w,int src_h,int dest_x,int dest_y,int dest_w,int dest_h) const981 void NativeThemeBase::DrawImageInt(
982     SkCanvas* sk_canvas, const gfx::ImageSkia& image,
983     int src_x, int src_y, int src_w, int src_h,
984     int dest_x, int dest_y, int dest_w, int dest_h) const {
985   scoped_ptr<gfx::Canvas> canvas(CommonThemeCreateCanvas(sk_canvas));
986   canvas->DrawImageInt(image, src_x, src_y, src_w, src_h,
987       dest_x, dest_y, dest_w, dest_h, true);
988 }
989 
DrawTiledImage(SkCanvas * sk_canvas,const gfx::ImageSkia & image,int src_x,int src_y,float tile_scale_x,float tile_scale_y,int dest_x,int dest_y,int w,int h) const990 void NativeThemeBase::DrawTiledImage(SkCanvas* sk_canvas,
991     const gfx::ImageSkia& image,
992     int src_x, int src_y, float tile_scale_x, float tile_scale_y,
993     int dest_x, int dest_y, int w, int h) const {
994   scoped_ptr<gfx::Canvas> canvas(CommonThemeCreateCanvas(sk_canvas));
995   canvas->TileImageInt(image, src_x, src_y, tile_scale_x,
996       tile_scale_y, dest_x, dest_y, w, h);
997 }
998 
SaturateAndBrighten(SkScalar * hsv,SkScalar saturate_amount,SkScalar brighten_amount) const999 SkColor NativeThemeBase::SaturateAndBrighten(SkScalar* hsv,
1000                                              SkScalar saturate_amount,
1001                                              SkScalar brighten_amount) const {
1002   SkScalar color[3];
1003   color[0] = hsv[0];
1004   color[1] = Clamp(hsv[1] + saturate_amount, 0.0, 1.0);
1005   color[2] = Clamp(hsv[2] + brighten_amount, 0.0, 1.0);
1006   return SkHSVToColor(color);
1007 }
1008 
GetArrowColor(State state) const1009 SkColor NativeThemeBase::GetArrowColor(State state) const {
1010   if (state != kDisabled)
1011     return SK_ColorBLACK;
1012 
1013   SkScalar track_hsv[3];
1014   SkColorToHSV(track_color_, track_hsv);
1015   SkScalar thumb_hsv[3];
1016   SkColorToHSV(thumb_inactive_color_, thumb_hsv);
1017   return OutlineColor(track_hsv, thumb_hsv);
1018 }
1019 
DrawVertLine(SkCanvas * canvas,int x,int y1,int y2,const SkPaint & paint) const1020 void NativeThemeBase::DrawVertLine(SkCanvas* canvas,
1021                                    int x,
1022                                    int y1,
1023                                    int y2,
1024                                    const SkPaint& paint) const {
1025   SkIRect skrect;
1026   skrect.set(x, y1, x + 1, y2 + 1);
1027   canvas->drawIRect(skrect, paint);
1028 }
1029 
DrawHorizLine(SkCanvas * canvas,int x1,int x2,int y,const SkPaint & paint) const1030 void NativeThemeBase::DrawHorizLine(SkCanvas* canvas,
1031                                     int x1,
1032                                     int x2,
1033                                     int y,
1034                                     const SkPaint& paint) const {
1035   SkIRect skrect;
1036   skrect.set(x1, y, x2 + 1, y + 1);
1037   canvas->drawIRect(skrect, paint);
1038 }
1039 
DrawBox(SkCanvas * canvas,const gfx::Rect & rect,const SkPaint & paint) const1040 void NativeThemeBase::DrawBox(SkCanvas* canvas,
1041                               const gfx::Rect& rect,
1042                               const SkPaint& paint) const {
1043   const int right = rect.x() + rect.width() - 1;
1044   const int bottom = rect.y() + rect.height() - 1;
1045   DrawHorizLine(canvas, rect.x(), right, rect.y(), paint);
1046   DrawVertLine(canvas, right, rect.y(), bottom, paint);
1047   DrawHorizLine(canvas, rect.x(), right, bottom, paint);
1048   DrawVertLine(canvas, rect.x(), rect.y(), bottom, paint);
1049 }
1050 
Clamp(SkScalar value,SkScalar min,SkScalar max) const1051 SkScalar NativeThemeBase::Clamp(SkScalar value,
1052                                 SkScalar min,
1053                                 SkScalar max) const {
1054   return std::min(std::max(value, min), max);
1055 }
1056 
OutlineColor(SkScalar * hsv1,SkScalar * hsv2) const1057 SkColor NativeThemeBase::OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const {
1058   // GTK Theme engines have way too much control over the layout of
1059   // the scrollbar. We might be able to more closely approximate its
1060   // look-and-feel, if we sent whole images instead of just colors
1061   // from the browser to the renderer. But even then, some themes
1062   // would just break.
1063   //
1064   // So, instead, we don't even try to 100% replicate the look of
1065   // the native scrollbar. We render our own version, but we make
1066   // sure to pick colors that blend in nicely with the system GTK
1067   // theme. In most cases, we can just sample a couple of pixels
1068   // from the system scrollbar and use those colors to draw our
1069   // scrollbar.
1070   //
1071   // This works fine for the track color and the overall thumb
1072   // color. But it fails spectacularly for the outline color used
1073   // around the thumb piece.  Not all themes have a clearly defined
1074   // outline. For some of them it is partially transparent, and for
1075   // others the thickness is very unpredictable.
1076   //
1077   // So, instead of trying to approximate the system theme, we
1078   // instead try to compute a reasonable looking choice based on the
1079   // known color of the track and the thumb piece. This is difficult
1080   // when trying to deal both with high- and low-contrast themes,
1081   // and both with positive and inverted themes.
1082   //
1083   // The following code has been tested to look OK with all of the
1084   // default GTK themes.
1085   SkScalar min_diff = Clamp((hsv1[1] + hsv2[1]) * 1.2f, 0.28f, 0.5f);
1086   SkScalar diff = Clamp(fabs(hsv1[2] - hsv2[2]) / 2, min_diff, 0.5f);
1087 
1088   if (hsv1[2] + hsv2[2] > 1.0)
1089     diff = -diff;
1090 
1091   return SaturateAndBrighten(hsv2, -0.2f, diff);
1092 }
1093 
1094 }  // namespace ui
1095