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.
5 #include "ui/views/color_chooser/color_chooser_view.h"
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"
27 const int kHueBarWidth = 20;
28 const int kSaturationValueSize = 200;
29 const int kMarginWidth = 5;
30 const int kSaturationValueIndicatorSize = 6;
31 const int kHueIndicatorSize = 5;
32 const int kBorderWidth = 1;
33 const int kTextfieldLengthInChars = 14;
35 string16 GetColorText(SkColor color) {
36 return ASCIIToUTF16(base::StringPrintf("#%02x%02x%02x",
42 bool GetColorFromText(const string16& text, SkColor* result) {
43 if (text.size() != 6 && !(text.size() == 7 && text[0] == '#'))
46 std::string input = UTF16ToUTF8((text.size() == 6) ? text : text.substr(1));
47 std::vector<uint8> hex;
48 if (!base::HexStringToBytes(input, &hex))
51 *result = SkColorSetRGB(hex[0], hex[1], hex[2]);
55 // A view that processes mouse events and gesture events using a common
57 class LocatedEventHandlerView : public views::View {
59 virtual ~LocatedEventHandlerView() {}
62 LocatedEventHandlerView() {}
64 // Handles an event (mouse or gesture) at the specified location.
65 virtual void ProcessEventAtLocation(const gfx::Point& location) = 0;
67 // views::View overrides:
68 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
69 ProcessEventAtLocation(event.location());
73 virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE {
74 ProcessEventAtLocation(event.location());
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());
87 DISALLOW_COPY_AND_ASSIGN(LocatedEventHandlerView);
90 void 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 };
97 points[1].iset(rect.width() + 1, 0);
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)));
104 paint.setShader(shader.get());
105 canvas->DrawRect(rect, paint);
112 ////////////////////////////////////////////////////////////////////////////////
113 // ColorChooserView::HueView
115 // The class to choose the hue of the color. It draws a vertical bar and
116 // the indicator for the currently selected hue.
117 class ColorChooserView::HueView : public LocatedEventHandlerView {
119 explicit HueView(ColorChooserView* chooser_view);
121 void OnHueChanged(SkScalar hue);
124 // LocatedEventHandlerView overrides:
125 virtual void ProcessEventAtLocation(const gfx::Point& point) OVERRIDE;
128 virtual gfx::Size GetPreferredSize() OVERRIDE;
129 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
131 ColorChooserView* chooser_view_;
134 DISALLOW_COPY_AND_ASSIGN(HueView);
137 ColorChooserView::HueView::HueView(ColorChooserView* chooser_view)
138 : chooser_view_(chooser_view),
140 set_focusable(false);
143 void 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) {
154 void 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)));
166 gfx::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);
172 void ColorChooserView::HueView::OnPaint(gfx::Canvas* canvas) {
174 // In the hue bar, saturation and value for the color should be always 100%.
178 canvas->FillRect(gfx::Rect(kHueIndicatorSize, 0,
179 kHueBarWidth + kBorderWidth, height() - 1),
181 int base_left = kHueIndicatorSize + kBorderWidth;
182 for (int y = 0; y < kSaturationValueSize; ++y) {
183 hsv[0] = SkScalarDiv(SkScalarMul(SkIntToScalar(360),
185 kSaturationValueSize - 1 - y)),
186 SkIntToScalar(kSaturationValueSize - 1));
187 canvas->FillRect(gfx::Rect(base_left, y + kBorderWidth, kHueBarWidth, 1),
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));
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);
222 ////////////////////////////////////////////////////////////////////////////////
223 // ColorChooserView::SaturationValueView
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
228 class ColorChooserView::SaturationValueView : public LocatedEventHandlerView {
230 explicit SaturationValueView(ColorChooserView* chooser_view);
232 void OnHueChanged(SkScalar hue);
233 void OnSaturationValueChanged(SkScalar saturation, SkScalar value);
236 // LocatedEventHandlerView overrides:
237 virtual void ProcessEventAtLocation(const gfx::Point& point) OVERRIDE;
240 virtual gfx::Size GetPreferredSize() OVERRIDE;
241 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
243 ColorChooserView* chooser_view_;
245 gfx::Point marker_position_;
247 DISALLOW_COPY_AND_ASSIGN(SaturationValueView);
250 ColorChooserView::SaturationValueView::SaturationValueView(
251 ColorChooserView* chooser_view)
252 : chooser_view_(chooser_view),
254 set_focusable(false);
255 set_border(Border::CreateSolidBorder(kBorderWidth, SK_ColorGRAY));
258 void ColorChooserView::SaturationValueView::OnHueChanged(SkScalar hue) {
265 void ColorChooserView::SaturationValueView::OnSaturationValueChanged(
268 SkScalar scalar_size = SkIntToScalar(kSaturationValueSize - 1);
269 int x = SkScalarFloorToInt(SkScalarMul(saturation, scalar_size)) +
271 int y = SkScalarFloorToInt(SkScalarMul(SK_Scalar1 - value, scalar_size)) +
273 if (gfx::Point(x, y) == marker_position_)
276 marker_position_.set_x(x);
277 marker_position_.set_y(y);
281 void 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);
294 gfx::Size ColorChooserView::SaturationValueView::GetPreferredSize() {
295 return gfx::Size(kSaturationValueSize + kBorderWidth * 2,
296 kSaturationValueSize + kBorderWidth * 2);
299 void ColorChooserView::SaturationValueView::OnPaint(gfx::Canvas* canvas) {
300 gfx::Rect color_bounds = bounds();
301 color_bounds.Inset(GetInsets());
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);
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 */,
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;
321 gfx::Rect(marker_position_.x(),
322 marker_position_.y() - kSaturationValueIndicatorSize,
323 1, kSaturationValueIndicatorSize * 2 + 1),
326 gfx::Rect(marker_position_.x() - kSaturationValueIndicatorSize,
327 marker_position_.y(),
328 kSaturationValueIndicatorSize * 2 + 1, 1),
331 OnPaintBorder(canvas);
334 ////////////////////////////////////////////////////////////////////////////////
335 // ColorChooserView::SelectedColorPatchView
337 // A view to simply show the selected color in a rectangle.
338 class ColorChooserView::SelectedColorPatchView : public views::View {
340 SelectedColorPatchView();
342 void SetColor(SkColor color);
345 DISALLOW_COPY_AND_ASSIGN(SelectedColorPatchView);
348 ColorChooserView::SelectedColorPatchView::SelectedColorPatchView() {
349 set_focusable(false);
351 set_border(Border::CreateSolidBorder(kBorderWidth, SK_ColorGRAY));
354 void ColorChooserView::SelectedColorPatchView::SetColor(SkColor color) {
356 set_background(Background::CreateSolidBackground(color));
358 background()->SetNativeControlColor(color);
362 ////////////////////////////////////////////////////////////////////////////////
366 ColorChooserView::ColorChooserView(ColorChooserListener* listener,
367 SkColor initial_color)
368 : listener_(listener) {
371 set_focusable(false);
372 set_background(Background::CreateSolidBackground(SK_ColorLTGRAY));
373 SetLayoutManager(new BoxLayout(BoxLayout::kVertical, kMarginWidth,
374 kMarginWidth, kMarginWidth));
376 View* container = new View();
377 container->SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, 0, 0,
379 saturation_value_ = new SaturationValueView(this);
380 container->AddChildView(saturation_value_);
381 hue_ = new HueView(this);
382 container->AddChildView(hue_);
383 AddChildView(container);
385 View* container2 = new View();
386 GridLayout* layout = new GridLayout(container2);
387 container2->SetLayoutManager(layout);
388 ColumnSet* columns = layout->AddColumnSet(0);
390 GridLayout::LEADING, GridLayout::FILL, 0, GridLayout::USE_PREF, 0, 0);
391 columns->AddPaddingColumn(0, kMarginWidth);
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);
403 OnColorChanged(initial_color);
406 ColorChooserView::~ColorChooserView() {
409 void 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));
418 void ColorChooserView::OnHueChosen(SkScalar hue) {
420 SkColor color = SkHSVToColor(255, hsv_);
422 listener_->OnColorChosen(color);
423 saturation_value_->OnHueChanged(hue);
424 selected_color_patch_->SetColor(color);
425 textfield_->SetText(GetColorText(color));
428 void ColorChooserView::OnSaturationValueChosen(SkScalar saturation,
430 hsv_[1] = saturation;
432 SkColor color = SkHSVToColor(255, hsv_);
434 listener_->OnColorChosen(color);
435 selected_color_patch_->SetColor(color);
436 textfield_->SetText(GetColorText(color));
439 View* ColorChooserView::GetInitiallyFocusedView() {
443 ui::ModalType ColorChooserView::GetModalType() const {
444 return ui::MODAL_TYPE_WINDOW;
447 void ColorChooserView::WindowClosing() {
449 listener_->OnColorChooserDialogClosed();
452 View* ColorChooserView::GetContentsView() {
456 void ColorChooserView::ContentsChanged(Textfield* sender,
457 const string16& new_contents) {
458 SkColor color = SK_ColorBLACK;
459 if (GetColorFromText(new_contents, &color)) {
460 SkColorToHSV(color, hsv_);
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);
470 bool 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)
476 GetWidget()->Close();