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