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