Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / native_theme / native_theme_base.cc
blobb0ea0850b2bafa5cfe018f1d34b6a03eb41cc51d
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/native_theme/native_theme_base.h"
7 #include <limits>
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "third_party/skia/include/core/SkPaint.h"
13 #include "third_party/skia/include/core/SkPath.h"
14 #include "third_party/skia/include/effects/SkGradientShader.h"
15 #include "ui/base/layout.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/base/ui_base_switches.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_utils.h"
20 #include "ui/gfx/geometry/rect.h"
21 #include "ui/gfx/geometry/size.h"
22 #include "ui/gfx/image/image_skia.h"
23 #include "ui/gfx/skia_util.h"
24 #include "ui/native_theme/common_theme.h"
25 #include "ui/resources/grit/ui_resources.h"
27 namespace {
29 // These are the default dimensions of radio buttons and checkboxes.
30 const int kCheckboxAndRadioWidth = 13;
31 const int kCheckboxAndRadioHeight = 13;
33 // These sizes match the sizes in Chromium Win.
34 const int kSliderThumbWidth = 11;
35 const int kSliderThumbHeight = 21;
37 const SkColor kSliderTrackBackgroundColor =
38 SkColorSetRGB(0xe3, 0xdd, 0xd8);
39 const SkColor kSliderThumbLightGrey = SkColorSetRGB(0xf4, 0xf2, 0xef);
40 const SkColor kSliderThumbDarkGrey = SkColorSetRGB(0xea, 0xe5, 0xe0);
41 const SkColor kSliderThumbBorderDarkGrey =
42 SkColorSetRGB(0x9d, 0x96, 0x8e);
44 const SkColor kTextBorderColor = SkColorSetRGB(0xa9, 0xa9, 0xa9);
46 const SkColor kMenuPopupBackgroundColor = SkColorSetRGB(210, 225, 246);
48 const unsigned int kDefaultScrollbarWidth = 15;
49 const unsigned int kDefaultScrollbarButtonLength = 14;
51 const SkColor kCheckboxTinyColor = SK_ColorGRAY;
52 const SkColor kCheckboxShadowColor = SkColorSetARGB(0x15, 0, 0, 0);
53 const SkColor kCheckboxShadowHoveredColor = SkColorSetARGB(0x1F, 0, 0, 0);
54 const SkColor kCheckboxShadowDisabledColor = SkColorSetARGB(0, 0, 0, 0);
55 const SkColor kCheckboxGradientColors[] = {
56 SkColorSetRGB(0xed, 0xed, 0xed),
57 SkColorSetRGB(0xde, 0xde, 0xde) };
58 const SkColor kCheckboxGradientPressedColors[] = {
59 SkColorSetRGB(0xe7, 0xe7, 0xe7),
60 SkColorSetRGB(0xd7, 0xd7, 0xd7) };
61 const SkColor kCheckboxGradientHoveredColors[] = {
62 SkColorSetRGB(0xf0, 0xf0, 0xf0),
63 SkColorSetRGB(0xe0, 0xe0, 0xe0) };
64 const SkColor kCheckboxGradientDisabledColors[] = {
65 SkColorSetARGB(0x80, 0xed, 0xed, 0xed),
66 SkColorSetARGB(0x80, 0xde, 0xde, 0xde) };
67 const SkColor kCheckboxBorderColor = SkColorSetARGB(0x40, 0, 0, 0);
68 const SkColor kCheckboxBorderHoveredColor = SkColorSetARGB(0x4D, 0, 0, 0);
69 const SkColor kCheckboxBorderDisabledColor = SkColorSetARGB(0x20, 0, 0, 0);
70 const SkColor kCheckboxStrokeColor = SkColorSetARGB(0xB3, 0, 0, 0);
71 const SkColor kCheckboxStrokeDisabledColor = SkColorSetARGB(0x59, 0, 0, 0);
72 const SkColor kRadioDotColor = SkColorSetRGB(0x66, 0x66, 0x66);
73 const SkColor kRadioDotDisabledColor = SkColorSetARGB(0x80, 0x66, 0x66, 0x66);
75 // Get lightness adjusted color.
76 SkColor BrightenColor(const color_utils::HSL& hsl, SkAlpha alpha,
77 double lightness_amount) {
78 color_utils::HSL adjusted = hsl;
79 adjusted.l += lightness_amount;
80 if (adjusted.l > 1.0)
81 adjusted.l = 1.0;
82 if (adjusted.l < 0.0)
83 adjusted.l = 0.0;
85 return color_utils::HSLToSkColor(adjusted, alpha);
88 } // namespace
90 namespace ui {
92 gfx::Size NativeThemeBase::GetPartSize(Part part,
93 State state,
94 const ExtraParams& extra) const {
95 gfx::Size size = CommonThemeGetPartSize(part, state, extra);
96 if (!size.IsEmpty())
97 return size;
99 switch (part) {
100 // Please keep these in the order of NativeTheme::Part.
101 case kCheckbox:
102 return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight);
103 case kInnerSpinButton:
104 return gfx::Size(scrollbar_width_, 0);
105 case kMenuList:
106 return gfx::Size(); // No default size.
107 case kMenuCheck:
108 case kMenuCheckBackground:
109 case kMenuPopupArrow:
110 NOTIMPLEMENTED();
111 break;
112 case kMenuPopupBackground:
113 return gfx::Size(); // No default size.
114 case kMenuPopupGutter:
115 case kMenuPopupSeparator:
116 NOTIMPLEMENTED();
117 break;
118 case kMenuItemBackground:
119 case kProgressBar:
120 case kPushButton:
121 return gfx::Size(); // No default size.
122 case kRadio:
123 return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight);
124 case kScrollbarDownArrow:
125 case kScrollbarUpArrow:
126 return gfx::Size(scrollbar_width_, scrollbar_button_length_);
127 case kScrollbarLeftArrow:
128 case kScrollbarRightArrow:
129 return gfx::Size(scrollbar_button_length_, scrollbar_width_);
130 case kScrollbarHorizontalThumb:
131 // This matches Firefox on Linux.
132 return gfx::Size(2 * scrollbar_width_, scrollbar_width_);
133 case kScrollbarVerticalThumb:
134 // This matches Firefox on Linux.
135 return gfx::Size(scrollbar_width_, 2 * scrollbar_width_);
136 case kScrollbarHorizontalTrack:
137 return gfx::Size(0, scrollbar_width_);
138 case kScrollbarVerticalTrack:
139 return gfx::Size(scrollbar_width_, 0);
140 case kScrollbarHorizontalGripper:
141 case kScrollbarVerticalGripper:
142 NOTIMPLEMENTED();
143 break;
144 case kSliderTrack:
145 return gfx::Size(); // No default size.
146 case kSliderThumb:
147 // These sizes match the sizes in Chromium Win.
148 return gfx::Size(kSliderThumbWidth, kSliderThumbHeight);
149 case kTabPanelBackground:
150 NOTIMPLEMENTED();
151 break;
152 case kTextField:
153 return gfx::Size(); // No default size.
154 case kTrackbarThumb:
155 case kTrackbarTrack:
156 case kWindowResizeGripper:
157 NOTIMPLEMENTED();
158 break;
159 default:
160 NOTREACHED() << "Unknown theme part: " << part;
161 break;
163 return gfx::Size();
166 void NativeThemeBase::PaintStateTransition(SkCanvas* canvas,
167 Part part,
168 State startState,
169 State endState,
170 double progress,
171 const gfx::Rect& rect) const {
172 if (rect.IsEmpty())
173 return;
175 // Currently state transition is animation only working for overlay scrollbars
176 // on Aura platforms.
177 switch (part) {
178 case kScrollbarHorizontalThumb:
179 case kScrollbarVerticalThumb:
180 PaintScrollbarThumbStateTransition(
181 canvas, startState, endState, progress, rect);
182 break;
183 default:
184 NOTREACHED() << "Does not support state transition for this part:"
185 << part;
186 break;
188 return;
191 void NativeThemeBase::Paint(SkCanvas* canvas,
192 Part part,
193 State state,
194 const gfx::Rect& rect,
195 const ExtraParams& extra) const {
196 if (rect.IsEmpty())
197 return;
199 switch (part) {
200 // Please keep these in the order of NativeTheme::Part.
201 case kComboboxArrow:
202 CommonThemePaintComboboxArrow(canvas, rect);
203 break;
204 case kCheckbox:
205 PaintCheckbox(canvas, state, rect, extra.button);
206 break;
207 case kInnerSpinButton:
208 PaintInnerSpinButton(canvas, state, rect, extra.inner_spin);
209 break;
210 case kMenuList:
211 PaintMenuList(canvas, state, rect, extra.menu_list);
212 break;
213 case kMenuCheck:
214 case kMenuCheckBackground:
215 case kMenuPopupArrow:
216 NOTIMPLEMENTED();
217 break;
218 case kMenuPopupBackground:
219 PaintMenuPopupBackground(canvas, rect.size(), extra.menu_background);
220 break;
221 case kMenuPopupGutter:
222 case kMenuPopupSeparator:
223 NOTIMPLEMENTED();
224 break;
225 case kMenuItemBackground:
226 PaintMenuItemBackground(canvas, state, rect, extra.menu_list);
227 break;
228 case kProgressBar:
229 PaintProgressBar(canvas, state, rect, extra.progress_bar);
230 break;
231 case kPushButton:
232 PaintButton(canvas, state, rect, extra.button);
233 break;
234 case kRadio:
235 PaintRadio(canvas, state, rect, extra.button);
236 break;
237 case kScrollbarDownArrow:
238 case kScrollbarUpArrow:
239 case kScrollbarLeftArrow:
240 case kScrollbarRightArrow:
241 if (scrollbar_button_length_ > 0)
242 PaintArrowButton(canvas, rect, part, state);
243 break;
244 case kScrollbarHorizontalThumb:
245 case kScrollbarVerticalThumb:
246 PaintScrollbarThumb(canvas, part, state, rect);
247 break;
248 case kScrollbarHorizontalTrack:
249 case kScrollbarVerticalTrack:
250 PaintScrollbarTrack(canvas, part, state, extra.scrollbar_track, rect);
251 break;
252 case kScrollbarHorizontalGripper:
253 case kScrollbarVerticalGripper:
254 // Invoked by views scrollbar code, don't care about for non-win
255 // implementations, so no NOTIMPLEMENTED.
256 break;
257 case kScrollbarCorner:
258 PaintScrollbarCorner(canvas, state, rect);
259 break;
260 case kSliderTrack:
261 PaintSliderTrack(canvas, state, rect, extra.slider);
262 break;
263 case kSliderThumb:
264 PaintSliderThumb(canvas, state, rect, extra.slider);
265 break;
266 case kTabPanelBackground:
267 NOTIMPLEMENTED();
268 break;
269 case kTextField:
270 PaintTextField(canvas, state, rect, extra.text_field);
271 break;
272 case kTrackbarThumb:
273 case kTrackbarTrack:
274 case kWindowResizeGripper:
275 NOTIMPLEMENTED();
276 break;
277 default:
278 NOTREACHED() << "Unknown theme part: " << part;
279 break;
283 NativeThemeBase::NativeThemeBase()
284 : scrollbar_width_(kDefaultScrollbarWidth),
285 scrollbar_button_length_(kDefaultScrollbarButtonLength) {
288 NativeThemeBase::~NativeThemeBase() {
291 void NativeThemeBase::PaintArrowButton(
292 SkCanvas* canvas,
293 const gfx::Rect& rect, Part direction, State state) const {
294 SkPaint paint;
296 // Calculate button color.
297 SkScalar trackHSV[3];
298 SkColorToHSV(track_color_, trackHSV);
299 SkColor buttonColor = SaturateAndBrighten(trackHSV, 0, 0.2f);
300 SkColor backgroundColor = buttonColor;
301 if (state == kPressed) {
302 SkScalar buttonHSV[3];
303 SkColorToHSV(buttonColor, buttonHSV);
304 buttonColor = SaturateAndBrighten(buttonHSV, 0, -0.1f);
305 } else if (state == kHovered) {
306 SkScalar buttonHSV[3];
307 SkColorToHSV(buttonColor, buttonHSV);
308 buttonColor = SaturateAndBrighten(buttonHSV, 0, 0.05f);
311 SkIRect skrect;
312 skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), rect.y()
313 + rect.height());
314 // Paint the background (the area visible behind the rounded corners).
315 paint.setColor(backgroundColor);
316 canvas->drawIRect(skrect, paint);
318 // Paint the button's outline and fill the middle
319 SkPath outline;
320 switch (direction) {
321 case kScrollbarUpArrow:
322 outline.moveTo(rect.x() + 0.5, rect.y() + rect.height() + 0.5);
323 outline.rLineTo(0, -(rect.height() - 2));
324 outline.rLineTo(2, -2);
325 outline.rLineTo(rect.width() - 5, 0);
326 outline.rLineTo(2, 2);
327 outline.rLineTo(0, rect.height() - 2);
328 break;
329 case kScrollbarDownArrow:
330 outline.moveTo(rect.x() + 0.5, rect.y() - 0.5);
331 outline.rLineTo(0, rect.height() - 2);
332 outline.rLineTo(2, 2);
333 outline.rLineTo(rect.width() - 5, 0);
334 outline.rLineTo(2, -2);
335 outline.rLineTo(0, -(rect.height() - 2));
336 break;
337 case kScrollbarRightArrow:
338 outline.moveTo(rect.x() - 0.5, rect.y() + 0.5);
339 outline.rLineTo(rect.width() - 2, 0);
340 outline.rLineTo(2, 2);
341 outline.rLineTo(0, rect.height() - 5);
342 outline.rLineTo(-2, 2);
343 outline.rLineTo(-(rect.width() - 2), 0);
344 break;
345 case kScrollbarLeftArrow:
346 outline.moveTo(rect.x() + rect.width() + 0.5, rect.y() + 0.5);
347 outline.rLineTo(-(rect.width() - 2), 0);
348 outline.rLineTo(-2, 2);
349 outline.rLineTo(0, rect.height() - 5);
350 outline.rLineTo(2, 2);
351 outline.rLineTo(rect.width() - 2, 0);
352 break;
353 default:
354 break;
356 outline.close();
358 paint.setStyle(SkPaint::kFill_Style);
359 paint.setColor(buttonColor);
360 canvas->drawPath(outline, paint);
362 paint.setAntiAlias(true);
363 paint.setStyle(SkPaint::kStroke_Style);
364 SkScalar thumbHSV[3];
365 SkColorToHSV(thumb_inactive_color_, thumbHSV);
366 paint.setColor(OutlineColor(trackHSV, thumbHSV));
367 canvas->drawPath(outline, paint);
369 PaintArrow(canvas, rect, direction, GetArrowColor(state));
372 void NativeThemeBase::PaintArrow(SkCanvas* gc,
373 const gfx::Rect& rect,
374 Part direction,
375 SkColor color) const {
376 int width_middle, length_middle;
377 if (direction == kScrollbarUpArrow || direction == kScrollbarDownArrow) {
378 width_middle = rect.width() / 2 + 1;
379 length_middle = rect.height() / 2 + 1;
380 } else {
381 length_middle = rect.width() / 2 + 1;
382 width_middle = rect.height() / 2 + 1;
385 SkPaint paint;
386 paint.setColor(color);
387 paint.setAntiAlias(false);
388 paint.setStyle(SkPaint::kFill_Style);
390 SkPath path;
391 // The constants in this block of code are hand-tailored to produce good
392 // looking arrows without anti-aliasing.
393 switch (direction) {
394 case kScrollbarUpArrow:
395 path.moveTo(rect.x() + width_middle - 4, rect.y() + length_middle + 2);
396 path.rLineTo(7, 0);
397 path.rLineTo(-4, -4);
398 break;
399 case kScrollbarDownArrow:
400 path.moveTo(rect.x() + width_middle - 4, rect.y() + length_middle - 3);
401 path.rLineTo(7, 0);
402 path.rLineTo(-4, 4);
403 break;
404 case kScrollbarRightArrow:
405 path.moveTo(rect.x() + length_middle - 3, rect.y() + width_middle - 4);
406 path.rLineTo(0, 7);
407 path.rLineTo(4, -4);
408 break;
409 case kScrollbarLeftArrow:
410 path.moveTo(rect.x() + length_middle + 1, rect.y() + width_middle - 5);
411 path.rLineTo(0, 9);
412 path.rLineTo(-4, -4);
413 break;
414 default:
415 break;
417 path.close();
419 gc->drawPath(path, paint);
422 void NativeThemeBase::PaintScrollbarTrack(SkCanvas* canvas,
423 Part part,
424 State state,
425 const ScrollbarTrackExtraParams& extra_params,
426 const gfx::Rect& rect) const {
427 SkPaint paint;
428 SkIRect skrect;
430 skrect.set(rect.x(), rect.y(), rect.right(), rect.bottom());
431 SkScalar track_hsv[3];
432 SkColorToHSV(track_color_, track_hsv);
433 paint.setColor(SaturateAndBrighten(track_hsv, 0, 0));
434 canvas->drawIRect(skrect, paint);
436 SkScalar thumb_hsv[3];
437 SkColorToHSV(thumb_inactive_color_, thumb_hsv);
439 paint.setColor(OutlineColor(track_hsv, thumb_hsv));
440 DrawBox(canvas, rect, paint);
443 void NativeThemeBase::PaintScrollbarThumb(SkCanvas* canvas,
444 Part part,
445 State state,
446 const gfx::Rect& rect) const {
447 const bool hovered = state == kHovered;
448 const int midx = rect.x() + rect.width() / 2;
449 const int midy = rect.y() + rect.height() / 2;
450 const bool vertical = part == kScrollbarVerticalThumb;
452 SkScalar thumb[3];
453 SkColorToHSV(hovered ? thumb_active_color_ : thumb_inactive_color_, thumb);
455 SkPaint paint;
456 paint.setColor(SaturateAndBrighten(thumb, 0, 0.02f));
458 SkIRect skrect;
459 if (vertical)
460 skrect.set(rect.x(), rect.y(), midx + 1, rect.y() + rect.height());
461 else
462 skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), midy + 1);
464 canvas->drawIRect(skrect, paint);
466 paint.setColor(SaturateAndBrighten(thumb, 0, -0.02f));
468 if (vertical) {
469 skrect.set(
470 midx + 1, rect.y(), rect.x() + rect.width(), rect.y() + rect.height());
471 } else {
472 skrect.set(
473 rect.x(), midy + 1, rect.x() + rect.width(), rect.y() + rect.height());
476 canvas->drawIRect(skrect, paint);
478 SkScalar track[3];
479 SkColorToHSV(track_color_, track);
480 paint.setColor(OutlineColor(track, thumb));
481 DrawBox(canvas, rect, paint);
483 if (rect.height() > 10 && rect.width() > 10) {
484 const int grippy_half_width = 2;
485 const int inter_grippy_offset = 3;
486 if (vertical) {
487 DrawHorizLine(canvas,
488 midx - grippy_half_width,
489 midx + grippy_half_width,
490 midy - inter_grippy_offset,
491 paint);
492 DrawHorizLine(canvas,
493 midx - grippy_half_width,
494 midx + grippy_half_width,
495 midy,
496 paint);
497 DrawHorizLine(canvas,
498 midx - grippy_half_width,
499 midx + grippy_half_width,
500 midy + inter_grippy_offset,
501 paint);
502 } else {
503 DrawVertLine(canvas,
504 midx - inter_grippy_offset,
505 midy - grippy_half_width,
506 midy + grippy_half_width,
507 paint);
508 DrawVertLine(canvas,
509 midx,
510 midy - grippy_half_width,
511 midy + grippy_half_width,
512 paint);
513 DrawVertLine(canvas,
514 midx + inter_grippy_offset,
515 midy - grippy_half_width,
516 midy + grippy_half_width,
517 paint);
522 void NativeThemeBase::PaintScrollbarCorner(SkCanvas* canvas,
523 State state,
524 const gfx::Rect& rect) const {
527 void NativeThemeBase::PaintCheckbox(SkCanvas* canvas,
528 State state,
529 const gfx::Rect& rect,
530 const ButtonExtraParams& button) const {
531 SkRect skrect = PaintCheckboxRadioCommon(canvas, state, rect,
532 SkIntToScalar(2));
533 if (!skrect.isEmpty()) {
534 // Draw the checkmark / dash.
535 SkPaint paint;
536 paint.setAntiAlias(true);
537 paint.setStyle(SkPaint::kStroke_Style);
538 if (state == kDisabled)
539 paint.setColor(kCheckboxStrokeDisabledColor);
540 else
541 paint.setColor(kCheckboxStrokeColor);
542 if (button.indeterminate) {
543 SkPath dash;
544 dash.moveTo(skrect.x() + skrect.width() * 0.16,
545 (skrect.y() + skrect.bottom()) / 2);
546 dash.rLineTo(skrect.width() * 0.68, 0);
547 paint.setStrokeWidth(SkFloatToScalar(skrect.height() * 0.2));
548 canvas->drawPath(dash, paint);
549 } else if (button.checked) {
550 SkPath check;
551 check.moveTo(skrect.x() + skrect.width() * 0.2,
552 skrect.y() + skrect.height() * 0.5);
553 check.rLineTo(skrect.width() * 0.2, skrect.height() * 0.2);
554 paint.setStrokeWidth(SkFloatToScalar(skrect.height() * 0.23));
555 check.lineTo(skrect.right() - skrect.width() * 0.2,
556 skrect.y() + skrect.height() * 0.2);
557 canvas->drawPath(check, paint);
562 // Draws the common elements of checkboxes and radio buttons.
563 // Returns the rectangle within which any additional decorations should be
564 // drawn, or empty if none.
565 SkRect NativeThemeBase::PaintCheckboxRadioCommon(
566 SkCanvas* canvas,
567 State state,
568 const gfx::Rect& rect,
569 const SkScalar borderRadius) const {
571 SkRect skrect = gfx::RectToSkRect(rect);
573 // Use the largest square that fits inside the provided rectangle.
574 // No other browser seems to support non-square widget, so accidentally
575 // having non-square sizes is common (eg. amazon and webkit dev tools).
576 if (skrect.width() != skrect.height()) {
577 SkScalar size = SkMinScalar(skrect.width(), skrect.height());
578 skrect.inset((skrect.width() - size) / 2, (skrect.height() - size) / 2);
581 // If the rectangle is too small then paint only a rectangle. We don't want
582 // to have to worry about '- 1' and '+ 1' calculations below having overflow
583 // or underflow.
584 if (skrect.width() <= 2) {
585 SkPaint paint;
586 paint.setColor(kCheckboxTinyColor);
587 paint.setStyle(SkPaint::kFill_Style);
588 canvas->drawRect(skrect, paint);
589 // Too small to draw anything more.
590 return SkRect::MakeEmpty();
593 // Make room for padding/drop shadow.
594 AdjustCheckboxRadioRectForPadding(&skrect);
596 // Draw the drop shadow below the widget.
597 if (state != kPressed) {
598 SkPaint paint;
599 paint.setAntiAlias(true);
600 SkRect shadowRect = skrect;
601 shadowRect.offset(0, 1);
602 if (state == kDisabled)
603 paint.setColor(kCheckboxShadowDisabledColor);
604 else if (state == kHovered)
605 paint.setColor(kCheckboxShadowHoveredColor);
606 else
607 paint.setColor(kCheckboxShadowColor);
608 paint.setStyle(SkPaint::kFill_Style);
609 canvas->drawRoundRect(shadowRect, borderRadius, borderRadius, paint);
612 // Draw the gradient-filled rectangle
613 SkPoint gradient_bounds[3];
614 gradient_bounds[0].set(skrect.x(), skrect.y());
615 gradient_bounds[1].set(skrect.x(), skrect.y() + skrect.height() * 0.38);
616 gradient_bounds[2].set(skrect.x(), skrect.bottom());
617 const SkColor* startEndColors;
618 if (state == kPressed)
619 startEndColors = kCheckboxGradientPressedColors;
620 else if (state == kHovered)
621 startEndColors = kCheckboxGradientHoveredColors;
622 else if (state == kDisabled)
623 startEndColors = kCheckboxGradientDisabledColors;
624 else /* kNormal */
625 startEndColors = kCheckboxGradientColors;
626 SkColor colors[3] = {startEndColors[0], startEndColors[0], startEndColors[1]};
627 skia::RefPtr<SkShader> shader = skia::AdoptRef(
628 SkGradientShader::CreateLinear(
629 gradient_bounds, colors, NULL, 3, SkShader::kClamp_TileMode));
630 SkPaint paint;
631 paint.setAntiAlias(true);
632 paint.setShader(shader.get());
633 paint.setStyle(SkPaint::kFill_Style);
634 canvas->drawRoundRect(skrect, borderRadius, borderRadius, paint);
635 paint.setShader(NULL);
637 // Draw the border.
638 if (state == kHovered)
639 paint.setColor(kCheckboxBorderHoveredColor);
640 else if (state == kDisabled)
641 paint.setColor(kCheckboxBorderDisabledColor);
642 else
643 paint.setColor(kCheckboxBorderColor);
644 paint.setStyle(SkPaint::kStroke_Style);
645 paint.setStrokeWidth(SkIntToScalar(1));
646 skrect.inset(SkFloatToScalar(.5f), SkFloatToScalar(.5f));
647 canvas->drawRoundRect(skrect, borderRadius, borderRadius, paint);
649 // Return the rectangle excluding the drop shadow for drawing any additional
650 // decorations.
651 return skrect;
654 void NativeThemeBase::PaintRadio(SkCanvas* canvas,
655 State state,
656 const gfx::Rect& rect,
657 const ButtonExtraParams& button) const {
659 // Most of a radio button is the same as a checkbox, except the the rounded
660 // square is a circle (i.e. border radius >= 100%).
661 const SkScalar radius = SkFloatToScalar(
662 static_cast<float>(std::max(rect.width(), rect.height())) / 2);
663 SkRect skrect = PaintCheckboxRadioCommon(canvas, state, rect, radius);
664 if (!skrect.isEmpty() && button.checked) {
665 // Draw the dot.
666 SkPaint paint;
667 paint.setAntiAlias(true);
668 paint.setStyle(SkPaint::kFill_Style);
669 if (state == kDisabled)
670 paint.setColor(kRadioDotDisabledColor);
671 else
672 paint.setColor(kRadioDotColor);
673 skrect.inset(skrect.width() * 0.25, skrect.height() * 0.25);
674 // Use drawRoundedRect instead of drawOval to be completely consistent
675 // with the border in PaintCheckboxRadioNewCommon.
676 canvas->drawRoundRect(skrect, radius, radius, paint);
680 void NativeThemeBase::PaintButton(SkCanvas* canvas,
681 State state,
682 const gfx::Rect& rect,
683 const ButtonExtraParams& button) const {
684 SkPaint paint;
685 const int kRight = rect.right();
686 const int kBottom = rect.bottom();
687 SkRect skrect = SkRect::MakeLTRB(rect.x(), rect.y(), kRight, kBottom);
688 SkColor base_color = button.background_color;
690 color_utils::HSL base_hsl;
691 color_utils::SkColorToHSL(base_color, &base_hsl);
693 // Our standard gradient is from 0xdd to 0xf8. This is the amount of
694 // increased luminance between those values.
695 SkColor light_color(BrightenColor(base_hsl, SkColorGetA(base_color), 0.105));
697 // If the button is too small, fallback to drawing a single, solid color
698 if (rect.width() < 5 || rect.height() < 5) {
699 paint.setColor(base_color);
700 canvas->drawRect(skrect, paint);
701 return;
704 paint.setColor(SK_ColorBLACK);
705 const int kLightEnd = state == kPressed ? 1 : 0;
706 const int kDarkEnd = !kLightEnd;
707 SkPoint gradient_bounds[2];
708 gradient_bounds[kLightEnd].iset(rect.x(), rect.y());
709 gradient_bounds[kDarkEnd].iset(rect.x(), kBottom - 1);
710 SkColor colors[2];
711 colors[0] = light_color;
712 colors[1] = base_color;
714 skia::RefPtr<SkShader> shader = skia::AdoptRef(
715 SkGradientShader::CreateLinear(
716 gradient_bounds, colors, NULL, 2, SkShader::kClamp_TileMode));
717 paint.setStyle(SkPaint::kFill_Style);
718 paint.setAntiAlias(true);
719 paint.setShader(shader.get());
721 canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint);
722 paint.setShader(NULL);
724 if (button.has_border) {
725 int border_alpha = state == kHovered ? 0x80 : 0x55;
726 if (button.is_focused) {
727 border_alpha = 0xff;
728 paint.setColor(GetSystemColor(kColorId_FocusedBorderColor));
730 paint.setStyle(SkPaint::kStroke_Style);
731 paint.setStrokeWidth(SkIntToScalar(1));
732 paint.setAlpha(border_alpha);
733 skrect.inset(SkFloatToScalar(.5f), SkFloatToScalar(.5f));
734 canvas->drawRoundRect(skrect, SkIntToScalar(1), SkIntToScalar(1), paint);
738 void NativeThemeBase::PaintTextField(SkCanvas* canvas,
739 State state,
740 const gfx::Rect& rect,
741 const TextFieldExtraParams& text) const {
742 SkRect bounds;
743 bounds.set(rect.x(), rect.y(), rect.right() - 1, rect.bottom() - 1);
745 SkPaint fill_paint;
746 fill_paint.setStyle(SkPaint::kFill_Style);
747 fill_paint.setColor(text.background_color);
748 canvas->drawRect(bounds, fill_paint);
750 // Text INPUT, listbox SELECT, and TEXTAREA have consistent borders.
751 // border: 1px solid #a9a9a9
752 SkPaint stroke_paint;
753 stroke_paint.setStyle(SkPaint::kStroke_Style);
754 stroke_paint.setColor(kTextBorderColor);
755 canvas->drawRect(bounds, stroke_paint);
758 void NativeThemeBase::PaintMenuList(
759 SkCanvas* canvas,
760 State state,
761 const gfx::Rect& rect,
762 const MenuListExtraParams& menu_list) const {
763 // If a border radius is specified, we let the WebCore paint the background
764 // and the border of the control.
765 if (!menu_list.has_border_radius) {
766 ButtonExtraParams button = { 0 };
767 button.background_color = menu_list.background_color;
768 button.has_border = menu_list.has_border;
769 PaintButton(canvas, state, rect, button);
772 SkPaint paint;
773 paint.setColor(SK_ColorBLACK);
774 paint.setAntiAlias(true);
775 paint.setStyle(SkPaint::kFill_Style);
777 static const int kArrowWidth = 6;
778 static const int kArrowHeight = 6;
780 gfx::Rect arrow(
781 menu_list.arrow_x,
782 menu_list.arrow_y - (kArrowHeight / 2),
783 kArrowWidth,
784 kArrowHeight);
786 // Constrain to the paint rect.
787 arrow.Intersect(rect);
789 SkPath path;
790 path.moveTo(arrow.x(), arrow.y());
791 path.lineTo(arrow.right(), arrow.y());
792 path.lineTo(arrow.x() + arrow.width() / 2, arrow.bottom());
793 path.close();
794 canvas->drawPath(path, paint);
797 void NativeThemeBase::PaintMenuPopupBackground(
798 SkCanvas* canvas,
799 const gfx::Size& size,
800 const MenuBackgroundExtraParams& menu_background) const {
801 canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode);
804 void NativeThemeBase::PaintMenuItemBackground(
805 SkCanvas* canvas,
806 State state,
807 const gfx::Rect& rect,
808 const MenuListExtraParams& menu_list) const {
809 // By default don't draw anything over the normal background.
812 void NativeThemeBase::PaintSliderTrack(SkCanvas* canvas,
813 State state,
814 const gfx::Rect& rect,
815 const SliderExtraParams& slider) const {
816 const int kMidX = rect.x() + rect.width() / 2;
817 const int kMidY = rect.y() + rect.height() / 2;
819 SkPaint paint;
820 paint.setColor(kSliderTrackBackgroundColor);
822 SkRect skrect;
823 if (slider.vertical) {
824 skrect.set(std::max(rect.x(), kMidX - 2),
825 rect.y(),
826 std::min(rect.right(), kMidX + 2),
827 rect.bottom());
828 } else {
829 skrect.set(rect.x(),
830 std::max(rect.y(), kMidY - 2),
831 rect.right(),
832 std::min(rect.bottom(), kMidY + 2));
834 canvas->drawRect(skrect, paint);
837 void NativeThemeBase::PaintSliderThumb(SkCanvas* canvas,
838 State state,
839 const gfx::Rect& rect,
840 const SliderExtraParams& slider) const {
841 const bool hovered = (state == kHovered) || slider.in_drag;
842 const int kMidX = rect.x() + rect.width() / 2;
843 const int kMidY = rect.y() + rect.height() / 2;
845 SkPaint paint;
846 paint.setColor(hovered ? SK_ColorWHITE : kSliderThumbLightGrey);
848 SkIRect skrect;
849 if (slider.vertical)
850 skrect.set(rect.x(), rect.y(), kMidX + 1, rect.bottom());
851 else
852 skrect.set(rect.x(), rect.y(), rect.right(), kMidY + 1);
854 canvas->drawIRect(skrect, paint);
856 paint.setColor(hovered ? kSliderThumbLightGrey : kSliderThumbDarkGrey);
858 if (slider.vertical)
859 skrect.set(kMidX + 1, rect.y(), rect.right(), rect.bottom());
860 else
861 skrect.set(rect.x(), kMidY + 1, rect.right(), rect.bottom());
863 canvas->drawIRect(skrect, paint);
865 paint.setColor(kSliderThumbBorderDarkGrey);
866 DrawBox(canvas, rect, paint);
868 if (rect.height() > 10 && rect.width() > 10) {
869 DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY, paint);
870 DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY - 3, paint);
871 DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY + 3, paint);
875 void NativeThemeBase::PaintInnerSpinButton(SkCanvas* canvas,
876 State state,
877 const gfx::Rect& rect,
878 const InnerSpinButtonExtraParams& spin_button) const {
879 if (spin_button.read_only)
880 state = kDisabled;
882 State north_state = state;
883 State south_state = state;
884 if (spin_button.spin_up)
885 south_state = south_state != kDisabled ? kNormal : kDisabled;
886 else
887 north_state = north_state != kDisabled ? kNormal : kDisabled;
889 gfx::Rect half = rect;
890 half.set_height(rect.height() / 2);
891 PaintArrowButton(canvas, half, kScrollbarUpArrow, north_state);
893 half.set_y(rect.y() + rect.height() / 2);
894 PaintArrowButton(canvas, half, kScrollbarDownArrow, south_state);
897 void NativeThemeBase::PaintProgressBar(SkCanvas* canvas,
898 State state,
899 const gfx::Rect& rect,
900 const ProgressBarExtraParams& progress_bar) const {
901 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
902 gfx::ImageSkia* bar_image = rb.GetImageSkiaNamed(IDR_PROGRESS_BAR);
903 gfx::ImageSkia* left_border_image = rb.GetImageSkiaNamed(
904 IDR_PROGRESS_BORDER_LEFT);
905 gfx::ImageSkia* right_border_image = rb.GetImageSkiaNamed(
906 IDR_PROGRESS_BORDER_RIGHT);
908 DCHECK(bar_image->width() > 0);
909 DCHECK(rect.width() > 0);
911 float tile_scale_y = static_cast<float>(rect.height()) / bar_image->height();
913 int dest_left_border_width = left_border_image->width();
914 int dest_right_border_width = right_border_image->width();
916 // Since an implicit float -> int conversion will truncate, we want to make
917 // sure that if a border is desired, it gets at least one pixel.
918 if (dest_left_border_width > 0) {
919 dest_left_border_width = dest_left_border_width * tile_scale_y;
920 dest_left_border_width = std::max(dest_left_border_width, 1);
922 if (dest_right_border_width > 0) {
923 dest_right_border_width = dest_right_border_width * tile_scale_y;
924 dest_right_border_width = std::max(dest_right_border_width, 1);
927 // Since the width of the progress bar may not be evenly divisible by the
928 // tile size, in order to make it look right we may need to draw some of the
929 // with a width of 1 pixel smaller than the rest of the tiles.
930 int new_tile_width = static_cast<int>(bar_image->width() * tile_scale_y);
931 new_tile_width = std::max(new_tile_width, 1);
933 float tile_scale_x = static_cast<float>(new_tile_width) / bar_image->width();
934 if (rect.width() % new_tile_width == 0) {
935 DrawTiledImage(canvas, *bar_image, 0, 0, tile_scale_x, tile_scale_y,
936 rect.x(), rect.y(),
937 rect.width(), rect.height());
938 } else {
939 int num_tiles = 1 + rect.width() / new_tile_width;
940 int overshoot = num_tiles * new_tile_width - rect.width();
941 // Since |overshoot| represents the number of tiles that were too big, draw
942 // |overshoot| tiles with their width reduced by 1.
943 int num_big_tiles = num_tiles - overshoot;
944 int num_small_tiles = overshoot;
945 int small_width = new_tile_width - 1;
946 float small_scale_x = static_cast<float>(small_width) / bar_image->width();
947 float big_scale_x = tile_scale_x;
949 gfx::Rect big_rect = rect;
950 gfx::Rect small_rect = rect;
951 big_rect.Inset(0, 0, num_small_tiles*small_width, 0);
952 small_rect.Inset(num_big_tiles*new_tile_width, 0, 0, 0);
954 DrawTiledImage(canvas, *bar_image, 0, 0, big_scale_x, tile_scale_y,
955 big_rect.x(), big_rect.y(), big_rect.width(), big_rect.height());
956 DrawTiledImage(canvas, *bar_image, 0, 0, small_scale_x, tile_scale_y,
957 small_rect.x(), small_rect.y(), small_rect.width(), small_rect.height());
959 if (progress_bar.value_rect_width) {
960 gfx::ImageSkia* value_image = rb.GetImageSkiaNamed(IDR_PROGRESS_VALUE);
962 new_tile_width = static_cast<int>(value_image->width() * tile_scale_y);
963 tile_scale_x = static_cast<float>(new_tile_width) /
964 value_image->width();
966 DrawTiledImage(canvas, *value_image, 0, 0, tile_scale_x, tile_scale_y,
967 progress_bar.value_rect_x,
968 progress_bar.value_rect_y,
969 progress_bar.value_rect_width,
970 progress_bar.value_rect_height);
973 DrawImageInt(canvas, *left_border_image, 0, 0, left_border_image->width(),
974 left_border_image->height(), rect.x(), rect.y(), dest_left_border_width,
975 rect.height());
977 int dest_x = rect.right() - dest_right_border_width;
978 DrawImageInt(canvas, *right_border_image, 0, 0, right_border_image->width(),
979 right_border_image->height(), dest_x, rect.y(),
980 dest_right_border_width, rect.height());
983 void NativeThemeBase::AdjustCheckboxRadioRectForPadding(SkRect* rect) const {
984 // By default we only take 1px from right and bottom for the drop shadow.
985 rect->iset(rect->x(), rect->y(), rect->right() - 1, rect->bottom() - 1);
988 bool NativeThemeBase::IntersectsClipRectInt(SkCanvas* canvas,
989 int x, int y, int w, int h) const {
990 SkRect clip;
991 return canvas->getClipBounds(&clip) &&
992 clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w),
993 SkIntToScalar(y + h));
996 void NativeThemeBase::DrawImageInt(
997 SkCanvas* sk_canvas, const gfx::ImageSkia& image,
998 int src_x, int src_y, int src_w, int src_h,
999 int dest_x, int dest_y, int dest_w, int dest_h) const {
1000 scoped_ptr<gfx::Canvas> canvas(CommonThemeCreateCanvas(sk_canvas));
1001 canvas->DrawImageInt(image, src_x, src_y, src_w, src_h,
1002 dest_x, dest_y, dest_w, dest_h, true);
1005 void NativeThemeBase::DrawTiledImage(SkCanvas* sk_canvas,
1006 const gfx::ImageSkia& image,
1007 int src_x, int src_y, float tile_scale_x, float tile_scale_y,
1008 int dest_x, int dest_y, int w, int h) const {
1009 scoped_ptr<gfx::Canvas> canvas(CommonThemeCreateCanvas(sk_canvas));
1010 canvas->TileImageInt(image, src_x, src_y, tile_scale_x,
1011 tile_scale_y, dest_x, dest_y, w, h);
1014 SkColor NativeThemeBase::SaturateAndBrighten(SkScalar* hsv,
1015 SkScalar saturate_amount,
1016 SkScalar brighten_amount) const {
1017 SkScalar color[3];
1018 color[0] = hsv[0];
1019 color[1] = Clamp(hsv[1] + saturate_amount, 0.0, 1.0);
1020 color[2] = Clamp(hsv[2] + brighten_amount, 0.0, 1.0);
1021 return SkHSVToColor(color);
1024 SkColor NativeThemeBase::GetArrowColor(State state) const {
1025 if (state != kDisabled)
1026 return SK_ColorBLACK;
1028 SkScalar track_hsv[3];
1029 SkColorToHSV(track_color_, track_hsv);
1030 SkScalar thumb_hsv[3];
1031 SkColorToHSV(thumb_inactive_color_, thumb_hsv);
1032 return OutlineColor(track_hsv, thumb_hsv);
1035 void NativeThemeBase::DrawVertLine(SkCanvas* canvas,
1036 int x,
1037 int y1,
1038 int y2,
1039 const SkPaint& paint) const {
1040 SkIRect skrect;
1041 skrect.set(x, y1, x + 1, y2 + 1);
1042 canvas->drawIRect(skrect, paint);
1045 void NativeThemeBase::DrawHorizLine(SkCanvas* canvas,
1046 int x1,
1047 int x2,
1048 int y,
1049 const SkPaint& paint) const {
1050 SkIRect skrect;
1051 skrect.set(x1, y, x2 + 1, y + 1);
1052 canvas->drawIRect(skrect, paint);
1055 void NativeThemeBase::DrawBox(SkCanvas* canvas,
1056 const gfx::Rect& rect,
1057 const SkPaint& paint) const {
1058 const int right = rect.x() + rect.width() - 1;
1059 const int bottom = rect.y() + rect.height() - 1;
1060 DrawHorizLine(canvas, rect.x(), right, rect.y(), paint);
1061 DrawVertLine(canvas, right, rect.y(), bottom, paint);
1062 DrawHorizLine(canvas, rect.x(), right, bottom, paint);
1063 DrawVertLine(canvas, rect.x(), rect.y(), bottom, paint);
1066 SkScalar NativeThemeBase::Clamp(SkScalar value,
1067 SkScalar min,
1068 SkScalar max) const {
1069 return std::min(std::max(value, min), max);
1072 SkColor NativeThemeBase::OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const {
1073 // GTK Theme engines have way too much control over the layout of
1074 // the scrollbar. We might be able to more closely approximate its
1075 // look-and-feel, if we sent whole images instead of just colors
1076 // from the browser to the renderer. But even then, some themes
1077 // would just break.
1079 // So, instead, we don't even try to 100% replicate the look of
1080 // the native scrollbar. We render our own version, but we make
1081 // sure to pick colors that blend in nicely with the system GTK
1082 // theme. In most cases, we can just sample a couple of pixels
1083 // from the system scrollbar and use those colors to draw our
1084 // scrollbar.
1086 // This works fine for the track color and the overall thumb
1087 // color. But it fails spectacularly for the outline color used
1088 // around the thumb piece. Not all themes have a clearly defined
1089 // outline. For some of them it is partially transparent, and for
1090 // others the thickness is very unpredictable.
1092 // So, instead of trying to approximate the system theme, we
1093 // instead try to compute a reasonable looking choice based on the
1094 // known color of the track and the thumb piece. This is difficult
1095 // when trying to deal both with high- and low-contrast themes,
1096 // and both with positive and inverted themes.
1098 // The following code has been tested to look OK with all of the
1099 // default GTK themes.
1100 SkScalar min_diff = Clamp((hsv1[1] + hsv2[1]) * 1.2f, 0.28f, 0.5f);
1101 SkScalar diff = Clamp(fabs(hsv1[2] - hsv2[2]) / 2, min_diff, 0.5f);
1103 if (hsv1[2] + hsv2[2] > 1.0)
1104 diff = -diff;
1106 return SaturateAndBrighten(hsv2, -0.2f, diff);
1109 } // namespace ui