diff options
Diffstat (limited to 'src/libui_sdl/libui/darwin/box.m')
-rw-r--r-- | src/libui_sdl/libui/darwin/box.m | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/darwin/box.m b/src/libui_sdl/libui/darwin/box.m new file mode 100644 index 0000000..18d536d --- /dev/null +++ b/src/libui_sdl/libui/darwin/box.m @@ -0,0 +1,469 @@ +// 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 = mkConstraint(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 = mkConstraint(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 = mkConstraint(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 = mkConstraint(self, self->secondaryStart, + NSLayoutRelationEqual, + [bc view], self->secondaryStart, + 1, 0, + @"uiBox secondary start constraint"); + [self addConstraint:c]; + [self->otherConstraints addObject:c]; + c = mkConstraint([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 = mkConstraint([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 = mkConstraint(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) + userbug("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); +} |