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