// 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(skia::PlatformCanvas* 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;

  canvas_->beginPlatformPaint();
  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();
  canvas_->endPlatformPaint();
}

// 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;

  canvas_->beginPlatformPaint();
  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();
  canvas_->endPlatformPaint();
}

void
Control::drawProgressBar(const SkIRect& fill_rect) {
  SkPaint paint;

  canvas_->beginPlatformPaint();
  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();
  canvas_->endPlatformPaint();
}

}  // namespace TestShellWebTheme