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