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