blob: 7fd92737484f33135b491db72a5e4fcabbcfcc67 [file] [log] [blame]
// Copyright (c) 2012 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 "ui/views/color_chooser/color_chooser_view.h"
#include <stdint.h>
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/canvas.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/color_chooser/color_chooser_listener.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_controller.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/widget/widget.h"
namespace {
const int kHueBarWidth = 20;
const int kSaturationValueSize = 200;
const int kMarginWidth = 5;
const int kSaturationValueIndicatorSize = 6;
const int kHueIndicatorSize = 5;
const int kBorderWidth = 1;
const int kTextfieldLengthInChars = 14;
base::string16 GetColorText(SkColor color) {
return base::ASCIIToUTF16(base::StringPrintf("#%02x%02x%02x",
SkColorGetR(color),
SkColorGetG(color),
SkColorGetB(color)));
}
bool GetColorFromText(const base::string16& text, SkColor* result) {
if (text.size() != 6 && !(text.size() == 7 && text[0] == '#'))
return false;
std::string input =
base::UTF16ToUTF8((text.size() == 6) ? text : text.substr(1));
std::vector<uint8_t> hex;
if (!base::HexStringToBytes(input, &hex))
return false;
*result = SkColorSetRGB(hex[0], hex[1], hex[2]);
return true;
}
// A view that processes mouse events and gesture events using a common
// interface.
class LocatedEventHandlerView : public views::View {
public:
~LocatedEventHandlerView() override {}
protected:
LocatedEventHandlerView() {}
// Handles an event (mouse or gesture) at the specified location.
virtual void ProcessEventAtLocation(const gfx::Point& location) = 0;
// views::View overrides:
bool OnMousePressed(const ui::MouseEvent& event) override {
ProcessEventAtLocation(event.location());
return true;
}
bool OnMouseDragged(const ui::MouseEvent& event) override {
ProcessEventAtLocation(event.location());
return true;
}
void OnGestureEvent(ui::GestureEvent* event) override {
if (event->type() == ui::ET_GESTURE_TAP ||
event->type() == ui::ET_GESTURE_TAP_DOWN ||
event->IsScrollGestureEvent()) {
ProcessEventAtLocation(event->location());
event->SetHandled();
}
}
DISALLOW_COPY_AND_ASSIGN(LocatedEventHandlerView);
};
void DrawGradientRect(const gfx::Rect& rect, SkColor start_color,
SkColor end_color, bool is_horizontal,
gfx::Canvas* canvas) {
SkColor colors[2] = { start_color, end_color };
SkPoint points[2];
points[0].iset(0, 0);
if (is_horizontal)
points[1].iset(rect.width() + 1, 0);
else
points[1].iset(0, rect.height() + 1);
SkPaint paint;
paint.setShader(SkGradientShader::MakeLinear(points, colors, NULL, 2,
SkShader::kClamp_TileMode));
canvas->DrawRect(rect, paint);
}
} // namespace
namespace views {
////////////////////////////////////////////////////////////////////////////////
// ColorChooserView::HueView
//
// The class to choose the hue of the color. It draws a vertical bar and
// the indicator for the currently selected hue.
class ColorChooserView::HueView : public LocatedEventHandlerView {
public:
explicit HueView(ColorChooserView* chooser_view);
void OnHueChanged(SkScalar hue);
private:
// LocatedEventHandlerView overrides:
void ProcessEventAtLocation(const gfx::Point& point) override;
// View overrides:
gfx::Size GetPreferredSize() const override;
void OnPaint(gfx::Canvas* canvas) override;
ColorChooserView* chooser_view_;
int level_;
DISALLOW_COPY_AND_ASSIGN(HueView);
};
ColorChooserView::HueView::HueView(ColorChooserView* chooser_view)
: chooser_view_(chooser_view),
level_(0) {
}
void ColorChooserView::HueView::OnHueChanged(SkScalar hue) {
SkScalar height = SkIntToScalar(kSaturationValueSize - 1);
SkScalar hue_max = SkIntToScalar(360);
int level = (hue_max - hue) * height / hue_max;
level += kBorderWidth;
if (level_ != level) {
level_ = level;
SchedulePaint();
}
}
void ColorChooserView::HueView::ProcessEventAtLocation(
const gfx::Point& point) {
level_ = std::max(kBorderWidth,
std::min(height() - 1 - kBorderWidth, point.y()));
int base_height = kSaturationValueSize - 1;
chooser_view_->OnHueChosen(360.f * (base_height - (level_ - kBorderWidth)) /
base_height);
SchedulePaint();
}
gfx::Size ColorChooserView::HueView::GetPreferredSize() const {
// We put indicators on the both sides of the hue bar.
return gfx::Size(kHueBarWidth + kHueIndicatorSize * 2 + kBorderWidth * 2,
kSaturationValueSize + kBorderWidth * 2);
}
void ColorChooserView::HueView::OnPaint(gfx::Canvas* canvas) {
SkScalar hsv[3];
// In the hue bar, saturation and value for the color should be always 100%.
hsv[1] = SK_Scalar1;
hsv[2] = SK_Scalar1;
canvas->FillRect(gfx::Rect(kHueIndicatorSize, 0,
kHueBarWidth + kBorderWidth, height() - 1),
SK_ColorGRAY);
int base_left = kHueIndicatorSize + kBorderWidth;
for (int y = 0; y < kSaturationValueSize; ++y) {
hsv[0] =
360.f * (kSaturationValueSize - 1 - y) / (kSaturationValueSize - 1);
canvas->FillRect(gfx::Rect(base_left, y + kBorderWidth, kHueBarWidth, 1),
SkHSVToColor(hsv));
}
// Put the triangular indicators besides.
SkPath left_indicator_path;
SkPath right_indicator_path;
left_indicator_path.moveTo(
SK_ScalarHalf, SkIntToScalar(level_ - kHueIndicatorSize));
left_indicator_path.lineTo(
kHueIndicatorSize, SkIntToScalar(level_));
left_indicator_path.lineTo(
SK_ScalarHalf, SkIntToScalar(level_ + kHueIndicatorSize));
left_indicator_path.lineTo(
SK_ScalarHalf, SkIntToScalar(level_ - kHueIndicatorSize));
right_indicator_path.moveTo(
SkIntToScalar(width()) - SK_ScalarHalf,
SkIntToScalar(level_ - kHueIndicatorSize));
right_indicator_path.lineTo(
SkIntToScalar(width() - kHueIndicatorSize) - SK_ScalarHalf,
SkIntToScalar(level_));
right_indicator_path.lineTo(
SkIntToScalar(width()) - SK_ScalarHalf,
SkIntToScalar(level_ + kHueIndicatorSize));
right_indicator_path.lineTo(
SkIntToScalar(width()) - SK_ScalarHalf,
SkIntToScalar(level_ - kHueIndicatorSize));
SkPaint indicator_paint;
indicator_paint.setColor(SK_ColorBLACK);
indicator_paint.setStyle(SkPaint::kFill_Style);
canvas->DrawPath(left_indicator_path, indicator_paint);
canvas->DrawPath(right_indicator_path, indicator_paint);
}
////////////////////////////////////////////////////////////////////////////////
// ColorChooserView::SaturationValueView
//
// The class to choose the saturation and the value of the color. It draws
// a square area and the indicator for the currently selected saturation and
// value.
class ColorChooserView::SaturationValueView : public LocatedEventHandlerView {
public:
explicit SaturationValueView(ColorChooserView* chooser_view);
void OnHueChanged(SkScalar hue);
void OnSaturationValueChanged(SkScalar saturation, SkScalar value);
private:
// LocatedEventHandlerView overrides:
void ProcessEventAtLocation(const gfx::Point& point) override;
// View overrides:
gfx::Size GetPreferredSize() const override;
void OnPaint(gfx::Canvas* canvas) override;
ColorChooserView* chooser_view_;
SkScalar hue_;
gfx::Point marker_position_;
DISALLOW_COPY_AND_ASSIGN(SaturationValueView);
};
ColorChooserView::SaturationValueView::SaturationValueView(
ColorChooserView* chooser_view)
: chooser_view_(chooser_view),
hue_(0) {
SetBorder(Border::CreateSolidBorder(kBorderWidth, SK_ColorGRAY));
}
void ColorChooserView::SaturationValueView::OnHueChanged(SkScalar hue) {
if (hue_ != hue) {
hue_ = hue;
SchedulePaint();
}
}
void ColorChooserView::SaturationValueView::OnSaturationValueChanged(
SkScalar saturation,
SkScalar value) {
SkScalar scalar_size = SkIntToScalar(kSaturationValueSize - 1);
int x = SkScalarFloorToInt(SkScalarMul(saturation, scalar_size)) +
kBorderWidth;
int y = SkScalarFloorToInt(SkScalarMul(SK_Scalar1 - value, scalar_size)) +
kBorderWidth;
if (gfx::Point(x, y) == marker_position_)
return;
marker_position_.set_x(x);
marker_position_.set_y(y);
SchedulePaint();
}
void ColorChooserView::SaturationValueView::ProcessEventAtLocation(
const gfx::Point& point) {
SkScalar scalar_size = SkIntToScalar(kSaturationValueSize - 1);
SkScalar saturation = (point.x() - kBorderWidth) / scalar_size;
SkScalar value = SK_Scalar1 - (point.y() - kBorderWidth) / scalar_size;
saturation = SkScalarPin(saturation, 0, SK_Scalar1);
value = SkScalarPin(value, 0, SK_Scalar1);
OnSaturationValueChanged(saturation, value);
chooser_view_->OnSaturationValueChosen(saturation, value);
}
gfx::Size ColorChooserView::SaturationValueView::GetPreferredSize() const {
return gfx::Size(kSaturationValueSize + kBorderWidth * 2,
kSaturationValueSize + kBorderWidth * 2);
}
void ColorChooserView::SaturationValueView::OnPaint(gfx::Canvas* canvas) {
gfx::Rect color_bounds = bounds();
color_bounds.Inset(GetInsets());
// Paints horizontal gradient first for saturation.
SkScalar hsv[3] = { hue_, SK_Scalar1, SK_Scalar1 };
SkScalar left_hsv[3] = { hue_, 0, SK_Scalar1 };
DrawGradientRect(color_bounds, SkHSVToColor(255, left_hsv),
SkHSVToColor(255, hsv), true /* is_horizontal */, canvas);
// Overlays vertical gradient for value.
SkScalar hsv_bottom[3] = { 0, SK_Scalar1, 0 };
DrawGradientRect(color_bounds, SK_ColorTRANSPARENT,
SkHSVToColor(255, hsv_bottom), false /* is_horizontal */,
canvas);
// Draw the crosshair marker.
// The background is very dark at the bottom of the view. Use a white
// marker in that case.
SkColor indicator_color =
(marker_position_.y() > width() * 3 / 4) ? SK_ColorWHITE : SK_ColorBLACK;
canvas->FillRect(
gfx::Rect(marker_position_.x(),
marker_position_.y() - kSaturationValueIndicatorSize,
1, kSaturationValueIndicatorSize * 2 + 1),
indicator_color);
canvas->FillRect(
gfx::Rect(marker_position_.x() - kSaturationValueIndicatorSize,
marker_position_.y(),
kSaturationValueIndicatorSize * 2 + 1, 1),
indicator_color);
OnPaintBorder(canvas);
}
////////////////////////////////////////////////////////////////////////////////
// ColorChooserView::SelectedColorPatchView
//
// A view to simply show the selected color in a rectangle.
class ColorChooserView::SelectedColorPatchView : public views::View {
public:
SelectedColorPatchView();
void SetColor(SkColor color);
private:
DISALLOW_COPY_AND_ASSIGN(SelectedColorPatchView);
};
ColorChooserView::SelectedColorPatchView::SelectedColorPatchView() {
SetVisible(true);
SetBorder(Border::CreateSolidBorder(kBorderWidth, SK_ColorGRAY));
}
void ColorChooserView::SelectedColorPatchView::SetColor(SkColor color) {
if (!background())
set_background(Background::CreateSolidBackground(color));
else
background()->SetNativeControlColor(color);
SchedulePaint();
}
////////////////////////////////////////////////////////////////////////////////
// ColorChooserView
//
ColorChooserView::ColorChooserView(ColorChooserListener* listener,
SkColor initial_color)
: listener_(listener) {
DCHECK(listener_);
set_background(Background::CreateSolidBackground(SK_ColorLTGRAY));
SetLayoutManager(new BoxLayout(BoxLayout::kVertical, kMarginWidth,
kMarginWidth, kMarginWidth));
View* container = new View();
container->SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, 0, 0,
kMarginWidth));
saturation_value_ = new SaturationValueView(this);
container->AddChildView(saturation_value_);
hue_ = new HueView(this);
container->AddChildView(hue_);
AddChildView(container);
View* container2 = new View();
GridLayout* layout = new GridLayout(container2);
container2->SetLayoutManager(layout);
ColumnSet* columns = layout->AddColumnSet(0);
columns->AddColumn(
GridLayout::LEADING, GridLayout::FILL, 0, GridLayout::USE_PREF, 0, 0);
columns->AddPaddingColumn(0, kMarginWidth);
columns->AddColumn(
GridLayout::FILL, GridLayout::FILL, 1, GridLayout::USE_PREF, 0, 0);
layout->StartRow(0, 0);
textfield_ = new Textfield();
textfield_->set_controller(this);
textfield_->set_default_width_in_chars(kTextfieldLengthInChars);
layout->AddView(textfield_);
selected_color_patch_ = new SelectedColorPatchView();
layout->AddView(selected_color_patch_);
AddChildView(container2);
OnColorChanged(initial_color);
}
ColorChooserView::~ColorChooserView() {
}
void ColorChooserView::OnColorChanged(SkColor color) {
SkColorToHSV(color, hsv_);
hue_->OnHueChanged(hsv_[0]);
saturation_value_->OnHueChanged(hsv_[0]);
saturation_value_->OnSaturationValueChanged(hsv_[1], hsv_[2]);
selected_color_patch_->SetColor(color);
textfield_->SetText(GetColorText(color));
}
void ColorChooserView::OnHueChosen(SkScalar hue) {
hsv_[0] = hue;
SkColor color = SkHSVToColor(255, hsv_);
if (listener_)
listener_->OnColorChosen(color);
saturation_value_->OnHueChanged(hue);
selected_color_patch_->SetColor(color);
textfield_->SetText(GetColorText(color));
}
void ColorChooserView::OnSaturationValueChosen(SkScalar saturation,
SkScalar value) {
hsv_[1] = saturation;
hsv_[2] = value;
SkColor color = SkHSVToColor(255, hsv_);
if (listener_)
listener_->OnColorChosen(color);
selected_color_patch_->SetColor(color);
textfield_->SetText(GetColorText(color));
}
bool ColorChooserView::CanMinimize() const {
return false;
}
View* ColorChooserView::GetInitiallyFocusedView() {
return textfield_;
}
ui::ModalType ColorChooserView::GetModalType() const {
return ui::MODAL_TYPE_WINDOW;
}
void ColorChooserView::WindowClosing() {
if (listener_)
listener_->OnColorChooserDialogClosed();
}
View* ColorChooserView::GetContentsView() {
return this;
}
void ColorChooserView::ContentsChanged(Textfield* sender,
const base::string16& new_contents) {
SkColor color = SK_ColorBLACK;
if (GetColorFromText(new_contents, &color)) {
SkColorToHSV(color, hsv_);
if (listener_)
listener_->OnColorChosen(color);
hue_->OnHueChanged(hsv_[0]);
saturation_value_->OnHueChanged(hsv_[0]);
saturation_value_->OnSaturationValueChanged(hsv_[1], hsv_[2]);
selected_color_patch_->SetColor(color);
}
}
bool ColorChooserView::HandleKeyEvent(Textfield* sender,
const ui::KeyEvent& key_event) {
if (key_event.type() != ui::ET_KEY_PRESSED ||
(key_event.key_code() != ui::VKEY_RETURN &&
key_event.key_code() != ui::VKEY_ESCAPE))
return false;
GetWidget()->Close();
return true;
}
} // namespace views