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