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