Revert of Merge duplicate code in invalidation helper. (patchset #2 id:40001 of https...
[chromium-blink-merge.git] / ui / native_theme / native_theme_mac.mm
blobb879f9d91c38d790382d389d6f03cb3929217fef
1 // Copyright (c) 2013 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_mac.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/basictypes.h"
10 #include "base/mac/mac_util.h"
11 #include "base/mac/scoped_cftyperef.h"
12 #include "base/mac/sdk_forward_declarations.h"
13 #import "skia/ext/skia_utils_mac.h"
14 #include "third_party/skia/include/effects/SkGradientShader.h"
15 #include "ui/gfx/geometry/rect.h"
16 #include "ui/gfx/skia_util.h"
17 #include "ui/native_theme/common_theme.h"
19 namespace {
21 const SkColor kScrollerTrackGradientColors[] = {
22     SkColorSetRGB(0xEF, 0xEF, 0xEF),
23     SkColorSetRGB(0xF9, 0xF9, 0xF9),
24     SkColorSetRGB(0xFD, 0xFD, 0xFD),
25     SkColorSetRGB(0xF6, 0xF6, 0xF6) };
26 const SkColor kScrollerTrackInnerBorderColor = SkColorSetRGB(0xE4, 0xE4, 0xE4);
27 const SkColor kScrollerTrackOuterBorderColor = SkColorSetRGB(0xEF, 0xEF, 0xEF);
28 const SkColor kScrollerThumbColor = SkColorSetARGB(0x38, 0, 0, 0);
29 const SkColor kScrollerThumbHoverColor = SkColorSetARGB(0x80, 0, 0, 0);
30 const int kScrollerTrackBorderWidth = 1;
32 // The amount the thumb is inset from both the ends and the sides of the track.
33 const int kScrollerThumbInset = 3;
35 // Values calculated by reading pixels and solving simultaneous equations
36 // derived from "A over B" alpha compositing. Steps: Sample the semi-transparent
37 // pixel over two backgrounds; P1, P2 over backgrounds B1, B2. Use the color
38 // value between 0.0 and 1.0 (i.e. divide by 255.0). Then,
39 // alpha = (P2 - P1 + B1 - B2) / (B1 - B2)
40 // color = (P1 - B1 + alpha * B1) / alpha.
41 const SkColor kMenuPopupBackgroundColor = SkColorSetARGB(255, 255, 255, 255);
42 const SkColor kMenuSeparatorColor = SkColorSetARGB(243, 228, 228, 228);
43 const SkColor kMenuBorderColor = SkColorSetARGB(60, 0, 0, 0);
45 const SkColor kMenuPopupBackgroundColorYosemite =
46     SkColorSetARGB(255, 230, 230, 230);
48 // Hardcoded color used for some existing dialogs in Chrome's Cocoa UI.
49 const SkColor kDialogBackgroundColor = SkColorSetRGB(251, 251, 251);
51 // On 10.6 and 10.7 there is no way to get components from system colors. Here,
52 // system colors are just opaque objects that can paint themselves and otherwise
53 // tell you nothing. In 10.8, some of the system color classes have incomplete
54 // implementations and throw exceptions even attempting to convert using
55 // -[NSColor colorUsingColorSpace:], so don't bother there either.
56 // This function paints a single pixel to a 1x1 swatch and reads it back.
57 SkColor GetSystemColorUsingSwatch(NSColor* color) {
58   SkColor swatch;
59   base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
60       CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
61   const size_t bytes_per_row = 4;
62   static_assert(sizeof(swatch) == bytes_per_row, "skcolor should be 4 bytes");
63   CGBitmapInfo bitmap_info =
64       kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
65   base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
66       &swatch, 1, 1, 8, bytes_per_row, color_space, bitmap_info));
68   NSGraphicsContext* drawing_context =
69       [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO];
70   [NSGraphicsContext saveGraphicsState];
71   [NSGraphicsContext setCurrentContext:drawing_context];
72   [color drawSwatchInRect:NSMakeRect(0, 0, 1, 1)];
73   [NSGraphicsContext restoreGraphicsState];
74   return swatch;
77 // NSColor has a number of methods that return system colors (i.e. controlled by
78 // user preferences). This function converts the color given by an NSColor class
79 // method to an SkColor. Official documentation suggests developers only rely on
80 // +[NSColor selectedTextBackgroundColor] and +[NSColor selectedControlColor],
81 // but other colors give a good baseline. For many, a gradient is involved; the
82 // palette chosen based on the enum value given by +[NSColor currentColorTint].
83 // Apple's documentation also suggests to use NSColorList, but the system color
84 // list is just populated with class methods on NSColor.
85 SkColor NSSystemColorToSkColor(NSColor* color) {
86   if (base::mac::IsOSMountainLionOrEarlier())
87     return GetSystemColorUsingSwatch(color);
89   // System colors use the an NSNamedColorSpace called "System", so first step
90   // is to convert the color into something that can be worked with.
91   NSColor* device_color =
92       [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
93   if (device_color)
94     return gfx::NSDeviceColorToSkColor(device_color);
96   // Sometimes the conversion is not possible, but we can get an approximation
97   // by going through a CGColorRef. Note that simply using NSColor methods for
98   // accessing components for system colors results in exceptions like
99   // "-numberOfComponents not valid for the NSColor NSNamedColorSpace System
100   // windowBackgroundColor; need to first convert colorspace." Hence the
101   // conversion first to CGColor.
102   CGColorRef cg_color = [color CGColor];
103   const size_t component_count = CGColorGetNumberOfComponents(cg_color);
104   if (component_count == 4)
105     return gfx::CGColorRefToSkColor(cg_color);
107   CHECK(component_count == 1 || component_count == 2);
108   // 1-2 components means a grayscale channel and maybe an alpha channel, which
109   // CGColorRefToSkColor will not like. But RGB is additive, so the conversion
110   // is easy (RGB to grayscale is less easy).
111   const CGFloat* components = CGColorGetComponents(cg_color);
112   CGFloat alpha = component_count == 2 ? components[1] : 1.0;
113   return SkColorSetARGB(SkScalarRoundToInt(255.0 * alpha),
114                         SkScalarRoundToInt(255.0 * components[0]),
115                         SkScalarRoundToInt(255.0 * components[0]),
116                         SkScalarRoundToInt(255.0 * components[0]));
119 }  // namespace
121 namespace ui {
123 // static
124 NativeTheme* NativeTheme::instance() {
125   return NativeThemeMac::instance();
128 // static
129 NativeThemeMac* NativeThemeMac::instance() {
130   CR_DEFINE_STATIC_LOCAL(NativeThemeMac, s_native_theme, ());
131   return &s_native_theme;
134 SkColor NativeThemeMac::GetSystemColor(ColorId color_id) const {
135   // TODO(tapted): Add caching for these, and listen for
136   // NSSystemColorsDidChangeNotification.
137   switch (color_id) {
138     case kColorId_WindowBackground:
139       return NSSystemColorToSkColor([NSColor windowBackgroundColor]);
140     case kColorId_DialogBackground:
141       return kDialogBackgroundColor;
143     case kColorId_FocusedBorderColor:
144     case kColorId_FocusedMenuButtonBorderColor:
145       return NSSystemColorToSkColor([NSColor keyboardFocusIndicatorColor]);
146     case kColorId_UnfocusedBorderColor:
147       return NSSystemColorToSkColor([NSColor controlColor]);
149     // Buttons and labels.
150     case kColorId_ButtonBackgroundColor:
151     case kColorId_ButtonHoverBackgroundColor:
152     case kColorId_HoverMenuButtonBorderColor:
153     case kColorId_LabelBackgroundColor:
154       return NSSystemColorToSkColor([NSColor controlBackgroundColor]);
155     case kColorId_ButtonEnabledColor:
156     case kColorId_EnabledMenuButtonBorderColor:
157     case kColorId_LabelEnabledColor:
158       return NSSystemColorToSkColor([NSColor controlTextColor]);
159     case kColorId_ButtonDisabledColor:
160     case kColorId_LabelDisabledColor:
161       return NSSystemColorToSkColor([NSColor disabledControlTextColor]);
162     case kColorId_ButtonHighlightColor:
163     case kColorId_ButtonHoverColor:
164       return NSSystemColorToSkColor([NSColor selectedControlTextColor]);
166     // Menus.
167     case kColorId_EnabledMenuItemForegroundColor:
168       return NSSystemColorToSkColor([NSColor controlTextColor]);
169     case kColorId_DisabledMenuItemForegroundColor:
170     case kColorId_DisabledEmphasizedMenuItemForegroundColor:
171       return NSSystemColorToSkColor([NSColor disabledControlTextColor]);
172     case kColorId_SelectedMenuItemForegroundColor:
173       return NSSystemColorToSkColor([NSColor selectedMenuItemTextColor]);
174     case kColorId_FocusedMenuItemBackgroundColor:
175     case kColorId_HoverMenuItemBackgroundColor:
176       return NSSystemColorToSkColor([NSColor selectedMenuItemColor]);
177     case kColorId_MenuBackgroundColor:
178       return kMenuPopupBackgroundColor;
179     case kColorId_MenuSeparatorColor:
180       return kMenuSeparatorColor;
181     case kColorId_MenuBorderColor:
182       return kMenuBorderColor;
184     // Text fields.
185     case kColorId_TextfieldDefaultColor:
186     case kColorId_TextfieldReadOnlyColor:
187       return NSSystemColorToSkColor([NSColor textColor]);
188     case kColorId_TextfieldDefaultBackground:
189     case kColorId_TextfieldReadOnlyBackground:
190       return NSSystemColorToSkColor([NSColor textBackgroundColor]);
191     case kColorId_TextfieldSelectionColor:
192       return NSSystemColorToSkColor([NSColor selectedTextColor]);
193     case kColorId_TextfieldSelectionBackgroundFocused:
194       return NSSystemColorToSkColor([NSColor selectedTextBackgroundColor]);
196     default:
197       break;  // TODO(tapted): Handle all values and remove the default case.
198   }
200   SkColor color;
201   if (CommonThemeGetSystemColor(color_id, &color))
202     return color;
204   NOTIMPLEMENTED() << " Invalid color_id: " << color_id;
205   return FallbackTheme::GetSystemColor(color_id);
208 void NativeThemeMac::PaintScrollbarTrack(
209     SkCanvas* canvas,
210     Part part,
211     State state,
212     const ScrollbarTrackExtraParams& extra_params,
213     const gfx::Rect& rect) const {
214   // Emulate the non-overlay scroller style from OSX 10.7 and later.
215   SkPoint gradient_bounds[2];
216   if (part == kScrollbarVerticalTrack) {
217     gradient_bounds[0].set(rect.x(), rect.y());
218     gradient_bounds[1].set(rect.right(), rect.y());
219   } else {
220     DCHECK_EQ(part, kScrollbarHorizontalTrack);
221     gradient_bounds[0].set(rect.x(), rect.y());
222     gradient_bounds[1].set(rect.x(), rect.bottom());
223   }
224   skia::RefPtr<SkShader> shader = skia::AdoptRef(
225       SkGradientShader::CreateLinear(gradient_bounds,
226                                      kScrollerTrackGradientColors,
227                                      NULL,
228                                      arraysize(kScrollerTrackGradientColors),
229                                      SkShader::kClamp_TileMode));
230   SkPaint gradient;
231   gradient.setShader(shader.get());
233   SkIRect track_rect = gfx::RectToSkIRect(rect);
234   canvas->drawIRect(track_rect, gradient);
236   // Draw inner and outer line borders.
237   if (part == kScrollbarVerticalTrack) {
238     SkPaint paint;
239     paint.setColor(kScrollerTrackInnerBorderColor);
240     canvas->drawRectCoords(track_rect.left(),
241                            track_rect.top(),
242                            track_rect.left() + kScrollerTrackBorderWidth,
243                            track_rect.bottom(),
244                            paint);
245     paint.setColor(kScrollerTrackOuterBorderColor);
246     canvas->drawRectCoords(track_rect.right() - kScrollerTrackBorderWidth,
247                            track_rect.top(),
248                            track_rect.right(),
249                            track_rect.bottom(),
250                            paint);
251   } else {
252     SkPaint paint;
253     paint.setColor(kScrollerTrackInnerBorderColor);
254     canvas->drawRectCoords(track_rect.left(),
255                            track_rect.top(),
256                            track_rect.right(),
257                            track_rect.top() + kScrollerTrackBorderWidth,
258                            paint);
259     paint.setColor(kScrollerTrackOuterBorderColor);
260     canvas->drawRectCoords(track_rect.left(),
261                            track_rect.bottom() - kScrollerTrackBorderWidth,
262                            track_rect.right(),
263                            track_rect.bottom(),
264                            paint);
265   }
268 void NativeThemeMac::PaintScrollbarThumb(SkCanvas* canvas,
269                                          Part part,
270                                          State state,
271                                          const gfx::Rect& rect) const {
272   gfx::Rect thumb_rect(rect);
273   switch (part) {
274     case kScrollbarHorizontalThumb:
275       thumb_rect.Inset(0, kScrollerTrackBorderWidth, 0, 0);
276       break;
277     case kScrollbarVerticalThumb:
278       thumb_rect.Inset(kScrollerTrackBorderWidth, 0, 0, 0);
279       break;
280     default:
281       NOTREACHED();
282       break;
283   }
285   thumb_rect.Inset(kScrollerThumbInset, kScrollerThumbInset);
287   SkPaint paint;
288   paint.setAntiAlias(true);
289   paint.setColor(state == kHovered ? thumb_active_color_
290                                    : thumb_inactive_color_);
291   const SkScalar radius = std::min(rect.width(), rect.height());
292   canvas->drawRoundRect(gfx::RectToSkRect(thumb_rect), radius, radius, paint);
295 void NativeThemeMac::PaintScrollbarCorner(SkCanvas* canvas,
296                                           State state,
297                                           const gfx::Rect& rect) const {
298   DCHECK_GT(rect.width(), 0);
299   DCHECK_GT(rect.height(), 0);
301   // Draw radial gradient from top-left corner.
302   skia::RefPtr<SkShader> shader = skia::AdoptRef(
303       SkGradientShader::CreateRadial(SkPoint::Make(rect.x(), rect.y()),
304                                      rect.width(),
305                                      kScrollerTrackGradientColors,
306                                      NULL,
307                                      arraysize(kScrollerTrackGradientColors),
308                                      SkShader::kClamp_TileMode));
309   SkPaint gradient;
310   gradient.setStyle(SkPaint::kFill_Style);
311   gradient.setAntiAlias(true);
312   gradient.setShader(shader.get());
313   canvas->drawRect(gfx::RectToSkRect(rect), gradient);
315   // Draw inner border corner point.
316   canvas->drawPoint(rect.x(), rect.y(), kScrollerTrackInnerBorderColor);
318   // Draw outer borders.
319   SkPaint paint;
320   paint.setColor(kScrollerTrackOuterBorderColor);
321   canvas->drawRectCoords(rect.right() - kScrollerTrackBorderWidth,
322                          rect.y(),
323                          rect.right(),
324                          rect.bottom(),
325                          paint);
326   canvas->drawRectCoords(rect.x(),
327                          rect.bottom() - kScrollerTrackBorderWidth,
328                          rect.right(),
329                          rect.bottom(),
330                          paint);
333 void NativeThemeMac::PaintMenuPopupBackground(
334     SkCanvas* canvas,
335     const gfx::Size& size,
336     const MenuBackgroundExtraParams& menu_background) const {
337   SkPaint paint;
338   paint.setAntiAlias(true);
339   if (base::mac::IsOSYosemiteOrLater())
340     paint.setColor(kMenuPopupBackgroundColorYosemite);
341   else
342     paint.setColor(kMenuPopupBackgroundColor);
343   const SkScalar radius = SkIntToScalar(menu_background.corner_radius);
344   SkRect rect = gfx::RectToSkRect(gfx::Rect(size));
345   canvas->drawRoundRect(rect, radius, radius, paint);
348 void NativeThemeMac::PaintMenuItemBackground(
349     SkCanvas* canvas,
350     State state,
351     const gfx::Rect& rect,
352     const MenuListExtraParams& menu_list) const {
353   SkPaint paint;
354   switch (state) {
355     case NativeTheme::kNormal:
356     case NativeTheme::kDisabled:
357       // Draw nothing over the regular background.
358       break;
359     case NativeTheme::kHovered:
360       // TODO(tapted): Draw a gradient, and use [NSColor currentControlTint] to
361       // pick colors. The System color "selectedMenuItemColor" is actually still
362       // blue for Graphite. And while "keyboardFocusIndicatorColor" does change,
363       // and is a good shade of gray, it's not blue enough for the Blue theme.
364       paint.setColor(GetSystemColor(kColorId_HoverMenuItemBackgroundColor));
365       canvas->drawRect(gfx::RectToSkRect(rect), paint);
366       break;
367     default:
368       NOTREACHED();
369       break;
370   }
373 NativeThemeMac::NativeThemeMac() {
374   set_scrollbar_button_length(0);
375   SetScrollbarColors(kScrollerThumbColor,
376                      kScrollerThumbHoverColor,
377                      kScrollerTrackGradientColors[0]);
380 NativeThemeMac::~NativeThemeMac() {
383 }  // namespace ui