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