1// Copyright (c) 2012 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 "ui/views/color_chooser/color_chooser_view.h"
6
7#include "base/logging.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/strings/stringprintf.h"
10#include "base/strings/utf_string_conversions.h"
11#include "skia/ext/refptr.h"
12#include "third_party/skia/include/effects/SkGradientShader.h"
13#include "ui/events/event.h"
14#include "ui/events/keycodes/keyboard_codes.h"
15#include "ui/gfx/canvas.h"
16#include "ui/views/background.h"
17#include "ui/views/border.h"
18#include "ui/views/color_chooser/color_chooser_listener.h"
19#include "ui/views/controls/textfield/textfield.h"
20#include "ui/views/controls/textfield/textfield_controller.h"
21#include "ui/views/layout/box_layout.h"
22#include "ui/views/layout/grid_layout.h"
23#include "ui/views/widget/widget.h"
24
25namespace {
26
27const int kHueBarWidth = 20;
28const int kSaturationValueSize = 200;
29const int kMarginWidth = 5;
30const int kSaturationValueIndicatorSize = 6;
31const int kHueIndicatorSize = 5;
32const int kBorderWidth = 1;
33const int kTextfieldLengthInChars = 14;
34
35base::string16 GetColorText(SkColor color) {
36  return base::ASCIIToUTF16(base::StringPrintf("#%02x%02x%02x",
37                                               SkColorGetR(color),
38                                               SkColorGetG(color),
39                                               SkColorGetB(color)));
40}
41
42bool GetColorFromText(const base::string16& text, SkColor* result) {
43  if (text.size() != 6 && !(text.size() == 7 && text[0] == '#'))
44    return false;
45
46  std::string input =
47      base::UTF16ToUTF8((text.size() == 6) ? text : text.substr(1));
48  std::vector<uint8> hex;
49  if (!base::HexStringToBytes(input, &hex))
50    return false;
51
52  *result = SkColorSetRGB(hex[0], hex[1], hex[2]);
53  return true;
54}
55
56// A view that processes mouse events and gesture events using a common
57// interface.
58class LocatedEventHandlerView : public views::View {
59 public:
60  virtual ~LocatedEventHandlerView() {}
61
62 protected:
63  LocatedEventHandlerView() {}
64
65  // Handles an event (mouse or gesture) at the specified location.
66  virtual void ProcessEventAtLocation(const gfx::Point& location) = 0;
67
68  // views::View overrides:
69  virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
70    ProcessEventAtLocation(event.location());
71    return true;
72  }
73
74  virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE {
75    ProcessEventAtLocation(event.location());
76    return true;
77  }
78
79  virtual void OnGestureEvent(ui::GestureEvent* event) OVERRIDE {
80    if (event->type() == ui::ET_GESTURE_TAP ||
81        event->type() == ui::ET_GESTURE_TAP_DOWN ||
82        event->IsScrollGestureEvent()) {
83      ProcessEventAtLocation(event->location());
84      event->SetHandled();
85    }
86  }
87
88  DISALLOW_COPY_AND_ASSIGN(LocatedEventHandlerView);
89};
90
91void DrawGradientRect(const gfx::Rect& rect, SkColor start_color,
92                      SkColor end_color, bool is_horizontal,
93                      gfx::Canvas* canvas) {
94  SkColor colors[2] = { start_color, end_color };
95  SkPoint points[2];
96  points[0].iset(0, 0);
97  if (is_horizontal)
98    points[1].iset(rect.width() + 1, 0);
99  else
100    points[1].iset(0, rect.height() + 1);
101  skia::RefPtr<SkShader> shader(skia::AdoptRef(
102      SkGradientShader::CreateLinear(points, colors, NULL, 2,
103                                     SkShader::kClamp_TileMode)));
104  SkPaint paint;
105  paint.setShader(shader.get());
106  canvas->DrawRect(rect, paint);
107}
108
109}  // namespace
110
111namespace views {
112
113////////////////////////////////////////////////////////////////////////////////
114// ColorChooserView::HueView
115//
116// The class to choose the hue of the color.  It draws a vertical bar and
117// the indicator for the currently selected hue.
118class ColorChooserView::HueView : public LocatedEventHandlerView {
119 public:
120  explicit HueView(ColorChooserView* chooser_view);
121
122  void OnHueChanged(SkScalar hue);
123
124 private:
125  // LocatedEventHandlerView overrides:
126  virtual void ProcessEventAtLocation(const gfx::Point& point) OVERRIDE;
127
128  // View overrides:
129  virtual gfx::Size GetPreferredSize() const OVERRIDE;
130  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
131
132  ColorChooserView* chooser_view_;
133  int level_;
134
135  DISALLOW_COPY_AND_ASSIGN(HueView);
136};
137
138ColorChooserView::HueView::HueView(ColorChooserView* chooser_view)
139    : chooser_view_(chooser_view),
140      level_(0) {
141  SetFocusable(false);
142}
143
144void ColorChooserView::HueView::OnHueChanged(SkScalar hue) {
145  SkScalar height = SkIntToScalar(kSaturationValueSize - 1);
146  SkScalar hue_max = SkIntToScalar(360);
147  int level = SkScalarDiv(SkScalarMul(hue_max - hue, height), hue_max);
148  level += kBorderWidth;
149  if (level_ != level) {
150    level_ = level;
151    SchedulePaint();
152  }
153}
154
155void ColorChooserView::HueView::ProcessEventAtLocation(
156    const gfx::Point& point) {
157  level_ = std::max(kBorderWidth,
158                    std::min(height() - 1 - kBorderWidth, point.y()));
159  int base_height = kSaturationValueSize - 1;
160  chooser_view_->OnHueChosen(SkScalarDiv(
161      SkScalarMul(SkIntToScalar(360),
162                  SkIntToScalar(base_height - (level_ - kBorderWidth))),
163      SkIntToScalar(base_height)));
164  SchedulePaint();
165}
166
167gfx::Size ColorChooserView::HueView::GetPreferredSize() const {
168  // We put indicators on the both sides of the hue bar.
169  return gfx::Size(kHueBarWidth + kHueIndicatorSize * 2 + kBorderWidth * 2,
170                   kSaturationValueSize + kBorderWidth * 2);
171}
172
173void ColorChooserView::HueView::OnPaint(gfx::Canvas* canvas) {
174  SkScalar hsv[3];
175  // In the hue bar, saturation and value for the color should be always 100%.
176  hsv[1] = SK_Scalar1;
177  hsv[2] = SK_Scalar1;
178
179  canvas->FillRect(gfx::Rect(kHueIndicatorSize, 0,
180                             kHueBarWidth + kBorderWidth, height() - 1),
181                   SK_ColorGRAY);
182  int base_left = kHueIndicatorSize + kBorderWidth;
183  for (int y = 0; y < kSaturationValueSize; ++y) {
184    hsv[0] = SkScalarDiv(SkScalarMul(SkIntToScalar(360),
185                                     SkIntToScalar(
186                                         kSaturationValueSize - 1 - y)),
187                    SkIntToScalar(kSaturationValueSize - 1));
188    canvas->FillRect(gfx::Rect(base_left, y + kBorderWidth, kHueBarWidth, 1),
189                     SkHSVToColor(hsv));
190  }
191
192  // Put the triangular indicators besides.
193  SkPath left_indicator_path;
194  SkPath right_indicator_path;
195  left_indicator_path.moveTo(
196      SK_ScalarHalf, SkIntToScalar(level_ - kHueIndicatorSize));
197  left_indicator_path.lineTo(
198      kHueIndicatorSize, SkIntToScalar(level_));
199  left_indicator_path.lineTo(
200      SK_ScalarHalf, SkIntToScalar(level_ + kHueIndicatorSize));
201  left_indicator_path.lineTo(
202      SK_ScalarHalf, SkIntToScalar(level_ - kHueIndicatorSize));
203  right_indicator_path.moveTo(
204      SkIntToScalar(width()) - SK_ScalarHalf,
205      SkIntToScalar(level_ - kHueIndicatorSize));
206  right_indicator_path.lineTo(
207      SkIntToScalar(width() - kHueIndicatorSize) - SK_ScalarHalf,
208      SkIntToScalar(level_));
209  right_indicator_path.lineTo(
210      SkIntToScalar(width()) - SK_ScalarHalf,
211      SkIntToScalar(level_ + kHueIndicatorSize));
212  right_indicator_path.lineTo(
213      SkIntToScalar(width()) - SK_ScalarHalf,
214      SkIntToScalar(level_ - kHueIndicatorSize));
215
216  SkPaint indicator_paint;
217  indicator_paint.setColor(SK_ColorBLACK);
218  indicator_paint.setStyle(SkPaint::kFill_Style);
219  canvas->DrawPath(left_indicator_path, indicator_paint);
220  canvas->DrawPath(right_indicator_path, indicator_paint);
221}
222
223////////////////////////////////////////////////////////////////////////////////
224// ColorChooserView::SaturationValueView
225//
226// The class to choose the saturation and the value of the color.  It draws
227// a square area and the indicator for the currently selected saturation and
228// value.
229class ColorChooserView::SaturationValueView : public LocatedEventHandlerView {
230 public:
231  explicit SaturationValueView(ColorChooserView* chooser_view);
232
233  void OnHueChanged(SkScalar hue);
234  void OnSaturationValueChanged(SkScalar saturation, SkScalar value);
235
236 private:
237  // LocatedEventHandlerView overrides:
238  virtual void ProcessEventAtLocation(const gfx::Point& point) OVERRIDE;
239
240  // View overrides:
241  virtual gfx::Size GetPreferredSize() const OVERRIDE;
242  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
243
244  ColorChooserView* chooser_view_;
245  SkScalar hue_;
246  gfx::Point marker_position_;
247
248  DISALLOW_COPY_AND_ASSIGN(SaturationValueView);
249};
250
251ColorChooserView::SaturationValueView::SaturationValueView(
252    ColorChooserView* chooser_view)
253    : chooser_view_(chooser_view),
254      hue_(0) {
255  SetFocusable(false);
256  SetBorder(Border::CreateSolidBorder(kBorderWidth, SK_ColorGRAY));
257}
258
259void ColorChooserView::SaturationValueView::OnHueChanged(SkScalar hue) {
260  if (hue_ != hue) {
261    hue_ = hue;
262    SchedulePaint();
263  }
264}
265
266void ColorChooserView::SaturationValueView::OnSaturationValueChanged(
267    SkScalar saturation,
268    SkScalar value) {
269  SkScalar scalar_size = SkIntToScalar(kSaturationValueSize - 1);
270  int x = SkScalarFloorToInt(SkScalarMul(saturation, scalar_size)) +
271      kBorderWidth;
272  int y = SkScalarFloorToInt(SkScalarMul(SK_Scalar1 - value, scalar_size)) +
273      kBorderWidth;
274  if (gfx::Point(x, y) == marker_position_)
275    return;
276
277  marker_position_.set_x(x);
278  marker_position_.set_y(y);
279  SchedulePaint();
280}
281
282void ColorChooserView::SaturationValueView::ProcessEventAtLocation(
283    const gfx::Point& point) {
284  SkScalar scalar_size = SkIntToScalar(kSaturationValueSize - 1);
285  SkScalar saturation = SkScalarDiv(
286      SkIntToScalar(point.x() - kBorderWidth), scalar_size);
287  SkScalar value = SK_Scalar1 - SkScalarDiv(
288      SkIntToScalar(point.y() - kBorderWidth), scalar_size);
289  saturation = SkScalarPin(saturation, 0, SK_Scalar1);
290  value = SkScalarPin(value, 0, SK_Scalar1);
291  OnSaturationValueChanged(saturation, value);
292  chooser_view_->OnSaturationValueChosen(saturation, value);
293}
294
295gfx::Size ColorChooserView::SaturationValueView::GetPreferredSize() const {
296  return gfx::Size(kSaturationValueSize + kBorderWidth * 2,
297                   kSaturationValueSize + kBorderWidth * 2);
298}
299
300void ColorChooserView::SaturationValueView::OnPaint(gfx::Canvas* canvas) {
301  gfx::Rect color_bounds = bounds();
302  color_bounds.Inset(GetInsets());
303
304  // Paints horizontal gradient first for saturation.
305  SkScalar hsv[3] = { hue_, SK_Scalar1, SK_Scalar1 };
306  SkScalar left_hsv[3] = { hue_, 0, SK_Scalar1 };
307  DrawGradientRect(color_bounds, SkHSVToColor(255, left_hsv),
308                   SkHSVToColor(255, hsv), true /* is_horizontal */, canvas);
309
310  // Overlays vertical gradient for value.
311  SkScalar hsv_bottom[3] = { 0, SK_Scalar1, 0 };
312  DrawGradientRect(color_bounds, SK_ColorTRANSPARENT,
313                   SkHSVToColor(255, hsv_bottom), false /* is_horizontal */,
314                   canvas);
315
316  // Draw the crosshair marker.
317  // The background is very dark at the bottom of the view.  Use a white
318  // marker in that case.
319  SkColor indicator_color =
320      (marker_position_.y() > width() * 3 / 4) ? SK_ColorWHITE : SK_ColorBLACK;
321  canvas->FillRect(
322      gfx::Rect(marker_position_.x(),
323                marker_position_.y() - kSaturationValueIndicatorSize,
324                1, kSaturationValueIndicatorSize * 2 + 1),
325      indicator_color);
326  canvas->FillRect(
327      gfx::Rect(marker_position_.x() - kSaturationValueIndicatorSize,
328                marker_position_.y(),
329                kSaturationValueIndicatorSize * 2 + 1, 1),
330      indicator_color);
331
332  OnPaintBorder(canvas);
333}
334
335////////////////////////////////////////////////////////////////////////////////
336// ColorChooserView::SelectedColorPatchView
337//
338// A view to simply show the selected color in a rectangle.
339class ColorChooserView::SelectedColorPatchView : public views::View {
340 public:
341  SelectedColorPatchView();
342
343  void SetColor(SkColor color);
344
345 private:
346  DISALLOW_COPY_AND_ASSIGN(SelectedColorPatchView);
347};
348
349ColorChooserView::SelectedColorPatchView::SelectedColorPatchView() {
350  SetFocusable(false);
351  SetVisible(true);
352  SetBorder(Border::CreateSolidBorder(kBorderWidth, SK_ColorGRAY));
353}
354
355void ColorChooserView::SelectedColorPatchView::SetColor(SkColor color) {
356  if (!background())
357    set_background(Background::CreateSolidBackground(color));
358  else
359    background()->SetNativeControlColor(color);
360  SchedulePaint();
361}
362
363////////////////////////////////////////////////////////////////////////////////
364// ColorChooserView
365//
366
367ColorChooserView::ColorChooserView(ColorChooserListener* listener,
368                                   SkColor initial_color)
369    : listener_(listener) {
370  DCHECK(listener_);
371
372  SetFocusable(false);
373  set_background(Background::CreateSolidBackground(SK_ColorLTGRAY));
374  SetLayoutManager(new BoxLayout(BoxLayout::kVertical, kMarginWidth,
375                                 kMarginWidth, kMarginWidth));
376
377  View* container = new View();
378  container->SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, 0, 0,
379                                            kMarginWidth));
380  saturation_value_ = new SaturationValueView(this);
381  container->AddChildView(saturation_value_);
382  hue_ = new HueView(this);
383  container->AddChildView(hue_);
384  AddChildView(container);
385
386  View* container2 = new View();
387  GridLayout* layout = new GridLayout(container2);
388  container2->SetLayoutManager(layout);
389  ColumnSet* columns = layout->AddColumnSet(0);
390  columns->AddColumn(
391      GridLayout::LEADING, GridLayout::FILL, 0, GridLayout::USE_PREF, 0, 0);
392  columns->AddPaddingColumn(0, kMarginWidth);
393  columns->AddColumn(
394      GridLayout::FILL, GridLayout::FILL, 1, GridLayout::USE_PREF, 0, 0);
395  layout->StartRow(0, 0);
396  textfield_ = new Textfield();
397  textfield_->set_controller(this);
398  textfield_->set_default_width_in_chars(kTextfieldLengthInChars);
399  layout->AddView(textfield_);
400  selected_color_patch_ = new SelectedColorPatchView();
401  layout->AddView(selected_color_patch_);
402  AddChildView(container2);
403
404  OnColorChanged(initial_color);
405}
406
407ColorChooserView::~ColorChooserView() {
408}
409
410void ColorChooserView::OnColorChanged(SkColor color) {
411  SkColorToHSV(color, hsv_);
412  hue_->OnHueChanged(hsv_[0]);
413  saturation_value_->OnHueChanged(hsv_[0]);
414  saturation_value_->OnSaturationValueChanged(hsv_[1], hsv_[2]);
415  selected_color_patch_->SetColor(color);
416  textfield_->SetText(GetColorText(color));
417}
418
419void ColorChooserView::OnHueChosen(SkScalar hue) {
420  hsv_[0] = hue;
421  SkColor color = SkHSVToColor(255, hsv_);
422  if (listener_)
423    listener_->OnColorChosen(color);
424  saturation_value_->OnHueChanged(hue);
425  selected_color_patch_->SetColor(color);
426  textfield_->SetText(GetColorText(color));
427}
428
429void ColorChooserView::OnSaturationValueChosen(SkScalar saturation,
430                                               SkScalar value) {
431  hsv_[1] = saturation;
432  hsv_[2] = value;
433  SkColor color = SkHSVToColor(255, hsv_);
434  if (listener_)
435    listener_->OnColorChosen(color);
436  selected_color_patch_->SetColor(color);
437  textfield_->SetText(GetColorText(color));
438}
439
440bool ColorChooserView::CanMinimize() const {
441  return false;
442}
443
444View* ColorChooserView::GetInitiallyFocusedView() {
445  return textfield_;
446}
447
448ui::ModalType ColorChooserView::GetModalType() const {
449  return ui::MODAL_TYPE_WINDOW;
450}
451
452void ColorChooserView::WindowClosing() {
453  if (listener_)
454    listener_->OnColorChooserDialogClosed();
455}
456
457View* ColorChooserView::GetContentsView() {
458  return this;
459}
460
461void ColorChooserView::ContentsChanged(Textfield* sender,
462                                       const base::string16& new_contents) {
463  SkColor color = SK_ColorBLACK;
464  if (GetColorFromText(new_contents, &color)) {
465    SkColorToHSV(color, hsv_);
466    if (listener_)
467      listener_->OnColorChosen(color);
468    hue_->OnHueChanged(hsv_[0]);
469    saturation_value_->OnHueChanged(hsv_[0]);
470    saturation_value_->OnSaturationValueChanged(hsv_[1], hsv_[2]);
471    selected_color_patch_->SetColor(color);
472  }
473}
474
475bool ColorChooserView::HandleKeyEvent(Textfield* sender,
476                                      const ui::KeyEvent& key_event) {
477  if (key_event.key_code() != ui::VKEY_RETURN &&
478      key_event.key_code() != ui::VKEY_ESCAPE)
479    return false;
480
481  GetWidget()->Close();
482  return true;
483}
484
485}  // namespace views
486