mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
1735 lines
53 KiB
Plaintext
1735 lines
53 KiB
Plaintext
/*
|
|
* CPColor.j
|
|
* AppKit
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2008, 280 North, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import <Foundation/CPObject.j>
|
|
@import "CPGraphicsContext.j"
|
|
|
|
@import "CGColor.j"
|
|
|
|
@import "_CPObject+Theme.j"
|
|
@import "CPCompatibility.j"
|
|
@import "CPImage.j"
|
|
|
|
|
|
/*!
|
|
Orientation to use with \c CPColorPattern for vertical patterns.
|
|
*/
|
|
CPColorPatternIsVertical = YES;
|
|
|
|
/*!
|
|
Orientation to use with \c CPColorPattern for horizontal patterns.
|
|
*/
|
|
CPColorPatternIsHorizontal = NO;
|
|
|
|
/// @cond IGNORE
|
|
|
|
var _redComponent = 0,
|
|
_greenComponent = 1,
|
|
_blueComponent = 2,
|
|
_alphaCompnent = 3;
|
|
|
|
var _hueComponent = 0,
|
|
_saturationComponent = 1,
|
|
_brightnessComponent = 2;
|
|
|
|
var cachedBlackColor,
|
|
cachedRedColor,
|
|
cachedGreenColor,
|
|
cachedBlueColor,
|
|
cachedYellowColor,
|
|
cachedGrayColor,
|
|
cachedLightGrayColor,
|
|
cachedDarkGrayColor,
|
|
cachedWhiteColor,
|
|
cachedBrownColor,
|
|
cachedCyanColor,
|
|
cachedMagentaColor,
|
|
cachedOrangeColor,
|
|
cachedPurpleColor,
|
|
cachedShadowColor,
|
|
cachedClearColor,
|
|
cachedThemeColor;
|
|
|
|
/// @endcond
|
|
|
|
/*!
|
|
@ingroup appkit
|
|
|
|
\c CPColor can be used to represent color
|
|
in an RGB or HSB model with an optional transparency value.</p>
|
|
|
|
<p>It also provides some class helper methods that
|
|
returns instances of commonly used colors.</p>
|
|
*/
|
|
@implementation CPColor : CPObject <CPTheme>
|
|
{
|
|
CPArray _components;
|
|
|
|
CPImage _patternImage;
|
|
CPString _cssString;
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Theming
|
|
|
|
+ (CPString)defaultThemeClass
|
|
{
|
|
return "color";
|
|
}
|
|
|
|
+ (CPDictionary)themeAttributes
|
|
{
|
|
return @{
|
|
@"alternate-selected-control-color": [CPNull null],
|
|
@"secondary-selected-control-color": [CPNull null],
|
|
@"selected-text-background-color": [CPNull null],
|
|
@"selected-text-inactive-background-color": [CPNull null],
|
|
@"css-based": NO
|
|
};
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Static methods
|
|
|
|
/*!
|
|
Creates a color in the RGB colorspace, with an alpha value.
|
|
Each component should be between the range of 0.0 to 1.0. For
|
|
the alpha component, a value of 1.0 is opaque, and 0.0 means
|
|
completely transparent.
|
|
|
|
@param red the red component of the color
|
|
@param green the green component of the color
|
|
@param blue the blue component of the color
|
|
@param alpha the alpha component
|
|
|
|
@return a color initialized to the values specified
|
|
*/
|
|
+ (CPColor)colorWithRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
|
|
{
|
|
return [[CPColor alloc] _initWithRGBA:[MAX(0.0, MIN(1.0, red)), MAX(0.0, MIN(1.0, green)), MAX(0.0, MIN(1.0, blue)), MAX(0.0, MIN(1.0, alpha))]];
|
|
}
|
|
|
|
/*!
|
|
@deprecated in favor of colorWithRed:green:blue:alpha:
|
|
|
|
Creates a color in the RGB colorspace, with an alpha value.
|
|
Each component should be between the range of 0.0 to 1.0. For
|
|
the alpha component, a value of 1.0 is opaque, and 0.0 means
|
|
completely transparent.
|
|
|
|
@param red the red component of the color
|
|
@param green the green component of the color
|
|
@param blue the blue component of the color
|
|
@param alpha the alpha component
|
|
|
|
@return a color initialized to the values specified
|
|
*/
|
|
+ (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
|
|
{
|
|
return [self colorWithRed:red green:green blue:blue alpha:alpha];
|
|
}
|
|
|
|
|
|
/*!
|
|
Creates a new color object with \c white for the RGB components.
|
|
For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent.
|
|
|
|
@param white a float between 0.0 and 1.0
|
|
@param alpha the alpha component between 0.0 and 1.0
|
|
|
|
@return a color initialized to the values specified
|
|
*/
|
|
+ (CPColor)colorWithWhite:(float)white alpha:(float)alpha
|
|
{
|
|
return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]];
|
|
}
|
|
|
|
/*!
|
|
@deprecated in favor of colorWithWhite:alpha:
|
|
|
|
Creates a new color object with \c white for the RGB components.
|
|
For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent.
|
|
|
|
@param white a float between 0.0 and 1.0
|
|
@param alpha the alpha component between 0.0 and 1.0
|
|
|
|
@return a color initialized to the values specified
|
|
*/
|
|
+ (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha
|
|
{
|
|
return [self colorWithWhite:white alpha:alpha];
|
|
}
|
|
|
|
/*!
|
|
Creates a new color based on the given HSB components.
|
|
|
|
Note: earlier versions of this method took a hue component as degrees between 0-360,
|
|
and saturation and brightness components as percent between 0-100. This method has
|
|
now been corrected to take all components in the 0-1 range as in Cocoa.
|
|
|
|
@param hue the hue component (0.0-1.0)
|
|
@param saturation the saturation component (0.0-1.0)
|
|
@param brightness the brightness component (0.0-1.0)
|
|
|
|
@return the initialized color
|
|
*/
|
|
+ (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness
|
|
{
|
|
return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:1.0];
|
|
}
|
|
|
|
/*!
|
|
Calibrated colors are not supported in Cappuccino.
|
|
|
|
This method has the same result as [CPColor colorWithHue:saturation:brightness:alpha:].
|
|
*/
|
|
+ (CPColor)colorWithCalibratedHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
|
|
{
|
|
return [self colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
|
|
}
|
|
|
|
/*!
|
|
Creates a new color based on the given HSB components.
|
|
|
|
Note: earlier versions of this method took a hue component as degrees between 0-360,
|
|
and saturation and brightness components as percent between 0-100. This method has
|
|
now been corrected to take all components in the 0-1 range as in Cocoa.
|
|
|
|
@param hue the hue component (0.0-1.0)
|
|
@param saturation the saturation component (0.0-1.0)
|
|
@param brightness the brightness component (0.0-1.0)
|
|
@param alpha the opacity component (0.0-1.0)
|
|
|
|
@return the initialized color
|
|
*/
|
|
+ (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness alpha:(float)alpha
|
|
{
|
|
// Clamp values.
|
|
hue = MAX(MIN(hue, 1.0), 0.0);
|
|
saturation = MAX(MIN(saturation, 1.0), 0.0);
|
|
brightness = MAX(MIN(brightness, 1.0), 0.0);
|
|
|
|
if (saturation === 0.0)
|
|
return [CPColor colorWithCalibratedWhite:brightness alpha:alpha];
|
|
|
|
var f = (hue * 360) % 60,
|
|
p = (brightness * (1 - saturation)),
|
|
q = (brightness * (60 - saturation * f)) / 60,
|
|
t = (brightness * (60 - saturation * (60 - f))) / 60,
|
|
b = brightness;
|
|
|
|
switch (FLOOR(hue * 6))
|
|
{
|
|
case 0:
|
|
case 6:
|
|
return [CPColor colorWithCalibratedRed:b green:t blue:p alpha:alpha];
|
|
case 1:
|
|
return [CPColor colorWithCalibratedRed:q green:b blue:p alpha:alpha];
|
|
case 2:
|
|
return [CPColor colorWithCalibratedRed:p green:b blue:t alpha:alpha];
|
|
case 3:
|
|
return [CPColor colorWithCalibratedRed:p green:q blue:b alpha:alpha];
|
|
case 4:
|
|
return [CPColor colorWithCalibratedRed:t green:p blue:b alpha:alpha];
|
|
case 5:
|
|
return [CPColor colorWithCalibratedRed:b green:p blue:q alpha:alpha];
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Creates an RGB color from a hexadecimal string. For example,
|
|
the a string of "FFFFFF" would return a white CPColor.
|
|
"FF0000" would return a pure red, "00FF00" would return a
|
|
pure blue, and "0000FF" would return a pure green.
|
|
|
|
@param hex a 6 character long string of hex
|
|
|
|
@return an initialized RGB color
|
|
*/
|
|
+ (CPColor)colorWithHexString:(string)hex
|
|
{
|
|
var rgba = hexToRGB(hex);
|
|
return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
|
|
}
|
|
|
|
/*!
|
|
Creates a color in the sRGB colorspace with the given components and alpha values.
|
|
Values below 0.0 are treated as 0.0 and values above 1.0 are treated as 1.0.
|
|
*/
|
|
+ (CPColor)colorWithSRGBRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
|
|
{
|
|
// TODO If Cappuccino is ported to a colorspace aware platform, this color should be in
|
|
// sRGBColorSpace.
|
|
return [self colorWithRed:red green:green blue:blue alpha:alpha];
|
|
}
|
|
|
|
/*!
|
|
Returns a black color object. (RGBA=[0.0, 0.0, 0.0, 1.0])
|
|
*/
|
|
+ (CPColor)blackColor
|
|
{
|
|
if (!cachedBlackColor)
|
|
cachedBlackColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]];
|
|
|
|
return cachedBlackColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a blue color object. (RGBA=[0.0, 0.0, 1.0, 1.0])
|
|
*/
|
|
+ (CPColor)blueColor
|
|
{
|
|
if (!cachedBlueColor)
|
|
cachedBlueColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]];
|
|
|
|
return cachedBlueColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a dark gray color object. (RGBA=[0.33 ,0.33, 0.33, 1.0])
|
|
*/
|
|
+ (CPColor)darkGrayColor
|
|
{
|
|
if (!cachedDarkGrayColor)
|
|
cachedDarkGrayColor = [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0];
|
|
|
|
return cachedDarkGrayColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a gray color object. (RGBA=[0.5, 0.5, 0.5, 1.0])
|
|
*/
|
|
+ (CPColor)grayColor
|
|
{
|
|
if (!cachedGrayColor)
|
|
cachedGrayColor = [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0];
|
|
|
|
return cachedGrayColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a green color object. (RGBA=[0.0, 1.0, 0.0, 1.0])
|
|
*/
|
|
+ (CPColor)greenColor
|
|
{
|
|
if (!cachedGreenColor)
|
|
cachedGreenColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]];
|
|
|
|
return cachedGreenColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a light gray color object (RGBA=[0.66, 0.66, 0.66, 1.0])
|
|
*/
|
|
+ (CPColor)lightGrayColor
|
|
{
|
|
if (!cachedLightGrayColor)
|
|
cachedLightGrayColor = [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0];
|
|
|
|
return cachedLightGrayColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a red color object (RGBA=[1.0, 0.0, 0.0, 1.0])
|
|
*/
|
|
+ (CPColor)redColor
|
|
{
|
|
if (!cachedRedColor)
|
|
cachedRedColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]];
|
|
|
|
return cachedRedColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a white color object (RGBA=[1.0, 1.0, 1.0, 1.0])
|
|
*/
|
|
+ (CPColor)whiteColor
|
|
{
|
|
if (!cachedWhiteColor)
|
|
cachedWhiteColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]];
|
|
|
|
return cachedWhiteColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a yellow color object (RGBA=[1.0, 1.0, 0.0, 1.0])
|
|
*/
|
|
+ (CPColor)yellowColor
|
|
{
|
|
if (!cachedYellowColor)
|
|
cachedYellowColor = [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]];
|
|
|
|
return cachedYellowColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a brown color object (RGBA=[0.6, 0.4, 0.2, 1.0])
|
|
*/
|
|
+ (CPColor)brownColor
|
|
{
|
|
if (!cachedBrownColor)
|
|
cachedBrownColor = [[CPColor alloc] _initWithRGBA:[0.6, 0.4, 0.2, 1.0]];
|
|
|
|
return cachedBrownColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a cyan color object (RGBA=[0.0, 1.0, 1.0, 1.0])
|
|
*/
|
|
+ (CPColor)cyanColor
|
|
{
|
|
if (!cachedCyanColor)
|
|
cachedCyanColor = [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 1.0, 1.0]];
|
|
|
|
return cachedCyanColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a magenta color object (RGBA=[1.0, 0.0, 1.0, 1.0])
|
|
*/
|
|
+ (CPColor)magentaColor
|
|
{
|
|
if (!cachedMagentaColor)
|
|
cachedMagentaColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 1.0, 1.0]];
|
|
|
|
return cachedMagentaColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a orange color object (RGBA=[1.0, 0.5, 0.0, 1.0])
|
|
*/
|
|
+ (CPColor)orangeColor
|
|
{
|
|
if (!cachedOrangeColor)
|
|
cachedOrangeColor = [[CPColor alloc] _initWithRGBA:[1.0, 0.5, 0.0, 1.0]];
|
|
|
|
return cachedOrangeColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a purple color object (RGBA=[0.5, 0.0, 0.5, 1.0])
|
|
*/
|
|
+ (CPColor)purpleColor
|
|
{
|
|
if (!cachedPurpleColor)
|
|
cachedPurpleColor = [[CPColor alloc] _initWithRGBA:[0.5, 0.0, 0.5, 1.0]];
|
|
|
|
return cachedPurpleColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a shadow looking color (RGBA=[0.0, 0.0, 0.0, 0.33])
|
|
*/
|
|
|
|
+ (CPColor)shadowColor
|
|
{
|
|
if (!cachedShadowColor)
|
|
cachedShadowColor = [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]];
|
|
|
|
return cachedShadowColor;
|
|
}
|
|
|
|
/*!
|
|
Returns a clear color (RGBA=[0.0, 0.0, 0.0, 0.0])
|
|
*/
|
|
|
|
+ (CPColor)clearColor
|
|
{
|
|
if (!cachedClearColor)
|
|
cachedClearColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
|
|
|
|
return cachedClearColor;
|
|
}
|
|
|
|
+ (CPColor)_cachedThemeColor
|
|
{
|
|
if (!cachedThemeColor)
|
|
cachedThemeColor = [self colorWithCalibratedWhite:0.0 alpha:0.0];
|
|
|
|
return cachedThemeColor;
|
|
}
|
|
|
|
+ (CPColor)alternateSelectedControlColor
|
|
{
|
|
return [[self _cachedThemeColor] valueForThemeAttribute:@"alternate-selected-control-color"];
|
|
}
|
|
|
|
+ (CPColor)secondarySelectedControlColor
|
|
{
|
|
return [[self _cachedThemeColor] valueForThemeAttribute:@"secondary-selected-control-color"];
|
|
}
|
|
|
|
/*!
|
|
Creates a color using a tile pattern with \c anImage
|
|
@param the image to tile
|
|
@return a tiled image color object
|
|
*/
|
|
+ (CPColor)colorWithPatternImage:(CPImage)anImage
|
|
{
|
|
return [[CPColor alloc] _initWithPatternImage:anImage];
|
|
}
|
|
|
|
/*!
|
|
Creates a CPColor from a valid CSS RGB string. Example, "rgb(32,64,129)".
|
|
|
|
@param aString a CSS color string
|
|
@return a color initialized to the value in the css string
|
|
*/
|
|
+ (CPColor)colorWithCSSString:(CPString)aString
|
|
{
|
|
return [[CPColor alloc] _initWithCSSString: aString];
|
|
}
|
|
|
|
+ (CPColor)selectedTextBackgroundColor
|
|
{
|
|
return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-background-color"] || [CPColor colorWithHexString:"99CCFF"];
|
|
}
|
|
|
|
+ (CPColor)_selectedTextBackgroundColorUnfocussed
|
|
{
|
|
return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-inactive-background-color"] || [CPColor colorWithHexString:"CCCCCC"];
|
|
}
|
|
|
|
/* @ignore */
|
|
- (id)_initWithCSSString:(CPString)aString
|
|
{
|
|
if (aString.indexOf("rgb") == CPNotFound)
|
|
return nil;
|
|
|
|
self = [super init];
|
|
|
|
var startingIndex = aString.indexOf("("),
|
|
parts = aString.substring(startingIndex + 1).split(',');
|
|
|
|
_components = [
|
|
parseInt(parts[0], 10) / 255.0,
|
|
parseInt(parts[1], 10) / 255.0,
|
|
parseInt(parts[2], 10) / 255.0,
|
|
parts[3] ? parseFloat(parts[3], 10) : 1.0
|
|
];
|
|
|
|
// We can't reuse aString as _cssString because the browser might not support the `rgba` syntax, and aString might
|
|
// use it (issue #1413.)
|
|
[self _initCSSStringFromComponents];
|
|
|
|
_theme = [CPTheme defaultTheme];
|
|
_themeState = CPThemeStateNormal;
|
|
[self _loadThemeAttributes];
|
|
|
|
return self;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (id)_initWithRGBA:(CPArray)components
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_components = components;
|
|
|
|
[self _initCSSStringFromComponents];
|
|
|
|
_theme = [CPTheme defaultTheme];
|
|
_themeState = CPThemeStateNormal;
|
|
[self _loadThemeAttributes];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)_initCSSStringFromComponents
|
|
{
|
|
var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
|
|
|
|
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
|
|
parseInt(_components[0] * 255.0) + ", " +
|
|
parseInt(_components[1] * 255.0) + ", " +
|
|
parseInt(_components[2] * 255.0) +
|
|
(hasAlpha ? (", " + _components[3]) : "") + ")";
|
|
}
|
|
|
|
/* @ignore */
|
|
- (id)_initWithPatternImage:(CPImage)anImage
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_patternImage = anImage;
|
|
_cssString = "url(\"" + [_patternImage filename] + "\")";
|
|
_components = [0.0, 0.0, 0.0, 1.0];
|
|
|
|
_theme = [CPTheme defaultTheme];
|
|
_themeState = CPThemeStateNormal;
|
|
[self _loadThemeAttributes];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Returns the image being used as the pattern for the tile in this color.
|
|
*/
|
|
- (CPImage)patternImage
|
|
{
|
|
return _patternImage;
|
|
}
|
|
|
|
/*!
|
|
Returns the alpha component of this color.
|
|
*/
|
|
- (float)alphaComponent
|
|
{
|
|
return _components[3];
|
|
}
|
|
|
|
/*!
|
|
Returns the blue component of this color.
|
|
*/
|
|
- (float)blueComponent
|
|
{
|
|
return _components[2];
|
|
}
|
|
|
|
/*!
|
|
Returns the green component of this color.
|
|
*/
|
|
- (float)greenComponent
|
|
{
|
|
return _components[1];
|
|
}
|
|
|
|
/*!
|
|
Return the red component of this color.
|
|
*/
|
|
- (float)redComponent
|
|
{
|
|
return _components[0];
|
|
}
|
|
|
|
/*!
|
|
Returns the RGBA components of this color in an array.
|
|
The index values are ordered as:
|
|
<pre>
|
|
<b>Index</b> <b>Component</b>
|
|
0 Red
|
|
1 Green
|
|
2 Blue
|
|
3 Alpha
|
|
</pre>
|
|
*/
|
|
- (CPArray)components
|
|
{
|
|
return _components;
|
|
}
|
|
|
|
/*!
|
|
Returns a new color with the same RGB as the receiver but a new alpha component.
|
|
|
|
@param anAlphaComponent the alpha component for the new color
|
|
|
|
@return a new color object
|
|
*/
|
|
- (CPColor)colorWithAlphaComponent:(float)anAlphaComponent
|
|
{
|
|
var components = _components.slice();
|
|
|
|
components[components.length - 1] = anAlphaComponent;
|
|
|
|
return [[[self class] alloc] _initWithRGBA:components];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver. This method is a placeholder that does nothing but may be implemented in the future.
|
|
*/
|
|
- (CPColor)colorUsingColorSpaceName:(id)aColorSpaceName
|
|
{
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Returns an array with the HSB values for this color.
|
|
|
|
The values are expressed as fractions between 0.0-1.0.
|
|
|
|
The index values are ordered as:
|
|
<pre>
|
|
<b>Index</b> <b>Component</b>
|
|
0 Hue
|
|
1 Saturation
|
|
2 Brightness
|
|
</pre>
|
|
*/
|
|
- (CPArray)hsbComponents
|
|
{
|
|
var red = ROUND(_components[_redComponent] * 255.0),
|
|
green = ROUND(_components[_greenComponent] * 255.0),
|
|
blue = ROUND(_components[_blueComponent] * 255.0);
|
|
|
|
var max = MAX(red, green, blue),
|
|
min = MIN(red, green, blue),
|
|
delta = max - min;
|
|
|
|
var brightness = max / 255.0,
|
|
saturation = (max != 0) ? delta / max : 0;
|
|
|
|
var hue;
|
|
|
|
if (saturation == 0)
|
|
{
|
|
hue = 0;
|
|
}
|
|
else
|
|
{
|
|
var rr = (max - red) / delta,
|
|
gr = (max - green) / delta,
|
|
br = (max - blue) / delta;
|
|
|
|
if (red == max)
|
|
hue = br - gr;
|
|
else if (green == max)
|
|
hue = 2 + rr - br;
|
|
else
|
|
hue = 4 + gr - rr;
|
|
|
|
hue /= 6;
|
|
if (hue < 0)
|
|
hue++;
|
|
}
|
|
|
|
return [
|
|
hue,
|
|
saturation,
|
|
brightness
|
|
];
|
|
}
|
|
|
|
/*!
|
|
Returns the hue component, the H in HSB, of the receiver.
|
|
*/
|
|
- (float)hueComponent
|
|
{
|
|
return [self hsbComponents][0];
|
|
}
|
|
|
|
/*!
|
|
Returns the saturation component, the S in HSB, of the receiver.
|
|
*/
|
|
- (float)saturationComponent
|
|
{
|
|
return [self hsbComponents][1];
|
|
}
|
|
|
|
/*!
|
|
Returns the brightness component, the B in HSB, of the receiver.
|
|
*/
|
|
- (float)brightnessComponent
|
|
{
|
|
return [self hsbComponents][2];
|
|
}
|
|
|
|
/*!
|
|
Returns the CSS representation of this color. The color will
|
|
be in one of the following forms:
|
|
<pre>
|
|
rgb(22,44,88)
|
|
rgba(22,44,88,0.5) // if there is an alpha
|
|
url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
|
|
</pre>
|
|
*/
|
|
- (CPString)cssString
|
|
{
|
|
return _cssString;
|
|
}
|
|
|
|
/*!
|
|
Returns a 6 character long hex string of this color.
|
|
*/
|
|
- (CPString)hexString
|
|
{
|
|
return rgbToHex([self redComponent], [self greenComponent], [self blueComponent]);
|
|
}
|
|
|
|
- (BOOL)isEqual:(CPColor)aColor
|
|
{
|
|
if (!aColor)
|
|
return NO;
|
|
|
|
if (aColor === self)
|
|
return YES;
|
|
|
|
if (![aColor isKindOfClass:CPColor])
|
|
return NO;
|
|
|
|
if (_patternImage || [aColor patternImage])
|
|
return [_patternImage isEqual:[aColor patternImage]];
|
|
|
|
// We don't require the components to be equal beyond 8 bits since otherwise
|
|
// simple rounding errors will make two colours which are exactly the same on
|
|
// screen compare unequal.
|
|
return ROUND([self redComponent] * 255.0) == ROUND([aColor redComponent] * 255.0) &&
|
|
ROUND([self greenComponent] * 255.0) == ROUND([aColor greenComponent] * 255.0) &&
|
|
ROUND([self blueComponent] * 255.0) == ROUND([aColor blueComponent] * 255.0) &&
|
|
[self alphaComponent] == [aColor alphaComponent];
|
|
}
|
|
|
|
- (CPString)description
|
|
{
|
|
var description = [super description],
|
|
patternImage = [self patternImage];
|
|
|
|
if (!patternImage)
|
|
return description + " " + [self cssString];
|
|
|
|
description += " {\n";
|
|
|
|
if ([patternImage isThreePartImage] || [patternImage isNinePartImage])
|
|
{
|
|
var slices = [patternImage imageSlices];
|
|
|
|
if ([patternImage isThreePartImage])
|
|
description += " orientation: " + ([patternImage isVertical] ? "vertical" : "horizontal") + ",\n";
|
|
|
|
description += " patternImage (" + slices.length + " part): [\n";
|
|
|
|
for (var i = 0; i < slices.length; ++i)
|
|
{
|
|
var imgDescription = [slices[i] description] || "nil";
|
|
|
|
description += imgDescription.replace(/^/mg, " ") + ",\n";
|
|
}
|
|
|
|
description = description.substr(0, description.length - 2) + "\n ]\n}";
|
|
}
|
|
else
|
|
description += ([patternImage description] || "nil").replace(/^/mg, " ") + "\n}";
|
|
|
|
return description;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPColor (CoreGraphicsExtensions)
|
|
|
|
/*!
|
|
Set's the receiver to be the fill and stroke color in the current graphics context
|
|
*/
|
|
- (void)set
|
|
{
|
|
[self setFill];
|
|
[self setStroke];
|
|
}
|
|
|
|
/*!
|
|
Set's the receiver to be the fill color in the current graphics context
|
|
*/
|
|
- (void)setFill
|
|
{
|
|
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
|
CGContextSetFillColor(ctx, self);
|
|
}
|
|
|
|
/*!
|
|
Set's the receiver to be the stroke color in the current graphics context
|
|
*/
|
|
- (void)setStroke
|
|
{
|
|
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
|
CGContextSetStrokeColor(ctx, self);
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPColor (Debugging)
|
|
|
|
+ (CPColor)randomColor
|
|
{
|
|
return [CPColor colorWithRed:RAND() green:RAND() blue:RAND() alpha:1.0];
|
|
}
|
|
|
|
+ (CPColor)checkerBoardColor
|
|
{
|
|
// Thanks to cocco http://stackoverflow.com/a/18368212/76900.
|
|
return [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEX////MzMw46qqDAAAAEElEQVQImWNg+M+AFeEQBgB+vw/xfUUZkgAAAABJRU5ErkJggg=="]];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
#pragma mark CSS Theming
|
|
|
|
// The code below adds support for CSS theming with 100% compatibility with current theming system.
|
|
// The idea is to extend CPColor (and CPImage) with CSS components and adapt low level UI components to
|
|
// support this new kind of CPColor/CPImage. See CPImageView, CPView and _CPImageAndTextView.
|
|
//
|
|
// To create a CPColor that uses CSS, simply use the new class method :
|
|
// + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
|
|
// where beforeDictionary & afterDictionary are related to ::before & ::after pseudo-elements.
|
|
// If you don't need them, just use the simplified class method :
|
|
// + (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary
|
|
//
|
|
// Example :
|
|
// buttonCssColor = [CPColor colorWithCSSDictionary:@{
|
|
// @"background-color": A3ColorBackgroundWhite,
|
|
// @"border-color": A3ColorActiveBorder,
|
|
// @"border-style": @"solid",
|
|
// @"border-width": @"1px",
|
|
// @"border-radius": @"3px",
|
|
// @"box-sizing": @"border-box"
|
|
// }
|
|
// beforeDictionary:@{
|
|
// @"background-color": @"rgb(225,225,225)",
|
|
// @"bottom": @"3px",
|
|
// @"content": @"''",
|
|
// @"position": @"absolute",
|
|
// @"right": @"21px",
|
|
// @"top": @"3px",
|
|
// @"width": @"1px"
|
|
// }
|
|
// afterDictionary:@{
|
|
// @"content": @"''",
|
|
// @"bottom": @"3px",
|
|
// @"right": @"6px",
|
|
// @"top": @"1px",
|
|
// @"position": @"absolute",
|
|
// @"height": @"14px",
|
|
// @"width": @"9px",
|
|
// @"margin": @"2px 0px 2px 0px",
|
|
// @"background-image": @"url(%%packed.png)",
|
|
// @"background-position": @"0px -64px",
|
|
// @"background-repeat": @"no-repeat",
|
|
// @"background-size": @"100px 400px"
|
|
// }];
|
|
//
|
|
// Remark : Please note the special URL of the background image used in this example : url(%%packed.png)
|
|
// During theme loading, "%%" will be replaced by the path to the theme blend resources folder.
|
|
// Typically, a CSS theme will use some (rare) images all packed together in a single image resource (see packed.png in Aristo3 theme)
|
|
//
|
|
// Also, please note that if you don't use one of the CSS components, you can either set it to nil (best solution) or to an empty dictionary, like :
|
|
// aCssColor = [CPColor colorWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:@{ ... }];
|
|
//
|
|
// You can use -(BOOL)isCSSBased to determine how to cope with it in your code.
|
|
// -(BOOL)hasCSSDictionary, -(BOOL)hasCSSBeforeDictionary and -(BOOL)hasCSSAfterDictionary are convience methods you can use.
|
|
//
|
|
// Remark : -(void)restorePreviousCSSState and -(DOMElement)applyCSSColorForView are meant to be used by low level UI widgets (like CPView) to implement
|
|
// CSS theme support.
|
|
|
|
@implementation CPColor (CSSTheming)
|
|
{
|
|
CPDictionary _cssDictionary @accessors(property=cssDictionary);
|
|
CPDictionary _cssBeforeDictionary @accessors(property=cssBeforeDictionary);
|
|
CPDictionary _cssAfterDictionary @accessors(property=cssAfterDictionary);
|
|
}
|
|
|
|
+ (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary
|
|
{
|
|
return [[CPColor alloc] _initWithCSSDictionary:aDictionary beforeDictionary:nil afterDictionary:nil];
|
|
}
|
|
|
|
+ (CPColor)colorWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
|
|
{
|
|
return [[CPColor alloc] _initWithCSSDictionary:aDictionary beforeDictionary:beforeDictionary afterDictionary:afterDictionary];
|
|
}
|
|
|
|
- (id)_initWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_cssDictionary = aDictionary;
|
|
_cssBeforeDictionary = beforeDictionary;
|
|
_cssAfterDictionary = afterDictionary;
|
|
_components = [0.0, 0.0, 0.0, 1.0];
|
|
|
|
_theme = [CPTheme defaultTheme];
|
|
_themeState = CPThemeStateNormal;
|
|
[self _loadThemeAttributes];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isCSSBased
|
|
{
|
|
return !!(_cssDictionary || _cssBeforeDictionary || _cssAfterDictionary);
|
|
}
|
|
|
|
- (BOOL)hasCSSDictionary
|
|
{
|
|
return ([_cssDictionary count] > 0);
|
|
}
|
|
|
|
- (BOOL)hasCSSBeforeDictionary
|
|
{
|
|
return ([_cssBeforeDictionary count] > 0);
|
|
}
|
|
|
|
- (BOOL)hasCSSAfterDictionary
|
|
{
|
|
return ([_cssAfterDictionary count] > 0);
|
|
}
|
|
|
|
- (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement
|
|
{
|
|
#if PLATFORM(DOM)
|
|
var aPreviousState = @deref(aPreviousStateRef);
|
|
|
|
for (var i = 0, count = aPreviousState.length; i < count; i++)
|
|
aDOMElement.style[aPreviousState[i][0]] = aPreviousState[i][1];
|
|
|
|
@deref(aPreviousStateRef) = @[];
|
|
#endif
|
|
}
|
|
|
|
- (DOMElement)applyCSSColorForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
|
|
{
|
|
#if PLATFORM(DOM)
|
|
var aPreviousState = @deref(aPreviousStateRef);
|
|
|
|
[_cssDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
|
{
|
|
[aPreviousState addObject:@[aKey, aDOMElement.style[aKey]]];
|
|
aDOMElement.style[aKey] = anObject;
|
|
}];
|
|
|
|
if ([self hasCSSBeforeDictionary] || [self hasCSSAfterDictionary])
|
|
{
|
|
// We need to create a unique class name
|
|
|
|
var styleClassName = @".CP" + [aView UID],
|
|
styleContent = @"";
|
|
|
|
if ([self hasCSSBeforeDictionary])
|
|
{
|
|
styleContent += styleClassName + @"::before { ";
|
|
|
|
[_cssBeforeDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
|
{
|
|
styleContent += aKey + ": " + anObject + "; ";
|
|
}];
|
|
|
|
styleContent += "} ";
|
|
}
|
|
|
|
if ([self hasCSSAfterDictionary])
|
|
{
|
|
styleContent += styleClassName + @"::after { ";
|
|
|
|
[_cssAfterDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
|
{
|
|
styleContent += aKey + ": " + anObject + "; ";
|
|
}];
|
|
|
|
styleContent += "} ";
|
|
}
|
|
|
|
var styleDescription = document.createTextNode(styleContent);
|
|
|
|
if (!aStyleNode)
|
|
{
|
|
aStyleNode = document.createElement("style");
|
|
|
|
aView._DOMElement.insertBefore(aStyleNode, aView._DOMElement.firstChild);
|
|
|
|
aStyleNode.appendChild(styleDescription);
|
|
}
|
|
else
|
|
{
|
|
aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild);
|
|
}
|
|
|
|
[aView setDOMClassName:@"CP"+[aView UID]];
|
|
}
|
|
else
|
|
{
|
|
// no before/after so remove aStyleNode if existing
|
|
|
|
if (aStyleNode)
|
|
{
|
|
aView._DOMElement.removeChild(aStyleNode);
|
|
aStyleNode = nil;
|
|
}
|
|
}
|
|
|
|
// Return actualised values
|
|
|
|
@deref(aPreviousStateRef) = aPreviousState;
|
|
|
|
return aStyleNode;
|
|
#endif
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
/// @cond IGNORE
|
|
var CPColorComponentsKey = @"CPColorComponentsKey",
|
|
CPColorPatternImageKey = @"CPColorPatternImageKey",
|
|
CPColorCssDictionaryKey = @"CPColorCssDictionaryKey",
|
|
CPColorCssBeforeDictionaryKey = @"CPColorCssBeforeDictionaryKey",
|
|
CPColorCssAfterDictionaryKey = @"CPColorCssAfterDictionaryKey";
|
|
/// @endcond
|
|
|
|
@implementation CPColor (CPCoding)
|
|
|
|
/*!
|
|
Initializes this color from the data archived in a coder.
|
|
@param aCoder the coder from which the color will be loaded
|
|
*/
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
if ([aCoder containsValueForKey:CPColorPatternImageKey])
|
|
self = [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
|
|
else if ([aCoder containsValueForKey:CPColorCssDictionaryKey])
|
|
self = [self _initWithCSSDictionary:[aCoder decodeObjectForKey:CPColorCssDictionaryKey]
|
|
beforeDictionary:[aCoder decodeObjectForKey:CPColorCssBeforeDictionaryKey]
|
|
afterDictionary:[aCoder decodeObjectForKey:CPColorCssAfterDictionaryKey]];
|
|
else
|
|
self = [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
|
|
|
|
[self _decodeThemeObjectsWithCoder:aCoder];
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Archives this color into a coder.
|
|
@param aCoder the coder into which the color will be archived.
|
|
*/
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
if (_patternImage)
|
|
[aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
|
|
else if (_cssDictionary)
|
|
{
|
|
[aCoder encodeObject:_cssDictionary forKey:CPColorCssDictionaryKey];
|
|
[aCoder encodeObject:_cssBeforeDictionary forKey:CPColorCssBeforeDictionaryKey];
|
|
[aCoder encodeObject:_cssAfterDictionary forKey:CPColorCssAfterDictionaryKey];
|
|
}
|
|
else
|
|
[aCoder encodeObject:_components forKey:CPColorComponentsKey];
|
|
|
|
[self _encodeThemeObjectsWithCoder:aCoder];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
/// @cond IGNORE
|
|
var hexCharacters = "0123456789ABCDEF";
|
|
|
|
/*
|
|
Used for the CPColor +colorWithHexString: implementation.
|
|
Returns an array of rgb components.
|
|
*/
|
|
var hexToRGB = function(hex)
|
|
{
|
|
if (hex.length == 3)
|
|
hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
|
|
|
|
if (hex.length != 6)
|
|
return null;
|
|
|
|
hex = hex.toUpperCase();
|
|
|
|
for (var i = 0; i < hex.length; i++)
|
|
if (hexCharacters.indexOf(hex.charAt(i)) == -1)
|
|
return null;
|
|
|
|
var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0,
|
|
green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0,
|
|
blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
|
|
|
|
return [red, green, blue, 1.0];
|
|
};
|
|
|
|
var rgbToHex = function(r,g,b)
|
|
{
|
|
return byteToHex(r) + byteToHex(g) + byteToHex(b);
|
|
};
|
|
|
|
var byteToHex = function(n)
|
|
{
|
|
if (!n || isNaN(n))
|
|
return "00";
|
|
|
|
n = FLOOR(MIN(255, MAX(0, 256 * n)));
|
|
|
|
return hexCharacters.charAt((n - n % 16) / 16) +
|
|
hexCharacters.charAt(n % 16);
|
|
};
|
|
|
|
/*!
|
|
To create a simple color with a pattern image:
|
|
CPColorWithImages(name, width, height{, bundle})
|
|
|
|
To create a color with a three-part/nine-part image using a pattern string:
|
|
CPColorWithImages(pattern, attributes{, bundle})
|
|
|
|
<pattern> must have one or more placeholders. There are there possible
|
|
placeholders available that together can be used to build a filename:
|
|
|
|
style A top-level style, for example normal (empty) and "default".
|
|
state The state of a view, for example "highlighted" and "disabled".
|
|
position The position of an image within a 3-part or 9-part image.
|
|
This must be provided.
|
|
|
|
Each placeholder is filled with the values you pass in a the attributes object.
|
|
Neither style nor state are necessary, if you omit them the next level of the
|
|
hierarchy is the top level.
|
|
|
|
Attributes
|
|
----------
|
|
styles: [...]
|
|
An array of style names. For example, if you want to generate the filenames
|
|
"button-bezel" and "default-button-bezel", you would use ["", "default"]
|
|
for the styles.
|
|
|
|
states: [...]
|
|
An array of state names.
|
|
|
|
positions: <spec>
|
|
Specifies the naming convention for the slices of the image. For 3-part images,
|
|
<spec> may be one of:
|
|
|
|
"@" Equivalent to ["left", "center", "right"] for horizontal
|
|
images, ["top", "center", "bottom"] for vertical images
|
|
"#" Equivalent to ["0", "1", "2"]
|
|
[...] An array of any three literal strings
|
|
|
|
For 9-part images, <spec> may be one of:
|
|
|
|
"@" | "abbrev" Equivalent to ["top-left", "top", "top-right",
|
|
"left", "center", "right",
|
|
"bottom-left", "bottom", "bottom-right"]
|
|
"full" Equivalent to ["top-left", "top-center", "top-right",
|
|
"center-left", "center-center", "center-right",
|
|
"bottom-left", "bottom-center", "bottom-right"]
|
|
"#" Equivalent to ["0", "1", "2", "3", "4", "5", "6", "7", "8"]
|
|
[...] An array of any nine literal strings
|
|
|
|
width: <number>
|
|
The width of the left image for horizontal 3-part images, the top image for
|
|
vertical 3-part images, or the top left corner for 9-part images.
|
|
|
|
height: <number>
|
|
The height of the left image for horizontal 3-part images, the top image for
|
|
vertical 3-part images, or the top left corner for 9-part images.
|
|
|
|
centerWidth: <number>
|
|
For a horizontal 3-part image that has a center slice width that is not 1.0,
|
|
set this for the width of the center slice. If omitted, it defaults
|
|
to 1.0. Not used for 9-part images.
|
|
|
|
rightWidth: <number>
|
|
For a horizontal 3-part image that has different left/right slice widths,
|
|
or a 9-part image that has different left/right corner slice widths,
|
|
set this for the width of the right slice. If omitted, the "width"
|
|
value is used for left and right widths.
|
|
|
|
centerHeight: <number>
|
|
For a vertical 3-part image that has a center slice height that is not 1.0,
|
|
set this for the height of the center slice. If omitted, it defaults
|
|
to 1.0. Not used for 9-part images.
|
|
|
|
bottomHeight: <number>
|
|
For a vertical image that has different top/bottom slice heights,
|
|
or a 9-part image that has different top/bottom corner slice heights,
|
|
set this for the height of the bottom slice. If omitted, the "height"
|
|
value is used for top and bottom heights.
|
|
|
|
centerIsNil: <BOOL>
|
|
For 9-part images, if the center image is nil, set this to YES.
|
|
If omitted or set to NO, a center image must be provided.
|
|
|
|
separator: <string>
|
|
The separator to use between components of the pattern.
|
|
If it is omitted, it defaults to "-".
|
|
|
|
orientation: PatternIsHorizontal | PatternIsVertical
|
|
The orientation of the image. This must be specified for 3-part images,
|
|
it should NOT be specified for 9-part images.
|
|
|
|
Using a pattern, all of this:
|
|
|
|
bezelColor = PatternColor(
|
|
[
|
|
["button-bezel-left.png", 4.0, 24.0],
|
|
["button-bezel-center.png", 1.0, 24.0],
|
|
["button-bezel-right.png", 4.0, 24.0]
|
|
],
|
|
PatternIsHorizontal),
|
|
|
|
highlightedBezelColor = PatternColor(
|
|
[
|
|
["button-bezel-highlighted-left.png", 4.0, 24.0],
|
|
["button-bezel-highlighted-center.png", 1.0, 24.0],
|
|
["button-bezel-highlighted-right.png", 4.0, 24.0]
|
|
],
|
|
PatternIsHorizontal),
|
|
|
|
disabledBezelColor = PatternColor(
|
|
[
|
|
["button-bezel-disabled-left.png", 4.0, 24.0],
|
|
["button-bezel-disabled-center.png", 1.0, 24.0],
|
|
["button-bezel-disabled-right.png", 4.0, 24.0]
|
|
],
|
|
PatternIsHorizontal),
|
|
|
|
defaultBezelColor = PatternColor(
|
|
[
|
|
["default-button-bezel-left.png", 4.0, 24.0],
|
|
["default-button-bezel-center.png", 1.0, 24.0],
|
|
["default-button-bezel-right.png", 4.0, 24.0]
|
|
],
|
|
PatternIsHorizontal),
|
|
|
|
defaultHighlightedBezelColor = PatternColor(
|
|
[
|
|
["default-button-bezel-highlighted-left.png", 4.0, 24.0],
|
|
["default-button-bezel-highlighted-center.png", 1.0, 24.0],
|
|
["default-button-bezel-highlighted-right.png", 4.0, 24.0]
|
|
],
|
|
PatternIsHorizontal),
|
|
|
|
defaultDisabledBezelColor = PatternColor(
|
|
[
|
|
["default-button-bezel-disabled-left.png", 4.0, 24.0],
|
|
["default-button-bezel-disabled-center.png", 1.0, 24.0],
|
|
["default-button-bezel-disabled-right.png", 4.0, 24.0]
|
|
],
|
|
PatternIsHorizontal)
|
|
|
|
can be replaced with this:
|
|
|
|
bezelColors = PatternColor(
|
|
"{style}button-bezel{state}{position}.png",
|
|
{
|
|
styles: ["", "default"],
|
|
states: ["", "highlighted", "disabled"],
|
|
positions: "@",
|
|
width: 4.0,
|
|
height: 24.0,
|
|
orientation: PatternIsHorizontal
|
|
})
|
|
|
|
Which would effectively create the following object:
|
|
|
|
{
|
|
"@":
|
|
{
|
|
"@": <ThreePartImage>:
|
|
[
|
|
["button-bezel-left.png", 4.0, 24.0],
|
|
["button-bezel-center.png", 1.0, 24.0],
|
|
["button-bezel-right.png", 4.0, 24.0]
|
|
],
|
|
"highlighted": <ThreePartImage>:
|
|
[
|
|
["button-bezel-highlighted-left.png", 4.0, 24.0],
|
|
["button-bezel-highlighted-center.png", 1.0, 24.0],
|
|
["button-bezel-highlighted-right.png", 4.0, 24.0]
|
|
],
|
|
"disabled": <ThreePartImage>:
|
|
[
|
|
["button-bezel-disabled-left.png", 4.0, 24.0],
|
|
["button-bezel-disabled-center.png", 1.0, 24.0],
|
|
["button-bezel-disabled-right.png", 4.0, 24.0]
|
|
]
|
|
},
|
|
|
|
"default":
|
|
{
|
|
"@": <ThreePartImage>:
|
|
[
|
|
["default-button-bezel-left.png", 4.0, 24.0],
|
|
["default-button-bezel-center.png", 1.0, 24.0],
|
|
["default-button-bezel-right.png", 4.0, 24.0]
|
|
],
|
|
"highlighted": <ThreePartImage>:
|
|
[
|
|
["default-button-bezel-highlighted-left.png", 4.0, 24.0],
|
|
["default-button-bezel-highlighted-center.png", 1.0, 24.0],
|
|
["default-button-bezel-highlighted-right.png", 4.0, 24.0]
|
|
],
|
|
"disabled": <ThreePartImage>:
|
|
[
|
|
["default-button-bezel-disabled-left.png", 4.0, 24.0],
|
|
["button-bezel-disabled-center.png", 1.0, 24.0],
|
|
["default-button-bezel-disabled-right.png", 4.0, 24.0]
|
|
]
|
|
}
|
|
}
|
|
|
|
To reference empty names in the pattern color, use the key "@".
|
|
So, for example, to reference the "default" style, "disabled" state,
|
|
you would use the following expression:
|
|
|
|
bezelColors["@"]["disabled"]
|
|
|
|
To create a color with a three-part pattern image explicitly:
|
|
CPColorWithImages(slices, orientation)
|
|
|
|
where slices is an array of three [name, width, height{, bundle}] arrays,
|
|
and orientation is CPColorPatternIsVertical or CPColorPatternIsHorizontal.
|
|
|
|
To create a color with a nine-part pattern image explicitly:
|
|
CPColorWithImages(slices);
|
|
|
|
where slices is an array of nine [name, width, height{, bundle}] arrays.
|
|
*/
|
|
function CPColorWithImages()
|
|
{
|
|
var slices = nil,
|
|
numParts = 0,
|
|
isVertical = false,
|
|
imageFactory = CPImageInBundle,
|
|
args = Array.prototype.slice.apply(arguments);
|
|
|
|
if (typeof(args[args.length - 1]) === "function")
|
|
imageFactory = args.pop();
|
|
|
|
switch (args.length)
|
|
{
|
|
case 1:
|
|
return imageFromSlices(args[0], isVertical, imageFactory);
|
|
|
|
case 2:
|
|
// New-style 3-part and 9-part images
|
|
if (typeof(args[0]) === "string")
|
|
return patternColorsFromPattern.call(this, args[0], args[1], imageFactory);
|
|
|
|
return imageFromSlices(args[0], args[1], imageFactory);
|
|
|
|
case 3:
|
|
case 4:
|
|
return [CPColor colorWithPatternImage:imageFactory(args[0], args[1], args[2], args[3])];
|
|
|
|
default:
|
|
throw("ERROR: Invalid argument count: " + args.length);
|
|
}
|
|
}
|
|
|
|
var imageFromSlices = function(slices, isVertical, imageFactory)
|
|
{
|
|
var imageSlices = [];
|
|
|
|
for (var i = 0; i < slices.length; ++i)
|
|
{
|
|
var slice = slices[i];
|
|
|
|
imageSlices.push(slice ? imageFactory(slice[0], slice[1], slice[2], slice[3]) : nil);
|
|
}
|
|
|
|
switch (slices.length)
|
|
{
|
|
case 3:
|
|
return [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:imageSlices isVertical:isVertical]];
|
|
|
|
case 9:
|
|
return [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:imageSlices]];
|
|
|
|
default:
|
|
throw("ERROR: Invalid number of image slices: " + slices.length);
|
|
}
|
|
};
|
|
|
|
var patternColorsFromPattern = function(pattern, attributes, imageFactory)
|
|
{
|
|
if (pattern.match(/^.*\{[^}]+\}/))
|
|
{
|
|
var width = attributes["width"],
|
|
height = attributes["height"],
|
|
separator = attributes["separator"] || "-",
|
|
orientation = attributes["orientation"],
|
|
rightWidth,
|
|
bottomHeight,
|
|
centerWidthHeight,
|
|
centerIsNil,
|
|
numParts,
|
|
isVertical;
|
|
|
|
// positions are mandatory
|
|
if (pattern.indexOf("{position}") < 0)
|
|
throw("ERROR: Pattern strings must have a {position} placeholder (\"" + pattern + "\")");
|
|
|
|
if (orientation === undefined)
|
|
{
|
|
numParts = 9;
|
|
|
|
if (attributes["centerIsNil"] !== undefined)
|
|
centerIsNil = attributes["centerIsNil"];
|
|
}
|
|
else
|
|
{
|
|
numParts = 3;
|
|
isVertical = orientation === PatternIsVertical;
|
|
|
|
if (isVertical)
|
|
{
|
|
if (attributes["centerHeight"])
|
|
centerWidthHeight = attributes["centerHeight"];
|
|
}
|
|
else
|
|
{
|
|
if (attributes["centerWidth"])
|
|
centerWidthHeight = attributes["centerWidth"];
|
|
}
|
|
}
|
|
|
|
if (attributes["rightWidth"])
|
|
rightWidth = attributes["rightWidth"];
|
|
|
|
if (attributes["bottomHeight"])
|
|
bottomHeight = attributes["bottomHeight"];
|
|
|
|
var positions = attributes["positions"] || "@",
|
|
states = nil,
|
|
styles = nil;
|
|
|
|
if (numParts === 3)
|
|
{
|
|
if (positions === "@")
|
|
{
|
|
if (isVertical)
|
|
positions = ["top", "center", "bottom"];
|
|
else
|
|
positions = ["left", "center", "right"];
|
|
}
|
|
else if (positions === "#")
|
|
positions = ["0", "1", "2"];
|
|
else
|
|
throw("ERROR: Invalid positions: " + positions)
|
|
}
|
|
else // numParts === 9
|
|
{
|
|
if (positions === "@" || positions === "abbrev")
|
|
positions = ["top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right"];
|
|
else if (positions === "full")
|
|
positions = ["top-left", "top-center", "top-right", "center-left", "center-center", "center-right", "bottom-left", "bottom-center", "bottom-right"];
|
|
else if (positions === "#")
|
|
positions = ["0", "1", "2", "3", "4", "5", "6", "7", "8"];
|
|
else
|
|
throw("ERROR: Invalid positions: " + positions)
|
|
}
|
|
|
|
// states
|
|
if (pattern.indexOf("{state}") >= 0)
|
|
{
|
|
states = attributes["states"];
|
|
|
|
if (!states)
|
|
throw("ERROR: {state} placeholder in the pattern (\"" + pattern + "\") but no states item in the attributes");
|
|
}
|
|
|
|
// styles
|
|
if (pattern.indexOf("{style}") >= 0)
|
|
{
|
|
styles = attributes["styles"];
|
|
|
|
if (!styles)
|
|
throw("ERROR: {style} placeholder in the pattern (\"" + pattern + "\") but no styles item in the attributes");
|
|
}
|
|
|
|
// Now assemble the hierarchy
|
|
var placeholder = "{position}",
|
|
pos = pattern.indexOf(placeholder),
|
|
i;
|
|
|
|
for (i = 0; i < positions.length; ++i)
|
|
positions[i] = pattern.replace(placeholder, pos === 0 ? positions[i] + separator : separator + positions[i]);
|
|
|
|
var slices = positions,
|
|
object = slices,
|
|
key,
|
|
sep;
|
|
|
|
if (states)
|
|
{
|
|
placeholder = "{state}";
|
|
pos = pattern.indexOf(placeholder);
|
|
object = {};
|
|
|
|
for (i = 0; i < states.length; ++i)
|
|
{
|
|
var state = states[i];
|
|
key = state || "@";
|
|
sep = state ? separator : "";
|
|
|
|
object[key] = slices.slice(0);
|
|
replacePlaceholderInArray(object[key], placeholder, pos === 0 ? state + sep : sep + state);
|
|
}
|
|
}
|
|
|
|
if (styles)
|
|
{
|
|
placeholder = "{style}";
|
|
pos = pattern.indexOf(placeholder);
|
|
|
|
var styleObject = {};
|
|
|
|
for (i = 0; i < styles.length; ++i)
|
|
{
|
|
var style = styles[i];
|
|
key = style || "@";
|
|
sep = style ? separator : "";
|
|
|
|
if (states)
|
|
{
|
|
styleObject[key] = cloneObject(object);
|
|
replacePlaceholderInObject(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
|
|
}
|
|
else
|
|
{
|
|
styleObject[key] = slices.slice(0);
|
|
replacePlaceholderInArray(styleObject[key], placeholder, pos === 0 ? style + sep : sep + style);
|
|
}
|
|
}
|
|
|
|
object = styleObject;
|
|
}
|
|
|
|
if (styles || states)
|
|
{
|
|
if (numParts === 3)
|
|
makeThreePartSlicesFromObject(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
|
|
else
|
|
makeNinePartSlicesFromObject(object, width, height, rightWidth, bottomHeight, centerIsNil);
|
|
|
|
makeImagesFromObject(object, isVertical, imageFactory);
|
|
return object;
|
|
}
|
|
else
|
|
{
|
|
if (numParts === 3)
|
|
makeThreePartSlicesFromArray(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
|
|
else
|
|
makeNinePartSlicesFromArray(object, width, height, rightWidth, bottomHeight, centerIsNil);
|
|
|
|
return imageFromSlices(object, isVertical, imageFactory);
|
|
}
|
|
}
|
|
else
|
|
throw("ERROR: No placeholders in slice pattern (\"" + pattern + "\")");
|
|
};
|
|
|
|
var replacePlaceholderInArray = function(array, find, replacement)
|
|
{
|
|
for (var i = 0; i < array.length; ++i)
|
|
array[i] = array[i].replace(find, replacement);
|
|
};
|
|
|
|
var replacePlaceholderInObject = function(object, find, replacement)
|
|
{
|
|
for (var key in object)
|
|
if (object.hasOwnProperty(key))
|
|
if (object[key].constructor === Array)
|
|
replacePlaceholderInArray(object[key], find, replacement);
|
|
else
|
|
replacePlaceholderInObject(object[key], find, replacement);
|
|
};
|
|
|
|
var cloneObject = function(object)
|
|
{
|
|
var clone = {};
|
|
|
|
for (var key in object)
|
|
if (object.hasOwnProperty(key))
|
|
if (object[key].constructor === Array)
|
|
clone[key] = object[key].slice(0);
|
|
else if (typeof(object[key]) === "object")
|
|
clone[key] = cloneObject(object[key]);
|
|
else
|
|
clone[key] = object[key];
|
|
|
|
return clone;
|
|
};
|
|
|
|
var makeThreePartSlicesFromObject = function(object, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
|
|
{
|
|
for (var key in object)
|
|
if (object.hasOwnProperty(key))
|
|
if (object[key].constructor === Array)
|
|
makeThreePartSlicesFromArray(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
|
|
else // object
|
|
makeThreePartSlicesFromObject(object[key], width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical);
|
|
};
|
|
|
|
var makeThreePartSlicesFromArray = function(array, width, height, centerWidthHeight, rightWidth, bottomHeight, isVertical)
|
|
{
|
|
array[0] = [array[0], width, height];
|
|
|
|
if (isVertical)
|
|
{
|
|
array[1] = [array[1], width, centerWidthHeight ? centerWidthHeight : 1.0];
|
|
array[2] = [array[2], width, bottomHeight ? bottomHeight : height];
|
|
}
|
|
else
|
|
{
|
|
array[1] = [array[1], centerWidthHeight ? centerWidthHeight : 1.0, height];
|
|
array[2] = [array[2], rightWidth ? rightWidth : width, height];
|
|
}
|
|
};
|
|
|
|
var makeNinePartSlicesFromObject = function(object, width, height, rightWidth, bottomHeight, centerIsNil)
|
|
{
|
|
for (var key in object)
|
|
if (object.hasOwnProperty(key))
|
|
if (object[key].constructor === Array)
|
|
makeNinePartSlicesFromArray(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
|
|
else // object
|
|
makeNinePartSlicesFromObject(object[key], width, height, rightWidth, bottomHeight, centerIsNil);
|
|
};
|
|
|
|
var makeNinePartSlicesFromArray = function(array, width, height, rightWidth, bottomHeight, centerIsNil)
|
|
{
|
|
rightWidth = rightWidth ? rightWidth : width;
|
|
bottomHeight = bottomHeight ? bottomHeight : height;
|
|
|
|
array[0] = [array[0], width, height]; // top-left
|
|
array[1] = [array[1], 1.0, height]; // top
|
|
array[2] = [array[2], rightWidth, height]; // top-right
|
|
array[3] = [array[3], width, 1.0]; // left
|
|
array[4] = centerIsNil ? nil : [array[4], 1.0, 1.0]; // center
|
|
array[5] = [array[5], rightWidth, 1.0]; // right
|
|
array[6] = [array[6], width, bottomHeight]; // bottom-left
|
|
array[7] = [array[7], 1.0, bottomHeight]; // bottom
|
|
array[8] = [array[8], rightWidth, bottomHeight]; // bottom-right
|
|
};
|
|
|
|
var makeImagesFromObject = function(object, isVertical, imageFactory)
|
|
{
|
|
for (var key in object)
|
|
if (object.hasOwnProperty(key))
|
|
if (object[key].constructor === Array)
|
|
object[key] = imageFromSlices(object[key], isVertical, imageFactory);
|
|
else // object
|
|
makeImagesFromObject(object[key], isVertical, imageFactory);
|
|
};
|
|
|
|
/// @endcond
|