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 kTextBorderColor = SkColorSetRGB(0xa9, 0xa9, 0xa9); 42 43 const SkColor kMenuPopupBackgroundColor = SkColorSetRGB(210, 225, 246); 44 45 const unsigned int kDefaultScrollbarWidth = 15; 46 const unsigned int kDefaultScrollbarButtonLength = 14; 47 48 const SkColor kCheckboxTinyColor = SK_ColorGRAY; 49 const SkColor kCheckboxShadowColor = SkColorSetARGB(0x15, 0, 0, 0); 50 const SkColor kCheckboxShadowHoveredColor = SkColorSetARGB(0x1F, 0, 0, 0); 51 const SkColor kCheckboxShadowDisabledColor = SkColorSetARGB(0, 0, 0, 0); 52 const SkColor kCheckboxGradientColors[] = { 53 SkColorSetRGB(0xed, 0xed, 0xed), 54 SkColorSetRGB(0xde, 0xde, 0xde) }; 55 const SkColor kCheckboxGradientPressedColors[] = { 56 SkColorSetRGB(0xe7, 0xe7, 0xe7), 57 SkColorSetRGB(0xd7, 0xd7, 0xd7) }; 58 const SkColor kCheckboxGradientHoveredColors[] = { 59 SkColorSetRGB(0xf0, 0xf0, 0xf0), 60 SkColorSetRGB(0xe0, 0xe0, 0xe0) }; 61 const SkColor kCheckboxGradientDisabledColors[] = { 62 SkColorSetARGB(0x80, 0xed, 0xed, 0xed), 63 SkColorSetARGB(0x80, 0xde, 0xde, 0xde) }; 64 const SkColor kCheckboxBorderColor = SkColorSetARGB(0x40, 0, 0, 0); 65 const SkColor kCheckboxBorderHoveredColor = SkColorSetARGB(0x4D, 0, 0, 0); 66 const SkColor kCheckboxBorderDisabledColor = SkColorSetARGB(0x20, 0, 0, 0); 67 const SkColor kCheckboxStrokeColor = SkColorSetARGB(0xB3, 0, 0, 0); 68 const SkColor kCheckboxStrokeDisabledColor = SkColorSetARGB(0x59, 0, 0, 0); 69 const SkColor kRadioDotColor = SkColorSetRGB(0x66, 0x66, 0x66); 70 const SkColor kRadioDotDisabledColor = SkColorSetARGB(0x80, 0x66, 0x66, 0x66); 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 SkRect bounds; 694 bounds.set(rect.x(), rect.y(), rect.right() - 1, rect.bottom() - 1); 695 696 SkPaint fill_paint; 697 fill_paint.setStyle(SkPaint::kFill_Style); 698 fill_paint.setColor(text.background_color); 699 canvas->drawRect(bounds, fill_paint); 700 701 // Text INPUT, listbox SELECT, and TEXTAREA have consistent borders. 702 // border: 1px solid #a9a9a9 703 SkPaint stroke_paint; 704 stroke_paint.setStyle(SkPaint::kStroke_Style); 705 stroke_paint.setColor(kTextBorderColor); 706 canvas->drawRect(bounds, stroke_paint); 707 } 708 709 void NativeThemeBase::PaintMenuList( 710 SkCanvas* canvas, 711 State state, 712 const gfx::Rect& rect, 713 const MenuListExtraParams& menu_list) const { 714 // If a border radius is specified, we let the WebCore paint the background 715 // and the border of the control. 716 if (!menu_list.has_border_radius) { 717 ButtonExtraParams button = { 0 }; 718 button.background_color = menu_list.background_color; 719 button.has_border = menu_list.has_border; 720 PaintButton(canvas, state, rect, button); 721 } 722 723 SkPaint paint; 724 paint.setColor(SK_ColorBLACK); 725 paint.setAntiAlias(true); 726 paint.setStyle(SkPaint::kFill_Style); 727 728 SkPath path; 729 path.moveTo(menu_list.arrow_x, menu_list.arrow_y - 3); 730 path.rLineTo(6, 0); 731 path.rLineTo(-3, 6); 732 path.close(); 733 canvas->drawPath(path, paint); 734 } 735 736 void NativeThemeBase::PaintMenuPopupBackground( 737 SkCanvas* canvas, 738 const gfx::Size& size, 739 const MenuBackgroundExtraParams& menu_background) const { 740 canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode); 741 } 742 743 void NativeThemeBase::PaintMenuItemBackground( 744 SkCanvas* canvas, 745 State state, 746 const gfx::Rect& rect, 747 const MenuListExtraParams& menu_list) const { 748 // By default don't draw anything over the normal background. 749 } 750 751 void NativeThemeBase::PaintSliderTrack(SkCanvas* canvas, 752 State state, 753 const gfx::Rect& rect, 754 const SliderExtraParams& slider) const { 755 const int kMidX = rect.x() + rect.width() / 2; 756 const int kMidY = rect.y() + rect.height() / 2; 757 758 SkPaint paint; 759 paint.setColor(kSliderTrackBackgroundColor); 760 761 SkRect skrect; 762 if (slider.vertical) { 763 skrect.set(std::max(rect.x(), kMidX - 2), 764 rect.y(), 765 std::min(rect.right(), kMidX + 2), 766 rect.bottom()); 767 } else { 768 skrect.set(rect.x(), 769 std::max(rect.y(), kMidY - 2), 770 rect.right(), 771 std::min(rect.bottom(), kMidY + 2)); 772 } 773 canvas->drawRect(skrect, paint); 774 } 775 776 void NativeThemeBase::PaintSliderThumb(SkCanvas* canvas, 777 State state, 778 const gfx::Rect& rect, 779 const SliderExtraParams& slider) const { 780 const bool hovered = (state == kHovered) || slider.in_drag; 781 const int kMidX = rect.x() + rect.width() / 2; 782 const int kMidY = rect.y() + rect.height() / 2; 783 784 SkPaint paint; 785 paint.setColor(hovered ? SK_ColorWHITE : kSliderThumbLightGrey); 786 787 SkIRect skrect; 788 if (slider.vertical) 789 skrect.set(rect.x(), rect.y(), kMidX + 1, rect.bottom()); 790 else 791 skrect.set(rect.x(), rect.y(), rect.right(), kMidY + 1); 792 793 canvas->drawIRect(skrect, paint); 794 795 paint.setColor(hovered ? kSliderThumbLightGrey : kSliderThumbDarkGrey); 796 797 if (slider.vertical) 798 skrect.set(kMidX + 1, rect.y(), rect.right(), rect.bottom()); 799 else 800 skrect.set(rect.x(), kMidY + 1, rect.right(), rect.bottom()); 801 802 canvas->drawIRect(skrect, paint); 803 804 paint.setColor(kSliderThumbBorderDarkGrey); 805 DrawBox(canvas, rect, paint); 806 807 if (rect.height() > 10 && rect.width() > 10) { 808 DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY, paint); 809 DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY - 3, paint); 810 DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY + 3, paint); 811 } 812 } 813 814 void NativeThemeBase::PaintInnerSpinButton(SkCanvas* canvas, 815 State state, 816 const gfx::Rect& rect, 817 const InnerSpinButtonExtraParams& spin_button) const { 818 if (spin_button.read_only) 819 state = kDisabled; 820 821 State north_state = state; 822 State south_state = state; 823 if (spin_button.spin_up) 824 south_state = south_state != kDisabled ? kNormal : kDisabled; 825 else 826 north_state = north_state != kDisabled ? kNormal : kDisabled; 827 828 gfx::Rect half = rect; 829 half.set_height(rect.height() / 2); 830 PaintArrowButton(canvas, half, kScrollbarUpArrow, north_state); 831 832 half.set_y(rect.y() + rect.height() / 2); 833 PaintArrowButton(canvas, half, kScrollbarDownArrow, south_state); 834 } 835 836 void NativeThemeBase::PaintProgressBar(SkCanvas* canvas, 837 State state, 838 const gfx::Rect& rect, 839 const ProgressBarExtraParams& progress_bar) const { 840 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 841 gfx::ImageSkia* bar_image = rb.GetImageSkiaNamed(IDR_PROGRESS_BAR); 842 gfx::ImageSkia* left_border_image = rb.GetImageSkiaNamed( 843 IDR_PROGRESS_BORDER_LEFT); 844 gfx::ImageSkia* right_border_image = rb.GetImageSkiaNamed( 845 IDR_PROGRESS_BORDER_RIGHT); 846 847 DCHECK(bar_image->width() > 0); 848 DCHECK(rect.width() > 0); 849 850 float tile_scale_y = static_cast<float>(rect.height()) / bar_image->height(); 851 852 int dest_left_border_width = left_border_image->width(); 853 int dest_right_border_width = right_border_image->width(); 854 855 // Since an implicit float -> int conversion will truncate, we want to make 856 // sure that if a border is desired, it gets at least one pixel. 857 if (dest_left_border_width > 0) { 858 dest_left_border_width = dest_left_border_width * tile_scale_y; 859 dest_left_border_width = std::max(dest_left_border_width, 1); 860 } 861 if (dest_right_border_width > 0) { 862 dest_right_border_width = dest_right_border_width * tile_scale_y; 863 dest_right_border_width = std::max(dest_right_border_width, 1); 864 } 865 866 // Since the width of the progress bar may not be evenly divisible by the 867 // tile size, in order to make it look right we may need to draw some of the 868 // with a width of 1 pixel smaller than the rest of the tiles. 869 int new_tile_width = static_cast<int>(bar_image->width() * tile_scale_y); 870 new_tile_width = std::max(new_tile_width, 1); 871 872 float tile_scale_x = static_cast<float>(new_tile_width) / bar_image->width(); 873 if (rect.width() % new_tile_width == 0) { 874 DrawTiledImage(canvas, *bar_image, 0, 0, tile_scale_x, tile_scale_y, 875 rect.x(), rect.y(), 876 rect.width(), rect.height()); 877 } else { 878 int num_tiles = 1 + rect.width() / new_tile_width; 879 int overshoot = num_tiles * new_tile_width - rect.width(); 880 // Since |overshoot| represents the number of tiles that were too big, draw 881 // |overshoot| tiles with their width reduced by 1. 882 int num_big_tiles = num_tiles - overshoot; 883 int num_small_tiles = overshoot; 884 int small_width = new_tile_width - 1; 885 float small_scale_x = static_cast<float>(small_width) / bar_image->width(); 886 float big_scale_x = tile_scale_x; 887 888 gfx::Rect big_rect = rect; 889 gfx::Rect small_rect = rect; 890 big_rect.Inset(0, 0, num_small_tiles*small_width, 0); 891 small_rect.Inset(num_big_tiles*new_tile_width, 0, 0, 0); 892 893 DrawTiledImage(canvas, *bar_image, 0, 0, big_scale_x, tile_scale_y, 894 big_rect.x(), big_rect.y(), big_rect.width(), big_rect.height()); 895 DrawTiledImage(canvas, *bar_image, 0, 0, small_scale_x, tile_scale_y, 896 small_rect.x(), small_rect.y(), small_rect.width(), small_rect.height()); 897 } 898 if (progress_bar.value_rect_width) { 899 gfx::ImageSkia* value_image = rb.GetImageSkiaNamed(IDR_PROGRESS_VALUE); 900 901 new_tile_width = static_cast<int>(value_image->width() * tile_scale_y); 902 tile_scale_x = static_cast<float>(new_tile_width) / 903 value_image->width(); 904 905 DrawTiledImage(canvas, *value_image, 0, 0, tile_scale_x, tile_scale_y, 906 progress_bar.value_rect_x, 907 progress_bar.value_rect_y, 908 progress_bar.value_rect_width, 909 progress_bar.value_rect_height); 910 } 911 912 DrawImageInt(canvas, *left_border_image, 0, 0, left_border_image->width(), 913 left_border_image->height(), rect.x(), rect.y(), dest_left_border_width, 914 rect.height()); 915 916 int dest_x = rect.right() - dest_right_border_width; 917 DrawImageInt(canvas, *right_border_image, 0, 0, right_border_image->width(), 918 right_border_image->height(), dest_x, rect.y(), 919 dest_right_border_width, rect.height()); 920 } 921 922 bool NativeThemeBase::IntersectsClipRectInt(SkCanvas* canvas, 923 int x, int y, int w, int h) const { 924 SkRect clip; 925 return canvas->getClipBounds(&clip) && 926 clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w), 927 SkIntToScalar(y + h)); 928 } 929 930 void NativeThemeBase::DrawImageInt( 931 SkCanvas* sk_canvas, const gfx::ImageSkia& image, 932 int src_x, int src_y, int src_w, int src_h, 933 int dest_x, int dest_y, int dest_w, int dest_h) const { 934 // TODO(pkotwicz): Do something better and don't infer device 935 // scale factor from canvas scale. 936 SkMatrix m = sk_canvas->getTotalMatrix(); 937 float device_scale = static_cast<float>(SkScalarAbs(m.getScaleX())); 938 scoped_ptr<gfx::Canvas> canvas(gfx::Canvas::CreateCanvasWithoutScaling( 939 sk_canvas, device_scale)); 940 canvas->DrawImageInt(image, src_x, src_y, src_w, src_h, 941 dest_x, dest_y, dest_w, dest_h, true); 942 } 943 944 void NativeThemeBase::DrawTiledImage(SkCanvas* sk_canvas, 945 const gfx::ImageSkia& image, 946 int src_x, int src_y, float tile_scale_x, float tile_scale_y, 947 int dest_x, int dest_y, int w, int h) const { 948 // TODO(pkotwicz): Do something better and don't infer device 949 // scale factor from canvas scale. 950 SkMatrix m = sk_canvas->getTotalMatrix(); 951 float device_scale = static_cast<float>(SkScalarAbs(m.getScaleX())); 952 scoped_ptr<gfx::Canvas> canvas(gfx::Canvas::CreateCanvasWithoutScaling( 953 sk_canvas, device_scale)); 954 canvas->TileImageInt(image, src_x, src_y, tile_scale_x, 955 tile_scale_y, dest_x, dest_y, w, h); 956 } 957 958 SkColor NativeThemeBase::SaturateAndBrighten(SkScalar* hsv, 959 SkScalar saturate_amount, 960 SkScalar brighten_amount) const { 961 SkScalar color[3]; 962 color[0] = hsv[0]; 963 color[1] = Clamp(hsv[1] + saturate_amount, 0.0, 1.0); 964 color[2] = Clamp(hsv[2] + brighten_amount, 0.0, 1.0); 965 return SkHSVToColor(color); 966 } 967 968 void NativeThemeBase::DrawVertLine(SkCanvas* canvas, 969 int x, 970 int y1, 971 int y2, 972 const SkPaint& paint) const { 973 SkIRect skrect; 974 skrect.set(x, y1, x + 1, y2 + 1); 975 canvas->drawIRect(skrect, paint); 976 } 977 978 void NativeThemeBase::DrawHorizLine(SkCanvas* canvas, 979 int x1, 980 int x2, 981 int y, 982 const SkPaint& paint) const { 983 SkIRect skrect; 984 skrect.set(x1, y, x2 + 1, y + 1); 985 canvas->drawIRect(skrect, paint); 986 } 987 988 void NativeThemeBase::DrawBox(SkCanvas* canvas, 989 const gfx::Rect& rect, 990 const SkPaint& paint) const { 991 const int right = rect.x() + rect.width() - 1; 992 const int bottom = rect.y() + rect.height() - 1; 993 DrawHorizLine(canvas, rect.x(), right, rect.y(), paint); 994 DrawVertLine(canvas, right, rect.y(), bottom, paint); 995 DrawHorizLine(canvas, rect.x(), right, bottom, paint); 996 DrawVertLine(canvas, rect.x(), rect.y(), bottom, paint); 997 } 998 999 SkScalar NativeThemeBase::Clamp(SkScalar value, 1000 SkScalar min, 1001 SkScalar max) const { 1002 return std::min(std::max(value, min), max); 1003 } 1004 1005 SkColor NativeThemeBase::OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const { 1006 // GTK Theme engines have way too much control over the layout of 1007 // the scrollbar. We might be able to more closely approximate its 1008 // look-and-feel, if we sent whole images instead of just colors 1009 // from the browser to the renderer. But even then, some themes 1010 // would just break. 1011 // 1012 // So, instead, we don't even try to 100% replicate the look of 1013 // the native scrollbar. We render our own version, but we make 1014 // sure to pick colors that blend in nicely with the system GTK 1015 // theme. In most cases, we can just sample a couple of pixels 1016 // from the system scrollbar and use those colors to draw our 1017 // scrollbar. 1018 // 1019 // This works fine for the track color and the overall thumb 1020 // color. But it fails spectacularly for the outline color used 1021 // around the thumb piece. Not all themes have a clearly defined 1022 // outline. For some of them it is partially transparent, and for 1023 // others the thickness is very unpredictable. 1024 // 1025 // So, instead of trying to approximate the system theme, we 1026 // instead try to compute a reasonable looking choice based on the 1027 // known color of the track and the thumb piece. This is difficult 1028 // when trying to deal both with high- and low-contrast themes, 1029 // and both with positive and inverted themes. 1030 // 1031 // The following code has been tested to look OK with all of the 1032 // default GTK themes. 1033 SkScalar min_diff = Clamp((hsv1[1] + hsv2[1]) * 1.2f, 0.28f, 0.5f); 1034 SkScalar diff = Clamp(fabs(hsv1[2] - hsv2[2]) / 2, min_diff, 0.5f); 1035 1036 if (hsv1[2] + hsv2[2] > 1.0) 1037 diff = -diff; 1038 1039 return SaturateAndBrighten(hsv2, -0.2f, diff); 1040 } 1041 1042 } // namespace ui 1043