From fc411c5c1c441713639c9dda97b6014877bead4f Mon Sep 17 00:00:00 2001 From: Martin Carlberg Date: Mon, 19 Oct 2015 16:38:53 +0200 Subject: [PATCH] Fixed: Cache ThemeAttribute Conflicts: AppKit/CPTheme.j --- AppKit/CPButtonBar.j | 12 +- AppKit/CPDatePicker/_CPDatePickerCalendar.j | 32 ++-- AppKit/CPStepper.j | 12 +- AppKit/CPTheme.j | 174 ++++++++++++++++---- AppKit/CPView.j | 15 +- AppKit/Themes/BlendKit/BKThemeDescriptor.j | 20 ++- AppKit/_CPAutocompleteMenu.j | 4 +- AppKit/_CPObject+Theme.j | 96 ++++++++--- 8 files changed, 280 insertions(+), 85 deletions(-) diff --git a/AppKit/CPButtonBar.j b/AppKit/CPButtonBar.j index f27819689..322d8c65a 100644 --- a/AppKit/CPButtonBar.j +++ b/AppKit/CPButtonBar.j @@ -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]; } diff --git a/AppKit/CPDatePicker/_CPDatePickerCalendar.j b/AppKit/CPDatePicker/_CPDatePickerCalendar.j index c4ecb3866..b3140cab1 100644 --- a/AppKit/CPDatePicker/_CPDatePickerCalendar.j +++ b/AppKit/CPDatePicker/_CPDatePickerCalendar.j @@ -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]; diff --git a/AppKit/CPStepper.j b/AppKit/CPStepper.j index 3436d062f..42442ea04 100644 --- a/AppKit/CPStepper.j +++ b/AppKit/CPStepper.j @@ -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 diff --git a/AppKit/CPTheme.j b/AppKit/CPTheme.j index 61b7f8afc..9dc12e1e1 100644 --- a/AppKit/CPTheme.j +++ b/AppKit/CPTheme.j @@ -557,6 +557,26 @@ CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular"); CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall"); CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); +CPThemeStateNormalString = String(CPThemeStateNormal); + + +CPThemeAttributeCache = {}; // CPThemeAttributeCache[_name][themeDefaultAttribute._UID][_defaultValue] -> CPThemeAttribute + +var stringify = JSON.stringify; + +function CPStringthemeAttributeCacheString(array) +{ + var returnString = "["; + + for (var i = 0, size = array.length; i < size; i++) + { + var aValue = array[i]; + returnString += aValue == null ? aValue : aValue._UID || (aValue.isa === _CPJavaScriptArray ? CPStringthemeAttributeCacheString(aValue) : stringify(aValue)) + } + + return returnString + "]"; +} + @implementation _CPThemeAttribute : CPObject { CPString _name; @@ -564,10 +584,26 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); CPDictionary _values @accessors(readonly, getter=values); JSObject _cache; + JSObject _setValueTransformCache; + JSObject _removeValueTransformCache; _CPThemeAttribute _themeDefaultAttribute; } -- (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue ++ (id)attributeWithName:(CPString)aName defaultValue:(id)aDefaultValue defaultAttribute:(_CPThemeAttribute)aDefaultAttribute +{ + var defaultAttributeCache = CPThemeAttributeCache[aName] || (CPThemeAttributeCache[aName] = {}), + defaultAttributeUID = aDefaultAttribute ? aDefaultAttribute._UID : nil, + defaultValueCache = defaultAttributeCache[defaultAttributeUID] || (defaultAttributeCache[defaultAttributeUID] = {}), + aDefaultValueString = aDefaultValue == null ? aDefaultValue : aDefaultValue._UID || (aDefaultValue.isa === _CPJavaScriptArray ? CPStringthemeAttributeCacheString(aDefaultValue) : JSON.stringify(aDefaultValue)), + attribute = defaultValueCache[aDefaultValueString]; + + if (!attribute) + attribute = defaultValueCache[aDefaultValueString] = [[_CPThemeAttribute alloc] initWithName:aName defaultValue:aDefaultValue defaultAttribute:aDefaultAttribute]; + + return attribute; +} + +- (id)initWithName:(CPString)aName defaultValue:(id)aDefaultValue defaultAttribute:(_CPThemeAttribute)aDefaultAttribute { self = [super init]; @@ -577,6 +613,8 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); _name = aName; _defaultValue = aDefaultValue; _values = @{}; + if (aDefaultAttribute) + _themeDefaultAttribute = aDefaultAttribute; } return self; @@ -597,7 +635,64 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); return [_values count] > 0; } -- (void)setValue:(id)aValue +- (_CPThemeAttribute)attributeBySettingValue:(id)aValue +{ + var transformStateDict = self._setValueTransformCacheN || (self._setValueTransformCacheN = {}), + transformValueDict = transformStateDict[CPThemeStateNormalString] || (transformStateDict[CPThemeStateNormalString] = {}), + aValueString = aValue == null ? aValue : aValue._UID || (aValue.isa === _CPJavaScriptArray ? CPStringthemeAttributeCacheString(aValue) : stringify(aValue)), + attribute = transformValueDict[aValueString]; + + if (!attribute) + { + attribute = transformValueDict[aValueString] = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute]; + if (aValue !== undefined && aValue !== nil) + attribute._values = @{ CPThemeStateNormalString: aValue }; + } + + return attribute; +} + +- (_CPThemeAttribute)attributeBySettingValue:(id)aValue forState:(ThemeState)aState +{ + var shouldRemoveValue = aValue === undefined || aValue === nil, + transformStateDict = shouldRemoveValue ? self._removeValueTransformCache || (self._removeValueTransformCache = {}) : self._setValueTransformCache || (self._setValueTransformCache = {}), + stateString = String(aState), + transformValueDict = transformStateDict[stateString] || (transformStateDict[stateString] = {}), + aValueString = aValue == null ? aValue : aValue._UID || (aValue.isa === _CPJavaScriptArray ? CPStringthemeAttributeCacheString(aValue) : stringify(aValue)), + attribute = transformValueDict[aValueString]; + + if (!attribute) + { + attribute = transformValueDict[aValueString] = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute]; + + var values = [_values copy]; +// var values = _values; + +/* if (values != null) + { + values = [values copy]; +*/ if (shouldRemoveValue) + [values removeObjectForKey:stateString]; + else + [values setObject:aValue forKey:stateString]; + attribute._values = values; +/* } + else + { + if (!shouldRemoveValue) + { + values = [[CPDictionary alloc] init]; + [values setObject:aValue forKey:stateString]; +// values = @{stateString: aValue}; + attribute._values = values; + } + } + */ } + + return attribute; +} + +/*- (void)setValue:(id)aValue { _cache = {}; @@ -615,7 +710,7 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); [_values removeObjectForKey:String(aState)]; else [_values setObject:aValue forKey:String(aState)]; -} +}*/ - (id)value { @@ -639,7 +734,7 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); if (aState._stateNameCount > 1) { var states = [_values allKeys], - count = states.length, + count = states ? states.length : 0, largestThemeState = 0; while (count--) @@ -674,52 +769,76 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); _cache[stateName] = value; - return value; +/* var now = [CPDate new]; + var elapsedSeconds = [now timeIntervalSinceReferenceDate] - [start timeIntervalSinceReferenceDate]; + + CPLog.trace(@"valueForState:" + aState + " in " + elapsedSeconds + @" seconds"); + +*/ return value; } -- (void)setParentAttribute:(_CPThemeAttribute)anAttribute +/*- (void)setParentAttribute:(_CPThemeAttribute)anAttribute { if (_themeDefaultAttribute === anAttribute) return; _cache = { }; _themeDefaultAttribute = anAttribute; -} +}*/ - (_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; } @end + +// This is used to pass 'parrentAttribute' to the coder +var ParentAttributeForCoder = nil; + @implementation _CPThemeAttribute (CPCoding) - (id)initWithCoder:(CPCoder)aCoder { - self = [super init]; + var aName = [aCoder decodeObjectForKey:@"name"], + aDefaultValue = [aCoder decodeObjectForKey:@"defaultValue"], + defaultAttributeCache = CPThemeAttributeCache[aName] || (CPThemeAttributeCache[aName] = {}), + defaultAttributeUID = ParentAttributeForCoder ? ParentAttributeForCoder._UID : nil, // Uses global variable as + defaultValueCache = defaultAttributeCache[defaultAttributeUID] || (defaultAttributeCache[defaultAttributeUID] = {}), + aDefaultValueString = aDefaultValue == null ? aDefaultValue : aDefaultValue._UID || (aDefaultValue.isa === _CPJavaScriptArray ? CPStringthemeAttributeCacheString(aDefaultValue) : stringify(aDefaultValue)), + attribute = defaultValueCache[aDefaultValueString]; + + if (attribute) + self = attribute; + else + self = [super init]; if (self) { - _cache = {}; + defaultValueCache[aDefaultValueString] = self; - _name = [aCoder decodeObjectForKey:@"name"]; - _defaultValue = [aCoder decodeObjectForKey:@"defaultValue"]; + _cache = {}; + _name = aName; _values = @{}; + _defaultValue = aDefaultValue; 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]; + self = [self attributeBySettingValue:[aCoder decodeObjectForKey:"value"] forState:state]; } else { @@ -731,7 +850,7 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); { var key = keys[count]; - [_values setObject:[encodedValues objectForKey:key] forKey:key]; + self = [self attributeBySettingValue:[encodedValues objectForKey:key] forState:key]; } } } @@ -745,7 +864,7 @@ CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); [aCoder encodeObject:_defaultValue forKey:@"defaultValue"]; var keys = [_values allKeys], - count = keys.length; + count = keys ? keys.length : 0; if (count === 1) { @@ -783,7 +902,7 @@ function CPThemeAttributeEncode(aCoder, aThemeAttribute) { var state = [values allKeys][0]; - if (state === String(CPThemeStateNormal)) + if (state === CPThemeStateNormalString) { [aCoder encodeObject:[values objectForKey:state] forKey:key]; @@ -803,28 +922,27 @@ function CPThemeAttributeEncode(aCoder, aThemeAttribute) function CPThemeAttributeDecode(aCoder, anAttributeName, aDefaultValue, aTheme, aClass) { - var key = "$a" + anAttributeName; + var key = "$a" + anAttributeName, + parentAttribute = (aTheme && aClass) ? [aTheme attributeWithName:anAttributeName forClass:aClass] : nil; if (![aCoder containsValueForKey:key]) - var attribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue]; - + var attribute = [_CPThemeAttribute attributeWithName:anAttributeName defaultValue:aDefaultValue defaultAttribute:parentAttribute]; else { + ParentAttributeForCoder = parentAttribute; + var attribute = [aCoder decodeObjectForKey:key]; + ParentAttributeForCoder = nil; + if (!attribute || !attribute.isa || ![attribute isKindOfClass:[_CPThemeAttribute class]]) { - var themeAttribute = [[_CPThemeAttribute alloc] initWithName:anAttributeName defaultValue:aDefaultValue]; + var themeAttribute = [_CPThemeAttribute attributeWithName:anAttributeName defaultValue:aDefaultValue defaultAttribute:parentAttribute]; - [themeAttribute setValue:attribute]; - - attribute = themeAttribute; + attribute = [themeAttribute attributeBySettingValue:attribute]; } } - if (aTheme && aClass) - [attribute setParentAttribute:[aTheme attributeWithName:anAttributeName forClass:aClass]]; - return attribute; } diff --git a/AppKit/CPView.j b/AppKit/CPView.j index 0057c0b89..3041765a7 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -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 diff --git a/AppKit/Themes/BlendKit/BKThemeDescriptor.j b/AppKit/Themes/BlendKit/BKThemeDescriptor.j index 801c6e6ae..188d24e46 100644 --- a/AppKit/Themes/BlendKit/BKThemeDescriptor.j +++ b/AppKit/Themes/BlendKit/BKThemeDescriptor.j @@ -231,10 +231,14 @@ var ItemSizes = { }, value = attributeValueState[1], state = attributeValueState[2]; - if (state) - [aView setValue:value forThemeAttribute:attribute inState:state]; - else + if (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]; + } } } @@ -303,10 +307,14 @@ var ItemSizes = { }, } } - if (state) - [aView setValue:value forThemeAttribute:attribute inState:state]; - else + if (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]; + } } } } diff --git a/AppKit/_CPAutocompleteMenu.j b/AppKit/_CPAutocompleteMenu.j index cf25c025d..f1b3ecb41 100644 --- a/AppKit/_CPAutocompleteMenu.j +++ b/AppKit/_CPAutocompleteMenu.j @@ -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"); diff --git a/AppKit/_CPObject+Theme.j b/AppKit/_CPObject+Theme.j index 1e37c1adc..e7f848e72 100644 --- a/AppKit/_CPObject+Theme.j +++ b/AppKit/_CPObject+Theme.j @@ -244,9 +244,7 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey", while (count--) { var attributeName = attributes[count--], - attribute = [[_CPThemeAttribute alloc] initWithName:attributeName defaultValue:attributes[count]]; - - [attribute setParentAttribute:[theme attributeWithName:attributeName forClass:themeClass]]; + attribute = [_CPThemeAttribute attributeWithName:attributeName defaultValue:attributes[count] defaultAttribute:[theme attributeWithName:attributeName forClass:themeClass]]; _themeAttributes[attributeName] = attribute; } @@ -303,48 +301,104 @@ 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 + 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]; + var a = [themeAttr attributeBySettingValue:aValue forState:aState]; + //print("setValue:forThemeAttribute:inState:" + aState + ": " + aName + ": " + aValue + "oldAttribute: " + [themeAttr._values description]); + _themeAttributes[aName] = a; +} + +- (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 + "'"]; + + var a = [themeAttr attributeBySettingValue:aValue forState:aState]; + //print("setValue:forThemeAttribute:inStates:" + aState + ": " + aName + ": " + aValue + "oldAttribute: " + [themeAttr._values description]); + _themeAttributes[aName] = a; } - (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]; + var a = [themeAttr attributeBySettingValue:aValue]; + //print("setValue:forThemeAttribute:" + ": " + aName + ": " + aValue + "oldAttribute: " + [themeAttr._values description]); + _themeAttributes[aName] = a; } - (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 @@ -369,10 +423,14 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey", value = attributeValueState[1], state = attributeValueState[2]; - if (state) - [self setValue:value forThemeAttribute:attribute inState:state]; - else + if (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]; + } } }