Merge pull request #2389 from mrcarlberg/faster_theme_attribute

Faster theme attribute handling
This commit is contained in:
Martin Carlberg
2015-11-01 20:42:13 +01:00
15 changed files with 340 additions and 151 deletions
+1 -1
View File
@@ -280,7 +280,7 @@ CPButtonImageOffset = 3.0;
break;
case CPOffState:
[self unsetThemeState:[CPThemeStateSelected, CPButtonStateMixed, CPThemeStateHighlighted]];
[self unsetThemeStates:[CPThemeStateSelected, CPButtonStateMixed, CPThemeStateHighlighted]];
}
}
+6 -6
View File
@@ -272,15 +272,15 @@
currentButtonOffset += width - 1;
}
[button setValue:normalColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateNormal, CPThemeStateBordered]];
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateHighlighted, CPThemeStateBordered, ]];
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateDisabled, CPThemeStateBordered]];
[button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered]];
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, ]];
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered]];
[button setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateBordered];
// FIXME shouldn't need this
[button setValue:normalColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateNormal, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateHighlighted, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:[CPThemeStateDisabled, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
[button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
[self addSubview:button];
}
+16 -16
View File
@@ -1114,30 +1114,30 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"font" inState:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:[CPThemeStateDisabled, CPThemeStateSelected]]forThemeAttribute:@"text-color" inState:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inState:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inState:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateHighlighted] forThemeAttribute:@"font" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-color" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inState:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inState:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inState:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inState:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inState:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"font" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-color" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-offset" inState:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[self addSubview:_textField];
+6 -6
View File
@@ -188,12 +188,12 @@
[_buttonUp setFrame:upFrame];
[_buttonDown setFrame:downFrame];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inState:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inState:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inState:[CPThemeStateBordered, CPThemeStateDisabled]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inState:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inState:[CPThemeStateBordered, CPThemeStateHighlighted]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inState:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inState:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inState:[CPThemeStateBordered, CPThemeStateDisabled]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inState:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inState:[CPThemeStateBordered, CPThemeStateHighlighted]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]];
}
- (void)_sizeToFit
+1 -1
View File
@@ -622,7 +622,7 @@ var CPTableHeaderViewResizeZone = 3.0,
[[headerView subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:YES];
// The underlying column header shows normal state
[headerView unsetThemeState:CPThemeStateHighlighted | CPThemeStateSelected];
[headerView unsetThemeStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
// Keep track of the location within the column header where the original mousedown occurred
_columnDragHeaderView = [_columnDragView viewWithTag:CPTableHeaderViewDragColumnHeaderTag];
-6
View File
@@ -6400,18 +6400,12 @@ var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
- (BOOL)setThemeState:(ThemeState)aState
{
if (aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
[super setThemeState:aState];
[self recursivelyPerformSelector:@selector(setThemeState:) withObject:aState startingFrom:self];
}
- (BOOL)unsetThemeState:(ThemeState)aState
{
if (aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
[super unsetThemeState:aState];
[self recursivelyPerformSelector:@selector(unsetThemeState:) withObject:aState startingFrom:self];
}
+125 -44
View File
@@ -36,7 +36,6 @@ var CPThemesByName = { },
/*!
@ingroup appkit
*/
@implementation CPTheme : CPObject
{
CPString _name;
@@ -327,6 +326,7 @@ function ThemeState(stateNames)
{
if (!stateNames.hasOwnProperty(key))
continue;
if (key !== 'normal')
{
this._stateNames[key] = true;
@@ -344,8 +344,10 @@ function ThemeState(stateNames)
this._stateNameString = stateNameKeys[0];
var stateNameLength = stateNameKeys.length;
for (var stateIndex = 1; stateIndex < stateNameLength; stateIndex++)
this._stateNameString = this._stateNameString + "+" + stateNameKeys[stateIndex];
this._stateNameCount = stateNameLength;
}
@@ -392,7 +394,19 @@ ThemeState.prototype.without = function(aState)
if (!aState || aState === [CPNull null])
return this;
var firstTransform = CPThemeWithoutTransform[this._stateNameString],
result;
if (firstTransform)
{
result = firstTransform[aState._stateNameString];
if (result)
return result;
}
var newStates = {};
for (var stateName in this._stateNames)
{
if (!this._stateNames.hasOwnProperty(stateName))
@@ -402,25 +416,54 @@ ThemeState.prototype.without = function(aState)
newStates[stateName] = true;
}
return ThemeState._cacheThemeState(new ThemeState(newStates));
result = ThemeState._cacheThemeState(new ThemeState(newStates));
if (!firstTransform)
firstTransform = CPThemeWithoutTransform[this._stateNameString] = {};
firstTransform[aState._stateNameString] = result;
return result;
}
ThemeState.prototype.and = function(aState)
{
return CPThemeState(this, aState);
var firstTransform = CPThemeAndTransform[this._stateNameString],
result;
if (firstTransform)
{
result = firstTransform[aState._stateNameString];
if (result)
return result;
}
result = CPThemeState(this, aState);
if (!firstTransform)
firstTransform = CPThemeAndTransform[this._stateNameString] = {};
firstTransform[aState._stateNameString] = result;
return result;
}
var CPThemeStates = {};
var CPThemeStates = {},
CPThemeWithoutTransform = {},
CPThemeAndTransform = {};
ThemeState._cacheThemeState = function(aState)
{
// We do this caching so themeState equality works. Basically, doing CPThemeState('foo+bar') === CPThemeState('bar', 'foo') will return true.
var themeState = CPThemeStates[String(aState)];
if (themeState === undefined)
{
themeState = aState;
CPThemeStates[String(themeState)] = themeState;
}
return themeState;
}
@@ -438,14 +481,17 @@ function CPThemeState()
throw "CPThemeState() must be called with at least one string argument";
var themeState;
if (arguments.length === 1 && typeof arguments[0] === 'string')
{
themeState = CPThemeStates[arguments[0]];
if (themeState !== undefined)
return themeState;
}
var stateNames = {};
for (var argIndex = 0; argIndex < arguments.length; argIndex++)
{
if (arguments[argIndex] === [CPNull null] || !arguments[argIndex])
@@ -457,12 +503,14 @@ function CPThemeState()
{
if (!arguments[argIndex]._stateNames.hasOwnProperty(stateName))
continue;
stateNames[stateName] = true;
}
}
else
{
var allNames = arguments[argIndex].split('+');
for (var nameIndex = 0; nameIndex < allNames.length; nameIndex++)
stateNames[allNames[nameIndex]] = true;
}
@@ -522,6 +570,9 @@ CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular");
CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall");
CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
CPThemeStateNormalString = String(CPThemeStateNormal);
@implementation _CPThemeAttribute : CPObject
{
CPString _name;
@@ -532,7 +583,7 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
_CPThemeAttribute _themeDefaultAttribute;
}
- (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue
- (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue defaultAttribute:(_CPThemeAttribute)aDefaultAttribute
{
self = [super init];
@@ -541,7 +592,9 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
_cache = { };
_name = aName;
_defaultValue = aDefaultValue;
_values = @{};
if (aDefaultAttribute)
_themeDefaultAttribute = aDefaultAttribute;
}
return self;
@@ -562,24 +615,41 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
return [_values count] > 0;
}
- (void)setValue:(id)aValue
- (_CPThemeAttribute)attributeBySettingValue:(id)aValue
{
_cache = {};
var attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute];
if (aValue === undefined || aValue === nil)
_values = @{};
else
_values = @{ String(CPThemeStateNormal): aValue };
if (aValue !== undefined && aValue !== nil)
attribute._values = @{ CPThemeStateNormalString: aValue };
return attribute;
}
- (void)setValue:(id)aValue forState:(ThemeState)aState
- (_CPThemeAttribute)attributeBySettingValue:(id)aValue forState:(ThemeState)aState
{
_cache = { };
var shouldRemoveValue = aValue === undefined || aValue === nil,
attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute],
values = _values;
if ((aValue === undefined) || (aValue === nil))
[_values removeObjectForKey:String(aState)];
else
[_values setObject:aValue forKey:String(aState)];
if (values != null)
{
values = [values copy];
if (shouldRemoveValue)
[values removeObjectForKey:String(aState)];
else
[values setObject:aValue forKey:String(aState)];
attribute._values = values;
}
else if (!shouldRemoveValue)
{
values = [[CPDictionary alloc] init];
[values setObject:aValue forKey:String(aState)];
attribute._values = values;
}
return attribute;
}
- (id)value
@@ -604,7 +674,7 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
if (aState._stateNameCount > 1)
{
var states = [_values allKeys],
count = states.length,
count = states ? states.length : 0,
largestThemeState = 0;
while (count--)
@@ -642,27 +712,41 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
return value;
}
- (void)setParentAttribute:(_CPThemeAttribute)anAttribute
- (_CPThemeAttribute)attributeBySettingParentAttribute:(_CPThemeAttribute)anAttribute
{
if (_themeDefaultAttribute === anAttribute)
return;
return self;
_cache = { };
_themeDefaultAttribute = anAttribute;
var attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:anAttribute];
attribute._values = [_values copy];
return attribute;
}
- (_CPThemeAttribute)attributeMergedWithAttribute:(_CPThemeAttribute)anAttribute
{
var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue];
var mergedAttribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute];
mergedAttribute._values = [_values copy];
[mergedAttribute._values addEntriesFromDictionary:anAttribute._values];
if (anAttribute._values)
mergedAttribute._values ? [mergedAttribute._values addEntriesFromDictionary:anAttribute._values] : [anAttribute._values copy];
return mergedAttribute;
}
- (CPString)description
{
return [super description] + @" Name: " + _name + @", defaultAttribute: " + _themeDefaultAttribute + @", defaultValue: " + _defaultValue + @", values: " + _values;
}
@end
// This is used to pass 'parrentAttribute' to the coder
var ParentAttributeForCoder = nil;
@implementation _CPThemeAttribute (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
@@ -676,13 +760,16 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
_name = [aCoder decodeObjectForKey:@"name"];
_defaultValue = [aCoder decodeObjectForKey:@"defaultValue"];
_values = @{};
_themeDefaultAttribute = ParentAttributeForCoder;
if ([aCoder containsValueForKey:@"value"])
{
var state = String(CPThemeStateNormal);
var state;
if ([aCoder containsValueForKey:@"state"])
state = [aCoder decodeObjectForKey:@"state"];
else
state = CPThemeStateNormalString
[_values setObject:[aCoder decodeObjectForKey:"value"] forKey:state];
}
@@ -710,7 +797,7 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
[aCoder encodeObject:_defaultValue forKey:@"defaultValue"];
var keys = [_values allKeys],
count = keys.length;
count = keys ? keys.length : 0;
if (count === 1)
{
@@ -748,7 +835,7 @@ function CPThemeAttributeEncode(aCoder, aThemeAttribute)
{
var state = [values allKeys][0];
if (state === String(CPThemeStateNormal))
if (state === CPThemeStateNormalString)
{
[aCoder encodeObject:[values objectForKey:state] forKey:key];
@@ -766,30 +853,24 @@ function CPThemeAttributeEncode(aCoder, aThemeAttribute)
return NO;
}
function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme, aClass)
function CPThemeAttributeDecode(aCoder, attribute)
{
var key = "$a" + anAttributeName;
var key = "$a" + attribute._name;
if (![aCoder containsValueForKey:key])
var attribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
else
if ([aCoder containsValueForKey:key])
{
var attribute = [aCoder decodeObjectForKey:key];
ParentAttributeForCoder = attribute._themeDefaultAttribute;
if (!attribute || !attribute.isa || ![attribute isKindOfClass:[_CPThemeAttribute class]])
{
var themeAttribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue];
var decodedAttribute = [aCoder decodeObjectForKey:key];
[themeAttribute setValue:attribute];
ParentAttributeForCoder = nil;
attribute = themeAttribute;
}
if (!decodedAttribute || !decodedAttribute.isa || ![decodedAttribute isKindOfClass:[_CPThemeAttribute class]])
attribute = [attribute attributeBySettingValue:decodedAttribute];
else
attribute = decodedAttribute;
}
if (aTheme && aClass)
[attribute setParentAttribute:[aTheme attributeWithName:anAttributeName forClass:aClass]];
return attribute;
}
-6
View File
@@ -1475,9 +1475,6 @@ CPTokenFieldDeleteButtonType = 1;
- (BOOL)setThemeState:(ThemeState)aState
{
if (aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
var r = [super setThemeState:aState];
// Share hover state with the disclosure and delete buttons.
@@ -1492,9 +1489,6 @@ CPTokenFieldDeleteButtonType = 1;
- (BOOL)unsetThemeState:(ThemeState)aState
{
if (aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
var r = [super unsetThemeState:aState];
// Share hover state with the disclosure and delete button.
+13 -2
View File
@@ -3386,8 +3386,19 @@ setBoundsOrigin:
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
}
[_subviews makeObjectsPerformSelector:@selector(_recomputeAppearance)];
}
// var start = [CPDate new];
for (var i = 0, size = [_subviews count]; i < size; i++)
{
[[_subviews objectAtIndex:i] _recomputeAppearance];
}
// [_subviews makeObjectsPerformSelector:@selector(_recomputeAppearance)];
/* var now = [CPDate new];
var elapsedSeconds = [now timeIntervalSinceReferenceDate] - [start timeIntervalSinceReferenceDate];
CPLog.trace(@"_recomputeAppearance " + [_subviews count] + " subviews in " + elapsedSeconds + @" seconds");
*/}
@end
+1 -1
View File
@@ -326,7 +326,7 @@ var themedButtonValues = nil,
var button = [self button];
[button setTitle:@"OK"];
[button setThemeState:[CPButtonStateBezelStyleRounded, CPThemeStateDefault]];
[button setThemeStates:[CPButtonStateBezelStyleRounded, CPThemeStateDefault]];
return button;
}
+12 -2
View File
@@ -232,7 +232,12 @@ var ItemSizes = { },
state = attributeValueState[2];
if (state)
[aView setValue:value forThemeAttribute:attribute inState:state];
{
if (state.isa && [state isKindOfClass:CPArray])
[aView setValue:value forThemeAttribute:attribute inStates:state];
else
[aView setValue:value forThemeAttribute:attribute inState:state];
}
else
[aView setValue:value forThemeAttribute:attribute];
}
@@ -304,7 +309,12 @@ var ItemSizes = { },
}
if (state)
[aView setValue:value forThemeAttribute:attribute inState:state];
{
if (state.isa && [state isKindOfClass:CPArray])
[aView setValue:value forThemeAttribute:attribute inStates:state];
else
[aView setValue:value forThemeAttribute:attribute inState:state];
}
else
[aView setValue:value forThemeAttribute:attribute];
}
+2 -2
View File
@@ -171,9 +171,9 @@ var _CPAutocompleteMenuMaximumHeight = 307;
var dataView = [tableColumn dataView],
fontNormal = [dataView valueForThemeAttribute:@"font" inState:CPThemeStateTableDataView],
fontSelected = [dataView valueForThemeAttribute:@"font" inState:[CPThemeStateTableDataView, CPThemeStateSelectedDataView]],
fontSelected = [dataView valueForThemeAttribute:@"font" inStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView]],
contentInsetNormal = [dataView valueForThemeAttribute:@"content-inset" inState:CPThemeStateTableDataView],
contentInsetSelected = [dataView valueForThemeAttribute:@"content-inset" inState:[CPThemeStateTableDataView, CPThemeStateSelectedDataView]];
contentInsetSelected = [dataView valueForThemeAttribute:@"content-inset" inStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView]];
var mergedString = contentArray.join("\n");
+146 -47
View File
@@ -23,8 +23,6 @@
@import "CPTheme.j"
var CachedThemeAttributes = nil;
var CPViewThemeClassKey = @"CPViewThemeClassKey",
CPViewThemeStateKey = @"CPViewThemeStateKey";
@@ -38,8 +36,11 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
- (unsigned)themeState;
- (BOOL)hasThemeState:(ThemeState)aState;
- (BOOL)hasThemeStates:(CPArray)states;
- (BOOL)setThemeState:(ThemeState)aState;
- (BOOL)setThemeStates:(CPArray)aState;
- (BOOL)unsetThemeState:(ThemeState)aState;
- (BOOL)unsetThemeStates:(CPArray)aState;
- (BOOL)hasThemeAttribute:(CPString)aName;
- (void)objectDidChangeTheme;
@@ -74,31 +75,55 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
- (BOOL)hasThemeState:(ThemeState)aState
{
if (aState.isa && [aState isKindOfClass:CPArray])
return _themeState.hasThemeState.apply(_themeState, aState);
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState && aState.isa && [aState isKindOfClass:CPArray])
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'hasThemeStates: instead: " + aState];
#endif
return _themeState.hasThemeState(aState);
}
- (BOOL)hasThemeStates:(CPArray)states
{
var i = [states count],
aState = [states objectAtIndex:--i];
while (i > 0)
aState = aState.and([states objectAtIndex:--i]);
return _themeState.hasThemeState(aState);
}
- (BOOL)setThemeState:(ThemeState)aState
{
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState && aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'setThemeStates: instead: " + aState];
#endif
if (_themeState.hasThemeState(aState))
return NO;
_themeState = CPThemeState(_themeState, aState);
_themeState = _themeState.and(aState);
return YES;
}
- (BOOL)unsetThemeState:(ThemeState)aState
{
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState && aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'unsetThemeStates: instead: " + aState];
#endif
var oldThemeState = _themeState;
_themeState = _themeState.without(aState);
if (oldThemeState === _themeState)
@@ -107,6 +132,28 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
return YES;
}
- (BOOL)setThemeStates:(CPArray)states
{
var i = [states count],
aState = [states objectAtIndex:--i];
while (i > 0)
aState = aState.and([states objectAtIndex:--i]);
return [self setThemeState:aState];
}
- (BOOL)unsetThemeStates:(CPArray)states
{
var i = [states count],
aState = [states objectAtIndex:--i];
while (i > 0)
aState = aState.and([states objectAtIndex:--i]);
return [self unsetThemeState:aState];
}
#pragma mark Theme Attributes
+ (CPString)defaultThemeClass
@@ -134,25 +181,30 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
[self _loadThemeAttributes];
}
+ (CPArray)_themeAttributes
{
if (!CachedThemeAttributes)
CachedThemeAttributes = {};
var NULL_THEME = {};
+ (CPArray)_themeAttributesForTheme:(CPTheme)theme andThemeClass:(CPString)themeClassName
{
var theClass = [self class],
CPObjectClass = [CPObject class],
attributes = [],
nullValue = [CPNull null];
theClassName = class_getName(theClass),
themeClassNameCache = theme != nil ? theme._cachedThemeAttributes || (theme._cachedThemeAttributes = {}) : NULL_THEME,
themedCacheAttributes = themeClassNameCache[themeClassName] || (themeClassNameCache[themeClassName] = {}),
attributes = themedCacheAttributes[theClassName];
if (attributes)
return attributes;
else
attributes = [];
var CPObjectClass = [CPObject class];
for (; theClass && theClass !== CPObjectClass; theClass = [theClass superclass])
{
var cachedAttributes = CachedThemeAttributes[class_getName(theClass)];
var cachedAttributes = themedCacheAttributes[class_getName(theClass)];
if (cachedAttributes)
{
attributes = attributes.length ? attributes.concat(cachedAttributes) : attributes;
CachedThemeAttributes[[self className]] = attributes;
break;
}
@@ -167,38 +219,34 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
while (attributeCount--)
{
var attributeName = attributeKeys[attributeCount],
attributeValue = [attributeDictionary objectForKey:attributeName];
attributeValue = [attributeDictionary objectForKey:attributeName],
themeAttribute = [[_CPThemeAttribute alloc] initWithName:attributeName defaultValue:attributeValue defaultAttribute:[theme attributeWithName:attributeName forClass:themeClassName]];
attributes.push(attributeValue === nullValue ? nil : attributeValue);
attributes.push(attributeName);
attributes.push(themeAttribute);
}
}
themedCacheAttributes[theClassName] = attributes;
return attributes;
}
- (void)_loadThemeAttributes
{
var theClass = [self class],
attributes = [theClass _themeAttributes],
attributes = [theClass _themeAttributesForTheme:[self theme] andThemeClass:[self themeClass]],
count = attributes.length;
if (!count)
return;
var theme = [self theme],
themeClass = [self themeClass];
_themeAttributes = {};
while (count--)
{
var attributeName = attributes[count--],
attribute = [[_CPThemeAttribute alloc] initWithName:attributeName defaultValue:attributes[count]];
var attribute = attributes[count];
[attribute setParentAttribute:[theme attributeWithName:attributeName forClass:themeClass]];
_themeAttributes[attributeName] = attribute;
_themeAttributes[attribute._name] = attribute;
}
}
@@ -228,7 +276,7 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
for (var attributeName in _themeAttributes)
{
if (_themeAttributes.hasOwnProperty(attributeName))
[_themeAttributes[attributeName] setParentAttribute:[theme attributeWithName:attributeName forClass:themeClass]];
_themeAttributes[attributeName] = [_themeAttributes[attributeName] attributeBySettingParentAttribute:[theme attributeWithName:attributeName forClass:themeClass]];
}
}
@@ -253,48 +301,96 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName inState:(ThemeState)aState
{
if (aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState.isa && [aState isKindOfClass:CPArray])
[CPException raise:CPInvalidArgumentException reason:self + @": aState can't be an array. Please use 'setValue:forThemeAttribute:inStates:' instead: " + aState];
#endif
if (!_themeAttributes || !_themeAttributes[aName])
var themeAttr = _themeAttributes && _themeAttributes[aName];
if (!themeAttr)
[CPException raise:CPInvalidArgumentException reason:[self className] + " does not contain theme attribute '" + aName + "'"];
[_themeAttributes[aName] setValue:aValue forState:aState];
_themeAttributes[aName] = [themeAttr attributeBySettingValue:aValue forState:aState];
}
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName inStates:(CPArray)states
{
var i = [states count],
aState = [states objectAtIndex:--i];
while (i > 0)
aState = aState.and([states objectAtIndex:--i]);
var themeAttr = _themeAttributes && _themeAttributes[aName];
if (!themeAttr)
[CPException raise:CPInvalidArgumentException reason:[self className] + " does not contain theme attribute '" + aName + "'"];
_themeAttributes[aName] = [themeAttr attributeBySettingValue:aValue forState:aState];
}
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
{
if (!_themeAttributes || !_themeAttributes[aName])
var themeAttr = _themeAttributes && _themeAttributes[aName];
if (!themeAttr)
[CPException raise:CPInvalidArgumentException reason:[self className] + " does not contain theme attribute '" + aName + "'"];
[_themeAttributes[aName] setValue:aValue];
_themeAttributes[aName] = [themeAttr attributeBySettingValue:aValue];
}
- (id)valueForThemeAttribute:(CPString)aName inState:(ThemeState)aState
{
#if DEBUG
if (aState.isa && [aState isKindOfClass:CPArray])
aState = CPThemeState.apply(null, aState);
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'valueForThemeAttribute:inStates:' instead: " + aState];
#endif
if (!_themeAttributes || !_themeAttributes[aName])
var themeAttr = _themeAttributes && _themeAttributes[aName];
if (!themeAttr)
[CPException raise:CPInvalidArgumentException reason:[self className] + " does not contain theme attribute '" + aName + "'"];
return [_themeAttributes[aName] valueForState:aState];
return [themeAttr valueForState:aState];
}
- (id)valueForThemeAttribute:(CPString)aName inStates:(CPArray)states
{
var i = [states count],
aState = [states objectAtIndex:--i];
while (i > 0)
aState = aState.and([states objectAtIndex:--i]);
var themeAttr = _themeAttributes && _themeAttributes[aName];
if (!themeAttr)
[CPException raise:CPInvalidArgumentException reason:[self className] + " does not contain theme attribute '" + aName + "'"];
return [themeAttr valueForState:aState];
}
- (id)valueForThemeAttribute:(CPString)aName
{
if (!_themeAttributes || !_themeAttributes[aName])
var themeAttr = _themeAttributes && _themeAttributes[aName];
if (!themeAttr)
[CPException raise:CPInvalidArgumentException reason:[self className] + " does not contain theme attribute '" + aName + "'"];
return [_themeAttributes[aName] value];
return [themeAttr value];
}
- (id)currentValueForThemeAttribute:(CPString)aName
{
if (!_themeAttributes || !_themeAttributes[aName])
var themeAttr = _themeAttributes && _themeAttributes[aName];
if (!themeAttr)
[CPException raise:CPInvalidArgumentException reason:[self className] + " does not contain theme attribute '" + aName + "'"];
return [_themeAttributes[aName] valueForState:_themeState];
return [themeAttr valueForState:_themeState];
}
- (BOOL)hasThemeAttribute:(CPString)aName
@@ -320,7 +416,10 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
state = attributeValueState[2];
if (state)
[self setValue:value forThemeAttribute:attribute inState:state];
if (state.isa && [state isKindOfClass:CPArray])
[self setValue:value forThemeAttribute:attribute inStates:state];
else
[self setValue:value forThemeAttribute:attribute inState:state];
else
[self setValue:value forThemeAttribute:attribute];
}
@@ -367,14 +466,14 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
var theClass = [self class],
themeClass = [self themeClass],
attributes = [theClass _themeAttributes],
attributes = [theClass _themeAttributesForTheme:_theme andThemeClass:themeClass],
count = attributes.length;
while (count--)
{
var attributeName = attributes[count--];
var attribute = attributes[count];
_themeAttributes[attributeName] = CPThemeAttributeDecode(aCoder, attributeName, attributes[count], _theme, themeClass);
_themeAttributes[attribute._name] = CPThemeAttributeDecode(aCoder, attribute);
}
}
+5 -5
View File
@@ -56,20 +56,20 @@
- (void)testThemeAttributeValueForState
{
var themeAttribute = [[_CPThemeAttribute alloc] initWithName:@"test" defaultValue:5];
var themeAttribute = [[_CPThemeAttribute alloc] initWithName:@"test" defaultValue:5 defaultAttribute:nil];
[self assertTrue:([themeAttribute valueForState:CPThemeState("aState")] == 5) message:"Return the default value for the theme attribute if the theme attribute has no value defined for the given state"];
[themeAttribute setValue:7 forState:CPThemeState("aState")];
themeAttribute = [themeAttribute attributeBySettingValue:7 forState:CPThemeState("aState")];
[self assertTrue:([themeAttribute valueForState:CPThemeState("aState")] == 7) message:"Return the correct value for the state if the state is defined"];
[themeAttribute setValue:8 forState:CPThemeState('normal')];
themeAttribute = [themeAttribute attributeBySettingValue:8 forState:CPThemeState('normal')];
[self assertTrue:([themeAttribute valueForState:CPThemeState("aState1")] == 8) message:"Return the normal value for the state if the theme attribute has no value defined for the given state but has a value for the normal state defined"];
[themeAttribute setValue:10 forState:CPThemeState('aState3+aState4')];
themeAttribute = [themeAttribute attributeBySettingValue:10 forState:CPThemeState('aState3+aState4')];
[self assertTrue:([themeAttribute valueForState:CPThemeState("aState3")] == 8) message:"Return the normal value for the state if the state is only a partial match on the theme attributes defined states"];
[self assertTrue:([themeAttribute valueForState:CPThemeState("aState4+aState3")] == 10) message:"Correctly match combined states on the theme attribute"];
[themeAttribute setValue:9 forState:CPThemeState('aState3')];
themeAttribute = [themeAttribute attributeBySettingValue:9 forState:CPThemeState('aState3')];
[self assertTrue:([themeAttribute valueForState:CPThemeState("aState8+aState3+aState4")] == 10) message:"Return the largest partial subset match for a combined state that isn't a perfect match"];
}
+6 -6
View File
@@ -69,7 +69,7 @@ var methodCalled;
[self assertTrue:[view hasThemeState:CPThemeStateDisabled] message:@"CPView should be in state CPThemeStateDisabled"];
[self assertTrue:[view hasThemeState:CPThemeStateBordered] message:@"CPView should be in state CPThemeStateBordered"];
[self assertTrue:[view hasThemeState:CPThemeState(CPThemeStateBordered, CPThemeStateDisabled)] message:@"CPView should be in the combined state of CPThemeStateDisabled and CPThemeStateBordered"];
[self assertTrue:[view hasThemeState:[CPThemeStateBordered, CPThemeStateDisabled]] message:@"hasThemeState works with an array argument"];
[self assertTrue:[view hasThemeStates:[CPThemeStateBordered, CPThemeStateDisabled]] message:@"hasThemeState works with an array argument"];
[self assertFalse:[view hasThemeState:CPThemeState(CPThemeStateNormal)] message:@"CPView should not be in CPThemeStateNormal"];
}
@@ -94,7 +94,7 @@ var methodCalled;
[self assert:String(CPThemeState(CPThemeStateDisabled, CPThemeStateHighlighted)) equals:String([view themeState]) message:@"The view should be in the combined state of CPThemeStateDisabled and CPThemeStateHighlighted"];
[view unsetThemeState:[view themeState]];
[view setThemeState:[CPThemeStateSelected, CPThemeStateDisabled]];
[view setThemeStates:[CPThemeStateSelected, CPThemeStateDisabled]];
[self assert:String(CPThemeState(CPThemeStateDisabled, CPThemeStateSelected)) equals:String([view themeState]) message:@"setThemeState works with array argument"];
}
@@ -117,20 +117,20 @@ var methodCalled;
[self assert:String(CPThemeStateNormal) equals:String([view themeState]) message:@"CPView should be able to unset a combined theme state"];
[view setThemeState:CPThemeState(CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateBordered)];
[view unsetThemeState:[CPThemeStateBordered, CPThemeStateHighlighted]];
[view unsetThemeStates:[CPThemeStateBordered, CPThemeStateHighlighted]];
[self assert:String(CPThemeStateDisabled) equals:String([view themeState]) message:@"unsetThemeState works with array argument"];
[view setThemeState:CPThemeStateDisabled];
[view unsetThemeState:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[view unsetThemeStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[self assert:String(CPThemeStateNormal) equals:String([view themeState]) message:@"CPView should be able to unset a combined theme state that has more theme states than the view currently has"];
[view setThemeState:CPThemeState(CPThemeStateDisabled, CPThemeStateBordered)];
var returnValue = [view unsetThemeState:[CPThemeStateDisabled, CPThemeStateHighlighted]];
var returnValue = [view unsetThemeStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[self assert:String(CPThemeStateBordered) equals:String([view themeState]) message:@"CPView should be able to unset a combined theme state that has not entirely overlapping themestates"];
[self assertTrue:returnValue message:@"When unsetThemeState successfully unsets anything, it return YES"];
[view setThemeState:CPThemeState(CPThemeStateDisabled, CPThemeStateBordered)];
var returnValue = [view unsetThemeState:[CPThemeStateSelected, CPThemeStateHighlighted]];
var returnValue = [view unsetThemeStates:[CPThemeStateSelected, CPThemeStateHighlighted]];
[self assert:String(CPThemeState(CPThemeStateDisabled, CPThemeStateBordered)) equals:String([view themeState]) message:@"CPView not unset any theme states it does not have"];
[self assertFalse:returnValue message:@"When unsetThemeState doesn't unset anything, it returns NO"];