NEW: effectiveAppearance and new CPThemeState

Each default (and only supported) CPAppearance now have an associated
theme state. As the appearance is correctly propagated in CPViews
hierarchy, it is now possible to have dark controls when the an
ancestor view has a dark appearance.

Added automated tests
This commit is contained in:
Antoine Mercadal
2015-08-13 23:40:27 -07:00
parent 6bf6f82cad
commit 541a7974e0
3 changed files with 183 additions and 10 deletions
+8 -2
View File
@@ -21,6 +21,7 @@
*/
@import <Foundation/Foundation.j>
@import "CPTheme.j"
CPAppearanceNameAqua = @"CPAppearanceNameAqua";
CPAppearanceNameLightContent = @"CPAppearanceNameLightContent";
@@ -41,6 +42,11 @@ var _CPAppearanceCurrent = nil,
@end
CPThemeStateAppearanceAqua = CPThemeState("appearance-aqua");
CPThemeStateAppearanceLightContent = CPThemeState("appearance-light-content");
CPThemeStateAppearanceVibrantLight = CPThemeState("appearance-vibrant-light");
CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark");
/*!
@ingroup appkit
@@ -51,9 +57,9 @@ var _CPAppearanceCurrent = nil,
*/
@implementation CPAppearance : CPObject
{
BOOL _allowsVibrancy @accessors(property=allowsVibrancy);
BOOL _allowsVibrancy @accessors(property=allowsVibrancy);
CPString _name;
CPString _name;
}
+80 -8
View File
@@ -242,7 +242,9 @@ var CPViewHighDPIDrawingEnabled = YES;
BOOL _isObserving;
BOOL _allowsVibrancy @accessors(property=allowsVibrancy);
CPAppearance _appearance @accessors(property=appearance);
CPAppearance _appearance @accessors(getter=appearance);
CPAppearance _effectiveAppearance;
BOOL _effectiveAppearanceCached;
}
/*
@@ -372,6 +374,8 @@ var CPViewHighDPIDrawingEnabled = YES;
_theme = [CPTheme defaultTheme];
_themeState = CPThemeStateNormal;
_effectiveAppearanceCached = NO;
#if PLATFORM(DOM)
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
@@ -842,9 +846,10 @@ var CPViewHighDPIDrawingEnabled = YES;
*/
- (void)viewDidMoveToSuperview
{
// if (_graphicsContext)
[self setNeedsDisplay:YES];
[self setNeedsDisplay:YES];
[self _resetCachedEffectiveAppearance];
[self _updateAppearanceThemeState];
}
/*!
@@ -3515,21 +3520,87 @@ setBoundsOrigin:
@end
@implementation CPView (Appeatance)
@implementation CPView (Appearance)
/*! Returns the received appearance if any, or ask the superview and returns it.
/*! Returns the receiver's appearance if any, or ask the superview and returns it.
*/
- (CPAppearance)effectiveAppearance
{
if (_appearance)
return _appearance;
if (_superview)
return [_superview appearance];
if (_effectiveAppearanceCached)
return _effectiveAppearance;
return nil;
_effectiveAppearance = [_superview effectiveAppearance];
_effectiveAppearanceCached = YES;
return _effectiveAppearance;
}
- (void)setAppearance:(CPAppearance)anAppearance
{
if ([_appearance isEqual:anAppearance])
return;
[self willChangeValueForKey:@"appearance"];
_appearance = anAppearance;
[self didChangeValueForKey:@"appearance"];
[self _resetCachedEffectiveAppearance];
[self _updateAppearanceThemeState];
}
/*! @ignore
*/
- (void)_resetCachedEffectiveAppearance
{
_effectiveAppearanceCached = NO;
[_subviews makeObjectsPerformSelector:@selector(_resetCachedEffectiveAppearance)];
}
- (void)_updateAppearanceThemeState
{
var effectiveAppearance = [self effectiveAppearance];
if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameAqua]])
{
[self setThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceLightContent];
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
}
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameLightContent]])
{
[self unsetThemeState:CPThemeStateAppearanceAqua];
[self setThemeState:CPThemeStateAppearanceLightContent];
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
}
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]])
{
[self unsetThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceLightContent];
[self setThemeState:CPThemeStateAppearanceVibrantLight];
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
}
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark]])
{
[self unsetThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceLightContent];
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
[self setThemeState:CPThemeStateAppearanceVibrantDark];
}
else
{
[self unsetThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceLightContent];
[self unsetThemeState:CPThemeStateAppearanceVibrantLight];
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
}
}
@end
var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
@@ -3669,6 +3740,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
}
_appearance = [aCoder decodeObjectForKey:CPViewAppearanceKey];
_effectiveAppearanceCached = NO;
[self setNeedsDisplay:YES];
[self setNeedsLayout];
+95
View File
@@ -771,6 +771,101 @@ var methodCalled;
[self assert:nil equals:view._toolTipFunctionOut];
}
- (void)testAppearanceDefaultvalue
{
[self assert:nil equals:[view appearance]];
[self assertFalse:view._effectiveAppearanceCached];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantDark]];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantLight]];
[self assert:nil equals:[view effectiveAppearance]];
[self assertTrue:view._effectiveAppearanceCached];
}
- (void)testAppearanceWithVibrantDark
{
[view setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark]];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark] equals:[view appearance]];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark] equals:[view effectiveAppearance]];
[self assertTrue:[view hasThemeState:CPThemeStateAppearanceVibrantDark]];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantLight]];
}
- (void)testAppearanceWithVibrantLight
{
[view setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight] equals:[view appearance]];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight] equals:[view effectiveAppearance]];
[self assertTrue:[view hasThemeState:CPThemeStateAppearanceVibrantLight]];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantDark]];
}
- (void)testAppearanceReset
{
[view setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]];
[view setAppearance:nil];
[self assert:nil equals:[view appearance]];
[self assert:nil equals:[view effectiveAppearance]];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantLight]];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantDark]];
}
- (void)testEffectiveAppearance
{
var secondView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[self assertFalse:secondView._effectiveAppearanceCached];
[view addSubview:secondView];
[self assertTrue:secondView._effectiveAppearanceCached];
[self assert:nil equals:[secondView appearance]];
[view setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]];
[self assertFalse:secondView._effectiveAppearanceCached];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight] equals:[secondView effectiveAppearance]];
[self assertTrue:secondView._effectiveAppearanceCached];
}
- (void)testEffectiveAppearanceWithMovingViews
{
var viewA = [[CPView alloc] initWithFrame:CGRectMakeZero()],
viewB = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[viewA setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]];
[viewB setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark]];
[viewA addSubview:view];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight] equals:[view effectiveAppearance]];
[self assertTrue:[view hasThemeState:CPThemeStateAppearanceVibrantLight]];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantDark]];
[viewB addSubview:view];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark] equals:[view effectiveAppearance]];
[self assertFalse:[view hasThemeState:CPThemeStateAppearanceVibrantLight]];
[self assertTrue:[view hasThemeState:CPThemeStateAppearanceVibrantDark]];
}
- (void)testEffectiveAppearanceReset
{
var viewA = [[CPView alloc] initWithFrame:CGRectMakeZero()],
viewB = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[viewA setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]];
[viewB setAppearance:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark]];
[viewA addSubview:view];
[self assert:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight] equals:[view effectiveAppearance]];
[view removeFromSuperview];
[self assert:nil equals:[view effectiveAppearance]];
}
@end
@implementation CPLayoutView : CPView