diff options
author | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2009-03-05 22:03:40 +0000 |
---|---|---|
committer | thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2009-03-05 22:03:40 +0000 |
commit | e1bcf5d6a4b813a63d42e6ccb37ca8054775d65b (patch) | |
tree | 370ae297052052fb9576f0c14e0a9243f8a8bc86 | |
parent | d4f38903d60f8b01c419f5056c2fb3cd4c6c6e60 (diff) |
- Added GTMLuminance for working with colors in HSL space easily.
- Added GTMTheme for doing product wide theme modifications.
-rw-r--r-- | AppKit/GTMNSColor+Luminance.h | 65 | ||||
-rw-r--r-- | AppKit/GTMNSColor+Luminance.m | 153 | ||||
-rw-r--r-- | AppKit/GTMNSColor+LuminanceTest.m | 72 | ||||
-rw-r--r-- | AppKit/GTMTheme.h | 78 | ||||
-rw-r--r-- | AppKit/GTMTheme.m | 390 | ||||
-rw-r--r-- | AppKit/GTMThemeTest.m | 67 | ||||
-rw-r--r-- | GTM.xcodeproj/project.pbxproj | 24 | ||||
-rw-r--r-- | ReleaseNotes.txt | 4 |
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 |