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