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