1 // Copyright 2014 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 "content/shell/renderer/test_runner/mock_web_theme_engine.h" 6 7 #include "base/logging.h" 8 #include "skia/ext/platform_canvas.h" 9 #include "third_party/WebKit/public/platform/WebRect.h" 10 #include "third_party/WebKit/public/platform/WebSize.h" 11 #include "third_party/skia/include/core/SkRect.h" 12 13 using blink::WebCanvas; 14 using blink::WebColor; 15 using blink::WebRect; 16 using blink::WebThemeEngine; 17 18 namespace content { 19 20 namespace { 21 22 const SkColor edgeColor = SK_ColorBLACK; 23 const SkColor readOnlyColor = SkColorSetRGB(0xe9, 0xc2, 0xa6); 24 25 } // namespace 26 27 SkColor bgColors(WebThemeEngine::State state) { 28 switch (state) { 29 case WebThemeEngine::StateDisabled: 30 return SkColorSetRGB(0xc9, 0xc9, 0xc9); 31 case WebThemeEngine::StateHover: 32 return SkColorSetRGB(0x43, 0xf9, 0xff); 33 case WebThemeEngine::StateNormal: 34 return SkColorSetRGB(0x89, 0xc4, 0xff); 35 case WebThemeEngine::StatePressed: 36 return SkColorSetRGB(0xa9, 0xff, 0x12); 37 case WebThemeEngine::StateFocused: 38 return SkColorSetRGB(0x00, 0xf3, 0xac); 39 case WebThemeEngine::StateReadonly: 40 return SkColorSetRGB(0xf3, 0xe0, 0xd0); 41 default: 42 NOTREACHED(); 43 } 44 return SkColorSetRGB(0x00, 0x00, 0xff); 45 } 46 47 blink::WebSize MockWebThemeEngine::getSize(WebThemeEngine::Part part) { 48 // FIXME: We use this constant to indicate we are being asked for the size of 49 // a part that we don't expect to be asked about. We return a garbage value 50 // rather than just asserting because this code doesn't have access to either 51 // WTF or base to raise an assertion or do any logging :(. 52 const blink::WebSize invalidPartSize = blink::WebSize(100, 100); 53 54 switch (part) { 55 case WebThemeEngine::PartScrollbarLeftArrow: 56 return blink::WebSize(17, 15); 57 case WebThemeEngine::PartScrollbarRightArrow: 58 return invalidPartSize; 59 case WebThemeEngine::PartScrollbarUpArrow: 60 return blink::WebSize(15, 17); 61 case WebThemeEngine::PartScrollbarDownArrow: 62 return invalidPartSize; 63 case WebThemeEngine::PartScrollbarHorizontalThumb: 64 return blink::WebSize(15, 15); 65 case WebThemeEngine::PartScrollbarVerticalThumb: 66 return blink::WebSize(15, 15); 67 case WebThemeEngine::PartScrollbarHorizontalTrack: 68 return blink::WebSize(0, 15); 69 case WebThemeEngine::PartScrollbarVerticalTrack: 70 return blink::WebSize(15, 0); 71 case WebThemeEngine::PartCheckbox: 72 case WebThemeEngine::PartRadio: 73 return blink::WebSize(13, 13); 74 case WebThemeEngine::PartSliderThumb: 75 return blink::WebSize(11, 21); 76 case WebThemeEngine::PartInnerSpinButton: 77 return blink::WebSize(15, 8); 78 default: 79 return invalidPartSize; 80 } 81 } 82 83 static SkIRect webRectToSkIRect(const WebRect& webRect) { 84 SkIRect irect; 85 irect.set(webRect.x, webRect.y, 86 webRect.x + webRect.width - 1, webRect.y + webRect.height - 1); 87 return irect; 88 } 89 90 static SkIRect validate(const SkIRect& rect, WebThemeEngine::Part part) { 91 switch (part) { 92 case WebThemeEngine::PartCheckbox: 93 case WebThemeEngine::PartRadio: { 94 SkIRect retval = rect; 95 96 // The maximum width and height is 13. 97 // Center the square in the passed rectangle. 98 const int maxControlSize = 13; 99 int controlSize = std::min(rect.width(), rect.height()); 100 controlSize = std::min(controlSize, maxControlSize); 101 102 retval.fLeft = rect.fLeft + (rect.width() / 2) - (controlSize / 2); 103 retval.fRight = retval.fLeft + controlSize - 1; 104 retval.fTop = rect.fTop + (rect.height() / 2) - (controlSize / 2); 105 retval.fBottom = retval.fTop + controlSize - 1; 106 107 return retval; 108 } 109 default: 110 return rect; 111 } 112 } 113 114 void box(SkCanvas* canvas, const SkIRect& rect, SkColor fillColor) { 115 SkPaint paint; 116 117 paint.setStyle(SkPaint::kFill_Style); 118 paint.setColor(fillColor); 119 canvas->drawIRect(rect, paint); 120 121 paint.setColor(edgeColor); 122 paint.setStyle(SkPaint::kStroke_Style); 123 canvas->drawIRect(rect, paint); 124 } 125 126 void line(SkCanvas* canvas, int x0, int y0, int x1, int y1, SkColor color) { 127 SkPaint paint; 128 paint.setColor(color); 129 canvas->drawLine(SkIntToScalar(x0), 130 SkIntToScalar(y0), 131 SkIntToScalar(x1), 132 SkIntToScalar(y1), 133 paint); 134 } 135 136 void triangle(SkCanvas* canvas, 137 int x0, 138 int y0, 139 int x1, 140 int y1, 141 int x2, 142 int y2, 143 SkColor color) { 144 SkPath path; 145 SkPaint paint; 146 147 paint.setColor(color); 148 paint.setStyle(SkPaint::kFill_Style); 149 path.incReserve(4); 150 path.moveTo(SkIntToScalar(x0), SkIntToScalar(y0)); 151 path.lineTo(SkIntToScalar(x1), SkIntToScalar(y1)); 152 path.lineTo(SkIntToScalar(x2), SkIntToScalar(y2)); 153 path.close(); 154 canvas->drawPath(path, paint); 155 156 paint.setColor(edgeColor); 157 paint.setStyle(SkPaint::kStroke_Style); 158 canvas->drawPath(path, paint); 159 } 160 161 void roundRect(SkCanvas* canvas, SkIRect irect, SkColor color) { 162 SkRect rect; 163 SkScalar radius = SkIntToScalar(5); 164 SkPaint paint; 165 166 rect.set(irect); 167 paint.setColor(color); 168 paint.setStyle(SkPaint::kFill_Style); 169 canvas->drawRoundRect(rect, radius, radius, paint); 170 171 paint.setColor(edgeColor); 172 paint.setStyle(SkPaint::kStroke_Style); 173 canvas->drawRoundRect(rect, radius, radius, paint); 174 } 175 176 void oval(SkCanvas* canvas, SkIRect irect, SkColor color) { 177 SkRect rect; 178 SkPaint paint; 179 180 rect.set(irect); 181 paint.setColor(color); 182 paint.setStyle(SkPaint::kFill_Style); 183 canvas->drawOval(rect, paint); 184 185 paint.setColor(edgeColor); 186 paint.setStyle(SkPaint::kStroke_Style); 187 canvas->drawOval(rect, paint); 188 } 189 190 void circle(SkCanvas* canvas, SkIRect irect, SkScalar radius, SkColor color) { 191 int left = irect.fLeft; 192 int width = irect.width(); 193 int height = irect.height(); 194 int top = irect.fTop; 195 196 SkScalar cy = SkIntToScalar(top + height / 2); 197 SkScalar cx = SkIntToScalar(left + width / 2); 198 SkPaint paint; 199 200 paint.setColor(color); 201 paint.setStyle(SkPaint::kFill_Style); 202 canvas->drawCircle(cx, cy, radius, paint); 203 204 paint.setColor(edgeColor); 205 paint.setStyle(SkPaint::kStroke_Style); 206 canvas->drawCircle(cx, cy, radius, paint); 207 } 208 209 void nestedBoxes(SkCanvas* canvas, 210 SkIRect irect, 211 int indentLeft, 212 int indentTop, 213 int indentRight, 214 int indentBottom, 215 SkColor outerColor, 216 SkColor innerColor) { 217 SkIRect lirect; 218 box(canvas, irect, outerColor); 219 lirect.set(irect.fLeft + indentLeft, 220 irect.fTop + indentTop, 221 irect.fRight - indentRight, 222 irect.fBottom - indentBottom); 223 box(canvas, lirect, innerColor); 224 } 225 226 void insetBox(SkCanvas* canvas, 227 SkIRect irect, 228 int indentLeft, 229 int indentTop, 230 int indentRight, 231 int indentBottom, 232 SkColor color) { 233 SkIRect lirect; 234 lirect.set(irect.fLeft + indentLeft, 235 irect.fTop + indentTop, 236 irect.fRight - indentRight, 237 irect.fBottom - indentBottom); 238 box(canvas, lirect, color); 239 } 240 241 void markState(SkCanvas* canvas, SkIRect irect, WebThemeEngine::State state) { 242 int left = irect.fLeft; 243 int right = irect.fRight; 244 int top = irect.fTop; 245 int bottom = irect.fBottom; 246 247 // The length of a triangle side for the corner marks. 248 const int triangleSize = 5; 249 250 switch (state) { 251 case WebThemeEngine::StateDisabled: 252 case WebThemeEngine::StateNormal: 253 // Don't visually mark these states (color is enough). 254 break; 255 256 case WebThemeEngine::StateReadonly: { 257 // The horizontal lines in a read only control are spaced by this amount. 258 const int readOnlyLineOffset = 5; 259 260 // Drawing lines across the control. 261 for (int i = top + readOnlyLineOffset; i < bottom; i += readOnlyLineOffset) 262 line(canvas, left + 1, i, right - 1, i, readOnlyColor); 263 break; 264 } 265 case WebThemeEngine::StateHover: 266 // Draw a triangle in the upper left corner of the control. (Win's "hot") 267 triangle(canvas, 268 left, top, 269 left + triangleSize, top, 270 left, top + triangleSize, 271 edgeColor); 272 break; 273 274 case WebThemeEngine::StateFocused: 275 // Draw a triangle in the bottom right corner of the control. 276 triangle(canvas, 277 right, bottom, 278 right - triangleSize, bottom, 279 right, bottom - triangleSize, 280 edgeColor); 281 break; 282 283 case WebThemeEngine::StatePressed: 284 // Draw a triangle in the bottom left corner of the control. 285 triangle(canvas, 286 left, bottom, 287 left, bottom - triangleSize, 288 left + triangleSize, bottom, 289 edgeColor); 290 break; 291 292 default: 293 // FIXME: Should we do something here to indicate that we got an invalid state? 294 // Unfortunately, we can't assert because we don't have access to WTF or base. 295 break; 296 } 297 } 298 299 void MockWebThemeEngine::paint(blink::WebCanvas* canvas, 300 WebThemeEngine::Part part, 301 WebThemeEngine::State state, 302 const blink::WebRect& rect, 303 const WebThemeEngine::ExtraParams* extraParams) { 304 SkIRect irect = webRectToSkIRect(rect); 305 SkPaint paint; 306 307 // Indent amounts for the check in a checkbox or radio button. 308 const int checkIndent = 3; 309 310 // Indent amounts for short and long sides of the scrollbar notches. 311 const int notchLongOffset = 1; 312 const int notchShortOffset = 4; 313 const int noOffset = 0; 314 315 // Indent amounts for the short and long sides of a scroll thumb box. 316 const int thumbLongIndent = 0; 317 const int thumbShortIndent = 2; 318 319 // Indents for the crosshatch on a scroll grip. 320 const int gripLongIndent = 3; 321 const int gripShortIndent = 5; 322 323 // Indents for the the slider track. 324 const int sliderIndent = 2; 325 326 int halfHeight = irect.height() / 2; 327 int halfWidth = irect.width() / 2; 328 int quarterHeight = irect.height() / 4; 329 int quarterWidth = irect.width() / 4; 330 int left = irect.fLeft; 331 int right = irect.fRight; 332 int top = irect.fTop; 333 int bottom = irect.fBottom; 334 335 switch (part) { 336 case WebThemeEngine::PartScrollbarDownArrow: 337 box(canvas, irect, bgColors(state)); 338 triangle(canvas, 339 left + quarterWidth, top + quarterHeight, 340 right - quarterWidth, top + quarterHeight, 341 left + halfWidth, bottom - quarterHeight, 342 edgeColor); 343 markState(canvas, irect, state); 344 break; 345 346 case WebThemeEngine::PartScrollbarLeftArrow: 347 box(canvas, irect, bgColors(state)); 348 triangle(canvas, 349 right - quarterWidth, top + quarterHeight, 350 right - quarterWidth, bottom - quarterHeight, 351 left + quarterWidth, top + halfHeight, 352 edgeColor); 353 break; 354 355 case WebThemeEngine::PartScrollbarRightArrow: 356 box(canvas, irect, bgColors(state)); 357 triangle(canvas, 358 left + quarterWidth, top + quarterHeight, 359 right - quarterWidth, top + halfHeight, 360 left + quarterWidth, bottom - quarterHeight, 361 edgeColor); 362 break; 363 364 case WebThemeEngine::PartScrollbarUpArrow: 365 box(canvas, irect, bgColors(state)); 366 triangle(canvas, 367 left + quarterWidth, bottom - quarterHeight, 368 left + halfWidth, top + quarterHeight, 369 right - quarterWidth, bottom - quarterHeight, 370 edgeColor); 371 markState(canvas, irect, state); 372 break; 373 374 case WebThemeEngine::PartScrollbarHorizontalThumb: { 375 // Draw a narrower box on top of the outside box. 376 nestedBoxes(canvas, irect, thumbLongIndent, thumbShortIndent, 377 thumbLongIndent, thumbShortIndent, 378 bgColors(state), bgColors(state)); 379 // Draw a horizontal crosshatch for the grip. 380 int longOffset = halfWidth - gripLongIndent; 381 line(canvas, 382 left + gripLongIndent, top + halfHeight, 383 right - gripLongIndent, top + halfHeight, 384 edgeColor); 385 line(canvas, 386 left + longOffset, top + gripShortIndent, 387 left + longOffset, bottom - gripShortIndent, 388 edgeColor); 389 line(canvas, 390 right - longOffset, top + gripShortIndent, 391 right - longOffset, bottom - gripShortIndent, 392 edgeColor); 393 markState(canvas, irect, state); 394 break; 395 } 396 397 case WebThemeEngine::PartScrollbarVerticalThumb: { 398 // Draw a shorter box on top of the outside box. 399 nestedBoxes(canvas, irect, thumbShortIndent, thumbLongIndent, 400 thumbShortIndent, thumbLongIndent, 401 bgColors(state), bgColors(state)); 402 // Draw a vertical crosshatch for the grip. 403 int longOffset = halfHeight - gripLongIndent; 404 line(canvas, 405 left + halfWidth, top + gripLongIndent, 406 left + halfWidth, bottom - gripLongIndent, 407 edgeColor); 408 line(canvas, 409 left + gripShortIndent, top + longOffset, 410 right - gripShortIndent, top + longOffset, 411 edgeColor); 412 line(canvas, 413 left + gripShortIndent, bottom - longOffset, 414 right - gripShortIndent, bottom - longOffset, 415 edgeColor); 416 markState(canvas, irect, state); 417 break; 418 } 419 420 case WebThemeEngine::PartScrollbarHorizontalTrack: { 421 int longOffset = halfHeight - notchLongOffset; 422 int shortOffset = irect.width() - notchShortOffset; 423 box(canvas, irect, bgColors(state)); 424 // back, notch on right 425 insetBox(canvas, 426 irect, 427 noOffset, 428 longOffset, 429 shortOffset, 430 longOffset, 431 edgeColor); 432 // forward, notch on right 433 insetBox(canvas, 434 irect, 435 shortOffset, 436 longOffset, 437 noOffset, 438 longOffset, 439 edgeColor); 440 markState(canvas, irect, state); 441 break; 442 } 443 444 case WebThemeEngine::PartScrollbarVerticalTrack: { 445 int longOffset = halfWidth - notchLongOffset; 446 int shortOffset = irect.height() - notchShortOffset; 447 box(canvas, irect, bgColors(state)); 448 // back, notch at top 449 insetBox(canvas, 450 irect, 451 longOffset, 452 noOffset, 453 longOffset, 454 shortOffset, 455 edgeColor); 456 // forward, notch at bottom 457 insetBox(canvas, 458 irect, 459 longOffset, 460 shortOffset, 461 longOffset, 462 noOffset, 463 edgeColor); 464 markState(canvas, irect, state); 465 break; 466 } 467 468 case WebThemeEngine::PartScrollbarCorner: { 469 SkIRect cornerRect = {rect.x, rect.y, rect.x + rect.width, rect.y + rect.height}; 470 paint.setColor(SK_ColorWHITE); 471 paint.setStyle(SkPaint::kFill_Style); 472 paint.setXfermodeMode(SkXfermode::kSrc_Mode); 473 paint.setAntiAlias(true); 474 canvas->drawIRect(cornerRect, paint); 475 break; 476 } 477 478 case WebThemeEngine::PartCheckbox: 479 if (extraParams->button.indeterminate) { 480 nestedBoxes(canvas, irect, 481 checkIndent, halfHeight, 482 checkIndent, halfHeight, 483 bgColors(state), edgeColor); 484 } else if (extraParams->button.checked) { 485 irect = validate(irect, part); 486 nestedBoxes(canvas, irect, 487 checkIndent, checkIndent, 488 checkIndent, checkIndent, 489 bgColors(state), edgeColor); 490 } else { 491 irect = validate(irect, part); 492 box(canvas, irect, bgColors(state)); 493 } 494 break; 495 496 case WebThemeEngine::PartRadio: 497 irect = validate(irect, part); 498 halfHeight = irect.height() / 2; 499 if (extraParams->button.checked) { 500 circle(canvas, irect, SkIntToScalar(halfHeight), bgColors(state)); 501 circle(canvas, irect, SkIntToScalar(halfHeight - checkIndent), edgeColor); 502 } else { 503 circle(canvas, irect, SkIntToScalar(halfHeight), bgColors(state)); 504 } 505 break; 506 507 case WebThemeEngine::PartButton: 508 roundRect(canvas, irect, bgColors(state)); 509 markState(canvas, irect, state); 510 break; 511 512 case WebThemeEngine::PartTextField: 513 paint.setColor(extraParams->textField.backgroundColor); 514 paint.setStyle(SkPaint::kFill_Style); 515 canvas->drawIRect(irect, paint); 516 517 paint.setColor(edgeColor); 518 paint.setStyle(SkPaint::kStroke_Style); 519 canvas->drawIRect(irect, paint); 520 521 markState(canvas, irect, state); 522 break; 523 524 case WebThemeEngine::PartMenuList: 525 if (extraParams->menuList.fillContentArea) { 526 box(canvas, irect, extraParams->menuList.backgroundColor); 527 } else { 528 SkPaint paint; 529 paint.setColor(edgeColor); 530 paint.setStyle(SkPaint::kStroke_Style); 531 canvas->drawIRect(irect, paint); 532 } 533 534 // clip the drop-down arrow to be inside the select box 535 if (extraParams->menuList.arrowX - 4 > irect.fLeft) 536 irect.fLeft = extraParams->menuList.arrowX - 4; 537 if (extraParams->menuList.arrowX + 12 < irect.fRight) 538 irect.fRight = extraParams->menuList.arrowX + 12; 539 540 irect.fTop = extraParams->menuList.arrowY - (extraParams->menuList.arrowHeight) / 2; 541 irect.fBottom = extraParams->menuList.arrowY + (extraParams->menuList.arrowHeight - 1) / 2; 542 halfWidth = irect.width() / 2; 543 quarterWidth = irect.width() / 4; 544 545 if (state == WebThemeEngine::StateFocused) // FIXME: draw differenty? 546 state = WebThemeEngine::StateNormal; 547 box(canvas, irect, bgColors(state)); 548 triangle(canvas, 549 irect.fLeft + quarterWidth, irect.fTop, 550 irect.fRight - quarterWidth, irect.fTop, 551 irect.fLeft + halfWidth, irect.fBottom, 552 edgeColor); 553 554 break; 555 556 case WebThemeEngine::PartSliderTrack: { 557 SkIRect lirect = irect; 558 559 // Draw a narrow rect for the track plus box hatches on the ends. 560 if (state == WebThemeEngine::StateFocused) // FIXME: draw differently? 561 state = WebThemeEngine::StateNormal; 562 if (extraParams->slider.vertical) { 563 lirect.inset(halfWidth - sliderIndent, noOffset); 564 box(canvas, lirect, bgColors(state)); 565 line(canvas, left, top, right, top, edgeColor); 566 line(canvas, left, bottom, right, bottom, edgeColor); 567 } else { 568 lirect.inset(noOffset, halfHeight - sliderIndent); 569 box(canvas, lirect, bgColors(state)); 570 line(canvas, left, top, left, bottom, edgeColor); 571 line(canvas, right, top, right, bottom, edgeColor); 572 } 573 break; 574 } 575 576 case WebThemeEngine::PartSliderThumb: 577 if (state == WebThemeEngine::StateFocused) // FIXME: draw differently? 578 state = WebThemeEngine::StateNormal; 579 oval(canvas, irect, bgColors(state)); 580 break; 581 582 case WebThemeEngine::PartInnerSpinButton: { 583 // stack half-height up and down arrows on top of each other 584 SkIRect lirect; 585 int halfHeight = rect.height / 2; 586 if (extraParams->innerSpin.readOnly) 587 state = blink::WebThemeEngine::StateDisabled; 588 589 lirect.set(rect.x, rect.y, rect.x + rect.width - 1, rect.y + halfHeight - 1); 590 box(canvas, lirect, bgColors(state)); 591 bottom = lirect.fBottom; 592 quarterHeight = lirect.height() / 4; 593 triangle(canvas, 594 left + quarterWidth, bottom - quarterHeight, 595 right - quarterWidth, bottom - quarterHeight, 596 left + halfWidth, top + quarterHeight, 597 edgeColor); 598 599 lirect.set(rect.x, rect.y + halfHeight, rect.x + rect.width - 1, 600 rect.y + 2 * halfHeight - 1); 601 top = lirect.fTop; 602 bottom = lirect.fBottom; 603 quarterHeight = lirect.height() / 4; 604 box(canvas, lirect, bgColors(state)); 605 triangle(canvas, 606 left + quarterWidth, top + quarterHeight, 607 right - quarterWidth, top + quarterHeight, 608 left + halfWidth, bottom - quarterHeight, 609 edgeColor); 610 markState(canvas, irect, state); 611 break; 612 } 613 case WebThemeEngine::PartProgressBar: { 614 paint.setColor(bgColors(state)); 615 paint.setStyle(SkPaint::kFill_Style); 616 canvas->drawIRect(irect, paint); 617 618 // Emulate clipping 619 SkIRect tofill = irect; 620 if (extraParams->progressBar.determinate) { 621 tofill.set(extraParams->progressBar.valueRectX, 622 extraParams->progressBar.valueRectY, 623 extraParams->progressBar.valueRectX + 624 extraParams->progressBar.valueRectWidth - 1, 625 extraParams->progressBar.valueRectY + 626 extraParams->progressBar.valueRectHeight); 627 } 628 629 tofill.intersect(irect, tofill); 630 paint.setColor(edgeColor); 631 paint.setStyle(SkPaint::kFill_Style); 632 canvas->drawIRect(tofill, paint); 633 634 markState(canvas, irect, state); 635 break; 636 } 637 default: 638 // FIXME: Should we do something here to indicate that we got an invalid part? 639 // Unfortunately, we can't assert because we don't have access to WTF or base. 640 break; 641 } 642 } 643 644 } // namespace content 645