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"
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) {
56 base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
57 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
58 const size_t bytes_per_row = 4;
59 static_assert(sizeof(swatch) == bytes_per_row, "skcolor should be 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];
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]];
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 const size_t component_count = CGColorGetNumberOfComponents(cg_color);
101 if (component_count == 4)
102 return gfx::CGColorRefToSkColor(cg_color);
104 CHECK(component_count == 1 || component_count == 2);
105 // 1-2 components means a grayscale channel and maybe an alpha channel, which
106 // CGColorRefToSkColor will not like. But RGB is additive, so the conversion
107 // is easy (RGB to grayscale is less easy).
108 const CGFloat* components = CGColorGetComponents(cg_color);
109 CGFloat alpha = component_count == 2 ? components[1] : 1.0;
110 return SkColorSetARGB(SkScalarRoundToInt(255.0 * alpha),
111 SkScalarRoundToInt(255.0 * components[0]),
112 SkScalarRoundToInt(255.0 * components[0]),
113 SkScalarRoundToInt(255.0 * components[0]));
121 NativeTheme* NativeTheme::instance() {
122 return NativeThemeMac::instance();
126 NativeThemeMac* NativeThemeMac::instance() {
127 CR_DEFINE_STATIC_LOCAL(NativeThemeMac, s_native_theme, ());
128 return &s_native_theme;
131 SkColor NativeThemeMac::GetSystemColor(ColorId color_id) const {
132 // TODO(tapted): Add caching for these, and listen for
133 // NSSystemColorsDidChangeNotification.
135 case kColorId_WindowBackground:
136 return NSSystemColorToSkColor([NSColor windowBackgroundColor]);
137 case kColorId_DialogBackground:
138 return kDialogBackgroundColor;
140 case kColorId_FocusedBorderColor:
141 case kColorId_FocusedMenuButtonBorderColor:
142 return NSSystemColorToSkColor([NSColor keyboardFocusIndicatorColor]);
143 case kColorId_UnfocusedBorderColor:
144 return NSSystemColorToSkColor([NSColor controlColor]);
146 // Buttons and labels.
147 case kColorId_ButtonBackgroundColor:
148 case kColorId_ButtonHoverBackgroundColor:
149 case kColorId_HoverMenuButtonBorderColor:
150 case kColorId_LabelBackgroundColor:
151 return NSSystemColorToSkColor([NSColor controlBackgroundColor]);
152 case kColorId_ButtonEnabledColor:
153 case kColorId_EnabledMenuButtonBorderColor:
154 case kColorId_LabelEnabledColor:
155 return NSSystemColorToSkColor([NSColor controlTextColor]);
156 case kColorId_ButtonDisabledColor:
157 case kColorId_LabelDisabledColor:
158 return NSSystemColorToSkColor([NSColor disabledControlTextColor]);
159 case kColorId_ButtonHighlightColor:
160 case kColorId_ButtonHoverColor:
161 return NSSystemColorToSkColor([NSColor selectedControlTextColor]);
164 case kColorId_EnabledMenuItemForegroundColor:
165 return NSSystemColorToSkColor([NSColor controlTextColor]);
166 case kColorId_DisabledMenuItemForegroundColor:
167 case kColorId_DisabledEmphasizedMenuItemForegroundColor:
168 return NSSystemColorToSkColor([NSColor disabledControlTextColor]);
169 case kColorId_SelectedMenuItemForegroundColor:
170 return NSSystemColorToSkColor([NSColor selectedMenuItemTextColor]);
171 case kColorId_FocusedMenuItemBackgroundColor:
172 case kColorId_HoverMenuItemBackgroundColor:
173 return NSSystemColorToSkColor([NSColor selectedMenuItemColor]);
174 case kColorId_MenuBackgroundColor:
175 return kMenuPopupBackgroundColor;
176 case kColorId_MenuSeparatorColor:
177 return kMenuSeparatorColor;
178 case kColorId_MenuBorderColor:
179 return kMenuBorderColor;
182 case kColorId_TextfieldDefaultColor:
183 case kColorId_TextfieldReadOnlyColor:
184 return NSSystemColorToSkColor([NSColor textColor]);
185 case kColorId_TextfieldDefaultBackground:
186 case kColorId_TextfieldReadOnlyBackground:
187 return NSSystemColorToSkColor([NSColor textBackgroundColor]);
188 case kColorId_TextfieldSelectionColor:
189 return NSSystemColorToSkColor([NSColor selectedTextColor]);
190 case kColorId_TextfieldSelectionBackgroundFocused:
191 return NSSystemColorToSkColor([NSColor selectedTextBackgroundColor]);
194 break; // TODO(tapted): Handle all values and remove the default case.
198 if (CommonThemeGetSystemColor(color_id, &color))
201 NOTIMPLEMENTED() << " Invalid color_id: " << color_id;
202 return FallbackTheme::GetSystemColor(color_id);
205 void NativeThemeMac::PaintScrollbarTrack(
209 const ScrollbarTrackExtraParams& extra_params,
210 const gfx::Rect& rect) const {
211 // Emulate the non-overlay scroller style from OSX 10.7 and later.
212 SkPoint gradient_bounds[2];
213 if (part == kScrollbarVerticalTrack) {
214 gradient_bounds[0].set(rect.x(), rect.y());
215 gradient_bounds[1].set(rect.right(), rect.y());
217 DCHECK_EQ(part, kScrollbarHorizontalTrack);
218 gradient_bounds[0].set(rect.x(), rect.y());
219 gradient_bounds[1].set(rect.x(), rect.bottom());
221 skia::RefPtr<SkShader> shader = skia::AdoptRef(
222 SkGradientShader::CreateLinear(gradient_bounds,
223 kScrollerTrackGradientColors,
225 arraysize(kScrollerTrackGradientColors),
226 SkShader::kClamp_TileMode));
228 gradient.setShader(shader.get());
230 SkIRect track_rect = gfx::RectToSkIRect(rect);
231 canvas->drawIRect(track_rect, gradient);
233 // Draw inner and outer line borders.
234 if (part == kScrollbarVerticalTrack) {
236 paint.setColor(kScrollerTrackInnerBorderColor);
237 canvas->drawRectCoords(track_rect.left(),
239 track_rect.left() + kScrollerTrackBorderWidth,
242 paint.setColor(kScrollerTrackOuterBorderColor);
243 canvas->drawRectCoords(track_rect.right() - kScrollerTrackBorderWidth,
250 paint.setColor(kScrollerTrackInnerBorderColor);
251 canvas->drawRectCoords(track_rect.left(),
254 track_rect.top() + kScrollerTrackBorderWidth,
256 paint.setColor(kScrollerTrackOuterBorderColor);
257 canvas->drawRectCoords(track_rect.left(),
258 track_rect.bottom() - kScrollerTrackBorderWidth,
265 void NativeThemeMac::PaintScrollbarThumb(SkCanvas* canvas,
268 const gfx::Rect& rect) const {
269 gfx::Rect thumb_rect(rect);
271 case kScrollbarHorizontalThumb:
272 thumb_rect.Inset(0, kScrollerTrackBorderWidth, 0, 0);
274 case kScrollbarVerticalThumb:
275 thumb_rect.Inset(kScrollerTrackBorderWidth, 0, 0, 0);
282 thumb_rect.Inset(kScrollerThumbInset, kScrollerThumbInset);
285 paint.setAntiAlias(true);
286 paint.setColor(state == kHovered ? thumb_active_color_
287 : thumb_inactive_color_);
288 const SkScalar radius = std::min(rect.width(), rect.height());
289 canvas->drawRoundRect(gfx::RectToSkRect(thumb_rect), radius, radius, paint);
292 void NativeThemeMac::PaintScrollbarCorner(SkCanvas* canvas,
294 const gfx::Rect& rect) const {
295 DCHECK_GT(rect.width(), 0);
296 DCHECK_GT(rect.height(), 0);
298 // Draw radial gradient from top-left corner.
299 skia::RefPtr<SkShader> shader = skia::AdoptRef(
300 SkGradientShader::CreateRadial(SkPoint::Make(rect.x(), rect.y()),
302 kScrollerTrackGradientColors,
304 arraysize(kScrollerTrackGradientColors),
305 SkShader::kClamp_TileMode));
307 gradient.setStyle(SkPaint::kFill_Style);
308 gradient.setAntiAlias(true);
309 gradient.setShader(shader.get());
310 canvas->drawRect(gfx::RectToSkRect(rect), gradient);
312 // Draw inner border corner point.
313 canvas->drawPoint(rect.x(), rect.y(), kScrollerTrackInnerBorderColor);
315 // Draw outer borders.
317 paint.setColor(kScrollerTrackOuterBorderColor);
318 canvas->drawRectCoords(rect.right() - kScrollerTrackBorderWidth,
323 canvas->drawRectCoords(rect.x(),
324 rect.bottom() - kScrollerTrackBorderWidth,
330 void NativeThemeMac::PaintMenuPopupBackground(
332 const gfx::Size& size,
333 const MenuBackgroundExtraParams& menu_background) const {
334 canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode);
337 void NativeThemeMac::PaintMenuItemBackground(
340 const gfx::Rect& rect,
341 const MenuListExtraParams& menu_list) const {
344 case NativeTheme::kNormal:
345 case NativeTheme::kDisabled:
346 // Draw nothing over the regular background.
348 case NativeTheme::kHovered:
349 // TODO(tapted): Draw a gradient, and use [NSColor currentControlTint] to
350 // pick colors. The System color "selectedMenuItemColor" is actually still
351 // blue for Graphite. And while "keyboardFocusIndicatorColor" does change,
352 // and is a good shade of gray, it's not blue enough for the Blue theme.
353 paint.setColor(GetSystemColor(kColorId_HoverMenuItemBackgroundColor));
354 canvas->drawRect(gfx::RectToSkRect(rect), paint);
362 NativeThemeMac::NativeThemeMac() {
363 set_scrollbar_button_length(0);
364 SetScrollbarColors(kScrollerThumbColor,
365 kScrollerThumbHoverColor,
366 kScrollerTrackGradientColors[0]);
369 NativeThemeMac::~NativeThemeMac() {