blob: 25cd94fd18c3b20101b32e77230167df497a80ad [file] [log] [blame]
// Copyright (c) 2010 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.
// This file implements a simple generic version of the WebKitThemeEngine,
// which is used to draw all the native controls on a web page. We use this
// file when running in layout test mode in order to remove any
// platform-specific rendering differences due to themes, colors, etc.
//
#include "webkit/tools/test_shell/test_shell_webthemecontrol.h"
#include "base/logging.h"
#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
namespace TestShellWebTheme {
const SkColor kEdgeColor = SK_ColorBLACK;
const SkColor kReadOnlyColor = SkColorSetRGB(0xe9, 0xc2, 0xa6);
const SkColor kFgColor = SK_ColorBLACK;
const SkColor kBgColors[] = {
SK_ColorBLACK, // Unknown
SkColorSetRGB(0xc9, 0xc9, 0xc9), // Disabled
SkColorSetRGB(0xf3, 0xe0, 0xd0), // Readonly
SkColorSetRGB(0x89, 0xc4, 0xff), // Normal
SkColorSetRGB(0x43, 0xf9, 0xff), // Hot
SkColorSetRGB(0x20, 0xf6, 0xcc), // Focused
SkColorSetRGB(0x00, 0xf3, 0xac), // Hover
SkColorSetRGB(0xa9, 0xff, 0x12), // Pressed
SkColorSetRGB(0xcc, 0xcc, 0xcc) // Indeterminate
};
SkIRect Validate(const SkIRect& rect, Control::Type ctype) {
SkIRect retval = rect;
if (ctype == Control::kUncheckedBox_Type ||
ctype == Control::kCheckedBox_Type ||
ctype == Control::kUncheckedRadio_Type ||
ctype == Control::kCheckedRadio_Type) {
// The maximum width and height is 13. Center the square in the passed
// rectangle.
const int kMaxControlSize = 13;
int control_size = std::min(rect.width(), rect.height());
control_size = std::min(control_size, kMaxControlSize);
retval.fLeft = rect.fLeft + (rect.width() / 2) - (control_size / 2);
retval.fRight = retval.fLeft + control_size - 1;
retval.fTop = rect.fTop + (rect.height() / 2) - (control_size / 2);
retval.fBottom = retval.fTop + control_size - 1;
}
return retval;
}
Control::Control(SkCanvas* canvas, const SkIRect& irect,
Type ctype, State cstate)
: canvas_(canvas),
irect_(Validate(irect, ctype)),
type_(ctype),
state_(cstate),
left_(irect_.fLeft),
right_(irect_.fRight),
top_(irect_.fTop),
bottom_(irect_.fBottom),
height_(irect_.height()),
width_(irect_.width()),
edge_color_(kEdgeColor),
bg_color_(kBgColors[cstate]),
fg_color_(kFgColor) {
}
Control::~Control() {
}
void Control::box(const SkIRect& rect, SkColor fill_color) {
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(fill_color);
canvas_->drawIRect(rect, paint);
paint.setColor(edge_color_);
paint.setStyle(SkPaint::kStroke_Style);
canvas_->drawIRect(rect, paint);
}
void Control::line(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 Control::triangle(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(edge_color_);
paint.setStyle(SkPaint::kStroke_Style);
canvas_->drawPath(path, paint);
}
void Control::roundRect(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(edge_color_);
paint.setStyle(SkPaint::kStroke_Style);
canvas_->drawRoundRect(rect, radius, radius, paint);
}
void Control::oval(SkColor color) {
SkRect rect;
SkPaint paint;
rect.set(irect_);
paint.setColor(color);
paint.setStyle(SkPaint::kFill_Style);
canvas_->drawOval(rect, paint);
paint.setColor(edge_color_);
paint.setStyle(SkPaint::kStroke_Style);
canvas_->drawOval(rect, paint);
}
void Control::circle(SkScalar radius, SkColor color) {
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(edge_color_);
paint.setStyle(SkPaint::kStroke_Style);
canvas_->drawCircle(cx, cy, radius, paint);
}
void Control::nested_boxes(int indent_left, int indent_top,
int indent_right, int indent_bottom,
SkColor outer_color, SkColor inner_color) {
SkIRect lirect;
box(irect_, outer_color);
lirect.set(irect_.fLeft + indent_left, irect_.fTop + indent_top,
irect_.fRight - indent_right, irect_.fBottom - indent_bottom);
box(lirect, inner_color);
}
void Control::markState() {
// The horizontal lines in a read only control are spaced by this amount.
const int kReadOnlyLineOffset = 5;
// The length of a triangle side for the corner marks.
const int kTriangleSize = 5;
switch (state_) {
case kUnknown_State: // FALLTHROUGH
case kDisabled_State: // FALLTHROUGH
case kNormal_State:
// Don't visually mark these states (color is enough).
break;
case kReadOnly_State:
// Drawing lines across the control.
for (int i = top_ + kReadOnlyLineOffset; i < bottom_ ;
i += kReadOnlyLineOffset) {
line(left_ + 1, i, right_ - 1, i, kReadOnlyColor);
}
break;
case kHot_State:
// Draw a triangle in the upper left corner of the control.
triangle(left_, top_,
left_ + kTriangleSize, top_,
left_, top_ + kTriangleSize, edge_color_);
break;
case kHover_State:
// Draw a triangle in the upper right corner of the control.
triangle(right_, top_,
right_, top_ + kTriangleSize,
right_ - kTriangleSize, top_, edge_color_);
break;
case kFocused_State:
// Draw a triangle in the bottom right corner of the control.
triangle(right_, bottom_,
right_ - kTriangleSize, bottom_,
right_, bottom_ - kTriangleSize, edge_color_);
break;
case kPressed_State:
// Draw a triangle in the bottom left corner of the control.
triangle(left_, bottom_,
left_, bottom_ - kTriangleSize,
left_ + kTriangleSize, bottom_, edge_color_);
break;
default:
NOTREACHED();
break;
}
}
void Control::draw() {
int half_width = width_ / 2;
int half_height = height_ / 2;
int quarter_width = width_ / 4;
int quarter_height = height_ / 4;
// Indent amounts for the check in a checkbox or radio button.
const int kCheckIndent = 3;
// Indent amounts for short and long sides of the scrollbar notches.
const int kNotchLongOffset = 1;
const int kNotchShortOffset = 4;
const int kNoOffset = 0;
int short_offset;
int long_offset;
// Indent amounts for the short and long sides of a scroll thumb box.
const int kThumbLongIndent = 0;
const int kThumbShortIndent = 2;
// Indents for the crosshatch on a scroll grip.
const int kGripLongIndent = 3;
const int kGripShortIndent = 5;
// Indents for the the slider track.
const int kSliderIndent = 2;
skia::ScopedPlatformPaint scoped_platform_paint(canvas_);
switch (type_) {
case kUnknown_Type:
NOTREACHED();
break;
case kTextField_Type:
// We render this by hand outside of this function.
NOTREACHED();
break;
case kPushButton_Type:
// push buttons render as a rounded rectangle
roundRect(bg_color_);
break;
case kUncheckedBox_Type:
// Unchecked boxes are simply plain boxes.
box(irect_, bg_color_);
break;
case kCheckedBox_Type:
nested_boxes(kCheckIndent, kCheckIndent, kCheckIndent, kCheckIndent,
bg_color_, fg_color_);
break;
case kIndeterminateCheckBox_Type:
// Indeterminate checkbox is a box containing '-'.
nested_boxes(kCheckIndent, height_ / 2, kCheckIndent, height_ / 2,
bg_color_, fg_color_);
break;
case kUncheckedRadio_Type:
circle(SkIntToScalar(half_height), bg_color_);
break;
case kCheckedRadio_Type:
circle(SkIntToScalar(half_height), bg_color_);
circle(SkIntToScalar(half_height - kCheckIndent), fg_color_);
break;
case kHorizontalScrollTrackBack_Type:
// Draw a box with a notch at the left.
long_offset = half_height - kNotchLongOffset;
short_offset = width_ - kNotchShortOffset;
nested_boxes(kNoOffset, long_offset, short_offset, long_offset,
bg_color_, edge_color_);
break;
case kHorizontalScrollTrackForward_Type:
// Draw a box with a notch at the right.
long_offset = half_height - kNotchLongOffset;
short_offset = width_ - kNotchShortOffset;
nested_boxes(short_offset, long_offset, kNoOffset, long_offset,
bg_color_, fg_color_);
break;
case kVerticalScrollTrackBack_Type:
// Draw a box with a notch at the top.
long_offset = half_width - kNotchLongOffset;
short_offset = height_ - kNotchShortOffset;
nested_boxes(long_offset, kNoOffset, long_offset, short_offset,
bg_color_, fg_color_);
break;
case kVerticalScrollTrackForward_Type:
// Draw a box with a notch at the bottom.
long_offset = half_width - kNotchLongOffset;
short_offset = height_ - kNotchShortOffset;
nested_boxes(long_offset, short_offset, long_offset, kNoOffset,
bg_color_, fg_color_);
break;
case kHorizontalScrollThumb_Type:
// Draw a narrower box on top of the outside box.
nested_boxes(kThumbLongIndent, kThumbShortIndent, kThumbLongIndent,
kThumbShortIndent, bg_color_, bg_color_);
break;
case kVerticalScrollThumb_Type:
// Draw a shorter box on top of the outside box.
nested_boxes(kThumbShortIndent, kThumbLongIndent, kThumbShortIndent,
kThumbLongIndent, bg_color_, bg_color_);
break;
case kHorizontalSliderThumb_Type:
// Slider thumbs are ovals.
oval(bg_color_);
break;
case kHorizontalScrollGrip_Type:
// Draw a horizontal crosshatch for the grip.
long_offset = half_width - kGripLongIndent;
line(left_ + kGripLongIndent, top_ + half_height,
right_ - kGripLongIndent, top_ + half_height, fg_color_);
line(left_ + long_offset, top_ + kGripShortIndent,
left_ + long_offset, bottom_ - kGripShortIndent, fg_color_);
line(right_ - long_offset, top_ + kGripShortIndent,
right_ - long_offset, bottom_ - kGripShortIndent, fg_color_);
break;
case kVerticalScrollGrip_Type:
// Draw a vertical crosshatch for the grip.
long_offset = half_height - kGripLongIndent;
line(left_ + half_width, top_ + kGripLongIndent,
left_ + half_width, bottom_ - kGripLongIndent, fg_color_);
line(left_ + kGripShortIndent, top_ + long_offset,
right_ - kGripShortIndent, top_ + long_offset, fg_color_);
line(left_ + kGripShortIndent, bottom_ - long_offset,
right_ - kGripShortIndent, bottom_ - long_offset, fg_color_);
break;
case kLeftArrow_Type:
// Draw a left arrow inside a box.
box(irect_, bg_color_);
triangle(right_ - quarter_width, top_ + quarter_height,
right_ - quarter_width, bottom_ - quarter_height,
left_ + quarter_width, top_ + half_height, fg_color_);
break;
case kRightArrow_Type:
// Draw a left arrow inside a box.
box(irect_, bg_color_);
triangle(left_ + quarter_width, top_ + quarter_height,
right_ - quarter_width, top_ + half_height,
left_ + quarter_width, bottom_ - quarter_height, fg_color_);
break;
case kUpArrow_Type:
// Draw an up arrow inside a box.
box(irect_, bg_color_);
triangle(left_ + quarter_width, bottom_ - quarter_height,
left_ + half_width, top_ + quarter_height,
right_ - quarter_width, bottom_ - quarter_height, fg_color_);
break;
case kDownArrow_Type:
// Draw a down arrow inside a box.
box(irect_, bg_color_);
triangle(left_ + quarter_width, top_ + quarter_height,
right_ - quarter_width, top_ + quarter_height,
left_ + half_width, bottom_ - quarter_height, fg_color_);
break;
case kHorizontalSliderTrack_Type:
// Draw a narrow rect for the track plus box hatches on the ends.
SkIRect lirect;
lirect = irect_;
lirect.inset(kNoOffset, half_height - kSliderIndent);
box(lirect, bg_color_);
line(left_, top_, left_, bottom_, edge_color_);
line(right_, top_, right_, bottom_, edge_color_);
break;
case kDropDownButton_Type:
// Draw a box with a big down arrow on top.
box(irect_, bg_color_);
triangle(left_ + quarter_width, top_,
right_ - quarter_width, top_,
left_ + half_width, bottom_, fg_color_);
break;
default:
NOTREACHED();
break;
}
markState();
}
// Because rendering a text field is dependent on input
// parameters the other controls don't have, we render it directly
// rather than trying to overcomplicate draw() further.
void Control::drawTextField(bool draw_edges, bool fill_content_area,
SkColor color) {
SkPaint paint;
skia::ScopedPlatformPaint scoped_platform_paint(canvas_);
if (fill_content_area) {
paint.setColor(color);
paint.setStyle(SkPaint::kFill_Style);
canvas_->drawIRect(irect_, paint);
}
if (draw_edges) {
paint.setColor(edge_color_);
paint.setStyle(SkPaint::kStroke_Style);
canvas_->drawIRect(irect_, paint);
}
markState();
}
void
Control::drawProgressBar(const SkIRect& fill_rect) {
SkPaint paint;
skia::ScopedPlatformPaint scoped_platform_paint(canvas_);
paint.setColor(bg_color_);
paint.setStyle(SkPaint::kFill_Style);
canvas_->drawIRect(irect_, paint);
// Emulate clipping
SkIRect tofill;
tofill.intersect(irect_, fill_rect);
paint.setColor(fg_color_);
paint.setStyle(SkPaint::kFill_Style);
canvas_->drawIRect(tofill, paint);
markState();
}
} // namespace TestShellWebTheme