aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AppKit/GTMNSColor+Luminance.h65
-rw-r--r--AppKit/GTMNSColor+Luminance.m153
-rw-r--r--AppKit/GTMNSColor+LuminanceTest.m72
-rw-r--r--AppKit/GTMTheme.h78
-rw-r--r--AppKit/GTMTheme.m390
-rw-r--r--AppKit/GTMThemeTest.m67
-rw-r--r--GTM.xcodeproj/project.pbxproj24
-rw-r--r--ReleaseNotes.txt4
8 files changed, 853 insertions, 0 deletions
diff --git a/AppKit/GTMNSColor+Luminance.h b/AppKit/GTMNSColor+Luminance.h
new file mode 100644
index 0000000..c1501cd
--- /dev/null
+++ b/AppKit/GTMNSColor+Luminance.h
@@ -0,0 +1,65 @@
+//
+// GTMNSColor+Luminance.h
+//
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMDefines.h"
+#import <Cocoa/Cocoa.h>
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+enum {
+ GTMColorationBaseHighlight,
+ GTMColorationBaseMidtone,
+ GTMColorationBaseShadow,
+ GTMColorationBasePenumbra,
+ GTMColorationLightHighlight,
+ GTMColorationLightMidtone,
+ GTMColorationLightShadow,
+ GTMColorationLightPenumbra,
+ GTMColorationDarkHighlight,
+ GTMColorationDarkMidtone,
+ GTMColorationDarkShadow,
+ GTMColorationDarkPenumbra
+};
+typedef NSUInteger GTMColorationUse;
+
+@interface NSColorSpace (GTMNSColorSpaceLuminanceHelpers)
++ (NSColorSpace *)gtm_labColorSpace;
+@end
+
+@interface NSColor (GTMLuminanceAdditions)
+- (CGFloat)gtm_luminance;
+
+// Create a color modified by lightening or darkening it (-1.0 to 1.0)
+- (NSColor *)gtm_colorByAdjustingLuminance:(CGFloat)luminance;
+
+// Create a color modified by lightening or darkening it (-1.0 to 1.0)
+- (NSColor *)gtm_colorByAdjustingLuminance:(CGFloat)luminance
+ saturation:(CGFloat)saturation;
+
+// Returns a color adjusted for a specific usage
+- (NSColor *)gtm_colorAdjustedFor:(GTMColorationUse)use;
+- (NSColor *)gtm_colorAdjustedFor:(GTMColorationUse)use faded:(BOOL)fade;
+
+// Returns whether the color is in the dark half of the spectrum
+- (BOOL)gtm_isDarkColor;
+
+// Returns a color that is legible on this color. (Nothing to do with textColor)
+- (NSColor *)gtm_legibleTextColor;
+@end
+
+#endif
diff --git a/AppKit/GTMNSColor+Luminance.m b/AppKit/GTMNSColor+Luminance.m
new file mode 100644
index 0000000..5b10dbe
--- /dev/null
+++ b/AppKit/GTMNSColor+Luminance.m
@@ -0,0 +1,153 @@
+//
+// GTMNSColor+Luminance.m
+//
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+#import "GTMNSColor+Luminance.h"
+
+static const CGFloat kGTMLuminanceDarkCutoff = 0.6;
+
+@implementation NSColorSpace (GTMNSColorSpaceLuminanceHelpers)
+
+// TODO(alcor): we may want to keep one of these around for performance reasons
++ (NSColorSpace *)gtm_labColorSpace {
+ // Observer= 2°, Illuminant= D65
+ // TODO(alcor): these should come from ColorSync
+ CGFloat whitePoint[3] = {0.95047, 1.0, 1.08883};
+ CGFloat blackPoint[3] = {0, 0, 0};
+ CGFloat range[4] = {-127, 127, -127, 127};
+ CGColorSpaceRef cs = CGColorSpaceCreateLab(whitePoint, blackPoint, range);
+ NSColorSpace *space = nil;
+ if (cs) {
+ space = [[[NSColorSpace alloc] initWithCGColorSpace:cs] autorelease];
+ CGColorSpaceRelease(cs);
+ }
+ return space;
+}
+@end
+
+@implementation NSColor (GTMLuminance)
+
+- (NSColor *)labColor {
+ return [self colorUsingColorSpace:[NSColorSpace gtm_labColorSpace]];
+}
+
+- (CGFloat)gtm_luminance {
+ CGFloat lab[4];
+ lab[0] = 0.0;
+ [[self labColor] getComponents:lab];
+ return lab[0] / 100.0;
+}
+
+- (NSColor *)gtm_colorByAdjustingLuminance:(CGFloat)luminance
+ saturation:(CGFloat)saturation {
+ CGFloat lab[4];
+ [[self labColor] getComponents:lab];
+ lab[0] *= 1.0 + luminance;
+ // If luminance is greater than 100, we desaturate it so that we don't get
+ // wild colors coming out of the forumula
+ if (lab[0] > 100) {
+ CGFloat clipping = lab[0] - 100;
+ CGFloat desaturation = (50.0 - clipping) / 50.0;
+ saturation = MIN(saturation, desaturation);
+ }
+ lab[1] *= saturation;
+ lab[2] *= saturation;
+ return [NSColor colorWithColorSpace:[NSColorSpace gtm_labColorSpace]
+ components:lab
+ count:sizeof(lab) / sizeof(lab[0])];
+}
+
+- (NSColor *)gtm_colorByAdjustingLuminance:(CGFloat)luminance {
+ return [self gtm_colorByAdjustingLuminance:luminance saturation:1.0];
+}
+
+// TODO(alcor): these constants are largely made up, come up with a consistent
+// set of values or at least guidelines
+- (NSColor *)gtm_colorAdjustedFor:(GTMColorationUse)use {
+ NSColor *color = nil;
+ switch (use) {
+ case GTMColorationBaseHighlight:
+ color = [self gtm_colorByAdjustingLuminance:0.15];
+ break;
+ case GTMColorationBaseMidtone:
+ color = self;
+ break;
+ case GTMColorationBaseShadow:
+ color = [self gtm_colorByAdjustingLuminance:-0.15];
+ break;
+ case GTMColorationBasePenumbra:
+ color = [self gtm_colorByAdjustingLuminance:-0.10];
+ break;
+ case GTMColorationLightHighlight:
+ color = [self gtm_colorByAdjustingLuminance:0.25];
+ color = [color blendedColorWithFraction:0.9 ofColor:[NSColor whiteColor]];
+ break;
+ case GTMColorationLightMidtone:
+ color = [self blendedColorWithFraction:0.8 ofColor:[NSColor whiteColor]];
+ break;
+ case GTMColorationLightShadow:
+ color = [self blendedColorWithFraction:0.7 ofColor:[NSColor whiteColor]];
+ color = [color gtm_colorByAdjustingLuminance:-0.02];
+ break;
+ case GTMColorationLightPenumbra:
+ color = [self blendedColorWithFraction:0.8 ofColor:[NSColor whiteColor]];
+ color = [color gtm_colorByAdjustingLuminance:-0.01];
+ break;
+ case GTMColorationDarkHighlight:
+ color = [self gtm_colorByAdjustingLuminance:-0.20];
+ break;
+ case GTMColorationDarkMidtone:
+ color = [self gtm_colorByAdjustingLuminance:-0.25];
+ break;
+ case GTMColorationDarkShadow:
+ color = [self gtm_colorByAdjustingLuminance:-0.30 saturation:1.4];
+ break;
+ case GTMColorationDarkPenumbra:
+ color = [self gtm_colorByAdjustingLuminance:-0.25];
+ break;
+ default:
+ _GTMDevLog(@"Invalid Coloration Use %d", use);
+ color = self;
+ break;
+ }
+ return color;
+}
+const CGFloat kDefaultFade = 0.3;
+
+- (NSColor *)gtm_colorAdjustedFor:(GTMColorationUse)use faded:(BOOL)fade {
+ NSColor *color = [self gtm_colorAdjustedFor:use];
+ if (fade) {
+ CGFloat luminance = [color gtm_luminance];
+ color = [color gtm_colorByAdjustingLuminance:
+ kDefaultFade * (1.0 - luminance)
+ saturation:kDefaultFade];
+ }
+ return color;
+}
+
+- (BOOL)gtm_isDarkColor {
+ return [self gtm_luminance] < kGTMLuminanceDarkCutoff;
+}
+
+- (NSColor *)gtm_legibleTextColor {
+ return [self gtm_isDarkColor] ? [NSColor whiteColor] : [NSColor blackColor];
+}
+
+@end
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
diff --git a/AppKit/GTMNSColor+LuminanceTest.m b/AppKit/GTMNSColor+LuminanceTest.m
new file mode 100644
index 0000000..82eb014
--- /dev/null
+++ b/AppKit/GTMNSColor+LuminanceTest.m
@@ -0,0 +1,72 @@
+//
+// GTMNSColor+LuminanceTest.m
+//
+// Copyright 2006-2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+#import "GTMSenTestCase.h"
+#import "GTMNSColor+Luminance.h"
+
+@interface GTMNSColor_LuminanceTest : GTMTestCase
+@end
+
+@implementation GTMNSColor_LuminanceTest
+
+- (void)testLuminance {
+ NSColor *midtone = [NSColor blueColor];
+ NSColor *darker = [midtone gtm_colorAdjustedFor:GTMColorationBaseShadow];
+ NSColor *lighter = [midtone gtm_colorAdjustedFor:GTMColorationBaseHighlight];
+ NSColor *lightest = [midtone gtm_colorAdjustedFor:GTMColorationLightHighlight];
+ NSColor *darkest = [midtone gtm_colorAdjustedFor:GTMColorationDarkShadow];
+
+ // The relationships of the other values are not set, so we don't test them yet
+ STAssertGreaterThanOrEqual([lightest gtm_luminance],
+ [lighter gtm_luminance], nil);
+ STAssertGreaterThanOrEqual([lighter gtm_luminance],
+ [midtone gtm_luminance], nil);
+ STAssertGreaterThanOrEqual([midtone gtm_luminance],
+ [darker gtm_luminance], nil);
+ STAssertGreaterThanOrEqual([darker gtm_luminance],
+ [darkest gtm_luminance], nil);
+ STAssertGreaterThanOrEqual([[NSColor whiteColor] gtm_luminance],
+ (CGFloat)0.95, nil);
+ STAssertGreaterThanOrEqual([[NSColor yellowColor] gtm_luminance],
+ (CGFloat)0.90, nil);
+ STAssertEqualsWithAccuracy([[NSColor blueColor] gtm_luminance],
+ (CGFloat)0.35, 0.10, nil);
+ STAssertEqualsWithAccuracy([[NSColor redColor] gtm_luminance],
+ (CGFloat)0.50, 0.10, nil);
+ STAssertLessThanOrEqual([[NSColor blackColor] gtm_luminance],
+ (CGFloat)0.30, nil);
+ STAssertTrue([[NSColor blackColor] gtm_isDarkColor], nil);
+ STAssertTrue([[NSColor blueColor] gtm_isDarkColor], nil);
+ STAssertTrue([[NSColor redColor] gtm_isDarkColor], nil);
+ STAssertTrue(![[NSColor whiteColor] gtm_isDarkColor], nil);
+ STAssertTrue(![[NSColor yellowColor] gtm_isDarkColor], nil);
+ STAssertGreaterThanOrEqual([[[NSColor blackColor] gtm_legibleTextColor]
+ gtm_luminance],
+ [[NSColor grayColor] gtm_luminance], nil);
+ STAssertLessThanOrEqual([[[NSColor whiteColor] gtm_legibleTextColor]
+ gtm_luminance],
+ [[NSColor grayColor] gtm_luminance], nil);
+}
+
+@end
+
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
diff --git a/AppKit/GTMTheme.h b/AppKit/GTMTheme.h
new file mode 100644
index 0000000..484e8dc
--- /dev/null
+++ b/AppKit/GTMTheme.h
@@ -0,0 +1,78 @@
+//
+// GTMTheme.h
+//
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import "GTMDefines.h"
+#import <Foundation/Foundation.h>
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+// Sent whenever the theme changes. Object => GTMTheme that changed
+GTM_EXTERN NSString *kGTMThemeDidChangeNotification;
+
+enum {
+ GTMThemeStyleTabBarSelected,
+ GTMThemeStyleTabBarDeselected,
+ GTMThemeStyleWindow,
+ GTMThemeStyleToolBar,
+ GTMThemeStyleToolBarButton,
+ GTMThemeStyleToolBarButtonPressed,
+ GTMThemeStyleBookmarksBar,
+};
+typedef NSUInteger GTMThemeStyle;
+
+// GTMTheme provides a range of values for procedural drawing of UI elements
+// based on interpolation of a single background color
+
+@interface GTMTheme : NSObject {
+ @private
+ NSColor *backgroundColor_; // bound to user defaults
+ NSImage *backgroundImage_; // bound to user defaults
+ NSMutableDictionary *values_; // cached values
+}
+
+// Access the global theme. By default this is bound to user defaults
++ (GTMTheme *)defaultTheme;
++ (void)setDefaultTheme:(GTMTheme *)theme;
+
+// returns base theme color
+- (NSColor *)backgroundColor;
+
+// base background color
+- (NSImage *)backgroundImage;
+
+// NSColor (or pattern color) for the background of the window
+- (NSColor *)windowBackgroundColor:(BOOL)active;
+
+// NSGradient for specific usage, active indicates whether the window is key
+- (NSGradient *)gradientForStyle:(GTMThemeStyle)style active:(BOOL)active;
+
+// Outline color for stroke, active indicates whether the window is key
+- (NSColor *)strokeColorForStyle:(GTMThemeStyle)style active:(BOOL)active;
+
+// Indicates whether luminance is dark or light
+- (BOOL)styleIsDark:(GTMThemeStyle)style active:(BOOL)active;
+
+// Background style for this style and state
+- (NSBackgroundStyle)interiorBackgroundStyleForStyle:(GTMThemeStyle)style
+ active:(BOOL)active;
+
+// NSColor version of the gradient (for window backgrounds, etc)
+- (NSColor *)patternColorForStyle:(GTMThemeStyle)style active:(BOOL)active;
+@end
+
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
diff --git a/AppKit/GTMTheme.m b/AppKit/GTMTheme.m
new file mode 100644
index 0000000..aa7c9be
--- /dev/null
+++ b/AppKit/GTMTheme.m
@@ -0,0 +1,390 @@
+//
+// GTMTheme.m
+//
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+#import "GTMTheme.h"
+#import "GTMNSColor+Luminance.h"
+
+static GTMTheme *gGTMDefaultTheme = nil;
+NSString *kGTMThemeDidChangeNotification = @"kGTMThemeDidChangeNotification";
+
+@interface GTMTheme ()
+- (void)bindToUserDefaults;
+- (void)sendChangeNotification;
+@end
+
+@implementation GTMTheme
+
++ (void)setDefaultTheme:(GTMTheme *)theme {
+ if (gGTMDefaultTheme != theme) {
+ [gGTMDefaultTheme release];
+ gGTMDefaultTheme = [theme retain];
+ [gGTMDefaultTheme sendChangeNotification];
+ }
+}
+
++ (GTMTheme *)defaultTheme {
+ @synchronized (self) {
+ if (!gGTMDefaultTheme) {
+ gGTMDefaultTheme = [[self alloc] init];
+ [gGTMDefaultTheme bindToUserDefaults];
+ }
+ }
+ return gGTMDefaultTheme;
+}
+
+- (void)bindToUserDefaults {
+ NSUserDefaultsController * controller
+ = [NSUserDefaultsController sharedUserDefaultsController];
+ [self bind:@"backgroundColor"
+ toObject:controller
+ withKeyPath:@"values.GTMThemeBackgroundColor"
+ options:[NSDictionary dictionaryWithObjectsAndKeys:
+ NSUnarchiveFromDataTransformerName,
+ NSValueTransformerNameBindingOption,
+ nil]];
+
+ [self bind:@"backgroundImage"
+ toObject:controller
+ withKeyPath:@"values.GTMThemeBackgroundImageData"
+ options:[NSDictionary dictionaryWithObjectsAndKeys:
+ NSUnarchiveFromDataTransformerName,
+ NSValueTransformerNameBindingOption,
+ nil]];
+}
+
+- (id)init {
+ self = [super init];
+ if (self != nil) {
+ values_ = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (void)finalize {
+ [self unbind:@"backgroundColor"];
+ [self unbind:@"backgroundImage"];
+ [super finalize];
+}
+
+- (void)dealloc {
+ [self unbind:@"backgroundColor"];
+ [self unbind:@"backgroundImage"];
+ [values_ release];
+ [super dealloc];
+}
+
+- (void)sendChangeNotification {
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kGTMThemeDidChangeNotification
+ object:self];
+}
+
+- (id)keyForSelector:(SEL)selector
+ style:(GTMThemeStyle)style
+ active:(BOOL)active {
+ return [NSString stringWithFormat:@"%p.%d.%d", selector, style, active];
+}
+
+- (id)valueForSelector:(SEL)selector
+ style:(GTMThemeStyle)style
+ active:(BOOL)active {
+ id value = [values_ objectForKey:
+ [self keyForSelector:selector style:style active:active]];
+ return value;
+}
+
+- (void)cacheValue:(id)value
+ forSelector:(SEL)selector
+ style:(GTMThemeStyle)style
+ active:(BOOL)active {
+ id key = [self keyForSelector:selector style:style active:active];
+ if (key && value) [values_ setObject:value forKey:key];
+}
+
+- (void)setBackgroundColor:(NSColor *)value {
+ if (backgroundColor_ != value) {
+ [backgroundColor_ release];
+ backgroundColor_ = [value retain];
+ [values_ removeAllObjects];
+ [self sendChangeNotification];
+ }
+}
+- (NSColor *)backgroundColor {
+ // For nil, we return a color that works with a normal textured window
+ if (!backgroundColor_)
+ return [NSColor colorWithCalibratedWhite:0.75 alpha:1.0];
+ return backgroundColor_;
+}
+
+- (void)setBackgroundImage:(NSImage *)value {
+ if (backgroundImage_ != value) {
+ [backgroundImage_ release];
+ backgroundImage_ = [value retain];
+ [self sendChangeNotification];
+ }
+}
+
+- (NSColor *)windowBackgroundColor:(BOOL)active {
+ NSColor *color = nil;
+ if (backgroundImage_) {
+ // TODO(alcor): dim images when disabled
+ color = [NSColor colorWithPatternImage:backgroundImage_];
+ } else if (backgroundColor_) {
+ color = [self patternColorForStyle:GTMThemeStyleWindow active:active];
+ }
+ return color;
+}
+
+- (NSImage *)backgroundImage {
+ return backgroundImage_;
+}
+
+- (NSBackgroundStyle)interiorBackgroundStyleForStyle:(GTMThemeStyle)style
+ active:(BOOL)active {
+ id value = [self valueForSelector:_cmd style:style active:active];
+ if (value) return [value intValue];
+
+ NSGradient *gradient = [self gradientForStyle:style active:active];
+ NSColor *color = [gradient interpolatedColorAtLocation:0.5];
+ BOOL dark = [color gtm_isDarkColor];
+ value = [NSNumber numberWithInt: dark ? NSBackgroundStyleLowered
+ : NSBackgroundStyleRaised];
+ [self cacheValue:value forSelector:_cmd style:style active:active];
+ return [value intValue];
+}
+
+- (BOOL)styleIsDark:(GTMThemeStyle)style active:(BOOL)active {
+ id value = [self valueForSelector:_cmd style:style active:active];
+ if (value) return [value boolValue];
+
+ if (style == GTMThemeStyleToolBarButtonPressed) {
+ value = [NSNumber numberWithBool:YES];
+ } else {
+ value = [NSNumber numberWithBool:[[self backgroundColor] gtm_isDarkColor]];
+ }
+ [self cacheValue:value forSelector:_cmd style:style active:active];
+ return [value boolValue];
+}
+
+- (NSColor *)patternColorForStyle:(GTMThemeStyle)style active:(BOOL)active {
+ NSColor *color = [self valueForSelector:_cmd style:style active:active];
+ if (color) return color;
+
+ NSGradient *gradient = [self gradientForStyle:style active:active];
+ if (gradient) {
+ // create a gradient image for the background
+ CGRect r = CGRectZero;
+ // TODO(alcor): figure out a better way to get an image that is the right
+ // size
+ r.size = CGSizeMake(4, 36);
+ size_t bytesPerRow = 4 * r.size.width;
+
+ CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+ CGContextRef context = CGBitmapContextCreate(NULL,
+ r.size.width,
+ r.size.height,
+ 8,
+ bytesPerRow,
+ space,
+ kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(space);
+ NSGraphicsContext *nsContext
+ = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:YES];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:nsContext];
+ [gradient drawInRect:NSMakeRect(0, 0, r.size.width, r.size.height)
+ angle:270];
+ [NSGraphicsContext restoreGraphicsState];
+
+ CGImageRef cgImage = CGBitmapContextCreateImage(context);
+ CGContextRelease(context);
+ NSBitmapImageRep *rep = nil;
+ if (cgImage) {
+ rep = [[[NSBitmapImageRep alloc] initWithCGImage:cgImage]
+ autorelease];
+ CGImageRelease(cgImage);
+ }
+
+ NSImage *image = [[[NSImage alloc] initWithSize:NSSizeFromCGSize(r.size)]
+ autorelease];
+ [image addRepresentation:rep];
+
+ color = [NSColor colorWithPatternImage:image];
+ }
+ [self cacheValue:color forSelector:_cmd style:style active:active];
+ return color;
+}
+
+- (NSGradient *)gradientForStyle:(GTMThemeStyle)style active:(BOOL)active {
+ NSGradient *gradient = [self valueForSelector:_cmd style:style active:active];
+ if (gradient) return gradient;
+
+ BOOL useDarkColors = backgroundImage_ != nil || style == GTMThemeStyleWindow;
+
+ NSUInteger uses[4];
+ if (useDarkColors) {
+ uses[0] = GTMColorationBaseHighlight;
+ uses[1] = GTMColorationBaseMidtone;
+ uses[2] = GTMColorationBaseShadow;
+ uses[3] = GTMColorationBasePenumbra;
+ } else {
+ uses[0] = GTMColorationLightHighlight;
+ uses[1] = GTMColorationLightMidtone;
+ uses[2] = GTMColorationLightShadow;
+ uses[3] = GTMColorationLightPenumbra;
+ }
+ NSColor *backgroundColor = [self backgroundColor];
+ switch (style) {
+ case GTMThemeStyleTabBarDeselected: {
+ NSColor *startColor = [[backgroundColor gtm_colorAdjustedFor:uses[2]
+ faded:!active]
+ colorWithAlphaComponent:0.667];
+ NSColor *endColor = [[backgroundColor gtm_colorAdjustedFor:uses[2]
+ faded:!active]
+ colorWithAlphaComponent:0.667];
+
+ gradient = [[[NSGradient alloc] initWithStartingColor:startColor
+ endingColor:endColor]
+ autorelease];
+ break;
+ }
+ case GTMThemeStyleTabBarSelected: {
+ NSColor *startColor = [backgroundColor gtm_colorAdjustedFor:uses[0]
+ faded:!active];
+ NSColor *endColor = [backgroundColor gtm_colorAdjustedFor:uses[1]
+ faded:!active];
+ gradient = [[[NSGradient alloc] initWithStartingColor:startColor
+ endingColor:endColor]
+ autorelease];
+ break;
+ }
+ case GTMThemeStyleWindow: {
+ CGFloat luminance = [backgroundColor gtm_luminance];
+
+ // Adjust luminance so it never hits black
+ if (luminance < 0.5) {
+ CGFloat adjustment = (0.5 - luminance) / 1.5;
+ backgroundColor
+ = [backgroundColor gtm_colorByAdjustingLuminance:adjustment];
+ }
+ NSColor *startColor = [backgroundColor gtm_colorAdjustedFor:uses[1]
+ faded:!active];
+ NSColor *endColor = [backgroundColor gtm_colorAdjustedFor:uses[2]
+ faded:!active];
+
+
+ if (!active) {
+ startColor = [startColor gtm_colorByAdjustingLuminance:0.1
+ saturation:0.5];
+ endColor = [endColor gtm_colorByAdjustingLuminance:0.1
+ saturation:0.5];
+
+ }
+ gradient = [[[NSGradient alloc] initWithStartingColor:startColor
+ endingColor:endColor]
+ autorelease];
+ break;
+ }
+ case GTMThemeStyleToolBar: {
+ NSColor *startColor = [backgroundColor gtm_colorAdjustedFor:uses[1]
+ faded:!active];
+ NSColor *endColor = [backgroundColor gtm_colorAdjustedFor:uses[2]
+ faded:!active];
+ gradient = [[[NSGradient alloc] initWithStartingColor:startColor
+ endingColor:endColor]
+ autorelease];
+ break;
+ }
+ case GTMThemeStyleToolBarButton: {
+ NSColor *startColor = [backgroundColor gtm_colorAdjustedFor:uses[0]
+ faded:!active];
+ NSColor *midColor = [backgroundColor gtm_colorAdjustedFor:uses[1]
+ faded:!active];
+ NSColor *endColor = [backgroundColor gtm_colorAdjustedFor:uses[2]
+ faded:!active];
+ NSColor *glowColor = [backgroundColor gtm_colorAdjustedFor:uses[3]
+ faded:!active];
+
+ gradient = [[[NSGradient alloc] initWithColorsAndLocations:
+ startColor, 0.0,
+ midColor, 0.5,
+ endColor, 0.9,
+ glowColor, 1.0,
+ nil] autorelease];
+ break;
+ }
+ case GTMThemeStyleToolBarButtonPressed: {
+ NSColor *startColor = [backgroundColor
+ gtm_colorAdjustedFor:GTMColorationBaseShadow
+ faded:!active];
+ NSColor *endColor = [backgroundColor
+ gtm_colorAdjustedFor:GTMColorationBaseMidtone
+ faded:!active];
+ gradient = [[[NSGradient alloc] initWithStartingColor:startColor
+ endingColor:endColor]
+ autorelease];
+ break;
+ }
+ case GTMThemeStyleBookmarksBar: {
+ NSColor *startColor = [backgroundColor gtm_colorAdjustedFor:uses[2]
+ faded:!active];
+ NSColor *endColor = [backgroundColor gtm_colorAdjustedFor:uses[3]
+ faded:!active];
+
+ gradient = [[[NSGradient alloc] initWithStartingColor:startColor
+ endingColor:endColor]
+ autorelease];
+
+ break;
+ }
+ default:
+ _GTMDevLog(@"Unexpected style: %d", style);
+ break;
+ }
+
+ [self cacheValue:gradient forSelector:_cmd style:style active:active];
+ return gradient;
+}
+
+- (NSColor *)strokeColorForStyle:(GTMThemeStyle)style active:(BOOL)active {
+ NSColor *color = [self valueForSelector:_cmd style:style active:active];
+ if (color) return color;
+ NSColor *backgroundColor = [self backgroundColor];
+ switch (style) {
+ case GTMThemeStyleToolBarButton:
+ color = [[backgroundColor gtm_colorAdjustedFor:GTMColorationDarkShadow
+ faded:!active]
+ colorWithAlphaComponent:0.3];
+ break;
+ case GTMThemeStyleToolBar:
+ case GTMThemeStyleBookmarksBar:
+ default:
+ color = [[self backgroundColor] gtm_colorAdjustedFor:GTMColorationBaseShadow
+ faded:!active];
+ break;
+ }
+
+ [self cacheValue:color forSelector:_cmd style:style active:active];
+ return color;
+}
+
+@end
+
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
diff --git a/AppKit/GTMThemeTest.m b/AppKit/GTMThemeTest.m
new file mode 100644
index 0000000..3e37c1f
--- /dev/null
+++ b/AppKit/GTMThemeTest.m
@@ -0,0 +1,67 @@
+//
+// GTMThemeTest.m
+//
+// Copyright 2009 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//
+
+#import <Cocoa/Cocoa.h>
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
+
+#import "GTMSenTestCase.h"
+#import "GTMTheme.h"
+
+@interface GTMThemeTest : GTMTestCase
+@end
+
+@implementation GTMThemeTest
+
+- (void)testTheming {
+ GTMTheme *theme = [GTMTheme defaultTheme];
+
+ // When there are no values, use window default colors
+ STAssertEqualObjects([theme backgroundColor],
+ [NSColor colorWithCalibratedWhite:0.75 alpha:1.0], nil);
+ STAssertNil([theme windowBackgroundColor:YES], nil);
+ STAssertNil([theme backgroundImage], nil);
+
+ NSColor *color = [NSColor redColor];
+ NSData *colorData = [NSArchiver archivedDataWithRootObject:color];
+ [[NSUserDefaults standardUserDefaults] setObject:colorData
+ forKey:@"GTMThemeBackgroundColor"];
+
+ STAssertNotNil([theme windowBackgroundColor:YES], nil);
+ STAssertNotNil([theme patternColorForStyle:GTMThemeStyleToolBar
+ active:YES], nil);
+ STAssertNotNil([theme strokeColorForStyle:GTMThemeStyleToolBar
+ active:YES], nil);
+ STAssertNotNil([theme gradientForStyle:GTMThemeStyleToolBar
+ active:YES], nil);
+
+ STAssertEqualObjects([theme backgroundColor],
+ color, nil);
+
+ // TODO(alcor): add more of these cases once the constants are more concrete
+ STAssertEquals([theme interiorBackgroundStyleForStyle:GTMThemeStyleToolBar
+ active:YES],
+ (NSBackgroundStyle)NSBackgroundStyleRaised, nil);
+
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:
+ @"GTMThemeBackgroundColor"];
+}
+
+@end
+
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj
index b76fa17..8812f4b 100644
--- a/GTM.xcodeproj/project.pbxproj
+++ b/GTM.xcodeproj/project.pbxproj
@@ -59,6 +59,12 @@
7F3EB3940E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */; };
7F3EB5540E5F0B0400A7A75E /* GTMUnitTestingImage.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */; };
7F3EB5870E5F0CBB00A7A75E /* GTMLargeTypeWindowTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */; };
+ 7F511DF90F4B0378009F41B6 /* GTMNSColor+Luminance.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F511DF30F4B0378009F41B6 /* GTMNSColor+Luminance.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 7F511DFA0F4B0378009F41B6 /* GTMNSColor+Luminance.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F511DF40F4B0378009F41B6 /* GTMNSColor+Luminance.m */; };
+ 7F511DFC0F4B0378009F41B6 /* GTMTheme.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F511DF60F4B0378009F41B6 /* GTMTheme.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 7F511DFE0F4B0378009F41B6 /* GTMTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F511DF80F4B0378009F41B6 /* GTMTheme.m */; };
+ 7F511E010F4B03B4009F41B6 /* GTMNSColor+LuminanceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F511DF50F4B0378009F41B6 /* GTMNSColor+LuminanceTest.m */; };
+ 7F511E020F4B03BC009F41B6 /* GTMThemeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F511DF70F4B0378009F41B6 /* GTMThemeTest.m */; };
84978FBC0EA6A67C00C5DC83 /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = F48FE2930D198D24009257D2 /* GTMSystemVersion.m */; };
84B91B8B0EA3CC2E0087500F /* GTMUnitTestingImage.10.6.0.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 84B91B890EA3CC2E0087500F /* GTMUnitTestingImage.10.6.0.tiff */; };
84B91B8C0EA3CC2E0087500F /* GTMUnitTestingWindow.10.6.0.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 84B91B8A0EA3CC2E0087500F /* GTMUnitTestingWindow.10.6.0.tiff */; };
@@ -357,6 +363,12 @@
7F3EB38C0E5E09C700A7A75E /* GTMNSImage+Scaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSImage+Scaling.h"; sourceTree = "<group>"; };
7F3EB38D0E5E09C700A7A75E /* GTMNSImage+Scaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSImage+Scaling.m"; sourceTree = "<group>"; };
7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSImage+ScalingTest.m"; sourceTree = "<group>"; };
+ 7F511DF30F4B0378009F41B6 /* GTMNSColor+Luminance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSColor+Luminance.h"; sourceTree = "<group>"; };
+ 7F511DF40F4B0378009F41B6 /* GTMNSColor+Luminance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+Luminance.m"; sourceTree = "<group>"; };
+ 7F511DF50F4B0378009F41B6 /* GTMNSColor+LuminanceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSColor+LuminanceTest.m"; sourceTree = "<group>"; };
+ 7F511DF60F4B0378009F41B6 /* GTMTheme.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMTheme.h; sourceTree = "<group>"; };
+ 7F511DF70F4B0378009F41B6 /* GTMThemeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMThemeTest.m; sourceTree = "<group>"; };
+ 7F511DF80F4B0378009F41B6 /* GTMTheme.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMTheme.m; sourceTree = "<group>"; };
848269C80E9FF4BD006E6D9C /* DebugSnowLeopardOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DebugSnowLeopardOrLater.xcconfig; sourceTree = "<group>"; };
848269C90E9FF4BD006E6D9C /* ReleaseSnowLeopardOrLater.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ReleaseSnowLeopardOrLater.xcconfig; sourceTree = "<group>"; };
84B91B890EA3CC2E0087500F /* GTMUnitTestingImage.10.6.0.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = GTMUnitTestingImage.10.6.0.tiff; sourceTree = "<group>"; };
@@ -789,12 +801,18 @@
F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */,
F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */,
F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */,
+ 7F511DF30F4B0378009F41B6 /* GTMNSColor+Luminance.h */,
+ 7F511DF40F4B0378009F41B6 /* GTMNSColor+Luminance.m */,
+ 7F511DF50F4B0378009F41B6 /* GTMNSColor+LuminanceTest.m */,
7F3EB38C0E5E09C700A7A75E /* GTMNSImage+Scaling.h */,
7F3EB38D0E5E09C700A7A75E /* GTMNSImage+Scaling.m */,
7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */,
8BA01B5B0F144BD800926923 /* GTMNSWorkspace+Running.m */,
8BA01B5C0F144BD800926923 /* GTMNSWorkspace+Running.h */,
8BA01B5F0F144BE500926923 /* GTMNSWorkspace+RunningTest.m */,
+ 7F511DF60F4B0378009F41B6 /* GTMTheme.h */,
+ 7F511DF80F4B0378009F41B6 /* GTMTheme.m */,
+ 7F511DF70F4B0378009F41B6 /* GTMThemeTest.m */,
F47F1C740D490E5C00925B8F /* GTMShading.h */,
F435E4840DC8F3DC0069CDE8 /* TestData */,
);
@@ -1061,6 +1079,8 @@
8BA01B5E0F144BD800926923 /* GTMNSWorkspace+Running.h in Headers */,
8B6C15930F356E6400E51E5D /* GTMNSObject+KeyValueObserving.h in Headers */,
1012DF560F4252BD004794DB /* GTMAbstractDOListener.h in Headers */,
+ 7F511DF90F4B0378009F41B6 /* GTMNSColor+Luminance.h in Headers */,
+ 7F511DFC0F4B0378009F41B6 /* GTMTheme.h in Headers */,
10998E920F4B5952007F179D /* GTMTransientRootProxy.h in Headers */,
10998EF50F4B5D1A007F179D /* GTMTransientRootSocketProxy.h in Headers */,
);
@@ -1465,6 +1485,8 @@
8BA01B5D0F144BD800926923 /* GTMNSWorkspace+Running.m in Sources */,
8B6C15940F356E6400E51E5D /* GTMNSObject+KeyValueObserving.m in Sources */,
1012DF570F4252BD004794DB /* GTMAbstractDOListener.m in Sources */,
+ 7F511DFA0F4B0378009F41B6 /* GTMNSColor+Luminance.m in Sources */,
+ 7F511DFE0F4B0378009F41B6 /* GTMTheme.m in Sources */,
10998E8F0F4B593E007F179D /* GTMTransientRootProxy.m in Sources */,
10998EF40F4B5D1A007F179D /* GTMTransientRootSocketProxy.m in Sources */,
);
@@ -1474,6 +1496,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 7F511E010F4B03B4009F41B6 /* GTMNSColor+LuminanceTest.m in Sources */,
+ 7F511E020F4B03BC009F41B6 /* GTMThemeTest.m in Sources */,
F42E082F0D19991400D5DDE0 /* GTMNSBezierPath+RoundRectTest.m in Sources */,
F42E09450D199BA400D5DDE0 /* GTMNSObject+UnitTesting.m in Sources */,
F43DCEC70D47BEA000959A62 /* GTMLoginItemsTest.m in Sources */,
diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index 96fb3ce..90c51ec 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -253,6 +253,10 @@ Changes since 1.5.1
- Cleaned up GTM so that it passes the Clang checker without any warnings.
+- Added GTMLuminance for working with colors in HSL space easily.
+
+- Added GTMTheme for doing product wide theme modifications.
+
Release 1.5.1
Changes since 1.5.0