libui/darwin/box.m

470 lines
11 KiB
Objective-C

// 15 august 2015
#import "uipriv_darwin.h"
// TODO hiding all stretchy controls still hugs trailing edge
@interface boxChild : NSObject
@property uiControl *c;
@property BOOL stretchy;
@property NSLayoutPriority oldPrimaryHuggingPri;
@property NSLayoutPriority oldSecondaryHuggingPri;
- (NSView *)view;
@end
@interface boxView : NSView {
uiBox *b;
NSMutableArray *children;
BOOL vertical;
int padded;
NSLayoutConstraint *first;
NSMutableArray *inBetweens;
NSLayoutConstraint *last;
NSMutableArray *otherConstraints;
NSLayoutAttribute primaryStart;
NSLayoutAttribute primaryEnd;
NSLayoutAttribute secondaryStart;
NSLayoutAttribute secondaryEnd;
NSLayoutAttribute primarySize;
NSLayoutConstraintOrientation primaryOrientation;
NSLayoutConstraintOrientation secondaryOrientation;
}
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;
- (void)onDestroy;
- (void)removeOurConstraints;
- (void)syncEnableStates:(int)enabled;
- (CGFloat)paddingAmount;
- (void)establishOurConstraints;
- (void)append:(uiControl *)c stretchy:(int)stretchy;
- (void)delete:(int)n;
- (int)isPadded;
- (void)setPadded:(int)p;
- (BOOL)hugsTrailing;
- (BOOL)hugsBottom;
- (int)nStretchy;
@end
struct uiBox {
uiDarwinControl c;
boxView *view;
};
@implementation boxChild
- (NSView *)view
{
return (NSView *) uiControlHandle(self.c);
}
@end
@implementation boxView
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb
{
self = [super initWithFrame:NSZeroRect];
if (self != nil) {
// the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid
self->b = bb;
self->vertical = vert;
self->padded = 0;
self->children = [NSMutableArray new];
self->inBetweens = [NSMutableArray new];
self->otherConstraints = [NSMutableArray new];
if (self->vertical) {
self->primaryStart = NSLayoutAttributeTop;
self->primaryEnd = NSLayoutAttributeBottom;
self->secondaryStart = NSLayoutAttributeLeading;
self->secondaryEnd = NSLayoutAttributeTrailing;
self->primarySize = NSLayoutAttributeHeight;
self->primaryOrientation = NSLayoutConstraintOrientationVertical;
self->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
} else {
self->primaryStart = NSLayoutAttributeLeading;
self->primaryEnd = NSLayoutAttributeTrailing;
self->secondaryStart = NSLayoutAttributeTop;
self->secondaryEnd = NSLayoutAttributeBottom;
self->primarySize = NSLayoutAttributeWidth;
self->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
self->secondaryOrientation = NSLayoutConstraintOrientationVertical;
}
}
return self;
}
- (void)onDestroy
{
boxChild *bc;
[self removeOurConstraints];
[self->inBetweens release];
[self->otherConstraints release];
for (bc in self->children) {
uiControlSetParent(bc.c, NULL);
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
uiControlDestroy(bc.c);
}
[self->children release];
}
- (void)removeOurConstraints
{
if (self->first != nil) {
[self removeConstraint:self->first];
[self->first release];
self->first = nil;
}
if ([self->inBetweens count] != 0) {
[self removeConstraints:self->inBetweens];
[self->inBetweens removeAllObjects];
}
if (self->last != nil) {
[self removeConstraint:self->last];
[self->last release];
self->last = nil;
}
if ([self->otherConstraints count] != 0) {
[self removeConstraints:self->otherConstraints];
[self->otherConstraints removeAllObjects];
}
}
- (void)syncEnableStates:(int)enabled
{
boxChild *bc;
for (bc in self->children)
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);
}
- (CGFloat)paddingAmount
{
if (!self->padded)
return 0.0;
return uiDarwinPaddingAmount(NULL);
}
- (void)establishOurConstraints
{
boxChild *bc;
CGFloat padding;
NSView *prev;
NSLayoutConstraint *c;
BOOL (*hugsSecondary)(uiDarwinControl *);
[self removeOurConstraints];
if ([self->children count] == 0)
return;
padding = [self paddingAmount];
// first arrange in the primary direction
prev = nil;
for (bc in self->children) {
if (!uiControlVisible(bc.c))
continue;
if (prev == nil) { // first view
self->first = uiprivMkConstraint(self, self->primaryStart,
NSLayoutRelationEqual,
[bc view], self->primaryStart,
1, 0,
@"uiBox first primary constraint");
[self addConstraint:self->first];
[self->first retain];
prev = [bc view];
continue;
}
// not the first; link it
c = uiprivMkConstraint(prev, self->primaryEnd,
NSLayoutRelationEqual,
[bc view], self->primaryStart,
1, -padding,
@"uiBox in-between primary constraint");
[self addConstraint:c];
[self->inBetweens addObject:c];
prev = [bc view];
}
if (prev == nil) // no control visible; act as if no controls
return;
self->last = uiprivMkConstraint(prev, self->primaryEnd,
NSLayoutRelationEqual,
self, self->primaryEnd,
1, 0,
@"uiBox last primary constraint");
[self addConstraint:self->last];
[self->last retain];
// then arrange in the secondary direction
hugsSecondary = uiDarwinControlHugsTrailingEdge;
if (!self->vertical)
hugsSecondary = uiDarwinControlHugsBottom;
for (bc in self->children) {
if (!uiControlVisible(bc.c))
continue;
c = uiprivMkConstraint(self, self->secondaryStart,
NSLayoutRelationEqual,
[bc view], self->secondaryStart,
1, 0,
@"uiBox secondary start constraint");
[self addConstraint:c];
[self->otherConstraints addObject:c];
c = uiprivMkConstraint([bc view], self->secondaryEnd,
NSLayoutRelationLessThanOrEqual,
self, self->secondaryEnd,
1, 0,
@"uiBox secondary end <= constraint");
if ((*hugsSecondary)(uiDarwinControl(bc.c)))
[c setPriority:NSLayoutPriorityDefaultLow];
[self addConstraint:c];
[self->otherConstraints addObject:c];
c = uiprivMkConstraint([bc view], self->secondaryEnd,
NSLayoutRelationEqual,
self, self->secondaryEnd,
1, 0,
@"uiBox secondary end == constraint");
if (!(*hugsSecondary)(uiDarwinControl(bc.c)))
[c setPriority:NSLayoutPriorityDefaultLow];
[self addConstraint:c];
[self->otherConstraints addObject:c];
}
// and make all stretchy controls the same size
if ([self nStretchy] == 0)
return;
prev = nil; // first stretchy view
for (bc in self->children) {
if (!uiControlVisible(bc.c))
continue;
if (!bc.stretchy)
continue;
if (prev == nil) {
prev = [bc view];
continue;
}
c = uiprivMkConstraint(prev, self->primarySize,
NSLayoutRelationEqual,
[bc view], self->primarySize,
1, 0,
@"uiBox stretchy size constraint");
[self addConstraint:c];
[self->otherConstraints addObject:c];
}
}
- (void)append:(uiControl *)c stretchy:(int)stretchy
{
boxChild *bc;
NSLayoutPriority priority;
int oldnStretchy;
bc = [boxChild new];
bc.c = c;
bc.stretchy = stretchy;
bc.oldPrimaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->primaryOrientation);
bc.oldSecondaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->secondaryOrientation);
uiControlSetParent(bc.c, uiControl(self->b));
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));
// if a control is stretchy, it should not hug in the primary direction
// otherwise, it should *forcibly* hug
if (bc.stretchy)
priority = NSLayoutPriorityDefaultLow;
else
// LONGTERM will default high work?
priority = NSLayoutPriorityRequired;
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), priority, self->primaryOrientation);
// make sure controls don't hug their secondary direction so they fill the width of the view
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), NSLayoutPriorityDefaultLow, self->secondaryOrientation);
oldnStretchy = [self nStretchy];
[self->children addObject:bc];
[self establishOurConstraints];
if (bc.stretchy)
if (oldnStretchy == 0)
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
[bc release]; // we don't need the initial reference now
}
- (void)delete:(int)n
{
boxChild *bc;
int stretchy;
bc = (boxChild *) [self->children objectAtIndex:n];
stretchy = bc.stretchy;
uiControlSetParent(bc.c, NULL);
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldPrimaryHuggingPri, self->primaryOrientation);
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldSecondaryHuggingPri, self->secondaryOrientation);
[self->children removeObjectAtIndex:n];
[self establishOurConstraints];
if (stretchy)
if ([self nStretchy] == 0)
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
}
- (int)isPadded
{
return self->padded;
}
- (void)setPadded:(int)p
{
CGFloat padding;
NSLayoutConstraint *c;
self->padded = p;
padding = [self paddingAmount];
for (c in self->inBetweens)
[c setConstant:-padding];
}
- (BOOL)hugsTrailing
{
if (self->vertical) // always hug if vertical
return YES;
return [self nStretchy] != 0;
}
- (BOOL)hugsBottom
{
if (!self->vertical) // always hug if horizontal
return YES;
return [self nStretchy] != 0;
}
- (int)nStretchy
{
boxChild *bc;
int n;
n = 0;
for (bc in self->children) {
if (!uiControlVisible(bc.c))
continue;
if (bc.stretchy)
n++;
}
return n;
}
@end
static void uiBoxDestroy(uiControl *c)
{
uiBox *b = uiBox(c);
[b->view onDestroy];
[b->view release];
uiFreeControl(uiControl(b));
}
uiDarwinControlDefaultHandle(uiBox, view)
uiDarwinControlDefaultParent(uiBox, view)
uiDarwinControlDefaultSetParent(uiBox, view)
uiDarwinControlDefaultToplevel(uiBox, view)
uiDarwinControlDefaultVisible(uiBox, view)
uiDarwinControlDefaultShow(uiBox, view)
uiDarwinControlDefaultHide(uiBox, view)
uiDarwinControlDefaultEnabled(uiBox, view)
uiDarwinControlDefaultEnable(uiBox, view)
uiDarwinControlDefaultDisable(uiBox, view)
static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
{
uiBox *b = uiBox(c);
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
return;
[b->view syncEnableStates:enabled];
}
uiDarwinControlDefaultSetSuperview(uiBox, view)
static BOOL uiBoxHugsTrailingEdge(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
return [b->view hugsTrailing];
}
static BOOL uiBoxHugsBottom(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
return [b->view hugsBottom];
}
static void uiBoxChildEdgeHuggingChanged(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
[b->view establishOurConstraints];
}
uiDarwinControlDefaultHuggingPriority(uiBox, view)
uiDarwinControlDefaultSetHuggingPriority(uiBox, view)
static void uiBoxChildVisibilityChanged(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
[b->view establishOurConstraints];
}
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
{
// LONGTERM on other platforms
// or at leat allow this and implicitly turn it into a spacer
if (c == NULL)
uiprivUserBug("You cannot add NULL to a uiBox.");
[b->view append:c stretchy:stretchy];
}
void uiBoxDelete(uiBox *b, int n)
{
[b->view delete:n];
}
int uiBoxPadded(uiBox *b)
{
return [b->view isPadded];
}
void uiBoxSetPadded(uiBox *b, int padded)
{
[b->view setPadded:padded];
}
static uiBox *finishNewBox(BOOL vertical)
{
uiBox *b;
uiDarwinNewControl(uiBox, b);
b->view = [[boxView alloc] initWithVertical:vertical b:b];
return b;
}
uiBox *uiNewHorizontalBox(void)
{
return finishNewBox(NO);
}
uiBox *uiNewVerticalBox(void)
{
return finishNewBox(YES);
}