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