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.
5 #include "components/test_runner/mock_web_theme_engine.h"
7 #if !defined(OS_MACOSX)
9 #include "base/logging.h"
10 #include "skia/ext/platform_canvas.h"
11 #include "third_party/WebKit/public/platform/WebRect.h"
12 #include "third_party/WebKit/public/platform/WebSize.h"
13 #include "third_party/skia/include/core/SkRect.h"
15 using blink::WebCanvas
;
16 using blink::WebColor
;
18 using blink::WebThemeEngine
;
20 namespace test_runner
{
24 const SkColor edgeColor
= SK_ColorBLACK
;
25 const SkColor readOnlyColor
= SkColorSetRGB(0xe9, 0xc2, 0xa6);
29 SkColor
bgColors(WebThemeEngine::State state
) {
31 case WebThemeEngine::StateDisabled
:
32 return SkColorSetRGB(0xc9, 0xc9, 0xc9);
33 case WebThemeEngine::StateHover
:
34 return SkColorSetRGB(0x43, 0xf9, 0xff);
35 case WebThemeEngine::StateNormal
:
36 return SkColorSetRGB(0x89, 0xc4, 0xff);
37 case WebThemeEngine::StatePressed
:
38 return SkColorSetRGB(0xa9, 0xff, 0x12);
39 case WebThemeEngine::StateFocused
:
40 return SkColorSetRGB(0x00, 0xf3, 0xac);
41 case WebThemeEngine::StateReadonly
:
42 return SkColorSetRGB(0xf3, 0xe0, 0xd0);
46 return SkColorSetRGB(0x00, 0x00, 0xff);
49 blink::WebSize
MockWebThemeEngine::getSize(WebThemeEngine::Part part
) {
50 // FIXME: We use this constant to indicate we are being asked for the size of
51 // a part that we don't expect to be asked about. We return a garbage value
52 // rather than just asserting because this code doesn't have access to either
53 // WTF or base to raise an assertion or do any logging :(.
54 const blink::WebSize invalidPartSize
= blink::WebSize(100, 100);
57 case WebThemeEngine::PartScrollbarLeftArrow
:
58 return blink::WebSize(17, 15);
59 case WebThemeEngine::PartScrollbarRightArrow
:
60 return invalidPartSize
;
61 case WebThemeEngine::PartScrollbarUpArrow
:
62 return blink::WebSize(15, 17);
63 case WebThemeEngine::PartScrollbarDownArrow
:
64 return invalidPartSize
;
65 case WebThemeEngine::PartScrollbarHorizontalThumb
:
66 return blink::WebSize(15, 15);
67 case WebThemeEngine::PartScrollbarVerticalThumb
:
68 return blink::WebSize(15, 15);
69 case WebThemeEngine::PartScrollbarHorizontalTrack
:
70 return blink::WebSize(0, 15);
71 case WebThemeEngine::PartScrollbarVerticalTrack
:
72 return blink::WebSize(15, 0);
73 case WebThemeEngine::PartCheckbox
:
74 case WebThemeEngine::PartRadio
:
75 return blink::WebSize(13, 13);
76 case WebThemeEngine::PartSliderThumb
:
77 return blink::WebSize(11, 21);
78 case WebThemeEngine::PartInnerSpinButton
:
79 return blink::WebSize(15, 8);
81 return invalidPartSize
;
85 static SkIRect
webRectToSkIRect(const WebRect
& webRect
) {
87 irect
.set(webRect
.x
, webRect
.y
, webRect
.x
+ webRect
.width
- 1,
88 webRect
.y
+ webRect
.height
- 1);
92 static SkIRect
validate(const SkIRect
& rect
, WebThemeEngine::Part part
) {
94 case WebThemeEngine::PartCheckbox
:
95 case WebThemeEngine::PartRadio
: {
96 SkIRect retval
= rect
;
98 // The maximum width and height is 13.
99 // Center the square in the passed rectangle.
100 const int maxControlSize
= 13;
101 int controlSize
= std::min(rect
.width(), rect
.height());
102 controlSize
= std::min(controlSize
, maxControlSize
);
104 retval
.fLeft
= rect
.fLeft
+ (rect
.width() / 2) - (controlSize
/ 2);
105 retval
.fRight
= retval
.fLeft
+ controlSize
- 1;
106 retval
.fTop
= rect
.fTop
+ (rect
.height() / 2) - (controlSize
/ 2);
107 retval
.fBottom
= retval
.fTop
+ controlSize
- 1;
116 void box(SkCanvas
* canvas
, const SkIRect
& rect
, SkColor fillColor
) {
119 paint
.setStyle(SkPaint::kFill_Style
);
120 paint
.setColor(fillColor
);
121 canvas
->drawIRect(rect
, paint
);
123 paint
.setColor(edgeColor
);
124 paint
.setStyle(SkPaint::kStroke_Style
);
125 canvas
->drawIRect(rect
, paint
);
128 void line(SkCanvas
* canvas
, int x0
, int y0
, int x1
, int y1
, SkColor color
) {
130 paint
.setColor(color
);
131 canvas
->drawLine(SkIntToScalar(x0
), SkIntToScalar(y0
), SkIntToScalar(x1
),
132 SkIntToScalar(y1
), paint
);
135 void triangle(SkCanvas
* canvas
,
146 paint
.setColor(color
);
147 paint
.setStyle(SkPaint::kFill_Style
);
149 path
.moveTo(SkIntToScalar(x0
), SkIntToScalar(y0
));
150 path
.lineTo(SkIntToScalar(x1
), SkIntToScalar(y1
));
151 path
.lineTo(SkIntToScalar(x2
), SkIntToScalar(y2
));
153 canvas
->drawPath(path
, paint
);
155 paint
.setColor(edgeColor
);
156 paint
.setStyle(SkPaint::kStroke_Style
);
157 canvas
->drawPath(path
, paint
);
160 void roundRect(SkCanvas
* canvas
, SkIRect irect
, SkColor color
) {
162 SkScalar radius
= SkIntToScalar(5);
166 paint
.setColor(color
);
167 paint
.setStyle(SkPaint::kFill_Style
);
168 canvas
->drawRoundRect(rect
, radius
, radius
, paint
);
170 paint
.setColor(edgeColor
);
171 paint
.setStyle(SkPaint::kStroke_Style
);
172 canvas
->drawRoundRect(rect
, radius
, radius
, paint
);
175 void oval(SkCanvas
* canvas
, SkIRect irect
, SkColor color
) {
180 paint
.setColor(color
);
181 paint
.setStyle(SkPaint::kFill_Style
);
182 canvas
->drawOval(rect
, paint
);
184 paint
.setColor(edgeColor
);
185 paint
.setStyle(SkPaint::kStroke_Style
);
186 canvas
->drawOval(rect
, paint
);
189 void circle(SkCanvas
* canvas
, SkIRect irect
, SkScalar radius
, SkColor color
) {
190 int left
= irect
.fLeft
;
191 int width
= irect
.width();
192 int height
= irect
.height();
193 int top
= irect
.fTop
;
195 SkScalar cy
= SkIntToScalar(top
+ height
/ 2);
196 SkScalar cx
= SkIntToScalar(left
+ width
/ 2);
199 paint
.setColor(color
);
200 paint
.setStyle(SkPaint::kFill_Style
);
201 canvas
->drawCircle(cx
, cy
, radius
, paint
);
203 paint
.setColor(edgeColor
);
204 paint
.setStyle(SkPaint::kStroke_Style
);
205 canvas
->drawCircle(cx
, cy
, radius
, paint
);
208 void nestedBoxes(SkCanvas
* canvas
,
215 SkColor innerColor
) {
217 box(canvas
, irect
, outerColor
);
218 lirect
.set(irect
.fLeft
+ indentLeft
, irect
.fTop
+ indentTop
,
219 irect
.fRight
- indentRight
, irect
.fBottom
- indentBottom
);
220 box(canvas
, lirect
, innerColor
);
223 void insetBox(SkCanvas
* canvas
,
231 lirect
.set(irect
.fLeft
+ indentLeft
, irect
.fTop
+ indentTop
,
232 irect
.fRight
- indentRight
, irect
.fBottom
- indentBottom
);
233 box(canvas
, lirect
, color
);
236 void markState(SkCanvas
* canvas
, SkIRect irect
, WebThemeEngine::State state
) {
237 int left
= irect
.fLeft
;
238 int right
= irect
.fRight
;
239 int top
= irect
.fTop
;
240 int bottom
= irect
.fBottom
;
242 // The length of a triangle side for the corner marks.
243 const int triangleSize
= 5;
246 case WebThemeEngine::StateDisabled
:
247 case WebThemeEngine::StateNormal
:
248 // Don't visually mark these states (color is enough).
251 case WebThemeEngine::StateReadonly
: {
252 // The horizontal lines in a read only control are spaced by this amount.
253 const int readOnlyLineOffset
= 5;
255 // Drawing lines across the control.
256 for (int i
= top
+ readOnlyLineOffset
; i
< bottom
;
257 i
+= readOnlyLineOffset
)
258 line(canvas
, left
+ 1, i
, right
- 1, i
, readOnlyColor
);
261 case WebThemeEngine::StateHover
:
262 // Draw a triangle in the upper left corner of the control. (Win's "hot")
263 triangle(canvas
, left
, top
, left
+ triangleSize
, top
, left
,
264 top
+ triangleSize
, edgeColor
);
267 case WebThemeEngine::StateFocused
:
268 // Draw a triangle in the bottom right corner of the control.
269 triangle(canvas
, right
, bottom
, right
- triangleSize
, bottom
, right
,
270 bottom
- triangleSize
, edgeColor
);
273 case WebThemeEngine::StatePressed
:
274 // Draw a triangle in the bottom left corner of the control.
275 triangle(canvas
, left
, bottom
, left
, bottom
- triangleSize
,
276 left
+ triangleSize
, bottom
, edgeColor
);
280 // FIXME: Should we do something here to indicate that we got an invalid
282 // Unfortunately, we can't assert because we don't have access to WTF or
288 void MockWebThemeEngine::paint(blink::WebCanvas
* canvas
,
289 WebThemeEngine::Part part
,
290 WebThemeEngine::State state
,
291 const blink::WebRect
& rect
,
292 const WebThemeEngine::ExtraParams
* extraParams
) {
293 SkIRect irect
= webRectToSkIRect(rect
);
296 // Indent amounts for the check in a checkbox or radio button.
297 const int checkIndent
= 3;
299 // Indent amounts for short and long sides of the scrollbar notches.
300 const int notchLongOffset
= 1;
301 const int notchShortOffset
= 4;
302 const int noOffset
= 0;
304 // Indent amounts for the short and long sides of a scroll thumb box.
305 const int thumbLongIndent
= 0;
306 const int thumbShortIndent
= 2;
308 // Indents for the crosshatch on a scroll grip.
309 const int gripLongIndent
= 3;
310 const int gripShortIndent
= 5;
312 // Indents for the the slider track.
313 const int sliderIndent
= 2;
315 int halfHeight
= irect
.height() / 2;
316 int halfWidth
= irect
.width() / 2;
317 int quarterHeight
= irect
.height() / 4;
318 int quarterWidth
= irect
.width() / 4;
319 int left
= irect
.fLeft
;
320 int right
= irect
.fRight
;
321 int top
= irect
.fTop
;
322 int bottom
= irect
.fBottom
;
325 case WebThemeEngine::PartScrollbarDownArrow
:
326 box(canvas
, irect
, bgColors(state
));
327 triangle(canvas
, left
+ quarterWidth
, top
+ quarterHeight
,
328 right
- quarterWidth
, top
+ quarterHeight
, left
+ halfWidth
,
329 bottom
- quarterHeight
, edgeColor
);
330 markState(canvas
, irect
, state
);
333 case WebThemeEngine::PartScrollbarLeftArrow
:
334 box(canvas
, irect
, bgColors(state
));
335 triangle(canvas
, right
- quarterWidth
, top
+ quarterHeight
,
336 right
- quarterWidth
, bottom
- quarterHeight
,
337 left
+ quarterWidth
, top
+ halfHeight
, edgeColor
);
340 case WebThemeEngine::PartScrollbarRightArrow
:
341 box(canvas
, irect
, bgColors(state
));
342 triangle(canvas
, left
+ quarterWidth
, top
+ quarterHeight
,
343 right
- quarterWidth
, top
+ halfHeight
, left
+ quarterWidth
,
344 bottom
- quarterHeight
, edgeColor
);
347 case WebThemeEngine::PartScrollbarUpArrow
:
348 box(canvas
, irect
, bgColors(state
));
349 triangle(canvas
, left
+ quarterWidth
, bottom
- quarterHeight
,
350 left
+ halfWidth
, top
+ quarterHeight
, right
- quarterWidth
,
351 bottom
- quarterHeight
, edgeColor
);
352 markState(canvas
, irect
, state
);
355 case WebThemeEngine::PartScrollbarHorizontalThumb
: {
356 // Draw a narrower box on top of the outside box.
357 nestedBoxes(canvas
, irect
, thumbLongIndent
, thumbShortIndent
,
358 thumbLongIndent
, thumbShortIndent
, bgColors(state
),
360 // Draw a horizontal crosshatch for the grip.
361 int longOffset
= halfWidth
- gripLongIndent
;
362 line(canvas
, left
+ gripLongIndent
, top
+ halfHeight
,
363 right
- gripLongIndent
, top
+ halfHeight
, edgeColor
);
364 line(canvas
, left
+ longOffset
, top
+ gripShortIndent
, left
+ longOffset
,
365 bottom
- gripShortIndent
, edgeColor
);
366 line(canvas
, right
- longOffset
, top
+ gripShortIndent
,
367 right
- longOffset
, bottom
- gripShortIndent
, edgeColor
);
368 markState(canvas
, irect
, state
);
372 case WebThemeEngine::PartScrollbarVerticalThumb
: {
373 // Draw a shorter box on top of the outside box.
374 nestedBoxes(canvas
, irect
, thumbShortIndent
, thumbLongIndent
,
375 thumbShortIndent
, thumbLongIndent
, bgColors(state
),
377 // Draw a vertical crosshatch for the grip.
378 int longOffset
= halfHeight
- gripLongIndent
;
379 line(canvas
, left
+ halfWidth
, top
+ gripLongIndent
, left
+ halfWidth
,
380 bottom
- gripLongIndent
, edgeColor
);
381 line(canvas
, left
+ gripShortIndent
, top
+ longOffset
,
382 right
- gripShortIndent
, top
+ longOffset
, edgeColor
);
383 line(canvas
, left
+ gripShortIndent
, bottom
- longOffset
,
384 right
- gripShortIndent
, bottom
- longOffset
, edgeColor
);
385 markState(canvas
, irect
, state
);
389 case WebThemeEngine::PartScrollbarHorizontalTrack
: {
390 int longOffset
= halfHeight
- notchLongOffset
;
391 int shortOffset
= irect
.width() - notchShortOffset
;
392 box(canvas
, irect
, bgColors(state
));
393 // back, notch on right
394 insetBox(canvas
, irect
, noOffset
, longOffset
, shortOffset
, longOffset
,
396 // forward, notch on right
397 insetBox(canvas
, irect
, shortOffset
, longOffset
, noOffset
, longOffset
,
399 markState(canvas
, irect
, state
);
403 case WebThemeEngine::PartScrollbarVerticalTrack
: {
404 int longOffset
= halfWidth
- notchLongOffset
;
405 int shortOffset
= irect
.height() - notchShortOffset
;
406 box(canvas
, irect
, bgColors(state
));
407 // back, notch at top
408 insetBox(canvas
, irect
, longOffset
, noOffset
, longOffset
, shortOffset
,
410 // forward, notch at bottom
411 insetBox(canvas
, irect
, longOffset
, shortOffset
, longOffset
, noOffset
,
413 markState(canvas
, irect
, state
);
417 case WebThemeEngine::PartScrollbarCorner
: {
418 SkIRect cornerRect
= {rect
.x
, rect
.y
, rect
.x
+ rect
.width
,
419 rect
.y
+ rect
.height
};
420 paint
.setColor(SK_ColorWHITE
);
421 paint
.setStyle(SkPaint::kFill_Style
);
422 paint
.setXfermodeMode(SkXfermode::kSrc_Mode
);
423 paint
.setAntiAlias(true);
424 canvas
->drawIRect(cornerRect
, paint
);
428 case WebThemeEngine::PartCheckbox
:
429 if (extraParams
->button
.indeterminate
) {
430 nestedBoxes(canvas
, irect
, checkIndent
, halfHeight
, checkIndent
,
431 halfHeight
, bgColors(state
), edgeColor
);
432 } else if (extraParams
->button
.checked
) {
433 irect
= validate(irect
, part
);
434 nestedBoxes(canvas
, irect
, checkIndent
, checkIndent
, checkIndent
,
435 checkIndent
, bgColors(state
), edgeColor
);
437 irect
= validate(irect
, part
);
438 box(canvas
, irect
, bgColors(state
));
442 case WebThemeEngine::PartRadio
:
443 irect
= validate(irect
, part
);
444 halfHeight
= irect
.height() / 2;
445 if (extraParams
->button
.checked
) {
446 circle(canvas
, irect
, SkIntToScalar(halfHeight
), bgColors(state
));
447 circle(canvas
, irect
, SkIntToScalar(halfHeight
- checkIndent
),
450 circle(canvas
, irect
, SkIntToScalar(halfHeight
), bgColors(state
));
454 case WebThemeEngine::PartButton
:
455 roundRect(canvas
, irect
, bgColors(state
));
456 markState(canvas
, irect
, state
);
459 case WebThemeEngine::PartTextField
:
460 paint
.setColor(extraParams
->textField
.backgroundColor
);
461 paint
.setStyle(SkPaint::kFill_Style
);
462 canvas
->drawIRect(irect
, paint
);
464 paint
.setColor(edgeColor
);
465 paint
.setStyle(SkPaint::kStroke_Style
);
466 canvas
->drawIRect(irect
, paint
);
468 markState(canvas
, irect
, state
);
471 case WebThemeEngine::PartMenuList
:
472 if (extraParams
->menuList
.fillContentArea
) {
473 box(canvas
, irect
, extraParams
->menuList
.backgroundColor
);
476 paint
.setColor(edgeColor
);
477 paint
.setStyle(SkPaint::kStroke_Style
);
478 canvas
->drawIRect(irect
, paint
);
481 // clip the drop-down arrow to be inside the select box
482 if (extraParams
->menuList
.arrowX
- 4 > irect
.fLeft
)
483 irect
.fLeft
= extraParams
->menuList
.arrowX
- 4;
484 if (extraParams
->menuList
.arrowX
+ 12 < irect
.fRight
)
485 irect
.fRight
= extraParams
->menuList
.arrowX
+ 12;
487 irect
.fTop
= extraParams
->menuList
.arrowY
-
488 (extraParams
->menuList
.arrowHeight
) / 2;
489 irect
.fBottom
= extraParams
->menuList
.arrowY
+
490 (extraParams
->menuList
.arrowHeight
- 1) / 2;
491 halfWidth
= irect
.width() / 2;
492 quarterWidth
= irect
.width() / 4;
494 if (state
== WebThemeEngine::StateFocused
) // FIXME: draw differenty?
495 state
= WebThemeEngine::StateNormal
;
496 box(canvas
, irect
, bgColors(state
));
497 triangle(canvas
, irect
.fLeft
+ quarterWidth
, irect
.fTop
,
498 irect
.fRight
- quarterWidth
, irect
.fTop
, irect
.fLeft
+ halfWidth
,
499 irect
.fBottom
, edgeColor
);
503 case WebThemeEngine::PartSliderTrack
: {
504 SkIRect lirect
= irect
;
506 // Draw a narrow rect for the track plus box hatches on the ends.
507 if (state
== WebThemeEngine::StateFocused
) // FIXME: draw differently?
508 state
= WebThemeEngine::StateNormal
;
509 if (extraParams
->slider
.vertical
) {
510 lirect
.inset(halfWidth
- sliderIndent
, noOffset
);
511 box(canvas
, lirect
, bgColors(state
));
512 line(canvas
, left
, top
, right
, top
, edgeColor
);
513 line(canvas
, left
, bottom
, right
, bottom
, edgeColor
);
515 lirect
.inset(noOffset
, halfHeight
- sliderIndent
);
516 box(canvas
, lirect
, bgColors(state
));
517 line(canvas
, left
, top
, left
, bottom
, edgeColor
);
518 line(canvas
, right
, top
, right
, bottom
, edgeColor
);
523 case WebThemeEngine::PartSliderThumb
:
524 if (state
== WebThemeEngine::StateFocused
) // FIXME: draw differently?
525 state
= WebThemeEngine::StateNormal
;
526 oval(canvas
, irect
, bgColors(state
));
529 case WebThemeEngine::PartInnerSpinButton
: {
530 // stack half-height up and down arrows on top of each other
532 int halfHeight
= rect
.height
/ 2;
533 if (extraParams
->innerSpin
.readOnly
)
534 state
= blink::WebThemeEngine::StateDisabled
;
536 lirect
.set(rect
.x
, rect
.y
, rect
.x
+ rect
.width
- 1,
537 rect
.y
+ halfHeight
- 1);
538 box(canvas
, lirect
, bgColors(state
));
539 bottom
= lirect
.fBottom
;
540 quarterHeight
= lirect
.height() / 4;
541 triangle(canvas
, left
+ quarterWidth
, bottom
- quarterHeight
,
542 right
- quarterWidth
, bottom
- quarterHeight
, left
+ halfWidth
,
543 top
+ quarterHeight
, edgeColor
);
545 lirect
.set(rect
.x
, rect
.y
+ halfHeight
, rect
.x
+ rect
.width
- 1,
546 rect
.y
+ 2 * halfHeight
- 1);
548 bottom
= lirect
.fBottom
;
549 quarterHeight
= lirect
.height() / 4;
550 box(canvas
, lirect
, bgColors(state
));
551 triangle(canvas
, left
+ quarterWidth
, top
+ quarterHeight
,
552 right
- quarterWidth
, top
+ quarterHeight
, left
+ halfWidth
,
553 bottom
- quarterHeight
, edgeColor
);
554 markState(canvas
, irect
, state
);
557 case WebThemeEngine::PartProgressBar
: {
558 paint
.setColor(bgColors(state
));
559 paint
.setStyle(SkPaint::kFill_Style
);
560 canvas
->drawIRect(irect
, paint
);
563 SkIRect tofill
= irect
;
564 if (extraParams
->progressBar
.determinate
) {
565 tofill
.set(extraParams
->progressBar
.valueRectX
,
566 extraParams
->progressBar
.valueRectY
,
567 extraParams
->progressBar
.valueRectX
+
568 extraParams
->progressBar
.valueRectWidth
- 1,
569 extraParams
->progressBar
.valueRectY
+
570 extraParams
->progressBar
.valueRectHeight
);
573 if (!tofill
.intersect(irect
))
576 paint
.setColor(edgeColor
);
577 paint
.setStyle(SkPaint::kFill_Style
);
578 canvas
->drawIRect(tofill
, paint
);
580 markState(canvas
, irect
, state
);
584 // FIXME: Should we do something here to indicate that we got an invalid
586 // Unfortunately, we can't assert because we don't have access to WTF or
592 } // namespace test_runner
594 #endif // !defined(OS_MACOSX)