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