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