Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / native_theme / native_theme_mac.mm
blob60c049cb27d95d5822bf4310969dfb0381a46c61
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 #include "ui/native_theme/common_theme.h"
14 #import "skia/ext/skia_utils_mac.h"
15 #include "third_party/skia/include/effects/SkGradientShader.h"
16 #include "ui/gfx/geometry/rect.h"
17 #include "ui/gfx/skia_util.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(251, 255, 255, 255);
42 const SkColor kMenuSeparatorColor = SkColorSetARGB(243, 228, 228, 228);
43 const SkColor kMenuBorderColor = SkColorSetARGB(60, 0, 0, 0);
45 // Hardcoded color used for some existing dialogs in Chrome's Cocoa UI.
46 const SkColor kDialogBackgroundColor = SkColorSetRGB(251, 251, 251);
48 // On 10.6 and 10.7 there is no way to get components from system colors. Here,
49 // system colors are just opaque objects that can paint themselves and otherwise
50 // tell you nothing. In 10.8, some of the system color classes have incomplete
51 // implementations and throw exceptions even attempting to convert using
52 // -[NSColor colorUsingColorSpace:], so don't bother there either.
53 // This function paints a single pixel to a 1x1 swatch and reads it back.
54 SkColor GetSystemColorUsingSwatch(NSColor* color) {
55   SkColor swatch;
56   base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
57       CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
58   const size_t bytes_per_row = 4;
59   COMPILE_ASSERT(sizeof(swatch) == bytes_per_row, skcolor_not_4_bytes);
60   CGBitmapInfo bitmap_info =
61       kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
62   base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
63       &swatch, 1, 1, 8, bytes_per_row, color_space, bitmap_info));
65   NSGraphicsContext* drawing_context =
66       [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO];
67   [NSGraphicsContext saveGraphicsState];
68   [NSGraphicsContext setCurrentContext:drawing_context];
69   [color drawSwatchInRect:NSMakeRect(0, 0, 1, 1)];
70   [NSGraphicsContext restoreGraphicsState];
71   return swatch;
74 // NSColor has a number of methods that return system colors (i.e. controlled by
75 // user preferences). This function converts the color given by an NSColor class
76 // method to an SkColor. Official documentation suggests developers only rely on
77 // +[NSColor selectedTextBackgroundColor] and +[NSColor selectedControlColor],
78 // but other colors give a good baseline. For many, a gradient is involved; the
79 // palette chosen based on the enum value given by +[NSColor currentColorTint].
80 // Apple's documentation also suggests to use NSColorList, but the system color
81 // list is just populated with class methods on NSColor.
82 SkColor NSSystemColorToSkColor(NSColor* color) {
83   if (base::mac::IsOSMountainLionOrEarlier())
84     return GetSystemColorUsingSwatch(color);
86   // System colors use the an NSNamedColorSpace called "System", so first step
87   // is to convert the color into something that can be worked with.
88   NSColor* device_color =
89       [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
90   if (device_color)
91     return gfx::NSDeviceColorToSkColor(device_color);
93   // Sometimes the conversion is not possible, but we can get an approximation
94   // by going through a CGColorRef. Note that simply using NSColor methods for
95   // accessing components for system colors results in exceptions like
96   // "-numberOfComponents not valid for the NSColor NSNamedColorSpace System
97   // windowBackgroundColor; need to first convert colorspace." Hence the
98   // conversion first to CGColor.
99   CGColorRef cg_color = [color CGColor];
100   if (CGColorGetNumberOfComponents(cg_color) == 4)
101     return gfx::CGColorRefToSkColor(cg_color);
103   CHECK_EQ(2u, CGColorGetNumberOfComponents(cg_color));
104   // Two components means a grayscale channel and an alpha channel, which
105   // CGColorRefToSkColor will not like. But RGB is additive, so the conversion
106   // is easy (RGB to grayscale is less easy).
107   const CGFloat* components = CGColorGetComponents(cg_color);
108   return SkColorSetARGB(SkScalarRoundToInt(255.0 * components[1]),
109                         SkScalarRoundToInt(255.0 * components[0]),
110                         SkScalarRoundToInt(255.0 * components[0]),
111                         SkScalarRoundToInt(255.0 * components[0]));
114 }  // namespace
116 namespace ui {
118 // static
119 NativeTheme* NativeTheme::instance() {
120   return NativeThemeMac::instance();
123 // static
124 NativeThemeMac* NativeThemeMac::instance() {
125   CR_DEFINE_STATIC_LOCAL(NativeThemeMac, s_native_theme, ());
126   return &s_native_theme;
129 SkColor NativeThemeMac::GetSystemColor(ColorId color_id) const {
130   // TODO(tapted): Add caching for these, and listen for
131   // NSSystemColorsDidChangeNotification.
132   switch (color_id) {
133     case kColorId_WindowBackground:
134       return NSSystemColorToSkColor([NSColor windowBackgroundColor]);
135     case kColorId_DialogBackground:
136       return kDialogBackgroundColor;
138     case kColorId_FocusedBorderColor:
139     case kColorId_FocusedMenuButtonBorderColor:
140       return NSSystemColorToSkColor([NSColor keyboardFocusIndicatorColor]);
141     case kColorId_UnfocusedBorderColor:
142       return NSSystemColorToSkColor([NSColor controlColor]);
144     // Buttons and labels.
145     case kColorId_ButtonBackgroundColor:
146     case kColorId_ButtonHoverBackgroundColor:
147     case kColorId_HoverMenuButtonBorderColor:
148     case kColorId_LabelBackgroundColor:
149       return NSSystemColorToSkColor([NSColor controlBackgroundColor]);
150     case kColorId_ButtonEnabledColor:
151     case kColorId_EnabledMenuButtonBorderColor:
152     case kColorId_LabelEnabledColor:
153       return NSSystemColorToSkColor([NSColor controlTextColor]);
154     case kColorId_ButtonDisabledColor:
155     case kColorId_LabelDisabledColor:
156       return NSSystemColorToSkColor([NSColor disabledControlTextColor]);
157     case kColorId_ButtonHighlightColor:
158     case kColorId_ButtonHoverColor:
159       return NSSystemColorToSkColor([NSColor selectedControlTextColor]);
161     // Menus.
162     case kColorId_EnabledMenuItemForegroundColor:
163       return NSSystemColorToSkColor([NSColor controlTextColor]);
164     case kColorId_DisabledMenuItemForegroundColor:
165     case kColorId_DisabledEmphasizedMenuItemForegroundColor:
166       return NSSystemColorToSkColor([NSColor disabledControlTextColor]);
167     case kColorId_SelectedMenuItemForegroundColor:
168       return NSSystemColorToSkColor([NSColor selectedMenuItemTextColor]);
169     case kColorId_FocusedMenuItemBackgroundColor:
170     case kColorId_HoverMenuItemBackgroundColor:
171       return NSSystemColorToSkColor([NSColor selectedMenuItemColor]);
172     case kColorId_MenuBackgroundColor:
173       return kMenuPopupBackgroundColor;
174     case kColorId_MenuSeparatorColor:
175       return kMenuSeparatorColor;
176     case kColorId_MenuBorderColor:
177       return kMenuBorderColor;
179     // Text fields.
180     case kColorId_TextfieldDefaultColor:
181     case kColorId_TextfieldReadOnlyColor:
182       return NSSystemColorToSkColor([NSColor textColor]);
183     case kColorId_TextfieldDefaultBackground:
184     case kColorId_TextfieldReadOnlyBackground:
185       return NSSystemColorToSkColor([NSColor textBackgroundColor]);
186     case kColorId_TextfieldSelectionColor:
187       return NSSystemColorToSkColor([NSColor selectedTextColor]);
188     case kColorId_TextfieldSelectionBackgroundFocused:
189       return NSSystemColorToSkColor([NSColor selectedTextBackgroundColor]);
191     default:
192       break;  // TODO(tapted): Handle all values and remove the default case.
193   }
195   SkColor color;
196   if (CommonThemeGetSystemColor(color_id, &color))
197     return color;
199   NOTIMPLEMENTED() << " Invalid color_id: " << color_id;
200   return FallbackTheme::GetSystemColor(color_id);
203 void NativeThemeMac::PaintScrollbarTrack(
204     SkCanvas* canvas,
205     Part part,
206     State state,
207     const ScrollbarTrackExtraParams& extra_params,
208     const gfx::Rect& rect) const {
209   // Emulate the non-overlay scroller style from OSX 10.7 and later.
210   SkPoint gradient_bounds[2];
211   if (part == kScrollbarVerticalTrack) {
212     gradient_bounds[0].set(rect.x(), rect.y());
213     gradient_bounds[1].set(rect.right(), rect.y());
214   } else {
215     DCHECK_EQ(part, kScrollbarHorizontalTrack);
216     gradient_bounds[0].set(rect.x(), rect.y());
217     gradient_bounds[1].set(rect.x(), rect.bottom());
218   }
219   skia::RefPtr<SkShader> shader = skia::AdoptRef(
220       SkGradientShader::CreateLinear(gradient_bounds,
221                                      kScrollerTrackGradientColors,
222                                      NULL,
223                                      arraysize(kScrollerTrackGradientColors),
224                                      SkShader::kClamp_TileMode));
225   SkPaint gradient;
226   gradient.setShader(shader.get());
228   SkIRect track_rect = gfx::RectToSkIRect(rect);
229   canvas->drawIRect(track_rect, gradient);
231   // Draw inner and outer line borders.
232   if (part == kScrollbarVerticalTrack) {
233     SkPaint paint;
234     paint.setColor(kScrollerTrackInnerBorderColor);
235     canvas->drawRectCoords(track_rect.left(),
236                            track_rect.top(),
237                            track_rect.left() + kScrollerTrackBorderWidth,
238                            track_rect.bottom(),
239                            paint);
240     paint.setColor(kScrollerTrackOuterBorderColor);
241     canvas->drawRectCoords(track_rect.right() - kScrollerTrackBorderWidth,
242                            track_rect.top(),
243                            track_rect.right(),
244                            track_rect.bottom(),
245                            paint);
246   } else {
247     SkPaint paint;
248     paint.setColor(kScrollerTrackInnerBorderColor);
249     canvas->drawRectCoords(track_rect.left(),
250                            track_rect.top(),
251                            track_rect.right(),
252                            track_rect.top() + kScrollerTrackBorderWidth,
253                            paint);
254     paint.setColor(kScrollerTrackOuterBorderColor);
255     canvas->drawRectCoords(track_rect.left(),
256                            track_rect.bottom() - kScrollerTrackBorderWidth,
257                            track_rect.right(),
258                            track_rect.bottom(),
259                            paint);
260   }
263 void NativeThemeMac::PaintScrollbarThumb(SkCanvas* canvas,
264                                          Part part,
265                                          State state,
266                                          const gfx::Rect& rect) const {
267   gfx::Rect thumb_rect(rect);
268   switch (part) {
269     case kScrollbarHorizontalThumb:
270       thumb_rect.Inset(0, kScrollerTrackBorderWidth, 0, 0);
271       break;
272     case kScrollbarVerticalThumb:
273       thumb_rect.Inset(kScrollerTrackBorderWidth, 0, 0, 0);
274       break;
275     default:
276       NOTREACHED();
277       break;
278   }
280   thumb_rect.Inset(kScrollerThumbInset, kScrollerThumbInset);
282   SkPaint paint;
283   paint.setAntiAlias(true);
284   paint.setColor(state == kHovered ? thumb_active_color_
285                                    : thumb_inactive_color_);
286   const SkScalar radius = std::min(rect.width(), rect.height());
287   canvas->drawRoundRect(gfx::RectToSkRect(thumb_rect), radius, radius, paint);
290 void NativeThemeMac::PaintScrollbarCorner(SkCanvas* canvas,
291                                           State state,
292                                           const gfx::Rect& rect) const {
293   DCHECK_GT(rect.width(), 0);
294   DCHECK_GT(rect.height(), 0);
296   // Draw radial gradient from top-left corner.
297   skia::RefPtr<SkShader> shader = skia::AdoptRef(
298       SkGradientShader::CreateRadial(SkPoint::Make(rect.x(), rect.y()),
299                                      rect.width(),
300                                      kScrollerTrackGradientColors,
301                                      NULL,
302                                      arraysize(kScrollerTrackGradientColors),
303                                      SkShader::kClamp_TileMode));
304   SkPaint gradient;
305   gradient.setStyle(SkPaint::kFill_Style);
306   gradient.setAntiAlias(true);
307   gradient.setShader(shader.get());
308   canvas->drawRect(gfx::RectToSkRect(rect), gradient);
310   // Draw inner border corner point.
311   canvas->drawPoint(rect.x(), rect.y(), kScrollerTrackInnerBorderColor);
313   // Draw outer borders.
314   SkPaint paint;
315   paint.setColor(kScrollerTrackOuterBorderColor);
316   canvas->drawRectCoords(rect.right() - kScrollerTrackBorderWidth,
317                          rect.y(),
318                          rect.right(),
319                          rect.bottom(),
320                          paint);
321   canvas->drawRectCoords(rect.x(),
322                          rect.bottom() - kScrollerTrackBorderWidth,
323                          rect.right(),
324                          rect.bottom(),
325                          paint);
328 void NativeThemeMac::PaintMenuPopupBackground(
329     SkCanvas* canvas,
330     const gfx::Size& size,
331     const MenuBackgroundExtraParams& menu_background) const {
332   canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode);
335 void NativeThemeMac::PaintMenuItemBackground(
336     SkCanvas* canvas,
337     State state,
338     const gfx::Rect& rect,
339     const MenuListExtraParams& menu_list) const {
340   SkPaint paint;
341   switch (state) {
342     case NativeTheme::kNormal:
343     case NativeTheme::kDisabled:
344       // Draw nothing over the regular background.
345       break;
346     case NativeTheme::kHovered:
347       // TODO(tapted): Draw a gradient, and use [NSColor currentControlTint] to
348       // pick colors. The System color "selectedMenuItemColor" is actually still
349       // blue for Graphite. And while "keyboardFocusIndicatorColor" does change,
350       // and is a good shade of gray, it's not blue enough for the Blue theme.
351       paint.setColor(GetSystemColor(kColorId_HoverMenuItemBackgroundColor));
352       canvas->drawRect(gfx::RectToSkRect(rect), paint);
353       break;
354     default:
355       NOTREACHED();
356       break;
357   }
360 NativeThemeMac::NativeThemeMac() {
361   set_scrollbar_button_length(0);
362   SetScrollbarColors(kScrollerThumbColor,
363                      kScrollerThumbHoverColor,
364                      kScrollerTrackGradientColors[0]);
367 NativeThemeMac::~NativeThemeMac() {
370 }  // namespace ui