| // 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; |
| static_assert(sizeof(swatch) == bytes_per_row, "skcolor should be 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 |