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