1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/native_theme/native_theme_base.h" 6 7 #include <limits> 8 9 #include "base/command_line.h" 10 #include "base/logging.h" 11 #include "base/memory/scoped_ptr.h" 12 #include "grit/ui_resources.h" 13 #include "third_party/skia/include/effects/SkGradientShader.h" 14 #include "ui/base/layout.h" 15 #include "ui/base/resource/resource_bundle.h" 16 #include "ui/base/ui_base_switches.h" 17 #include "ui/gfx/canvas.h" 18 #include "ui/gfx/color_utils.h" 19 #include "ui/gfx/image/image_skia.h" 20 #include "ui/gfx/rect.h" 21 #include "ui/gfx/size.h" 22 #include "ui/gfx/skia_util.h" 23 24 namespace { 25 26 // These are the default dimensions of radio buttons and checkboxes. 27 const int kCheckboxAndRadioWidth = 13; 28 const int kCheckboxAndRadioHeight = 13; 29 30 // These sizes match the sizes in Chromium Win. 31 const int kSliderThumbWidth = 11; 32 const int kSliderThumbHeight = 21; 33 34 const SkColor kSliderTrackBackgroundColor = 35 SkColorSetRGB(0xe3, 0xdd, 0xd8); 36 const SkColor kSliderThumbLightGrey = SkColorSetRGB(0xf4, 0xf2, 0xef); 37 const SkColor kSliderThumbDarkGrey = SkColorSetRGB(0xea, 0xe5, 0xe0); 38 const SkColor kSliderThumbBorderDarkGrey = 39 SkColorSetRGB(0x9d, 0x96, 0x8e); 40 41 const SkColor kMenuPopupBackgroundColor = SkColorSetRGB(210, 225, 246); 42 43 const unsigned int kDefaultScrollbarWidth = 15; 44 const unsigned int kDefaultScrollbarButtonLength = 14; 45 46 const SkColor kCheckboxTinyColor = SK_ColorGRAY; 47 const SkColor kCheckboxShadowColor = SkColorSetARGB(0x15, 0, 0, 0); 48 const SkColor kCheckboxShadowHoveredColor = SkColorSetARGB(0x1F, 0, 0, 0); 49 const SkColor kCheckboxShadowDisabledColor = SkColorSetARGB(0, 0, 0, 0); 50 const SkColor kCheckboxGradientColors[] = { 51 SkColorSetRGB(0xed, 0xed, 0xed), 52 SkColorSetRGB(0xde, 0xde, 0xde) }; 53 const SkColor kCheckboxGradientPressedColors[] = { 54 SkColorSetRGB(0xe7, 0xe7, 0xe7), 55 SkColorSetRGB(0xd7, 0xd7, 0xd7) }; 56 const SkColor kCheckboxGradientHoveredColors[] = { 57 SkColorSetRGB(0xf0, 0xf0, 0xf0), 58 SkColorSetRGB(0xe0, 0xe0, 0xe0) }; 59 const SkColor kCheckboxGradientDisabledColors[] = { 60 SkColorSetARGB(0x80, 0xed, 0xed, 0xed), 61 SkColorSetARGB(0x80, 0xde, 0xde, 0xde) }; 62 const SkColor kCheckboxBorderColor = SkColorSetARGB(0x40, 0, 0, 0); 63 const SkColor kCheckboxBorderHoveredColor = SkColorSetARGB(0x4D, 0, 0, 0); 64 const SkColor kCheckboxBorderDisabledColor = SkColorSetARGB(0x20, 0, 0, 0); 65 const SkColor kCheckboxStrokeColor = SkColorSetARGB(0xB3, 0, 0, 0); 66 const SkColor kCheckboxStrokeDisabledColor = SkColorSetARGB(0x59, 0, 0, 0); 67 const SkColor kRadioDotColor = SkColorSetRGB(0x66, 0x66, 0x66); 68 const SkColor kRadioDotDisabledColor = SkColorSetARGB(0x80, 0x66, 0x66, 0x66); 69 70 const SkColor kInputInvalidColor = SkColorSetRGB(0xde, 0x49, 0x32); 71 72 // Get lightness adjusted color. 73 SkColor BrightenColor(const color_utils::HSL& hsl, SkAlpha alpha, 74 double lightness_amount) { 75 color_utils::HSL adjusted = hsl; 76 adjusted.l += lightness_amount; 77 if (adjusted.l > 1.0) 78 adjusted.l = 1.0; 79 if (adjusted.l < 0.0) 80 adjusted.l = 0.0; 81 82 return color_utils::HSLToSkColor(adjusted, alpha); 83 } 84 85 } // namespace 86 87 namespace ui { 88 89 gfx::Size NativeThemeBase::GetPartSize(Part part, 90 State state, 91 const ExtraParams& extra) const { 92 switch (part) { 93 // Please keep these in the order of NativeTheme::Part. 94 case kCheckbox: 95 return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight); 96 case kInnerSpinButton: 97 return gfx::Size(scrollbar_width_, 0); 98 case kMenuList: 99 return gfx::Size(); // No default size. 100 case kMenuCheck: 101 case kMenuCheckBackground: 102 case kMenuPopupArrow: 103 NOTIMPLEMENTED(); 104 break; 105 case kMenuPopupBackground: 106 return gfx::Size(); // No default size. 107 case kMenuPopupGutter: 108 case kMenuPopupSeparator: 109 NOTIMPLEMENTED(); 110 break; 111 case kMenuItemBackground: 112 case kProgressBar: 113 case kPushButton: 114 return gfx::Size(); // No default size. 115 case kRadio: 116 return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight); 117 case kScrollbarDownArrow: 118 case kScrollbarUpArrow: 119 return gfx::Size(scrollbar_width_, scrollbar_button_length_); 120 case kScrollbarLeftArrow: 121 case kScrollbarRightArrow: 122 return gfx::Size(scrollbar_button_length_, scrollbar_width_); 123 case kScrollbarHorizontalThumb: 124 // This matches Firefox on Linux. 125 return gfx::Size(2 * scrollbar_width_, scrollbar_width_); 126 case kScrollbarVerticalThumb: 127 // This matches Firefox on Linux. 128 return gfx::Size(scrollbar_width_, 2 * scrollbar_width_); 129 case kScrollbarHorizontalTrack: 130 return gfx::Size(0, scrollbar_width_); 131 case kScrollbarVerticalTrack: 132 return gfx::Size(scrollbar_width_, 0); 133 case kScrollbarHorizontalGripper: 134 case kScrollbarVerticalGripper: 135 NOTIMPLEMENTED(); 136 break; 137 case kSliderTrack: 138 return gfx::Size(); // No default size. 139 case kSliderThumb: 140 // These sizes match the sizes in Chromium Win. 141 return gfx::Size(kSliderThumbWidth, kSliderThumbHeight); 142 case kTabPanelBackground: 143 NOTIMPLEMENTED(); 144 break; 145 case kTextField: 146 return gfx::Size(); // No default size. 147 case kTrackbarThumb: 148 case kTrackbarTrack: 149 case kWindowResizeGripper: 150 NOTIMPLEMENTED(); 151 break; 152 default: 153 NOTREACHED() << "Unknown theme part: " << part; 154 break; 155 } 156 return gfx::Size(); 157 } 158 159 void NativeThemeBase::Paint(SkCanvas* canvas, 160 Part part, 161 State state, 162 const gfx::Rect& rect, 163 const ExtraParams& extra) const { 164 if (rect.IsEmpty()) 165 return; 166 167 switch (part) { 168 // Please keep these in the order of NativeTheme::Part. 169 case kCheckbox: 170 PaintCheckbox(canvas, state, rect, extra.button); 171 break; 172 case kInnerSpinButton: 173 PaintInnerSpinButton(canvas, state, rect, extra.inner_spin); 174 break; 175 case kMenuList: 176 PaintMenuList(canvas, state, rect, extra.menu_list); 177 break; 178 case kMenuCheck: 179 case kMenuCheckBackground: 180 case kMenuPopupArrow: 181 NOTIMPLEMENTED(); 182 break; 183 case kMenuPopupBackground: 184 PaintMenuPopupBackground(canvas, rect.size(), extra.menu_background); 185 break; 186 case kMenuPopupGutter: 187 case kMenuPopupSeparator: 188 NOTIMPLEMENTED(); 189 break; 190 case kMenuItemBackground: 191 PaintMenuItemBackground(canvas, state, rect, extra.menu_list); 192 break; 193 case kProgressBar: 194 PaintProgressBar(canvas, state, rect, extra.progress_bar); 195 break; 196 case kPushButton: 197 PaintButton(canvas, state, rect, extra.button); 198 break; 199 case kRadio: 200 PaintRadio(canvas, state, rect, extra.button); 201 break; 202 case kScrollbarDownArrow: 203 case kScrollbarUpArrow: 204 case kScrollbarLeftArrow: 205 case kScrollbarRightArrow: 206 PaintArrowButton(canvas, rect, part, state); 207 break; 208 case kScrollbarHorizontalThumb: 209 case kScrollbarVerticalThumb: 210 PaintScrollbarThumb(canvas, part, state, rect); 211 break; 212 case kScrollbarHorizontalTrack: 213 case kScrollbarVerticalTrack: 214 PaintScrollbarTrack(canvas, part, state, extra.scrollbar_track, rect); 215 break; 216 case kScrollbarHorizontalGripper: 217 case kScrollbarVerticalGripper: 218 // Invoked by views scrollbar code, don't care about for non-win 219 // implementations, so no NOTIMPLEMENTED. 220 break; 221 case 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 244 NativeThemeBase::NativeThemeBase() 245 : scrollbar_width_(kDefaultScrollbarWidth), 246 scrollbar_button_length_(kDefaultScrollbarButtonLength) { 247 } 248 249 NativeThemeBase::~NativeThemeBase() { 250 } 251 252 void 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 378 void 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 399 void 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 478 void 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. 516 SkRect 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 605 void 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 631 void 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 689 void 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 779 void 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 806 void 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 813 void 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 821 void 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 846 void 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 884 void 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 906 void 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 992 bool 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 1000 void 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 1015 void 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 1030 SkColor 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 1040 void 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 1050 void 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 1060 void 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 1071 SkScalar NativeThemeBase::Clamp(SkScalar value, 1072 SkScalar min, 1073 SkScalar max) const { 1074 return std::min(std::max(value, min), max); 1075 } 1076 1077 SkColor 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