summaryrefslogtreecommitdiffstats
path: root/ui/native_theme/native_theme_mac.mm
blob: 7c9a156797ccc9e25c4026eb6bba1ac7957eb7bf (plain)
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;
  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