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. 4 5#include "ui/native_theme/native_theme_mac.h" 6 7#import <Cocoa/Cocoa.h> 8 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" 18 19namespace { 20 21const SkColor kScrollerTrackGradientColors[] = { 22 SkColorSetRGB(0xEF, 0xEF, 0xEF), 23 SkColorSetRGB(0xF9, 0xF9, 0xF9), 24 SkColorSetRGB(0xFD, 0xFD, 0xFD), 25 SkColorSetRGB(0xF6, 0xF6, 0xF6) }; 26const SkColor kScrollerTrackInnerBorderColor = SkColorSetRGB(0xE4, 0xE4, 0xE4); 27const SkColor kScrollerTrackOuterBorderColor = SkColorSetRGB(0xEF, 0xEF, 0xEF); 28const SkColor kScrollerThumbColor = SkColorSetARGB(0x38, 0, 0, 0); 29const SkColor kScrollerThumbHoverColor = SkColorSetARGB(0x80, 0, 0, 0); 30const int kScrollerTrackBorderWidth = 1; 31 32// The amount the thumb is inset from both the ends and the sides of the track. 33const int kScrollerThumbInset = 3; 34 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. 41const SkColor kMenuPopupBackgroundColor = SkColorSetARGB(251, 255, 255, 255); 42const SkColor kMenuSeparatorColor = SkColorSetARGB(243, 228, 228, 228); 43const SkColor kMenuBorderColor = SkColorSetARGB(60, 0, 0, 0); 44 45// Hardcoded color used for some existing dialogs in Chrome's Cocoa UI. 46const SkColor kDialogBackgroundColor = SkColorSetRGB(251, 251, 251); 47 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. 54SkColor 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)); 64 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; 72} 73 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. 82SkColor NSSystemColorToSkColor(NSColor* color) { 83 if (base::mac::IsOSMountainLionOrEarlier()) 84 return GetSystemColorUsingSwatch(color); 85 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); 92 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); 102 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])); 112} 113 114} // namespace 115 116namespace ui { 117 118// static 119NativeTheme* NativeTheme::instance() { 120 return NativeThemeMac::instance(); 121} 122 123// static 124NativeThemeMac* NativeThemeMac::instance() { 125 CR_DEFINE_STATIC_LOCAL(NativeThemeMac, s_native_theme, ()); 126 return &s_native_theme; 127} 128 129SkColor 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; 137 138 case kColorId_FocusedBorderColor: 139 case kColorId_FocusedMenuButtonBorderColor: 140 return NSSystemColorToSkColor([NSColor keyboardFocusIndicatorColor]); 141 case kColorId_UnfocusedBorderColor: 142 return NSSystemColorToSkColor([NSColor controlColor]); 143 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]); 160 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; 178 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]); 190 191 default: 192 break; // TODO(tapted): Handle all values and remove the default case. 193 } 194 195 SkColor color; 196 if (CommonThemeGetSystemColor(color_id, &color)) 197 return color; 198 199 NOTIMPLEMENTED() << " Invalid color_id: " << color_id; 200 return FallbackTheme::GetSystemColor(color_id); 201} 202 203void 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()); 227 228 SkIRect track_rect = gfx::RectToSkIRect(rect); 229 canvas->drawIRect(track_rect, gradient); 230 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 } 261} 262 263void 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 } 279 280 thumb_rect.Inset(kScrollerThumbInset, kScrollerThumbInset); 281 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); 288} 289 290void 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); 295 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); 309 310 // Draw inner border corner point. 311 canvas->drawPoint(rect.x(), rect.y(), kScrollerTrackInnerBorderColor); 312 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); 326} 327 328void NativeThemeMac::PaintMenuPopupBackground( 329 SkCanvas* canvas, 330 const gfx::Size& size, 331 const MenuBackgroundExtraParams& menu_background) const { 332 canvas->drawColor(kMenuPopupBackgroundColor, SkXfermode::kSrc_Mode); 333} 334 335void 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 } 358} 359 360NativeThemeMac::NativeThemeMac() { 361 set_scrollbar_button_length(0); 362 SetScrollbarColors(kScrollerThumbColor, 363 kScrollerThumbHoverColor, 364 kScrollerTrackGradientColors[0]); 365} 366 367NativeThemeMac::~NativeThemeMac() { 368} 369 370} // namespace ui 371