Fixed: Aristo3 ready and modern Cocoa behavior for CPBox (#2924)

This commit is contained in:
Didier Korthoudt authored and GitHub committed 2020-10-02 13:28:06 +02:00
1 parent 010057d9e1
commit 412baf7872
2 files changed
+321 -75

No files matched your search

+319 -74
View File
@@ -22,13 +22,14 @@
@import "CPTextField.j"
@import "CPView.j"
@import <Foundation/CPGeometry.j>
// CPBoxType
@typedef CPBoxType
CPBoxPrimary = 0;
CPBoxSecondary = 1;
CPBoxSecondary = 1; // Deprecated
CPBoxSeparator = 2;
CPBoxOldStyle = 3;
CPBoxOldStyle = 3; // Deprecated
CPBoxCustom = 4;
// CPBorderType
@@ -58,11 +59,13 @@ CPBelowBottom = 6;
@implementation CPBox : CPView
{
CPBoxType _boxType;
CPBorderType _borderType;
CPBorderType _borderType; // deprecated
CPView _contentView;
CPView _boxView; // needed for CSS theming, will be transparent for non CSS themes
BOOL _transparent @accessors(getter=isTransparent);
CPString _title @accessors(getter=title);
int _titlePosition @accessors(getter=titlePosition);
CPString _title @accessors(getter=title);
int _titlePosition @accessors(getter=titlePosition);
CPTextField _titleView;
}
@@ -90,8 +93,15 @@ CPBelowBottom = 6;
@"inner-shadow-size": 6.0,
@"inner-shadow-color": [CPNull null],
@"content-margin": CGSizeMakeZero(),
@"nib2cib-adjustment-primary-frame": CGRectMake(4, -4, -8, -6)
};
@"title-font": [CPNull null],
@"title-left-offset": 5.0,
@"title-top-offset": 0.0,
@"title-color": [CPNull null],
@"nib2cib-adjustment-primary-frame": CGRectMake(4, -4, -8, -6),
@"content-adjustment": CGRectMakeZero(),
@"min-y-correction-no-title": 0,
@"min-y-correction-title": 0
};
}
+ (id)boxEnclosingView:(CPView)aView
@@ -115,16 +125,26 @@ CPBelowBottom = 6;
if (self)
{
_borderType = CPBezelBorder;
_borderType = CPGrooveBorder; // Was CPBezelBorder but Cocoa default is CPGrooveBorder
_boxType = CPBoxPrimary;
_titlePosition = CPNoTitle;
_titleView = [CPTextField labelWithTitle:@""];
[_titleView setFont:[self titleFont]];
[_titleView setTextColor:[self titleColor]];
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
_contentView = [[CPView alloc] initWithFrame:[self bounds]];
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[self setAutoresizesSubviews:YES];
[self addSubview:_contentView];
[self addSubview:_boxView];
[_boxView setAutoresizesSubviews:YES];
[_boxView addSubview:_contentView];
[self sizeToFit];
}
return self;
@@ -156,6 +176,8 @@ CPBelowBottom = 6;
*/
- (CPBorderType)borderType
{
CPLog.warn("CPBox borderType is deprecated.");
return _borderType;
}
@@ -178,7 +200,8 @@ CPBelowBottom = 6;
return;
_borderType = aBorderType;
[self setNeedsDisplay:YES];
[self refreshDisplay];
}
/*!
@@ -218,13 +241,30 @@ CPBelowBottom = 6;
*/
- (void)setBoxType:(CPBoxType)aBoxType
{
if ((aBoxType == CPBoxSecondary) || (aBoxType == CPBoxOldStyle))
CPLog.warn("CPBox setBoxType: CPBoxSecondary and CPBoxOldStyle are deprecated.");
if (_boxType === aBoxType)
return;
_boxType = aBoxType;
[self setNeedsDisplay:YES];
[self refreshDisplay];
}
- (void)setTransparent:(BOOL)shouldBeTransparent
{
if (_transparent == shouldBeTransparent)
return;
_transparent = shouldBeTransparent;
[self _manageTitlePositioning];
}
/*!
The receiver’s border color. It must be a custom box (that is, it has a type of CPBoxCustom) and it must have a border style of CPLineBorder.
*/
- (CPColor)borderColor
{
return [self valueForThemeAttribute:@"border-color"];
@@ -232,12 +272,21 @@ CPBelowBottom = 6;
- (void)setBorderColor:(CPColor)color
{
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setBorderColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderColor. Ignored.");
return;
}
if ([color isEqual:[self borderColor]])
return;
[self setValue:color forThemeAttribute:@"border-color"];
}
/*!
The receiver’s border width. It must be a custom box (that is, it has a type of CPBoxCustom) and it must have a border style of CPLineBorder.
*/
- (float)borderWidth
{
return [self valueForThemeAttribute:@"border-width"];
@@ -245,12 +294,21 @@ CPBelowBottom = 6;
- (void)setBorderWidth:(float)width
{
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setBorderWidth: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderWidth. Ignored.");
return;
}
if (width === [self borderWidth])
return;
[self setValue:width forThemeAttribute:@"border-width"];
}
/*!
The receiver’s corner radius. It must be a custom box (that is, it has a type of CPBoxCustom) and it must have a border style of CPLineBorder.
*/
- (float)cornerRadius
{
return [self valueForThemeAttribute:@"corner-radius"];
@@ -258,12 +316,21 @@ CPBelowBottom = 6;
- (void)setCornerRadius:(float)radius
{
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setCornerRadius: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setCornerRadius. Ignored.");
return;
}
if (radius === [self cornerRadius])
return;
[self setValue:radius forThemeAttribute:@"corner-radius"];
}
/*!
The receiver’s background color. It must be a custom box (that is, it has a type of CPBoxCustom) and it must have a border style of CPLineBorder.
*/
- (CPColor)fillColor
{
return [self valueForThemeAttribute:@"background-color"];
@@ -271,6 +338,12 @@ CPBelowBottom = 6;
- (void)setFillColor:(CPColor)color
{
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setFillColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setFillColor. Ignored.");
return;
}
if ([color isEqual:[self fillColor]])
return;
@@ -287,21 +360,20 @@ CPBelowBottom = 6;
if (aView === _contentView)
return;
var borderWidth = [self borderWidth],
contentMargin = [self valueForThemeAttribute:@"content-margin"];
[aView setFrame:CGRectInset([self bounds], contentMargin.width + borderWidth, contentMargin.height + borderWidth)];
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
// A nil contentView is allowed (tested in Cocoa 2013-02-22).
if (!aView)
[_contentView removeFromSuperview];
else if (_contentView)
[self replaceSubview:_contentView with:aView];
[_boxView replaceSubview:_contentView with:aView];
else
[self addSubview:aView];
[_boxView addSubview:aView];
_contentView = aView;
[self sizeToFit];
[self refreshDisplay];
}
- (CGSize)contentViewMargins
@@ -321,9 +393,14 @@ CPBelowBottom = 6;
{
var offset = [self _titleHeightOffset],
borderWidth = [self borderWidth],
contentMargin = [self valueForThemeAttribute:@"content-margin"];
contentMargin = [self valueForThemeAttribute:@"content-margin"],
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
[self setFrame:CGRectInset(aRect, -(contentMargin.width + borderWidth), -(contentMargin.height + offset[0] + borderWidth))];
[self setFrame:CGRectMake(aRect.origin.x - contentAdjustment.origin.x - contentMargin.width + borderWidth,
aRect.origin.y - contentAdjustment.origin.y - contentMargin.height + borderWidth - minYCorrection,
aRect.size.width + 2 * contentMargin.width - contentAdjustment.size.width,
aRect.size.height + 2 * contentMargin.height - contentAdjustment.size.height)];
}
- (void)setTitle:(CPString)aTitle
@@ -348,14 +425,42 @@ CPBelowBottom = 6;
- (CPFont)titleFont
{
return [_titleView font];
if ([self hasThemeAttribute:@"title-font"])
return [self valueForThemeAttribute:@"title-font"];
else
return [_titleView font];
}
- (void)setTitleFont:(CPFont)aFont
{
if ([aFont isEqual:[self titleFont]])
return;
if ([self hasThemeAttribute:@"title-font"])
[self setValue:aFont forThemeAttribute:@"title-font"];
[_titleView setFont:aFont];
}
- (CPColor)titleColor
{
if ([self hasThemeAttribute:@"title-color"])
return [self valueForThemeAttribute:@"title-color"];
else
return [_titleView textColor];
}
- (void)setTitleColor:(CPColor)aColor
{
if ([aColor isEqual:[self titleColor]])
return;
if ([self hasThemeAttribute:@"title-color"])
[self setValue:aColor forThemeAttribute:@"title-color"];
[_titleView setTextColor:aColor];
}
/*!
Return the text field used to display the receiver's title.
@@ -366,25 +471,39 @@ CPBelowBottom = 6;
return _titleView;
}
/*!
Return the rectangle in which the receiver’s title is drawn.
*/
- (CGRect)titleRect
{
return [_titleView frame];
}
- (void)_manageTitlePositioning
{
if (_titlePosition == CPNoTitle)
if ((_titlePosition == CPNoTitle) || _transparent)
{
[_titleView removeFromSuperview];
[self setNeedsDisplay:YES];
if (_boxType !== CPBoxSeparator)
[self sizeToFit];
[self refreshDisplay];
return;
}
[_titleView setStringValue:_title];
[_titleView sizeToFit];
[self addSubview:_titleView];
var titleLeftOffset = [self valueForThemeAttribute:@"title-left-offset"],
titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
switch (_titlePosition)
{
case CPAtTop:
case CPAboveTop:
case CPBelowTop:
[_titleView setFrameOrigin:CGPointMake(5.0, 0.0)];
[_titleView setFrameOrigin:CGPointMake(titleLeftOffset, titleTopOffset)]; // FIXME: was 0.0
[_titleView setAutoresizingMask:CPViewNotSizable];
break;
@@ -392,39 +511,51 @@ CPBelowBottom = 6;
case CPAtBottom:
case CPBelowBottom:
var h = [_titleView frameSize].height;
[_titleView setFrameOrigin:CGPointMake(5.0, [self frameSize].height - h)];
[_titleView setFrameOrigin:CGPointMake(titleLeftOffset, [self frameSize].height - h - titleTopOffset)];
[_titleView setAutoresizingMask:CPViewMinYMargin];
break;
}
if (!_transparent)
[self addSubview:_titleView];
[self sizeToFit];
[self setNeedsDisplay:YES];
[self refreshDisplay];
}
- (void)sizeToFit
{
var contentFrame = [_contentView frame],
offset = [self _titleHeightOffset],
contentMargin = [self valueForThemeAttribute:@"content-margin"];
var offset = [self _titleHeightOffset],
size = [self frameSize];
if (!contentFrame)
[_boxView setFrame:CGRectMake(0, offset[1], size.width, size.height - offset[0])];
if (!_contentView)
return;
[_contentView setFrameOrigin:CGPointMake(contentMargin.width, contentMargin.height + offset[1])];
var boxSize = [_boxView frameSize],
contentMargin = [self valueForThemeAttribute:@"content-margin"],
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
borderWidth = [self valueForThemeAttribute:@"border-width"],
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
[_contentView setFrame:CGRectMake(contentAdjustment.origin.x + contentMargin.width - borderWidth,
contentAdjustment.origin.y + contentMargin.height - borderWidth + minYCorrection,
boxSize.width - 2 * contentMargin.width + contentAdjustment.size.width,
boxSize.height - 2 * contentMargin.height + contentAdjustment.size.height)];
}
- (float)_titleHeightOffset
- (CPArray)_titleHeightOffset
{
if (_titlePosition == CPNoTitle)
return [0.0, 0.0];
var titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
switch (_titlePosition)
{
case CPAtTop:
return [[_titleView frameSize].height, [_titleView frameSize].height];
return [[_titleView frameSize].height + titleTopOffset, [_titleView frameSize].height + titleTopOffset];
case CPAtBottom:
return [[_titleView frameSize].height, 0.0];
return [[_titleView frameSize].height + titleTopOffset, 0.0];
default:
return [0.0, 0.0];
@@ -441,21 +572,24 @@ CPBelowBottom = 6;
- (void)drawRect:(CGRect)rect
{
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
return;
var bounds = [self bounds];
switch (_boxType)
if (_boxType === CPBoxSeparator)
{
case CPBoxSeparator:
// NSBox does not include a horizontal flag for the separator type. We have to determine
// the type of separator to draw by the width and height of the frame.
if (CGRectGetWidth(bounds) === 5.0)
return [self _drawVerticalSeparatorInRect:bounds];
else if (CGRectGetHeight(bounds) === 5.0)
return [self _drawHorizontalSeparatorInRect:bounds];
break;
// NSBox does not include a horizontal flag for the separator type. We have to determine
// the type of separator to draw by the width and height of the frame.
if (CGRectGetWidth(bounds) === 5.0)
return [self _drawVerticalSeparatorInRect:bounds];
else if (CGRectGetHeight(bounds) === 5.0)
return [self _drawHorizontalSeparatorInRect:bounds];
}
if (_transparent)
return;
if (_titlePosition == CPAtTop)
{
bounds.origin.y += [_titleView frameSize].height;
@@ -476,9 +610,6 @@ CPBelowBottom = 6;
switch (_borderType)
{
case CPBezelBorder:
[self _drawBezelBorderInRect:bounds];
break;
case CPGrooveBorder:
case CPLineBorder:
[self _drawLineBorderInRect:bounds];
@@ -595,12 +726,70 @@ CPBelowBottom = 6;
@end
#pragma mark -
@implementation CPBox (CSSTheming)
- (void)layoutSubviews
{
if (![self isCSSBased] || (_boxType === CPBoxCustom))
return;
var bounds = [self bounds];
if (_boxType === CPBoxSeparator)
{
if (bounds.size.width === 5.0)
{
// Vertical separator
[_boxView setFrame:CGRectMake(2,0,1,bounds.size.height)];
}
else
{
// Horizontal separator
[_boxView setFrame:CGRectMake(0,2,bounds.size.width,1)];
}
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"border-color"]];
return;
}
// All types of boxes (beside custom which is not covered here) always draw the same way, unless they are CPNoBorder.
if ((_borderType !== CPNoBorder) && !_transparent)
{
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"background-color"]];
return;
}
// No border or transparent
[_boxView setBackgroundColor:nil];
}
- (BOOL)isCSSBased
{
return [[self theme] isCSSBased];
}
- (void)refreshDisplay
{
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
[self setNeedsLayout:YES];
else
[self setNeedsDisplay:YES];
}
@end
#pragma mark -
var CPBoxTypeKey = @"CPBoxTypeKey",
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
CPBoxTitle = @"CPBoxTitle",
CPBoxTitlePosition = @"CPBoxTitlePosition",
CPBoxTitleView = @"CPBoxTitleView",
CPBoxContentView = @"CPBoxContentView";
CPBoxTitleKey = @"CPBoxTitleKey",
CPBoxTitlePositionKey = @"CPBoxTitlePositionKey",
CPBoxTitleViewKey = @"CPBoxTitleViewKey",
CPBoxContentViewKey = @"CPBoxContentViewKey",
CPBoxBoxViewKey = @"CPBoxBoxViewKey";
@implementation CPBox (CPCoding)
@@ -613,33 +802,56 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
_boxType = [aCoder decodeIntForKey:CPBoxTypeKey];
_borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey];
_title = [aCoder decodeObjectForKey:CPBoxTitle];
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePosition];
_titleView = [aCoder decodeObjectForKey:CPBoxTitleView] || [CPTextField labelWithTitle:_title];
_title = [aCoder decodeObjectForKey:CPBoxTitleKey];
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePositionKey];
if (_boxType != CPBoxSeparator)
// Important : see comment on encodeWithCoder below
_boxView = [aCoder decodeObjectForKey:CPBoxBoxViewKey];
if (!_boxView)
{
// FIXME: we have a problem with CIB decoding here.
// We should be able to simply add : _contentView = [self subviews][0]
// but first box subview seems to be malformed (badly decoded).
// For example, when deployed, this view doesn't have its _trackingAreas array initialized.
// As a (temporary) workaround, we encode/decode the _contentView property. We then transfer the subview hierarchy
// and replace the first (and only) box subview with this _contentView
// We're coming from nib2cib.
_contentView = [aCoder decodeObjectForKey:CPBoxContentView] || [[CPView alloc] initWithFrame:[self bounds]];
var malformedContentView = [self subviews][0];
[_contentView setSubviews:[malformedContentView subviews]];
[self replaceSubview:malformedContentView with:_contentView];
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
_titleView = [CPTextField labelWithTitle:_title];
}
else
{
_titlePosition = CPNoTitle;
// We're coming from elsewhere
_titleView = [aCoder decodeObjectForKey:CPBoxTitleViewKey];
}
[self setAutoresizesSubviews:YES];
_contentView = [aCoder decodeObjectForKey:CPBoxContentViewKey];
// FIXME: super-mega-hyper-trick : _contentView has a superview which is not normal !
// FIXME: (see encodeWithCoder to understand why this is not possible)
// FIXME: we fix this by hand. This is horrible so please find a structural solution !
if (_contentView)
_contentView._superview = nil;
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_boxView setAutoresizesSubviews:YES];
[self setAutoresizesSubviews:YES];
if (_contentView)
[_boxView setSubviews:@[_contentView]];
[self addSubview:_boxView];
[self addSubview:_titleView];
if (_boxType === CPBoxSeparator)
_titlePosition = CPNoTitle;
[_titleView setFont:[self titleFont]];
[_titleView setTextColor:[self titleColor]];
[self _manageTitlePositioning];
[self refreshDisplay];
}
return self;
@@ -647,14 +859,47 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
- (void)encodeWithCoder:(CPCoder)aCoder
{
// We have to distinguish between 2 cases :
// - we come from nib2cib
// - we come from elsewhere
//
// When coming from nib2cib, we have no _boxView, _contentView, _titleView.
// We fix _contentView to be the first (and only) subview.
// They will have to be added on decoding.
//
// When coming from elsewhere, we remove _boxView (and thus _contentView) and _titleView
// from the view hierarchy as we'll already encode them via variables.
// They will be putted back during decoding. This way, we reduce the space and speed needed for coding.
var subviews = [self subviews];
if (!_boxView)
{
// We're coming from nib2cib.
_contentView = subviews[0];
[_contentView removeFromSuperview];
}
else
{
// We're coming from elsewhere.
[_boxView removeFromSuperview];
[_titleView removeFromSuperview];
}
[super encodeWithCoder:aCoder];
[self setSubviews:subviews];
[aCoder encodeInt:_boxType forKey:CPBoxTypeKey];
[aCoder encodeInt:_borderType forKey:CPBoxBorderTypeKey];
[aCoder encodeObject:_title forKey:CPBoxTitle];
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePosition];
[aCoder encodeObject:_titleView forKey:CPBoxTitleView];
[aCoder encodeObject:_contentView forKey:CPBoxContentView];
[aCoder encodeObject:_title forKey:CPBoxTitleKey];
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePositionKey];
[aCoder encodeConditionalObject:_contentView forKey:CPBoxContentViewKey];
[aCoder encodeConditionalObject:_titleView forKey:CPBoxTitleViewKey];
[aCoder encodeConditionalObject:_boxView forKey:CPBoxBoxViewKey];
}
@end
+2 -1
View File
@@ -32,6 +32,7 @@
{
_boxType = [aCoder decodeIntForKey:@"NSBoxType"];
_borderType = [aCoder decodeIntForKey:@"NSBorderType"];
_transparent = [aCoder decodeBoolForKey:@"NSTransparent"];
var borderColor = [aCoder decodeObjectForKey:@"NSBorderColor2"],
fillColor = [aCoder decodeObjectForKey:@"NSFillColor2"],
@@ -70,7 +71,7 @@
- (CGRect)_nib2CibAdjustment
{
if ((_boxType === CPBoxPrimary) || (_boxType === CPBoxSecondary))
if ((_boxType !== CPBoxSeparator) && ((_boxType === CPBoxPrimary) || (_boxType === CPBoxSecondary)))
{
// We use a special nib2cib-adjustment-frame for primary/secondary boxes
var theme = [Nib2Cib defaultTheme],