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