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
25namespace {
26
27// These are the default dimensions of radio buttons and checkboxes.
28const int kCheckboxAndRadioWidth = 13;
29const int kCheckboxAndRadioHeight = 13;
30
31// These sizes match the sizes in Chromium Win.
32const int kSliderThumbWidth = 11;
33const int kSliderThumbHeight = 21;
34
35const SkColor kSliderTrackBackgroundColor =
36    SkColorSetRGB(0xe3, 0xdd, 0xd8);
37const SkColor kSliderThumbLightGrey = SkColorSetRGB(0xf4, 0xf2, 0xef);
38const SkColor kSliderThumbDarkGrey = SkColorSetRGB(0xea, 0xe5, 0xe0);
39const SkColor kSliderThumbBorderDarkGrey =
40    SkColorSetRGB(0x9d, 0x96, 0x8e);
41
42const SkColor kTextBorderColor = SkColorSetRGB(0xa9, 0xa9, 0xa9);
43
44const SkColor kMenuPopupBackgroundColor = SkColorSetRGB(210, 225, 246);
45
46const unsigned int kDefaultScrollbarWidth = 15;
47const unsigned int kDefaultScrollbarButtonLength = 14;
48
49const SkColor kCheckboxTinyColor = SK_ColorGRAY;
50const SkColor kCheckboxShadowColor = SkColorSetARGB(0x15, 0, 0, 0);
51const SkColor kCheckboxShadowHoveredColor = SkColorSetARGB(0x1F, 0, 0, 0);
52const SkColor kCheckboxShadowDisabledColor = SkColorSetARGB(0, 0, 0, 0);
53const SkColor kCheckboxGradientColors[] = {
54    SkColorSetRGB(0xed, 0xed, 0xed),
55    SkColorSetRGB(0xde, 0xde, 0xde) };
56const SkColor kCheckboxGradientPressedColors[] = {
57    SkColorSetRGB(0xe7, 0xe7, 0xe7),
58    SkColorSetRGB(0xd7, 0xd7, 0xd7) };
59const SkColor kCheckboxGradientHoveredColors[] = {
60    SkColorSetRGB(0xf0, 0xf0, 0xf0),
61    SkColorSetRGB(0xe0, 0xe0, 0xe0) };
62const SkColor kCheckboxGradientDisabledColors[] = {
63    SkColorSetARGB(0x80, 0xed, 0xed, 0xed),
64    SkColorSetARGB(0x80, 0xde, 0xde, 0xde) };
65const SkColor kCheckboxBorderColor = SkColorSetARGB(0x40, 0, 0, 0);
66const SkColor kCheckboxBorderHoveredColor = SkColorSetARGB(0x4D, 0, 0, 0);
67const SkColor kCheckboxBorderDisabledColor = SkColorSetARGB(0x20, 0, 0, 0);
68const SkColor kCheckboxStrokeColor = SkColorSetARGB(0xB3, 0, 0, 0);
69const SkColor kCheckboxStrokeDisabledColor = SkColorSetARGB(0x59, 0, 0, 0);
70const SkColor kRadioDotColor = SkColorSetRGB(0x66, 0x66, 0x66);
71const SkColor kRadioDotDisabledColor = SkColorSetARGB(0x80, 0x66, 0x66, 0x66);
72
73// Get lightness adjusted color.
74SkColor 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
88namespace ui {
89
90gfx::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
164void 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
189void 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
281NativeThemeBase::NativeThemeBase()
282    : scrollbar_width_(kDefaultScrollbarWidth),
283      scrollbar_button_length_(kDefaultScrollbarButtonLength) {
284}
285
286NativeThemeBase::~NativeThemeBase() {
287}
288
289void 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
370void 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
420void 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
441void 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
520void NativeThemeBase::PaintScrollbarCorner(SkCanvas* canvas,
521                                           State state,
522                                           const gfx::Rect& rect) const {
523}
524
525void 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.
563SkRect 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
652void 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
678void 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
736void 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
756void 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
783void 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
790void 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
798void 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
823void 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
861void 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
883void 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
969bool 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
977void 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
986void 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
995SkColor 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
1005SkColor 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
1016void 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
1026void 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
1036void 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
1047SkScalar NativeThemeBase::Clamp(SkScalar value,
1048                                SkScalar min,
1049                                SkScalar max) const {
1050  return std::min(std::max(value, min), max);
1051}
1052
1053SkColor 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
1091