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