New: Added dynamic attributes set for CSS based themes (#2701)

This commit is contained in:
Didier Korthoudt
2020-04-06 13:10:34 +02:00
committed by GitHub
parent 2d9d354a05
commit e7d5afe90e
8 changed files with 277 additions and 1 deletions
+32
View File
@@ -26,6 +26,7 @@
@import "_CPMenuManager.j"
@class _CPMenuView
@class CPMenuItem
var _CPMenuWindowPool = [],
_CPMenuWindowPoolCapacity = 5,
@@ -419,6 +420,21 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
#pragma mark -
@implementation _CPMenuWindow (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[[self contentView] _setThemeIncludingDescendants:aTheme];
[_menuView _setThemeIncludingDescendants:aTheme];
[_menuView tile];
}
@end
#pragma mark -
/*
@ignore
*/
@@ -552,6 +568,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[view setFrameOrigin:CGPointMake(0.0, y)];
[view _setThemeIncludingDescendants:[CPTheme defaultTheme]];
[self addSubview:view];
var size = [view minSize],
@@ -582,3 +599,18 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
}
@end
#pragma mark -
@implementation _CPMenuView (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[_menuItemViews makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
for (var i = 0, items = [[self menu] itemArray], count = [items count]; i < count; i++)
[[items[i] _menuItemView] _setThemeIncludingDescendants:aTheme];
}
@end
+14
View File
@@ -897,6 +897,20 @@ CPControlKeyMask
@end
#pragma mark -
@implementation CPMenuItem (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[_view _setThemeIncludingDescendants:aTheme];
[_menuItemView _setThemeIncludingDescendants:aTheme];
}
@end
#pragma mark -
var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
CPMenuItemTitleKey = @"CPMenuItemTitleKey",
@@ -352,6 +352,22 @@
@end
#pragma mark -
@implementation _CPMenuItemStandardView (CSSTheming)
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[[self subviews] makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
}
@end
#pragma mark -
@implementation _CPMenuItemSubmenuIndicatorView : CPView
{
CPColor _color;
+25
View File
@@ -252,6 +252,31 @@
@end
#pragma mark -
@implementation _CPMenuItemView (CSSTheming)
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[_view _setThemeIncludingDescendants:aTheme];
// Items must also perform this (without this, only the selected item does)
[_imageAndTextView _setThemeIncludingDescendants:aTheme];
[_submenuView _setThemeIncludingDescendants:aTheme];
[[_menuItem view] _setThemeIncludingDescendants:aTheme];
if ([_view respondsToSelector:@selector(update)])
[_view update];
}
@end
#pragma mark -
@implementation _CPMenuItemArrowView : CPView
{
CPColor _color;
+175
View File
@@ -30,6 +30,7 @@
@class _CPThemeAttribute
@class CPImage
@class CPColor
@class CPApplication
var CPThemesByName = { },
CPThemeDefaultTheme = nil,
@@ -339,9 +340,143 @@ var CPThemeNameKey = @"CPThemeNameKey",
//
// The method -(void)setCSSResourcesPath is meant to be used only by CPThemeBlend during theme loading in order to
// replace the special path "%%" in CSS components (like in url(%%packed.png) ) with the path to the theme blend resources folder.
//
// DYNAMIC ATTRIBUTES SET:
//
// If the default theme is CSS based and exposes some dynamic attributes (like Aristo3 does for color schema), you can switch "live"
// to another set of values by calling [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:aDynamicSet] where aDynamicSet is a
// dictionary with keys being the names of exposed dynamic attributes (see the themeDescriptor.j of the CSS based theme you use)
// and values, accordingly to the type of the corresponding default values provided by the theme (CPColor, a CSS color string, ...).
// You can provide key-value pairs for a subset of the exposed dynamic attributes. If no key-value pair is specified for a dynamic
// attribute, the theme default value will be used.
//
// To restore default theme values, simply pass nil as dynamic set : [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:nil]
//
// During theme loading, if your application responds to -(CPDictionary)themeDynamicAttributesSet, the returned values set is directly
// applied to the default set. This avoids displaying the theme with its default values and then redisplaying it with your values.
// The -(CPDictionary)themeDynamicAttributesSet must be declared in a CPApplication category (and not in your application delegate) as
// it will be called during application initialisation, before the delegate has been defined.
//
// To use default theme values, simply return nil.
//
//
// TYPICAL USAGE:
//
// One can imagine that a user can choose between some predefined color schemas. You can store this user preference in standard user defaults:
//
// [[CPUserDefaults standardUserDefaults] setObject:ConstantRepresentingAColorSchema forKey:@"userTheme"];
// [[CPUserDefaults standardUserDefaults] synchronize];
//
// As you want to use the choosen color schema at run time, you've implemented -(CPDictionary)themeDynamicAttributesSet (see below), so updating
// the UI as soon as the user has made his choice is easy :
//
// [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:[[CPApplication sharedApplication] themeDynamicAttributesSet]];
//
// If the user chooses the theme default color schema, you can then simply remove the user default:
//
// [[CPUserDefaults standardUserDefaults] removeObjectForKey:@"userTheme"];
// [[CPUserDefaults standardUserDefaults] synchronize];
//
// Your CPApplication category could then be something like:
//
// @implementation CPApplication (themeColorSchema)
//
// - (CPDictionary)themeDynamicAttributesSet
// {
// var userTheme = [[CPUserDefaults standardUserDefaults] objectForKey:@"userTheme"];
//
// if (!userTheme)
// // Return nil to have the theme default color schema
// return nil;
//
// switch (userTheme)
// {
// case ConstantRepresentingAColorSchema:
// return @{
// @"ExposedDynamicAttribute1": [CPColor colorWithRed:245.0/255.0 green:164.0/255.0 blue:9.0/255.0 alpha:0.85],
// @"ExposedDynamicAttribute2": [CPColor colorWithRed:245.0/255.0 green:164.0/255.0 blue:9.0/255.0 alpha:0.85],
// @"ExposedDynamicAttribute3": @"rgb(245,164,9)",
// @"ExposedDynamicAttribute4": @"url(./Resources/mypacked.png)"
// };
// break;
//
// case AnotherConstantRepresentingAColorSchema:
// return @{
// @"ExposedDynamicAttribute1": [CPColor colorWithRed:39.0/255.0 green:216.0/255.0 blue:93.0/255.0 alpha:0.85],
// @"ExposedDynamicAttribute3": @"rgb(39,216,93)"
// };
// break;
//
// ...
// }
// }
//
// @end
var _savedThemesByName = { };
@implementation CPTheme (CSSTheming)
- (void)_initializeDynamicAttributesAndResourcesPathWithBundle:(CPBundle)aBundle
{
// First, save a template of the theme so dynamic attributes and images placeholders are preserved
_savedThemesByName[_name] = [CPKeyedArchiver archivedDataWithRootObject:self];
// Then, compute the dynamic attributes set that will be applied to the theme
// Does the application delegate provide a dynamic attributes set ?
var dynamicSet = ([[CPApplication sharedApplication] respondsToSelector:@selector(themeDynamicAttributesSet)]) ? [[CPApplication sharedApplication] themeDynamicAttributesSet] : nil;
// Take the one defined in the theme
var defaultDynamicSet = [self valueForAttributeWithName:@"dynamic-set" forClass:[CPView class]];
// Update the default dynamic attributes set with the one provided by the application
[dynamicSet enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
[defaultDynamicSet setObject:anObject forKey:aKey];
}];
// Apply the resulting set
[self applyDynamicSet:defaultDynamicSet];
// Then fix resources path in CSS directives
[self setCSSResourcesPath:[aBundle resourcePath]];
}
+ (void)updateDefaultThemeDynamicAttributesSetWithSet:(CPDictionary)dynamicSet
{
// First, get a copy of the theme template
var newTheme = [CPKeyedUnarchiver unarchiveObjectWithData:_savedThemesByName[[CPThemeDefaultTheme name]]];
// Take the dynamic set defined in the theme
var defaultDynamicSet = [newTheme valueForAttributeWithName:@"dynamic-set" forClass:[CPView class]];
// Update the default dynamic attributes set with the one provided by the application
[dynamicSet enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
[defaultDynamicSet setObject:anObject forKey:aKey];
}];
// Apply the resulting set to the theme
[newTheme applyDynamicSet:defaultDynamicSet];
// Then, fix resources path in CSS directives
[newTheme setCSSResourcesPath:[[[[CPApplication sharedApplication] themeBlend] bundle] resourcePath]];
// Now apply the new theme variation
[CPTheme setDefaultTheme:newTheme];
var windows = [[CPApplication sharedApplication] windows];
for (var i = 0, count = windows.length; i < count; i++)
{
[windows[i] _setThemeIncludingDescendants:newTheme];
}
// Update the menu bar
[CPMenu updateMenuBarAttributesWithTheme:newTheme];
}
- (void)setCSSResourcesPath:(CPString)pathToResources
{
[_attributes enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
@@ -372,6 +507,46 @@ var CPThemeNameKey = @"CPThemeNameKey",
}];
}
- (void)applyDynamicSet:(CPDictionary)dynamicSet
{
[_attributes enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
[anObject enumerateKeysAndObjectsUsingBlock:function(aKey2, anObject2, stop2)
{
var anObject2Values = [anObject2 values];
[anObject2Values enumerateKeysAndObjectsUsingBlock:function(aKey3, anObject3, stop3)
{
if (anObject3.isa)
{
if (([anObject3 isKindOfClass:CPImage] || [anObject3 isKindOfClass:CPColor]) && [anObject3 cssDictionary])
{
// We have a CSS defined image or color
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssDictionary]];
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssBeforeDictionary]];
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssAfterDictionary]];
}
else if ([anObject3 isKindOfClass:CPString])
{
// We have a direct string value
if ([dynamicSet containsKey:anObject3])
[anObject2Values setObject:[dynamicSet objectForKey:anObject3] forKey:aKey3];
}
}
}];
}];
}];
}
- (void)_applyDynamicSet:(CPDictionary)dynamicSet toCSSDictionary:(CPDictionary)aDictionary
{
[aDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
if ([dynamicSet containsKey:anObject])
[aDictionary setObject:[dynamicSet objectForKey:anObject] forKey:aKey];
}];
}
- (BOOL)isCSSBased
{
return !![self valueForAttributeWithName:@"css-based" forClass:[CPView class]];
+1 -1
View File
@@ -103,7 +103,7 @@
aTheme = [CPTheme themeNamed:aThemeName];
if ([aTheme isCSSBased])
[aTheme setCSSResourcesPath:[aBundle resourcePath]];
[aTheme _initializeDynamicAttributesAndResourcesPathWithBundle:aBundle];
}
[_loadDelegate blendDidFinishLoading:self];
+1
View File
@@ -333,6 +333,7 @@ var CPViewHighDPIDrawingEnabled = YES;
{
return @{
@"css-based": NO,
@"dynamic-set": [CPNull null],
@"nib2cib-adjustment-frame": CGRectMakeZero()
};
}
+13
View File
@@ -4389,6 +4389,19 @@ var interpolate = function(fromValue, toValue, progress)
@end
#pragma mark -
@implementation CPWindow (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[[self contentView] _setThemeIncludingDescendants:aTheme];
}
@end
#pragma mark -
function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel)
{
return { windowView:aWindowView, contentRect:aContentRect, hasShadow:hasShadow, level:aLevel };