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 "content/shell/renderer/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
;
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
,
88 webRect
.x
+ webRect
.width
- 1, 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
),
138 void triangle(SkCanvas
* canvas
,
149 paint
.setColor(color
);
150 paint
.setStyle(SkPaint::kFill_Style
);
152 path
.moveTo(SkIntToScalar(x0
), SkIntToScalar(y0
));
153 path
.lineTo(SkIntToScalar(x1
), SkIntToScalar(y1
));
154 path
.lineTo(SkIntToScalar(x2
), SkIntToScalar(y2
));
156 canvas
->drawPath(path
, paint
);
158 paint
.setColor(edgeColor
);
159 paint
.setStyle(SkPaint::kStroke_Style
);
160 canvas
->drawPath(path
, paint
);
163 void roundRect(SkCanvas
* canvas
, SkIRect irect
, SkColor color
) {
165 SkScalar radius
= SkIntToScalar(5);
169 paint
.setColor(color
);
170 paint
.setStyle(SkPaint::kFill_Style
);
171 canvas
->drawRoundRect(rect
, radius
, radius
, paint
);
173 paint
.setColor(edgeColor
);
174 paint
.setStyle(SkPaint::kStroke_Style
);
175 canvas
->drawRoundRect(rect
, radius
, radius
, paint
);
178 void oval(SkCanvas
* canvas
, SkIRect irect
, SkColor color
) {
183 paint
.setColor(color
);
184 paint
.setStyle(SkPaint::kFill_Style
);
185 canvas
->drawOval(rect
, paint
);
187 paint
.setColor(edgeColor
);
188 paint
.setStyle(SkPaint::kStroke_Style
);
189 canvas
->drawOval(rect
, paint
);
192 void circle(SkCanvas
* canvas
, SkIRect irect
, SkScalar radius
, SkColor color
) {
193 int left
= irect
.fLeft
;
194 int width
= irect
.width();
195 int height
= irect
.height();
196 int top
= irect
.fTop
;
198 SkScalar cy
= SkIntToScalar(top
+ height
/ 2);
199 SkScalar cx
= SkIntToScalar(left
+ width
/ 2);
202 paint
.setColor(color
);
203 paint
.setStyle(SkPaint::kFill_Style
);
204 canvas
->drawCircle(cx
, cy
, radius
, paint
);
206 paint
.setColor(edgeColor
);
207 paint
.setStyle(SkPaint::kStroke_Style
);
208 canvas
->drawCircle(cx
, cy
, radius
, paint
);
211 void nestedBoxes(SkCanvas
* canvas
,
218 SkColor innerColor
) {
220 box(canvas
, irect
, outerColor
);
221 lirect
.set(irect
.fLeft
+ indentLeft
,
222 irect
.fTop
+ indentTop
,
223 irect
.fRight
- indentRight
,
224 irect
.fBottom
- indentBottom
);
225 box(canvas
, lirect
, innerColor
);
228 void insetBox(SkCanvas
* canvas
,
236 lirect
.set(irect
.fLeft
+ indentLeft
,
237 irect
.fTop
+ indentTop
,
238 irect
.fRight
- indentRight
,
239 irect
.fBottom
- indentBottom
);
240 box(canvas
, lirect
, color
);
243 void markState(SkCanvas
* canvas
, SkIRect irect
, WebThemeEngine::State state
) {
244 int left
= irect
.fLeft
;
245 int right
= irect
.fRight
;
246 int top
= irect
.fTop
;
247 int bottom
= irect
.fBottom
;
249 // The length of a triangle side for the corner marks.
250 const int triangleSize
= 5;
253 case WebThemeEngine::StateDisabled
:
254 case WebThemeEngine::StateNormal
:
255 // Don't visually mark these states (color is enough).
258 case WebThemeEngine::StateReadonly
: {
259 // The horizontal lines in a read only control are spaced by this amount.
260 const int readOnlyLineOffset
= 5;
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
);
267 case WebThemeEngine::StateHover
:
268 // Draw a triangle in the upper left corner of the control. (Win's "hot")
271 left
+ triangleSize
, top
,
272 left
, top
+ triangleSize
,
276 case WebThemeEngine::StateFocused
:
277 // Draw a triangle in the bottom right corner of the control.
280 right
- triangleSize
, bottom
,
281 right
, bottom
- triangleSize
,
285 case WebThemeEngine::StatePressed
:
286 // Draw a triangle in the bottom left corner of the control.
289 left
, bottom
- triangleSize
,
290 left
+ triangleSize
, bottom
,
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.
301 void MockWebThemeEngine::paint(blink::WebCanvas
* canvas
,
302 WebThemeEngine::Part part
,
303 WebThemeEngine::State state
,
304 const blink::WebRect
& rect
,
305 const WebThemeEngine::ExtraParams
* extraParams
) {
306 SkIRect irect
= webRectToSkIRect(rect
);
309 // Indent amounts for the check in a checkbox or radio button.
310 const int checkIndent
= 3;
312 // Indent amounts for short and long sides of the scrollbar notches.
313 const int notchLongOffset
= 1;
314 const int notchShortOffset
= 4;
315 const int noOffset
= 0;
317 // Indent amounts for the short and long sides of a scroll thumb box.
318 const int thumbLongIndent
= 0;
319 const int thumbShortIndent
= 2;
321 // Indents for the crosshatch on a scroll grip.
322 const int gripLongIndent
= 3;
323 const int gripShortIndent
= 5;
325 // Indents for the the slider track.
326 const int sliderIndent
= 2;
328 int halfHeight
= irect
.height() / 2;
329 int halfWidth
= irect
.width() / 2;
330 int quarterHeight
= irect
.height() / 4;
331 int quarterWidth
= irect
.width() / 4;
332 int left
= irect
.fLeft
;
333 int right
= irect
.fRight
;
334 int top
= irect
.fTop
;
335 int bottom
= irect
.fBottom
;
338 case WebThemeEngine::PartScrollbarDownArrow
:
339 box(canvas
, irect
, bgColors(state
));
341 left
+ quarterWidth
, top
+ quarterHeight
,
342 right
- quarterWidth
, top
+ quarterHeight
,
343 left
+ halfWidth
, bottom
- quarterHeight
,
345 markState(canvas
, irect
, state
);
348 case WebThemeEngine::PartScrollbarLeftArrow
:
349 box(canvas
, irect
, bgColors(state
));
351 right
- quarterWidth
, top
+ quarterHeight
,
352 right
- quarterWidth
, bottom
- quarterHeight
,
353 left
+ quarterWidth
, top
+ halfHeight
,
357 case WebThemeEngine::PartScrollbarRightArrow
:
358 box(canvas
, irect
, bgColors(state
));
360 left
+ quarterWidth
, top
+ quarterHeight
,
361 right
- quarterWidth
, top
+ halfHeight
,
362 left
+ quarterWidth
, bottom
- quarterHeight
,
366 case WebThemeEngine::PartScrollbarUpArrow
:
367 box(canvas
, irect
, bgColors(state
));
369 left
+ quarterWidth
, bottom
- quarterHeight
,
370 left
+ halfWidth
, top
+ quarterHeight
,
371 right
- quarterWidth
, bottom
- quarterHeight
,
373 markState(canvas
, irect
, state
);
376 case WebThemeEngine::PartScrollbarHorizontalThumb
: {
377 // Draw a narrower box on top of the outside box.
378 nestedBoxes(canvas
, irect
, thumbLongIndent
, thumbShortIndent
,
379 thumbLongIndent
, thumbShortIndent
,
380 bgColors(state
), bgColors(state
));
381 // Draw a horizontal crosshatch for the grip.
382 int longOffset
= halfWidth
- gripLongIndent
;
384 left
+ gripLongIndent
, top
+ halfHeight
,
385 right
- gripLongIndent
, top
+ halfHeight
,
388 left
+ longOffset
, top
+ gripShortIndent
,
389 left
+ longOffset
, bottom
- gripShortIndent
,
392 right
- longOffset
, top
+ gripShortIndent
,
393 right
- longOffset
, bottom
- gripShortIndent
,
395 markState(canvas
, irect
, state
);
399 case WebThemeEngine::PartScrollbarVerticalThumb
: {
400 // Draw a shorter box on top of the outside box.
401 nestedBoxes(canvas
, irect
, thumbShortIndent
, thumbLongIndent
,
402 thumbShortIndent
, thumbLongIndent
,
403 bgColors(state
), bgColors(state
));
404 // Draw a vertical crosshatch for the grip.
405 int longOffset
= halfHeight
- gripLongIndent
;
407 left
+ halfWidth
, top
+ gripLongIndent
,
408 left
+ halfWidth
, bottom
- gripLongIndent
,
411 left
+ gripShortIndent
, top
+ longOffset
,
412 right
- gripShortIndent
, top
+ longOffset
,
415 left
+ gripShortIndent
, bottom
- longOffset
,
416 right
- gripShortIndent
, bottom
- longOffset
,
418 markState(canvas
, irect
, state
);
422 case WebThemeEngine::PartScrollbarHorizontalTrack
: {
423 int longOffset
= halfHeight
- notchLongOffset
;
424 int shortOffset
= irect
.width() - notchShortOffset
;
425 box(canvas
, irect
, bgColors(state
));
426 // back, notch on right
434 // forward, notch on right
442 markState(canvas
, irect
, state
);
446 case WebThemeEngine::PartScrollbarVerticalTrack
: {
447 int longOffset
= halfWidth
- notchLongOffset
;
448 int shortOffset
= irect
.height() - notchShortOffset
;
449 box(canvas
, irect
, bgColors(state
));
450 // back, notch at top
458 // forward, notch at bottom
466 markState(canvas
, irect
, state
);
470 case WebThemeEngine::PartScrollbarCorner
: {
471 SkIRect cornerRect
= {rect
.x
, rect
.y
, rect
.x
+ rect
.width
, rect
.y
+ rect
.height
};
472 paint
.setColor(SK_ColorWHITE
);
473 paint
.setStyle(SkPaint::kFill_Style
);
474 paint
.setXfermodeMode(SkXfermode::kSrc_Mode
);
475 paint
.setAntiAlias(true);
476 canvas
->drawIRect(cornerRect
, paint
);
480 case WebThemeEngine::PartCheckbox
:
481 if (extraParams
->button
.indeterminate
) {
482 nestedBoxes(canvas
, irect
,
483 checkIndent
, halfHeight
,
484 checkIndent
, halfHeight
,
485 bgColors(state
), edgeColor
);
486 } else if (extraParams
->button
.checked
) {
487 irect
= validate(irect
, part
);
488 nestedBoxes(canvas
, irect
,
489 checkIndent
, checkIndent
,
490 checkIndent
, checkIndent
,
491 bgColors(state
), edgeColor
);
493 irect
= validate(irect
, part
);
494 box(canvas
, irect
, bgColors(state
));
498 case WebThemeEngine::PartRadio
:
499 irect
= validate(irect
, part
);
500 halfHeight
= irect
.height() / 2;
501 if (extraParams
->button
.checked
) {
502 circle(canvas
, irect
, SkIntToScalar(halfHeight
), bgColors(state
));
503 circle(canvas
, irect
, SkIntToScalar(halfHeight
- checkIndent
), edgeColor
);
505 circle(canvas
, irect
, SkIntToScalar(halfHeight
), bgColors(state
));
509 case WebThemeEngine::PartButton
:
510 roundRect(canvas
, irect
, bgColors(state
));
511 markState(canvas
, irect
, state
);
514 case WebThemeEngine::PartTextField
:
515 paint
.setColor(extraParams
->textField
.backgroundColor
);
516 paint
.setStyle(SkPaint::kFill_Style
);
517 canvas
->drawIRect(irect
, paint
);
519 paint
.setColor(edgeColor
);
520 paint
.setStyle(SkPaint::kStroke_Style
);
521 canvas
->drawIRect(irect
, paint
);
523 markState(canvas
, irect
, state
);
526 case WebThemeEngine::PartMenuList
:
527 if (extraParams
->menuList
.fillContentArea
) {
528 box(canvas
, irect
, extraParams
->menuList
.backgroundColor
);
531 paint
.setColor(edgeColor
);
532 paint
.setStyle(SkPaint::kStroke_Style
);
533 canvas
->drawIRect(irect
, paint
);
536 // clip the drop-down arrow to be inside the select box
537 if (extraParams
->menuList
.arrowX
- 4 > irect
.fLeft
)
538 irect
.fLeft
= extraParams
->menuList
.arrowX
- 4;
539 if (extraParams
->menuList
.arrowX
+ 12 < irect
.fRight
)
540 irect
.fRight
= extraParams
->menuList
.arrowX
+ 12;
542 irect
.fTop
= extraParams
->menuList
.arrowY
- (extraParams
->menuList
.arrowHeight
) / 2;
543 irect
.fBottom
= extraParams
->menuList
.arrowY
+ (extraParams
->menuList
.arrowHeight
- 1) / 2;
544 halfWidth
= irect
.width() / 2;
545 quarterWidth
= irect
.width() / 4;
547 if (state
== WebThemeEngine::StateFocused
) // FIXME: draw differenty?
548 state
= WebThemeEngine::StateNormal
;
549 box(canvas
, irect
, bgColors(state
));
551 irect
.fLeft
+ quarterWidth
, irect
.fTop
,
552 irect
.fRight
- quarterWidth
, irect
.fTop
,
553 irect
.fLeft
+ halfWidth
, irect
.fBottom
,
558 case WebThemeEngine::PartSliderTrack
: {
559 SkIRect lirect
= irect
;
561 // Draw a narrow rect for the track plus box hatches on the ends.
562 if (state
== WebThemeEngine::StateFocused
) // FIXME: draw differently?
563 state
= WebThemeEngine::StateNormal
;
564 if (extraParams
->slider
.vertical
) {
565 lirect
.inset(halfWidth
- sliderIndent
, noOffset
);
566 box(canvas
, lirect
, bgColors(state
));
567 line(canvas
, left
, top
, right
, top
, edgeColor
);
568 line(canvas
, left
, bottom
, right
, bottom
, edgeColor
);
570 lirect
.inset(noOffset
, halfHeight
- sliderIndent
);
571 box(canvas
, lirect
, bgColors(state
));
572 line(canvas
, left
, top
, left
, bottom
, edgeColor
);
573 line(canvas
, right
, top
, right
, bottom
, edgeColor
);
578 case WebThemeEngine::PartSliderThumb
:
579 if (state
== WebThemeEngine::StateFocused
) // FIXME: draw differently?
580 state
= WebThemeEngine::StateNormal
;
581 oval(canvas
, irect
, bgColors(state
));
584 case WebThemeEngine::PartInnerSpinButton
: {
585 // stack half-height up and down arrows on top of each other
587 int halfHeight
= rect
.height
/ 2;
588 if (extraParams
->innerSpin
.readOnly
)
589 state
= blink::WebThemeEngine::StateDisabled
;
591 lirect
.set(rect
.x
, rect
.y
, rect
.x
+ rect
.width
- 1, rect
.y
+ halfHeight
- 1);
592 box(canvas
, lirect
, bgColors(state
));
593 bottom
= lirect
.fBottom
;
594 quarterHeight
= lirect
.height() / 4;
596 left
+ quarterWidth
, bottom
- quarterHeight
,
597 right
- quarterWidth
, bottom
- quarterHeight
,
598 left
+ halfWidth
, top
+ quarterHeight
,
601 lirect
.set(rect
.x
, rect
.y
+ halfHeight
, rect
.x
+ rect
.width
- 1,
602 rect
.y
+ 2 * halfHeight
- 1);
604 bottom
= lirect
.fBottom
;
605 quarterHeight
= lirect
.height() / 4;
606 box(canvas
, lirect
, bgColors(state
));
608 left
+ quarterWidth
, top
+ quarterHeight
,
609 right
- quarterWidth
, top
+ quarterHeight
,
610 left
+ halfWidth
, bottom
- quarterHeight
,
612 markState(canvas
, irect
, state
);
615 case WebThemeEngine::PartProgressBar
: {
616 paint
.setColor(bgColors(state
));
617 paint
.setStyle(SkPaint::kFill_Style
);
618 canvas
->drawIRect(irect
, paint
);
621 SkIRect tofill
= irect
;
622 if (extraParams
->progressBar
.determinate
) {
623 tofill
.set(extraParams
->progressBar
.valueRectX
,
624 extraParams
->progressBar
.valueRectY
,
625 extraParams
->progressBar
.valueRectX
+
626 extraParams
->progressBar
.valueRectWidth
- 1,
627 extraParams
->progressBar
.valueRectY
+
628 extraParams
->progressBar
.valueRectHeight
);
631 if (!tofill
.intersect(irect
))
634 paint
.setColor(edgeColor
);
635 paint
.setStyle(SkPaint::kFill_Style
);
636 canvas
->drawIRect(tofill
, paint
);
638 markState(canvas
, irect
, state
);
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.
648 } // namespace content
650 #endif // !defined(OS_MACOSX)