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