blob: fed1934d510633f1974f475aa4f40417178e7ae9 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/test_runner/mock_web_theme_engine.h"
#if !defined(OS_MACOSX)
#include "base/logging.h"
#include "build/build_config.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/WebKit/public/platform/WebRect.h"
#include "third_party/WebKit/public/platform/WebSize.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRect.h"
using blink::WebCanvas;
using blink::WebColor;
using blink::WebRect;
using blink::WebThemeEngine;
namespace test_runner {
namespace {
const SkColor edgeColor = SK_ColorBLACK;
const SkColor readOnlyColor = SkColorSetRGB(0xe9, 0xc2, 0xa6);
} // namespace
SkColor bgColors(WebThemeEngine::State state) {
switch (state) {
case WebThemeEngine::StateDisabled:
return SkColorSetRGB(0xc9, 0xc9, 0xc9);
case WebThemeEngine::StateHover:
return SkColorSetRGB(0x43, 0xf9, 0xff);
case WebThemeEngine::StateNormal:
return SkColorSetRGB(0x89, 0xc4, 0xff);
case WebThemeEngine::StatePressed:
return SkColorSetRGB(0xa9, 0xff, 0x12);
case WebThemeEngine::StateFocused:
return SkColorSetRGB(0x00, 0xf3, 0xac);
case WebThemeEngine::StateReadonly:
return SkColorSetRGB(0xf3, 0xe0, 0xd0);
default:
NOTREACHED();
}
return SkColorSetRGB(0x00, 0x00, 0xff);
}
blink::WebSize MockWebThemeEngine::getSize(WebThemeEngine::Part part) {
// FIXME: We use this constant to indicate we are being asked for the size of
// a part that we don't expect to be asked about. We return a garbage value
// rather than just asserting because this code doesn't have access to either
// WTF or base to raise an assertion or do any logging :(.
const blink::WebSize invalidPartSize = blink::WebSize(100, 100);
switch (part) {
case WebThemeEngine::PartScrollbarLeftArrow:
return blink::WebSize(17, 15);
case WebThemeEngine::PartScrollbarRightArrow:
return invalidPartSize;
case WebThemeEngine::PartScrollbarUpArrow:
return blink::WebSize(15, 17);
case WebThemeEngine::PartScrollbarDownArrow:
return invalidPartSize;
case WebThemeEngine::PartScrollbarHorizontalThumb:
return blink::WebSize(15, 15);
case WebThemeEngine::PartScrollbarVerticalThumb:
return blink::WebSize(15, 15);
case WebThemeEngine::PartScrollbarHorizontalTrack:
return blink::WebSize(0, 15);
case WebThemeEngine::PartScrollbarVerticalTrack:
return blink::WebSize(15, 0);
case WebThemeEngine::PartCheckbox:
case WebThemeEngine::PartRadio:
return blink::WebSize(13, 13);
case WebThemeEngine::PartSliderThumb:
return blink::WebSize(11, 21);
case WebThemeEngine::PartInnerSpinButton:
return blink::WebSize(15, 8);
default:
return invalidPartSize;
}
}
static SkIRect webRectToSkIRect(const WebRect& webRect) {
SkIRect irect;
irect.set(webRect.x, webRect.y, webRect.x + webRect.width - 1,
webRect.y + webRect.height - 1);
return irect;
}
static SkIRect validate(const SkIRect& rect, WebThemeEngine::Part part) {
switch (part) {
case WebThemeEngine::PartCheckbox:
case WebThemeEngine::PartRadio: {
SkIRect retval = rect;
// The maximum width and height is 13.
// Center the square in the passed rectangle.
const int maxControlSize = 13;
int controlSize = std::min(rect.width(), rect.height());
controlSize = std::min(controlSize, maxControlSize);
retval.fLeft = rect.fLeft + (rect.width() / 2) - (controlSize / 2);
retval.fRight = retval.fLeft + controlSize - 1;
retval.fTop = rect.fTop + (rect.height() / 2) - (controlSize / 2);
retval.fBottom = retval.fTop + controlSize - 1;
return retval;
}
default:
return rect;
}
}
void box(SkCanvas* canvas, const SkIRect& rect, SkColor fillColor) {
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(fillColor);
canvas->drawIRect(rect, paint);
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawIRect(rect, paint);
}
void line(SkCanvas* canvas, int x0, int y0, int x1, int y1, SkColor color) {
SkPaint paint;
paint.setColor(color);
canvas->drawLine(SkIntToScalar(x0), SkIntToScalar(y0), SkIntToScalar(x1),
SkIntToScalar(y1), paint);
}
void triangle(SkCanvas* canvas,
int x0,
int y0,
int x1,
int y1,
int x2,
int y2,
SkColor color) {
SkPath path;
SkPaint paint;
paint.setColor(color);
paint.setStyle(SkPaint::kFill_Style);
path.incReserve(4);
path.moveTo(SkIntToScalar(x0), SkIntToScalar(y0));
path.lineTo(SkIntToScalar(x1), SkIntToScalar(y1));
path.lineTo(SkIntToScalar(x2), SkIntToScalar(y2));
path.close();
canvas->drawPath(path, paint);
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(path, paint);
}
void roundRect(SkCanvas* canvas, SkIRect irect, SkColor color) {
SkRect rect;
SkScalar radius = SkIntToScalar(5);
SkPaint paint;
rect.set(irect);
paint.setColor(color);
paint.setStyle(SkPaint::kFill_Style);
canvas->drawRoundRect(rect, radius, radius, paint);
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawRoundRect(rect, radius, radius, paint);
}
void oval(SkCanvas* canvas, SkIRect irect, SkColor color) {
SkRect rect;
SkPaint paint;
rect.set(irect);
paint.setColor(color);
paint.setStyle(SkPaint::kFill_Style);
canvas->drawOval(rect, paint);
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawOval(rect, paint);
}
void circle(SkCanvas* canvas, SkIRect irect, SkScalar radius, SkColor color) {
int left = irect.fLeft;
int width = irect.width();
int height = irect.height();
int top = irect.fTop;
SkScalar cy = SkIntToScalar(top + height / 2);
SkScalar cx = SkIntToScalar(left + width / 2);
SkPaint paint;
paint.setColor(color);
paint.setStyle(SkPaint::kFill_Style);
canvas->drawCircle(cx, cy, radius, paint);
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawCircle(cx, cy, radius, paint);
}
void nestedBoxes(SkCanvas* canvas,
SkIRect irect,
int indentLeft,
int indentTop,
int indentRight,
int indentBottom,
SkColor outerColor,
SkColor innerColor) {
SkIRect lirect;
box(canvas, irect, outerColor);
lirect.set(irect.fLeft + indentLeft, irect.fTop + indentTop,
irect.fRight - indentRight, irect.fBottom - indentBottom);
box(canvas, lirect, innerColor);
}
void insetBox(SkCanvas* canvas,
SkIRect irect,
int indentLeft,
int indentTop,
int indentRight,
int indentBottom,
SkColor color) {
SkIRect lirect;
lirect.set(irect.fLeft + indentLeft, irect.fTop + indentTop,
irect.fRight - indentRight, irect.fBottom - indentBottom);
box(canvas, lirect, color);
}
void markState(SkCanvas* canvas, SkIRect irect, WebThemeEngine::State state) {
int left = irect.fLeft;
int right = irect.fRight;
int top = irect.fTop;
int bottom = irect.fBottom;
// The length of a triangle side for the corner marks.
const int triangleSize = 5;
switch (state) {
case WebThemeEngine::StateDisabled:
case WebThemeEngine::StateNormal:
// Don't visually mark these states (color is enough).
break;
case WebThemeEngine::StateReadonly: {
// The horizontal lines in a read only control are spaced by this amount.
const int readOnlyLineOffset = 5;
// Drawing lines across the control.
for (int i = top + readOnlyLineOffset; i < bottom;
i += readOnlyLineOffset)
line(canvas, left + 1, i, right - 1, i, readOnlyColor);
break;
}
case WebThemeEngine::StateHover:
// Draw a triangle in the upper left corner of the control. (Win's "hot")
triangle(canvas, left, top, left + triangleSize, top, left,
top + triangleSize, edgeColor);
break;
case WebThemeEngine::StateFocused:
// Draw a triangle in the bottom right corner of the control.
triangle(canvas, right, bottom, right - triangleSize, bottom, right,
bottom - triangleSize, edgeColor);
break;
case WebThemeEngine::StatePressed:
// Draw a triangle in the bottom left corner of the control.
triangle(canvas, left, bottom, left, bottom - triangleSize,
left + triangleSize, bottom, edgeColor);
break;
default:
// FIXME: Should we do something here to indicate that we got an invalid
// state?
// Unfortunately, we can't assert because we don't have access to WTF or
// base.
break;
}
}
void MockWebThemeEngine::paint(blink::WebCanvas* canvas,
WebThemeEngine::Part part,
WebThemeEngine::State state,
const blink::WebRect& rect,
const WebThemeEngine::ExtraParams* extraParams) {
SkIRect irect = webRectToSkIRect(rect);
SkPaint paint;
// Indent amounts for the check in a checkbox or radio button.
const int checkIndent = 3;
// Indent amounts for short and long sides of the scrollbar notches.
const int notchLongOffset = 1;
const int notchShortOffset = 4;
const int noOffset = 0;
// Indent amounts for the short and long sides of a scroll thumb box.
const int thumbLongIndent = 0;
const int thumbShortIndent = 2;
// Indents for the crosshatch on a scroll grip.
const int gripLongIndent = 3;
const int gripShortIndent = 5;
// Indents for the the slider track.
const int sliderIndent = 2;
int halfHeight = irect.height() / 2;
int halfWidth = irect.width() / 2;
int quarterHeight = irect.height() / 4;
int quarterWidth = irect.width() / 4;
int left = irect.fLeft;
int right = irect.fRight;
int top = irect.fTop;
int bottom = irect.fBottom;
switch (part) {
case WebThemeEngine::PartScrollbarDownArrow:
box(canvas, irect, bgColors(state));
triangle(canvas, left + quarterWidth, top + quarterHeight,
right - quarterWidth, top + quarterHeight, left + halfWidth,
bottom - quarterHeight, edgeColor);
markState(canvas, irect, state);
break;
case WebThemeEngine::PartScrollbarLeftArrow:
box(canvas, irect, bgColors(state));
triangle(canvas, right - quarterWidth, top + quarterHeight,
right - quarterWidth, bottom - quarterHeight,
left + quarterWidth, top + halfHeight, edgeColor);
break;
case WebThemeEngine::PartScrollbarRightArrow:
box(canvas, irect, bgColors(state));
triangle(canvas, left + quarterWidth, top + quarterHeight,
right - quarterWidth, top + halfHeight, left + quarterWidth,
bottom - quarterHeight, edgeColor);
break;
case WebThemeEngine::PartScrollbarUpArrow:
box(canvas, irect, bgColors(state));
triangle(canvas, left + quarterWidth, bottom - quarterHeight,
left + halfWidth, top + quarterHeight, right - quarterWidth,
bottom - quarterHeight, edgeColor);
markState(canvas, irect, state);
break;
case WebThemeEngine::PartScrollbarHorizontalThumb: {
// Draw a narrower box on top of the outside box.
nestedBoxes(canvas, irect, thumbLongIndent, thumbShortIndent,
thumbLongIndent, thumbShortIndent, bgColors(state),
bgColors(state));
// Draw a horizontal crosshatch for the grip.
int longOffset = halfWidth - gripLongIndent;
line(canvas, left + gripLongIndent, top + halfHeight,
right - gripLongIndent, top + halfHeight, edgeColor);
line(canvas, left + longOffset, top + gripShortIndent, left + longOffset,
bottom - gripShortIndent, edgeColor);
line(canvas, right - longOffset, top + gripShortIndent,
right - longOffset, bottom - gripShortIndent, edgeColor);
markState(canvas, irect, state);
break;
}
case WebThemeEngine::PartScrollbarVerticalThumb: {
// Draw a shorter box on top of the outside box.
nestedBoxes(canvas, irect, thumbShortIndent, thumbLongIndent,
thumbShortIndent, thumbLongIndent, bgColors(state),
bgColors(state));
// Draw a vertical crosshatch for the grip.
int longOffset = halfHeight - gripLongIndent;
line(canvas, left + halfWidth, top + gripLongIndent, left + halfWidth,
bottom - gripLongIndent, edgeColor);
line(canvas, left + gripShortIndent, top + longOffset,
right - gripShortIndent, top + longOffset, edgeColor);
line(canvas, left + gripShortIndent, bottom - longOffset,
right - gripShortIndent, bottom - longOffset, edgeColor);
markState(canvas, irect, state);
break;
}
case WebThemeEngine::PartScrollbarHorizontalTrack: {
int longOffset = halfHeight - notchLongOffset;
int shortOffset = irect.width() - notchShortOffset;
box(canvas, irect, bgColors(state));
// back, notch on right
insetBox(canvas, irect, noOffset, longOffset, shortOffset, longOffset,
edgeColor);
// forward, notch on right
insetBox(canvas, irect, shortOffset, longOffset, noOffset, longOffset,
edgeColor);
markState(canvas, irect, state);
break;
}
case WebThemeEngine::PartScrollbarVerticalTrack: {
int longOffset = halfWidth - notchLongOffset;
int shortOffset = irect.height() - notchShortOffset;
box(canvas, irect, bgColors(state));
// back, notch at top
insetBox(canvas, irect, longOffset, noOffset, longOffset, shortOffset,
edgeColor);
// forward, notch at bottom
insetBox(canvas, irect, longOffset, shortOffset, longOffset, noOffset,
edgeColor);
markState(canvas, irect, state);
break;
}
case WebThemeEngine::PartScrollbarCorner: {
SkIRect cornerRect = {rect.x, rect.y, rect.x + rect.width,
rect.y + rect.height};
paint.setColor(SK_ColorWHITE);
paint.setStyle(SkPaint::kFill_Style);
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
paint.setAntiAlias(true);
canvas->drawIRect(cornerRect, paint);
break;
}
case WebThemeEngine::PartCheckbox:
if (extraParams->button.indeterminate) {
nestedBoxes(canvas, irect, checkIndent, halfHeight, checkIndent,
halfHeight, bgColors(state), edgeColor);
} else if (extraParams->button.checked) {
irect = validate(irect, part);
nestedBoxes(canvas, irect, checkIndent, checkIndent, checkIndent,
checkIndent, bgColors(state), edgeColor);
} else {
irect = validate(irect, part);
box(canvas, irect, bgColors(state));
}
break;
case WebThemeEngine::PartRadio:
irect = validate(irect, part);
halfHeight = irect.height() / 2;
if (extraParams->button.checked) {
circle(canvas, irect, SkIntToScalar(halfHeight), bgColors(state));
circle(canvas, irect, SkIntToScalar(halfHeight - checkIndent),
edgeColor);
} else {
circle(canvas, irect, SkIntToScalar(halfHeight), bgColors(state));
}
break;
case WebThemeEngine::PartButton:
roundRect(canvas, irect, bgColors(state));
markState(canvas, irect, state);
break;
case WebThemeEngine::PartTextField:
paint.setColor(extraParams->textField.backgroundColor);
paint.setStyle(SkPaint::kFill_Style);
canvas->drawIRect(irect, paint);
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawIRect(irect, paint);
markState(canvas, irect, state);
break;
case WebThemeEngine::PartMenuList:
if (extraParams->menuList.fillContentArea) {
box(canvas, irect, extraParams->menuList.backgroundColor);
} else {
SkPaint paint;
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kStroke_Style);
canvas->drawIRect(irect, paint);
}
// clip the drop-down arrow to be inside the select box
if (extraParams->menuList.arrowX - 4 > irect.fLeft)
irect.fLeft = extraParams->menuList.arrowX - 4;
if (extraParams->menuList.arrowX + 12 < irect.fRight)
irect.fRight = extraParams->menuList.arrowX + 12;
irect.fTop = extraParams->menuList.arrowY -
(extraParams->menuList.arrowSize) / 2;
irect.fBottom = extraParams->menuList.arrowY +
(extraParams->menuList.arrowSize - 1) / 2;
halfWidth = irect.width() / 2;
quarterWidth = irect.width() / 4;
if (state == WebThemeEngine::StateFocused) // FIXME: draw differenty?
state = WebThemeEngine::StateNormal;
box(canvas, irect, bgColors(state));
triangle(canvas, irect.fLeft + quarterWidth, irect.fTop,
irect.fRight - quarterWidth, irect.fTop, irect.fLeft + halfWidth,
irect.fBottom, edgeColor);
break;
case WebThemeEngine::PartSliderTrack: {
SkIRect lirect = irect;
// Draw a narrow rect for the track plus box hatches on the ends.
if (state == WebThemeEngine::StateFocused) // FIXME: draw differently?
state = WebThemeEngine::StateNormal;
if (extraParams->slider.vertical) {
lirect.inset(halfWidth - sliderIndent, noOffset);
box(canvas, lirect, bgColors(state));
line(canvas, left, top, right, top, edgeColor);
line(canvas, left, bottom, right, bottom, edgeColor);
} else {
lirect.inset(noOffset, halfHeight - sliderIndent);
box(canvas, lirect, bgColors(state));
line(canvas, left, top, left, bottom, edgeColor);
line(canvas, right, top, right, bottom, edgeColor);
}
break;
}
case WebThemeEngine::PartSliderThumb:
if (state == WebThemeEngine::StateFocused) // FIXME: draw differently?
state = WebThemeEngine::StateNormal;
oval(canvas, irect, bgColors(state));
break;
case WebThemeEngine::PartInnerSpinButton: {
// stack half-height up and down arrows on top of each other
SkIRect lirect;
int halfHeight = rect.height / 2;
if (extraParams->innerSpin.readOnly)
state = blink::WebThemeEngine::StateDisabled;
lirect.set(rect.x, rect.y, rect.x + rect.width - 1,
rect.y + halfHeight - 1);
box(canvas, lirect, bgColors(state));
bottom = lirect.fBottom;
quarterHeight = lirect.height() / 4;
triangle(canvas, left + quarterWidth, bottom - quarterHeight,
right - quarterWidth, bottom - quarterHeight, left + halfWidth,
top + quarterHeight, edgeColor);
lirect.set(rect.x, rect.y + halfHeight, rect.x + rect.width - 1,
rect.y + 2 * halfHeight - 1);
top = lirect.fTop;
bottom = lirect.fBottom;
quarterHeight = lirect.height() / 4;
box(canvas, lirect, bgColors(state));
triangle(canvas, left + quarterWidth, top + quarterHeight,
right - quarterWidth, top + quarterHeight, left + halfWidth,
bottom - quarterHeight, edgeColor);
markState(canvas, irect, state);
break;
}
case WebThemeEngine::PartProgressBar: {
paint.setColor(bgColors(state));
paint.setStyle(SkPaint::kFill_Style);
canvas->drawIRect(irect, paint);
// Emulate clipping
SkIRect tofill = irect;
if (extraParams->progressBar.determinate) {
tofill.set(extraParams->progressBar.valueRectX,
extraParams->progressBar.valueRectY,
extraParams->progressBar.valueRectX +
extraParams->progressBar.valueRectWidth - 1,
extraParams->progressBar.valueRectY +
extraParams->progressBar.valueRectHeight);
}
if (!tofill.intersect(irect))
tofill.setEmpty();
paint.setColor(edgeColor);
paint.setStyle(SkPaint::kFill_Style);
canvas->drawIRect(tofill, paint);
markState(canvas, irect, state);
break;
}
default:
// FIXME: Should we do something here to indicate that we got an invalid
// part?
// Unfortunately, we can't assert because we don't have access to WTF or
// base.
break;
}
}
} // namespace test_runner
#endif // !defined(OS_MACOSX)