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 440View* ColorChooserView::GetInitiallyFocusedView() { 441 return textfield_; 442} 443 444ui::ModalType ColorChooserView::GetModalType() const { 445 return ui::MODAL_TYPE_WINDOW; 446} 447 448void ColorChooserView::WindowClosing() { 449 if (listener_) 450 listener_->OnColorChooserDialogClosed(); 451} 452 453View* ColorChooserView::GetContentsView() { 454 return this; 455} 456 457void ColorChooserView::ContentsChanged(Textfield* sender, 458 const base::string16& new_contents) { 459 SkColor color = SK_ColorBLACK; 460 if (GetColorFromText(new_contents, &color)) { 461 SkColorToHSV(color, hsv_); 462 if (listener_) 463 listener_->OnColorChosen(color); 464 hue_->OnHueChanged(hsv_[0]); 465 saturation_value_->OnHueChanged(hsv_[0]); 466 saturation_value_->OnSaturationValueChanged(hsv_[1], hsv_[2]); 467 selected_color_patch_->SetColor(color); 468 } 469} 470 471bool ColorChooserView::HandleKeyEvent(Textfield* sender, 472 const ui::KeyEvent& key_event) { 473 if (key_event.key_code() != ui::VKEY_RETURN && 474 key_event.key_code() != ui::VKEY_ESCAPE) 475 return false; 476 477 GetWidget()->Close(); 478 return true; 479} 480 481} // namespace views 482