Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ui / native_theme / native_theme_base.cc
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 "third_party/skia/include/effects/SkGradientShader.h"
13 #include "ui/base/layout.h"
14 #include "ui/base/resource/resource_bundle.h"
15 #include "ui/base/ui_base_switches.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/color_utils.h"
18 #include "ui/gfx/image/image_skia.h"
19 #include "ui/gfx/rect.h"
20 #include "ui/gfx/size.h"
21 #include "ui/gfx/skia_util.h"
22 #include "ui/native_theme/common_theme.h"
23 #include "ui/resources/grit/ui_resources.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.
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
90 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
164 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
189 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       if (scrollbar_button_length_ > 0)
240         PaintArrowButton(canvas, rect, part, state);
241       break;
242     case kScrollbarHorizontalThumb:
243     case kScrollbarVerticalThumb:
244       PaintScrollbarThumb(canvas, part, state, rect);
245       break;
246     case kScrollbarHorizontalTrack:
247     case kScrollbarVerticalTrack:
248       PaintScrollbarTrack(canvas, part, state, extra.scrollbar_track, rect);
249       break;
250     case kScrollbarHorizontalGripper:
251     case kScrollbarVerticalGripper:
252       // Invoked by views scrollbar code, don't care about for non-win
253       // implementations, so no NOTIMPLEMENTED.
254       break;
255     case kScrollbarCorner:
256       PaintScrollbarCorner(canvas, state, rect);
257       break;
258     case kSliderTrack:
259       PaintSliderTrack(canvas, state, rect, extra.slider);
260       break;
261     case kSliderThumb:
262       PaintSliderThumb(canvas, state, rect, extra.slider);
263       break;
264     case kTabPanelBackground:
265       NOTIMPLEMENTED();
266       break;
267     case kTextField:
268       PaintTextField(canvas, state, rect, extra.text_field);
269       break;
270     case kTrackbarThumb:
271     case kTrackbarTrack:
272     case kWindowResizeGripper:
273       NOTIMPLEMENTED();
274       break;
275     default:
276       NOTREACHED() << "Unknown theme part: " << part;
277       break;
278   }
279 }
280
281 NativeThemeBase::NativeThemeBase()
282     : scrollbar_width_(kDefaultScrollbarWidth),
283       scrollbar_button_length_(kDefaultScrollbarButtonLength) {
284 }
285
286 NativeThemeBase::~NativeThemeBase() {
287 }
288
289 void NativeThemeBase::PaintArrowButton(
290     SkCanvas* canvas,
291     const gfx::Rect& rect, Part direction, State state) const {
292   SkPaint paint;
293
294   // Calculate button color.
295   SkScalar trackHSV[3];
296   SkColorToHSV(track_color_, trackHSV);
297   SkColor buttonColor = SaturateAndBrighten(trackHSV, 0, 0.2f);
298   SkColor backgroundColor = buttonColor;
299   if (state == kPressed) {
300     SkScalar buttonHSV[3];
301     SkColorToHSV(buttonColor, buttonHSV);
302     buttonColor = SaturateAndBrighten(buttonHSV, 0, -0.1f);
303   } else if (state == kHovered) {
304     SkScalar buttonHSV[3];
305     SkColorToHSV(buttonColor, buttonHSV);
306     buttonColor = SaturateAndBrighten(buttonHSV, 0, 0.05f);
307   }
308
309   SkIRect skrect;
310   skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), rect.y()
311       + rect.height());
312   // Paint the background (the area visible behind the rounded corners).
313   paint.setColor(backgroundColor);
314   canvas->drawIRect(skrect, paint);
315
316   // Paint the button's outline and fill the middle
317   SkPath outline;
318   switch (direction) {
319     case kScrollbarUpArrow:
320       outline.moveTo(rect.x() + 0.5, rect.y() + rect.height() + 0.5);
321       outline.rLineTo(0, -(rect.height() - 2));
322       outline.rLineTo(2, -2);
323       outline.rLineTo(rect.width() - 5, 0);
324       outline.rLineTo(2, 2);
325       outline.rLineTo(0, rect.height() - 2);
326       break;
327     case kScrollbarDownArrow:
328       outline.moveTo(rect.x() + 0.5, rect.y() - 0.5);
329       outline.rLineTo(0, rect.height() - 2);
330       outline.rLineTo(2, 2);
331       outline.rLineTo(rect.width() - 5, 0);
332       outline.rLineTo(2, -2);
333       outline.rLineTo(0, -(rect.height() - 2));
334       break;
335     case kScrollbarRightArrow:
336       outline.moveTo(rect.x() - 0.5, rect.y() + 0.5);
337       outline.rLineTo(rect.width() - 2, 0);
338       outline.rLineTo(2, 2);
339       outline.rLineTo(0, rect.height() - 5);
340       outline.rLineTo(-2, 2);
341       outline.rLineTo(-(rect.width() - 2), 0);
342       break;
343     case kScrollbarLeftArrow:
344       outline.moveTo(rect.x() + rect.width() + 0.5, rect.y() + 0.5);
345       outline.rLineTo(-(rect.width() - 2), 0);
346       outline.rLineTo(-2, 2);
347       outline.rLineTo(0, rect.height() - 5);
348       outline.rLineTo(2, 2);
349       outline.rLineTo(rect.width() - 2, 0);
350       break;
351     default:
352       break;
353   }
354   outline.close();
355
356   paint.setStyle(SkPaint::kFill_Style);
357   paint.setColor(buttonColor);
358   canvas->drawPath(outline, paint);
359
360   paint.setAntiAlias(true);
361   paint.setStyle(SkPaint::kStroke_Style);
362   SkScalar thumbHSV[3];
363   SkColorToHSV(thumb_inactive_color_, thumbHSV);
364   paint.setColor(OutlineColor(trackHSV, thumbHSV));
365   canvas->drawPath(outline, paint);
366
367   PaintArrow(canvas, rect, direction, GetArrowColor(state));
368 }
369
370 void NativeThemeBase::PaintArrow(SkCanvas* gc,
371                                  const gfx::Rect& rect,
372                                  Part direction,
373                                  SkColor color) const {
374   int width_middle, length_middle;
375   if (direction == kScrollbarUpArrow || direction == kScrollbarDownArrow) {
376     width_middle = rect.width() / 2 + 1;
377     length_middle = rect.height() / 2 + 1;
378   } else {
379     length_middle = rect.width() / 2 + 1;
380     width_middle = rect.height() / 2 + 1;
381   }
382
383   SkPaint paint;
384   paint.setColor(color);
385   paint.setAntiAlias(false);
386   paint.setStyle(SkPaint::kFill_Style);
387
388   SkPath path;
389   // The constants in this block of code are hand-tailored to produce good
390   // looking arrows without anti-aliasing.
391   switch (direction) {
392     case kScrollbarUpArrow:
393       path.moveTo(rect.x() + width_middle - 4, rect.y() + length_middle + 2);
394       path.rLineTo(7, 0);
395       path.rLineTo(-4, -4);
396       break;
397     case kScrollbarDownArrow:
398       path.moveTo(rect.x() + width_middle - 4, rect.y() + length_middle - 3);
399       path.rLineTo(7, 0);
400       path.rLineTo(-4, 4);
401       break;
402     case kScrollbarRightArrow:
403       path.moveTo(rect.x() + length_middle - 3, rect.y() + width_middle - 4);
404       path.rLineTo(0, 7);
405       path.rLineTo(4, -4);
406       break;
407     case kScrollbarLeftArrow:
408       path.moveTo(rect.x() + length_middle + 1, rect.y() + width_middle - 5);
409       path.rLineTo(0, 9);
410       path.rLineTo(-4, -4);
411       break;
412     default:
413       break;
414   }
415   path.close();
416
417   gc->drawPath(path, paint);
418 }
419
420 void NativeThemeBase::PaintScrollbarTrack(SkCanvas* canvas,
421     Part part,
422     State state,
423     const ScrollbarTrackExtraParams& extra_params,
424     const gfx::Rect& rect) const {
425   SkPaint paint;
426   SkIRect skrect;
427
428   skrect.set(rect.x(), rect.y(), rect.right(), rect.bottom());
429   SkScalar track_hsv[3];
430   SkColorToHSV(track_color_, track_hsv);
431   paint.setColor(SaturateAndBrighten(track_hsv, 0, 0));
432   canvas->drawIRect(skrect, paint);
433
434   SkScalar thumb_hsv[3];
435   SkColorToHSV(thumb_inactive_color_, thumb_hsv);
436
437   paint.setColor(OutlineColor(track_hsv, thumb_hsv));
438   DrawBox(canvas, rect, paint);
439 }
440
441 void NativeThemeBase::PaintScrollbarThumb(SkCanvas* canvas,
442                                            Part part,
443                                            State state,
444                                            const gfx::Rect& rect) const {
445   const bool hovered = state == kHovered;
446   const int midx = rect.x() + rect.width() / 2;
447   const int midy = rect.y() + rect.height() / 2;
448   const bool vertical = part == kScrollbarVerticalThumb;
449
450   SkScalar thumb[3];
451   SkColorToHSV(hovered ? thumb_active_color_ : thumb_inactive_color_, thumb);
452
453   SkPaint paint;
454   paint.setColor(SaturateAndBrighten(thumb, 0, 0.02f));
455
456   SkIRect skrect;
457   if (vertical)
458     skrect.set(rect.x(), rect.y(), midx + 1, rect.y() + rect.height());
459   else
460     skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), midy + 1);
461
462   canvas->drawIRect(skrect, paint);
463
464   paint.setColor(SaturateAndBrighten(thumb, 0, -0.02f));
465
466   if (vertical) {
467     skrect.set(
468         midx + 1, rect.y(), rect.x() + rect.width(), rect.y() + rect.height());
469   } else {
470     skrect.set(
471         rect.x(), midy + 1, rect.x() + rect.width(), rect.y() + rect.height());
472   }
473
474   canvas->drawIRect(skrect, paint);
475
476   SkScalar track[3];
477   SkColorToHSV(track_color_, track);
478   paint.setColor(OutlineColor(track, thumb));
479   DrawBox(canvas, rect, paint);
480
481   if (rect.height() > 10 && rect.width() > 10) {
482     const int grippy_half_width = 2;
483     const int inter_grippy_offset = 3;
484     if (vertical) {
485       DrawHorizLine(canvas,
486                     midx - grippy_half_width,
487                     midx + grippy_half_width,
488                     midy - inter_grippy_offset,
489                     paint);
490       DrawHorizLine(canvas,
491                     midx - grippy_half_width,
492                     midx + grippy_half_width,
493                     midy,
494                     paint);
495       DrawHorizLine(canvas,
496                     midx - grippy_half_width,
497                     midx + grippy_half_width,
498                     midy + inter_grippy_offset,
499                     paint);
500     } else {
501       DrawVertLine(canvas,
502                    midx - inter_grippy_offset,
503                    midy - grippy_half_width,
504                    midy + grippy_half_width,
505                    paint);
506       DrawVertLine(canvas,
507                    midx,
508                    midy - grippy_half_width,
509                    midy + grippy_half_width,
510                    paint);
511       DrawVertLine(canvas,
512                    midx + inter_grippy_offset,
513                    midy - grippy_half_width,
514                    midy + grippy_half_width,
515                    paint);
516     }
517   }
518 }
519
520 void NativeThemeBase::PaintScrollbarCorner(SkCanvas* canvas,
521                                            State state,
522                                            const gfx::Rect& rect) const {
523 }
524
525 void NativeThemeBase::PaintCheckbox(SkCanvas* canvas,
526                                     State state,
527                                     const gfx::Rect& rect,
528                                     const ButtonExtraParams& button) const {
529   SkRect skrect = PaintCheckboxRadioCommon(canvas, state, rect,
530                                            SkIntToScalar(2));
531   if (!skrect.isEmpty()) {
532     // Draw the checkmark / dash.
533     SkPaint paint;
534     paint.setAntiAlias(true);
535     paint.setStyle(SkPaint::kStroke_Style);
536     if (state == kDisabled)
537       paint.setColor(kCheckboxStrokeDisabledColor);
538     else
539       paint.setColor(kCheckboxStrokeColor);
540     if (button.indeterminate) {
541       SkPath dash;
542       dash.moveTo(skrect.x() + skrect.width() * 0.16,
543                   (skrect.y() + skrect.bottom()) / 2);
544       dash.rLineTo(skrect.width() * 0.68, 0);
545       paint.setStrokeWidth(SkFloatToScalar(skrect.height() * 0.2));
546       canvas->drawPath(dash, paint);
547     } else if (button.checked) {
548       SkPath check;
549       check.moveTo(skrect.x() + skrect.width() * 0.2,
550                    skrect.y() + skrect.height() * 0.5);
551       check.rLineTo(skrect.width() * 0.2, skrect.height() * 0.2);
552       paint.setStrokeWidth(SkFloatToScalar(skrect.height() * 0.23));
553       check.lineTo(skrect.right() - skrect.width() * 0.2,
554                    skrect.y() + skrect.height() * 0.2);
555       canvas->drawPath(check, paint);
556     }
557   }
558 }
559
560 // Draws the common elements of checkboxes and radio buttons.
561 // Returns the rectangle within which any additional decorations should be
562 // drawn, or empty if none.
563 SkRect NativeThemeBase::PaintCheckboxRadioCommon(
564     SkCanvas* canvas,
565     State state,
566     const gfx::Rect& rect,
567     const SkScalar borderRadius) const {
568
569   SkRect skrect = gfx::RectToSkRect(rect);
570
571   // Use the largest square that fits inside the provided rectangle.
572   // No other browser seems to support non-square widget, so accidentally
573   // having non-square sizes is common (eg. amazon and webkit dev tools).
574   if (skrect.width() != skrect.height()) {
575     SkScalar size = SkMinScalar(skrect.width(), skrect.height());
576     skrect.inset((skrect.width() - size) / 2, (skrect.height() - size) / 2);
577   }
578
579   // If the rectangle is too small then paint only a rectangle.  We don't want
580   // to have to worry about '- 1' and '+ 1' calculations below having overflow
581   // or underflow.
582   if (skrect.width() <= 2) {
583     SkPaint paint;
584     paint.setColor(kCheckboxTinyColor);
585     paint.setStyle(SkPaint::kFill_Style);
586     canvas->drawRect(skrect, paint);
587     // Too small to draw anything more.
588     return SkRect::MakeEmpty();
589   }
590
591   // Make room for the drop shadow.
592   skrect.iset(skrect.x(), skrect.y(), skrect.right() - 1, skrect.bottom() - 1);
593
594   // Draw the drop shadow below the widget.
595   if (state != kPressed) {
596     SkPaint paint;
597     paint.setAntiAlias(true);
598     SkRect shadowRect = skrect;
599     shadowRect.offset(0, 1);
600     if (state == kDisabled)
601      paint.setColor(kCheckboxShadowDisabledColor);
602     else if (state == kHovered)
603       paint.setColor(kCheckboxShadowHoveredColor);
604     else
605       paint.setColor(kCheckboxShadowColor);
606     paint.setStyle(SkPaint::kFill_Style);
607     canvas->drawRoundRect(shadowRect, borderRadius, borderRadius, paint);
608   }
609
610   // Draw the gradient-filled rectangle
611   SkPoint gradient_bounds[3];
612   gradient_bounds[0].set(skrect.x(), skrect.y());
613   gradient_bounds[1].set(skrect.x(), skrect.y() + skrect.height() * 0.38);
614   gradient_bounds[2].set(skrect.x(), skrect.bottom());
615   const SkColor* startEndColors;
616   if (state == kPressed)
617     startEndColors = kCheckboxGradientPressedColors;
618   else if (state == kHovered)
619     startEndColors = kCheckboxGradientHoveredColors;
620   else if (state == kDisabled)
621     startEndColors = kCheckboxGradientDisabledColors;
622   else /* kNormal */
623     startEndColors = kCheckboxGradientColors;
624   SkColor colors[3] = {startEndColors[0], startEndColors[0], startEndColors[1]};
625   skia::RefPtr<SkShader> shader = skia::AdoptRef(
626       SkGradientShader::CreateLinear(
627           gradient_bounds, colors, NULL, 3, SkShader::kClamp_TileMode));
628   SkPaint paint;
629   paint.setAntiAlias(true);
630   paint.setShader(shader.get());
631   paint.setStyle(SkPaint::kFill_Style);
632   canvas->drawRoundRect(skrect, borderRadius, borderRadius, paint);
633   paint.setShader(NULL);
634
635   // Draw the border.
636   if (state == kHovered)
637     paint.setColor(kCheckboxBorderHoveredColor);
638   else if (state == kDisabled)
639     paint.setColor(kCheckboxBorderDisabledColor);
640   else
641     paint.setColor(kCheckboxBorderColor);
642   paint.setStyle(SkPaint::kStroke_Style);
643   paint.setStrokeWidth(SkIntToScalar(1));
644   skrect.inset(SkFloatToScalar(.5f), SkFloatToScalar(.5f));
645   canvas->drawRoundRect(skrect, borderRadius, borderRadius, paint);
646
647   // Return the rectangle excluding the drop shadow for drawing any additional
648   // decorations.
649   return skrect;
650 }
651
652 void NativeThemeBase::PaintRadio(SkCanvas* canvas,
653                                   State state,
654                                   const gfx::Rect& rect,
655                                   const ButtonExtraParams& button) const {
656
657   // Most of a radio button is the same as a checkbox, except the the rounded
658   // square is a circle (i.e. border radius >= 100%).
659   const SkScalar radius = SkFloatToScalar(
660       static_cast<float>(std::max(rect.width(), rect.height())) / 2);
661   SkRect skrect = PaintCheckboxRadioCommon(canvas, state, rect, radius);
662   if (!skrect.isEmpty() && button.checked) {
663     // Draw the dot.
664     SkPaint paint;
665     paint.setAntiAlias(true);
666     paint.setStyle(SkPaint::kFill_Style);
667     if (state == kDisabled)
668       paint.setColor(kRadioDotDisabledColor);
669     else
670       paint.setColor(kRadioDotColor);
671     skrect.inset(skrect.width() * 0.25, skrect.height() * 0.25);
672     // Use drawRoundedRect instead of drawOval to be completely consistent
673     // with the border in PaintCheckboxRadioNewCommon.
674     canvas->drawRoundRect(skrect, radius, radius, paint);
675   }
676 }
677
678 void NativeThemeBase::PaintButton(SkCanvas* canvas,
679                                   State state,
680                                   const gfx::Rect& rect,
681                                   const ButtonExtraParams& button) const {
682   SkPaint paint;
683   const int kRight = rect.right();
684   const int kBottom = rect.bottom();
685   SkRect skrect = SkRect::MakeLTRB(rect.x(), rect.y(), kRight, kBottom);
686   SkColor base_color = button.background_color;
687
688   color_utils::HSL base_hsl;
689   color_utils::SkColorToHSL(base_color, &base_hsl);
690
691   // Our standard gradient is from 0xdd to 0xf8. This is the amount of
692   // increased luminance between those values.
693   SkColor light_color(BrightenColor(base_hsl, SkColorGetA(base_color), 0.105));
694
695   // If the button is too small, fallback to drawing a single, solid color
696   if (rect.width() < 5 || rect.height() < 5) {
697     paint.setColor(base_color);
698     canvas->drawRect(skrect, paint);
699     return;
700   }
701
702   paint.setColor(SK_ColorBLACK);
703   const int kLightEnd = state == kPressed ? 1 : 0;
704   const int kDarkEnd = !kLightEnd;
705   SkPoint gradient_bounds[2];
706   gradient_bounds[kLightEnd].iset(rect.x(), rect.y());
707   gradient_bounds[kDarkEnd].iset(rect.x(), kBottom - 1);
708   SkColor colors[2];
709   colors[0] = light_color;
710   colors[1] = base_color;
711
712   skia::RefPtr<SkShader> shader = skia::AdoptRef(
713       SkGradientShader::CreateLinear(
714           gradient_bounds, colors, NULL, 2, SkShader::kClamp_TileMode));
715   paint.setStyle(SkPaint::kFill_Style);
716   paint.setAntiAlias(true);
717   paint.setShader(shader.get());
718
719   canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint);
720   paint.setShader(NULL);
721
722   if (button.has_border) {
723     int border_alpha = state == kHovered ? 0x80 : 0x55;
724     if (button.is_focused) {
725       border_alpha = 0xff;
726       paint.setColor(GetSystemColor(kColorId_FocusedBorderColor));
727     }
728     paint.setStyle(SkPaint::kStroke_Style);
729     paint.setStrokeWidth(SkIntToScalar(1));
730     paint.setAlpha(border_alpha);
731     skrect.inset(SkFloatToScalar(.5f), SkFloatToScalar(.5f));
732     canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint);
733   }
734 }
735
736 void NativeThemeBase::PaintTextField(SkCanvas* canvas,
737                                      State state,
738                                      const gfx::Rect& rect,
739                                      const TextFieldExtraParams& text) const {
740   SkRect bounds;
741   bounds.set(rect.x(), rect.y(), rect.right() - 1, rect.bottom() - 1);
742
743   SkPaint fill_paint;
744   fill_paint.setStyle(SkPaint::kFill_Style);
745   fill_paint.setColor(text.background_color);
746   canvas->drawRect(bounds, fill_paint);
747
748   // Text INPUT, listbox SELECT, and TEXTAREA have consistent borders.
749   // border: 1px solid #a9a9a9
750   SkPaint stroke_paint;
751   stroke_paint.setStyle(SkPaint::kStroke_Style);
752   stroke_paint.setColor(kTextBorderColor);
753   canvas->drawRect(bounds, stroke_paint);
754 }
755
756 void NativeThemeBase::PaintMenuList(
757     SkCanvas* canvas,
758     State state,
759     const gfx::Rect& rect,
760     const MenuListExtraParams& menu_list) const {
761   // If a border radius is specified, we let the WebCore paint the background
762   // and the border of the control.
763   if (!menu_list.has_border_radius) {
764     ButtonExtraParams button = { 0 };
765     button.background_color = menu_list.background_color;
766     button.has_border = menu_list.has_border;
767     PaintButton(canvas, state, rect, button);
768   }
769
770   SkPaint paint;
771   paint.setColor(SK_ColorBLACK);
772   paint.setAntiAlias(true);
773   paint.setStyle(SkPaint::kFill_Style);
774
775   SkPath path;
776   path.moveTo(menu_list.arrow_x, menu_list.arrow_y - 3);
777   path.rLineTo(6, 0);
778   path.rLineTo(-3, 6);
779   path.close();
780   canvas->drawPath(path, paint);
781 }
782
783 void NativeThemeBase::PaintMenuPopupBackground(
784     SkCanvas* canvas,
785     const gfx::Size& size,
786     const MenuBackgroundExtraParams& menu_background) const {
787   canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode);
788 }
789
790 void NativeThemeBase::PaintMenuItemBackground(
791     SkCanvas* canvas,
792     State state,
793     const gfx::Rect& rect,
794     const MenuListExtraParams& menu_list) const {
795   // By default don't draw anything over the normal background.
796 }
797
798 void NativeThemeBase::PaintSliderTrack(SkCanvas* canvas,
799                                        State state,
800                                        const gfx::Rect& rect,
801                                        const SliderExtraParams& slider) const {
802   const int kMidX = rect.x() + rect.width() / 2;
803   const int kMidY = rect.y() + rect.height() / 2;
804
805   SkPaint paint;
806   paint.setColor(kSliderTrackBackgroundColor);
807
808   SkRect skrect;
809   if (slider.vertical) {
810     skrect.set(std::max(rect.x(), kMidX - 2),
811                rect.y(),
812                std::min(rect.right(), kMidX + 2),
813                rect.bottom());
814   } else {
815     skrect.set(rect.x(),
816                std::max(rect.y(), kMidY - 2),
817                rect.right(),
818                std::min(rect.bottom(), kMidY + 2));
819   }
820   canvas->drawRect(skrect, paint);
821 }
822
823 void NativeThemeBase::PaintSliderThumb(SkCanvas* canvas,
824                                        State state,
825                                        const gfx::Rect& rect,
826                                        const SliderExtraParams& slider) const {
827   const bool hovered = (state == kHovered) || slider.in_drag;
828   const int kMidX = rect.x() + rect.width() / 2;
829   const int kMidY = rect.y() + rect.height() / 2;
830
831   SkPaint paint;
832   paint.setColor(hovered ? SK_ColorWHITE : kSliderThumbLightGrey);
833
834   SkIRect skrect;
835   if (slider.vertical)
836     skrect.set(rect.x(), rect.y(), kMidX + 1, rect.bottom());
837   else
838     skrect.set(rect.x(), rect.y(), rect.right(), kMidY + 1);
839
840   canvas->drawIRect(skrect, paint);
841
842   paint.setColor(hovered ? kSliderThumbLightGrey : kSliderThumbDarkGrey);
843
844   if (slider.vertical)
845     skrect.set(kMidX + 1, rect.y(), rect.right(), rect.bottom());
846   else
847     skrect.set(rect.x(), kMidY + 1, rect.right(), rect.bottom());
848
849   canvas->drawIRect(skrect, paint);
850
851   paint.setColor(kSliderThumbBorderDarkGrey);
852   DrawBox(canvas, rect, paint);
853
854   if (rect.height() > 10 && rect.width() > 10) {
855     DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY, paint);
856     DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY - 3, paint);
857     DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY + 3, paint);
858   }
859 }
860
861 void NativeThemeBase::PaintInnerSpinButton(SkCanvas* canvas,
862     State state,
863     const gfx::Rect& rect,
864     const InnerSpinButtonExtraParams& spin_button) const {
865   if (spin_button.read_only)
866     state = kDisabled;
867
868   State north_state = state;
869   State south_state = state;
870   if (spin_button.spin_up)
871     south_state = south_state != kDisabled ? kNormal : kDisabled;
872   else
873     north_state = north_state != kDisabled ? kNormal : kDisabled;
874
875   gfx::Rect half = rect;
876   half.set_height(rect.height() / 2);
877   PaintArrowButton(canvas, half, kScrollbarUpArrow, north_state);
878
879   half.set_y(rect.y() + rect.height() / 2);
880   PaintArrowButton(canvas, half, kScrollbarDownArrow, south_state);
881 }
882
883 void NativeThemeBase::PaintProgressBar(SkCanvas* canvas,
884     State state,
885     const gfx::Rect& rect,
886     const ProgressBarExtraParams& progress_bar) const {
887   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
888   gfx::ImageSkia* bar_image = rb.GetImageSkiaNamed(IDR_PROGRESS_BAR);
889   gfx::ImageSkia* left_border_image = rb.GetImageSkiaNamed(
890       IDR_PROGRESS_BORDER_LEFT);
891   gfx::ImageSkia* right_border_image = rb.GetImageSkiaNamed(
892       IDR_PROGRESS_BORDER_RIGHT);
893
894   DCHECK(bar_image->width() > 0);
895   DCHECK(rect.width() > 0);
896
897   float tile_scale_y = static_cast<float>(rect.height()) / bar_image->height();
898
899   int dest_left_border_width = left_border_image->width();
900   int dest_right_border_width = right_border_image->width();
901
902   // Since an implicit float -> int conversion will truncate, we want to make
903   // sure that if a border is desired, it gets at least one pixel.
904   if (dest_left_border_width > 0) {
905     dest_left_border_width = dest_left_border_width * tile_scale_y;
906     dest_left_border_width = std::max(dest_left_border_width, 1);
907   }
908   if (dest_right_border_width > 0) {
909     dest_right_border_width = dest_right_border_width * tile_scale_y;
910     dest_right_border_width = std::max(dest_right_border_width, 1);
911   }
912
913   // Since the width of the progress bar may not be evenly divisible by the
914   // tile size, in order to make it look right we may need to draw some of the
915   // with a width of 1 pixel smaller than the rest of the tiles.
916   int new_tile_width = static_cast<int>(bar_image->width() * tile_scale_y);
917   new_tile_width = std::max(new_tile_width, 1);
918
919   float tile_scale_x = static_cast<float>(new_tile_width) / bar_image->width();
920   if (rect.width() % new_tile_width == 0) {
921     DrawTiledImage(canvas, *bar_image, 0, 0, tile_scale_x, tile_scale_y,
922         rect.x(), rect.y(),
923         rect.width(), rect.height());
924   } else {
925     int num_tiles = 1 + rect.width() / new_tile_width;
926     int overshoot = num_tiles * new_tile_width - rect.width();
927     // Since |overshoot| represents the number of tiles that were too big, draw
928     // |overshoot| tiles with their width reduced by 1.
929     int num_big_tiles = num_tiles - overshoot;
930     int num_small_tiles = overshoot;
931     int small_width = new_tile_width - 1;
932     float small_scale_x = static_cast<float>(small_width) / bar_image->width();
933     float big_scale_x = tile_scale_x;
934
935     gfx::Rect big_rect = rect;
936     gfx::Rect small_rect = rect;
937     big_rect.Inset(0, 0, num_small_tiles*small_width, 0);
938     small_rect.Inset(num_big_tiles*new_tile_width, 0, 0, 0);
939
940     DrawTiledImage(canvas, *bar_image, 0, 0, big_scale_x, tile_scale_y,
941       big_rect.x(), big_rect.y(), big_rect.width(), big_rect.height());
942     DrawTiledImage(canvas, *bar_image, 0, 0, small_scale_x, tile_scale_y,
943       small_rect.x(), small_rect.y(), small_rect.width(), small_rect.height());
944   }
945   if (progress_bar.value_rect_width) {
946     gfx::ImageSkia* value_image = rb.GetImageSkiaNamed(IDR_PROGRESS_VALUE);
947
948     new_tile_width = static_cast<int>(value_image->width() * tile_scale_y);
949     tile_scale_x = static_cast<float>(new_tile_width) /
950         value_image->width();
951
952     DrawTiledImage(canvas, *value_image, 0, 0, tile_scale_x, tile_scale_y,
953         progress_bar.value_rect_x,
954         progress_bar.value_rect_y,
955         progress_bar.value_rect_width,
956         progress_bar.value_rect_height);
957   }
958
959   DrawImageInt(canvas, *left_border_image, 0, 0, left_border_image->width(),
960       left_border_image->height(), rect.x(), rect.y(), dest_left_border_width,
961       rect.height());
962
963   int dest_x = rect.right() - dest_right_border_width;
964   DrawImageInt(canvas, *right_border_image, 0, 0, right_border_image->width(),
965                right_border_image->height(), dest_x, rect.y(),
966                dest_right_border_width, rect.height());
967 }
968
969 bool NativeThemeBase::IntersectsClipRectInt(SkCanvas* canvas,
970                                             int x, int y, int w, int h) const {
971   SkRect clip;
972   return canvas->getClipBounds(&clip) &&
973       clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w),
974                      SkIntToScalar(y + h));
975 }
976
977 void NativeThemeBase::DrawImageInt(
978     SkCanvas* sk_canvas, const gfx::ImageSkia& image,
979     int src_x, int src_y, int src_w, int src_h,
980     int dest_x, int dest_y, int dest_w, int dest_h) const {
981   scoped_ptr<gfx::Canvas> canvas(CommonThemeCreateCanvas(sk_canvas));
982   canvas->DrawImageInt(image, src_x, src_y, src_w, src_h,
983       dest_x, dest_y, dest_w, dest_h, true);
984 }
985
986 void NativeThemeBase::DrawTiledImage(SkCanvas* sk_canvas,
987     const gfx::ImageSkia& image,
988     int src_x, int src_y, float tile_scale_x, float tile_scale_y,
989     int dest_x, int dest_y, int w, int h) const {
990   scoped_ptr<gfx::Canvas> canvas(CommonThemeCreateCanvas(sk_canvas));
991   canvas->TileImageInt(image, src_x, src_y, tile_scale_x,
992       tile_scale_y, dest_x, dest_y, w, h);
993 }
994
995 SkColor NativeThemeBase::SaturateAndBrighten(SkScalar* hsv,
996                                              SkScalar saturate_amount,
997                                              SkScalar brighten_amount) const {
998   SkScalar color[3];
999   color[0] = hsv[0];
1000   color[1] = Clamp(hsv[1] + saturate_amount, 0.0, 1.0);
1001   color[2] = Clamp(hsv[2] + brighten_amount, 0.0, 1.0);
1002   return SkHSVToColor(color);
1003 }
1004
1005 SkColor NativeThemeBase::GetArrowColor(State state) const {
1006   if (state != kDisabled)
1007     return SK_ColorBLACK;
1008
1009   SkScalar track_hsv[3];
1010   SkColorToHSV(track_color_, track_hsv);
1011   SkScalar thumb_hsv[3];
1012   SkColorToHSV(thumb_inactive_color_, thumb_hsv);
1013   return OutlineColor(track_hsv, thumb_hsv);
1014 }
1015
1016 void NativeThemeBase::DrawVertLine(SkCanvas* canvas,
1017                                    int x,
1018                                    int y1,
1019                                    int y2,
1020                                    const SkPaint& paint) const {
1021   SkIRect skrect;
1022   skrect.set(x, y1, x + 1, y2 + 1);
1023   canvas->drawIRect(skrect, paint);
1024 }
1025
1026 void NativeThemeBase::DrawHorizLine(SkCanvas* canvas,
1027                                     int x1,
1028                                     int x2,
1029                                     int y,
1030                                     const SkPaint& paint) const {
1031   SkIRect skrect;
1032   skrect.set(x1, y, x2 + 1, y + 1);
1033   canvas->drawIRect(skrect, paint);
1034 }
1035
1036 void NativeThemeBase::DrawBox(SkCanvas* canvas,
1037                               const gfx::Rect& rect,
1038                               const SkPaint& paint) const {
1039   const int right = rect.x() + rect.width() - 1;
1040   const int bottom = rect.y() + rect.height() - 1;
1041   DrawHorizLine(canvas, rect.x(), right, rect.y(), paint);
1042   DrawVertLine(canvas, right, rect.y(), bottom, paint);
1043   DrawHorizLine(canvas, rect.x(), right, bottom, paint);
1044   DrawVertLine(canvas, rect.x(), rect.y(), bottom, paint);
1045 }
1046
1047 SkScalar NativeThemeBase::Clamp(SkScalar value,
1048                                 SkScalar min,
1049                                 SkScalar max) const {
1050   return std::min(std::max(value, min), max);
1051 }
1052
1053 SkColor NativeThemeBase::OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const {
1054   // GTK Theme engines have way too much control over the layout of
1055   // the scrollbar. We might be able to more closely approximate its
1056   // look-and-feel, if we sent whole images instead of just colors
1057   // from the browser to the renderer. But even then, some themes
1058   // would just break.
1059   //
1060   // So, instead, we don't even try to 100% replicate the look of
1061   // the native scrollbar. We render our own version, but we make
1062   // sure to pick colors that blend in nicely with the system GTK
1063   // theme. In most cases, we can just sample a couple of pixels
1064   // from the system scrollbar and use those colors to draw our
1065   // scrollbar.
1066   //
1067   // This works fine for the track color and the overall thumb
1068   // color. But it fails spectacularly for the outline color used
1069   // around the thumb piece.  Not all themes have a clearly defined
1070   // outline. For some of them it is partially transparent, and for
1071   // others the thickness is very unpredictable.
1072   //
1073   // So, instead of trying to approximate the system theme, we
1074   // instead try to compute a reasonable looking choice based on the
1075   // known color of the track and the thumb piece. This is difficult
1076   // when trying to deal both with high- and low-contrast themes,
1077   // and both with positive and inverted themes.
1078   //
1079   // The following code has been tested to look OK with all of the
1080   // default GTK themes.
1081   SkScalar min_diff = Clamp((hsv1[1] + hsv2[1]) * 1.2f, 0.28f, 0.5f);
1082   SkScalar diff = Clamp(fabs(hsv1[2] - hsv2[2]) / 2, min_diff, 0.5f);
1083
1084   if (hsv1[2] + hsv2[2] > 1.0)
1085     diff = -diff;
1086
1087   return SaturateAndBrighten(hsv2, -0.2f, diff);
1088 }
1089
1090 }  // namespace ui