2015-08-15 22:32:34 -05:00
|
|
|
// 15 august 2015
|
|
|
|
#import "uipriv_darwin.h"
|
|
|
|
|
2015-08-17 00:58:23 -05:00
|
|
|
// TODOs:
|
2016-05-01 15:25:05 -05:00
|
|
|
// - tab on page 2 is glitched initially
|
2015-08-23 22:10:17 -05:00
|
|
|
// - 10.8: if we switch to page 4, then switch back to page 1, check Spaced, and go back to page 4, some controls (progress bar, popup button) are clipped on the sides
|
2016-05-01 10:43:34 -05:00
|
|
|
// - calling layoutSubtreeIfNeeded on a superview of the box will cause the following intrinsic content size thing to not work until the window is resized in the primary direction; this is bad if we ever add a Splitter...
|
2016-05-01 15:25:05 -05:00
|
|
|
// - moving around randomly through the tabs does this too
|
2016-05-01 10:25:35 -05:00
|
|
|
|
2016-05-06 18:21:41 -05:00
|
|
|
@interface boxView : NSView
|
|
|
|
@property uiBox *b;
|
2016-05-06 19:34:02 -05:00
|
|
|
@property NSLayoutConstraint *last;
|
|
|
|
@property BOOL willRelayout;
|
|
|
|
@property BOOL stretchy;
|
2016-05-06 18:21:41 -05:00
|
|
|
@end
|
|
|
|
|
2015-08-15 22:32:34 -05:00
|
|
|
struct uiBox {
|
|
|
|
uiDarwinControl c;
|
2016-05-06 18:21:41 -05:00
|
|
|
boxView *view;
|
2015-08-15 22:32:34 -05:00
|
|
|
BOOL vertical;
|
|
|
|
int padded;
|
|
|
|
NSMutableArray *children; // []NSValue<uiControl *>
|
|
|
|
NSMutableArray *stretchy; // []NSNumber
|
2016-04-30 16:45:44 -05:00
|
|
|
NSLayoutAttribute primaryStart;
|
|
|
|
NSLayoutAttribute primaryEnd;
|
|
|
|
NSLayoutAttribute secondaryStart;
|
|
|
|
NSLayoutAttribute secondaryEnd;
|
|
|
|
NSLayoutAttribute primarySize;
|
2015-08-15 22:32:34 -05:00
|
|
|
NSLayoutConstraintOrientation primaryOrientation;
|
|
|
|
NSLayoutConstraintOrientation secondaryOrientation;
|
|
|
|
};
|
|
|
|
|
|
|
|
static uiControl *childAt(uiBox *b, uintmax_t n)
|
|
|
|
{
|
|
|
|
NSValue *v;
|
|
|
|
|
|
|
|
v = (NSValue *) [b->children objectAtIndex:n];
|
|
|
|
return (uiControl *) [v pointerValue];
|
|
|
|
}
|
|
|
|
|
2016-04-25 11:35:01 -05:00
|
|
|
static void uiBoxDestroy(uiControl *c)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
2016-04-25 11:35:01 -05:00
|
|
|
uiBox *b = uiBox(c);
|
2015-08-15 22:32:34 -05:00
|
|
|
uintmax_t i;
|
|
|
|
uiControl *child;
|
|
|
|
NSView *childView;
|
|
|
|
|
2015-08-16 22:16:42 -05:00
|
|
|
for (i = 0; i < [b->children count]; i++) {
|
2015-08-15 22:32:34 -05:00
|
|
|
child = childAt(b, i);
|
|
|
|
childView = (NSView *) uiControlHandle(child);
|
|
|
|
[childView removeFromSuperview];
|
|
|
|
uiControlSetParent(child, NULL);
|
|
|
|
uiControlDestroy(child);
|
|
|
|
}
|
|
|
|
[b->children release];
|
|
|
|
[b->stretchy release];
|
2016-04-25 11:35:01 -05:00
|
|
|
[b->view release];
|
|
|
|
uiFreeControl(uiControl(b));
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2016-04-25 11:35:01 -05:00
|
|
|
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)
|
|
|
|
|
2016-04-25 16:52:16 -05:00
|
|
|
static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
|
2015-08-21 00:09:07 -05:00
|
|
|
{
|
|
|
|
uiBox *b = uiBox(c);
|
|
|
|
NSUInteger i;
|
|
|
|
|
2016-04-25 17:07:29 -05:00
|
|
|
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
|
|
|
|
return;
|
2015-08-21 00:09:07 -05:00
|
|
|
for (i = 0; i < [b->children count]; i++) {
|
|
|
|
NSValue *v;
|
|
|
|
uiControl *child;
|
|
|
|
|
|
|
|
v = (NSValue *) [b->children objectAtIndex:i];
|
|
|
|
// TODO change all these other instances of casts to conversions
|
|
|
|
child = uiControl([v pointerValue]);
|
2016-04-25 16:52:16 -05:00
|
|
|
uiDarwinControlSyncEnableState(uiDarwinControl(child), enabled);
|
2015-08-21 00:09:07 -05:00
|
|
|
}
|
|
|
|
}
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2016-04-25 11:35:01 -05:00
|
|
|
uiDarwinControlDefaultSetSuperview(uiBox, view)
|
2016-05-06 20:50:14 -05:00
|
|
|
uiDarwinControlDefaultChildrenShouldAllowSpaceAtTrailingEdge(uiBox, view)
|
|
|
|
uiDarwinControlDefaultChildrenShouldAllowSpaceAtBottom(uiBox, view)
|
2016-04-25 11:35:01 -05:00
|
|
|
|
2016-04-30 16:45:44 -05:00
|
|
|
static int isStretchy(uiBox *b, uintmax_t n)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
2016-04-30 16:45:44 -05:00
|
|
|
NSNumber *num;
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2016-04-30 16:45:44 -05:00
|
|
|
num = (NSNumber *) [b->stretchy objectAtIndex:n];
|
|
|
|
return [num intValue];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2016-05-06 18:21:41 -05:00
|
|
|
static NSView *boxChildView(uiBox *b, uintmax_t n)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
2016-04-30 16:45:44 -05:00
|
|
|
NSValue *val;
|
|
|
|
uiControl *c;
|
|
|
|
|
|
|
|
val = (NSValue *) [b->children objectAtIndex:n];
|
|
|
|
c = (uiControl *) [val pointerValue];
|
|
|
|
return (NSView *) uiControlHandle(c);
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2016-05-06 18:21:41 -05:00
|
|
|
@implementation boxView
|
|
|
|
|
2016-05-06 19:34:02 -05:00
|
|
|
- (NSLayoutConstraint *)mkLast:(NSLayoutRelation)relation on:(NSView *)view
|
|
|
|
{
|
|
|
|
return mkConstraint(view, self.b->primaryEnd,
|
|
|
|
relation,
|
|
|
|
self.b->view, self.b->primaryEnd,
|
|
|
|
1, 0,
|
|
|
|
@"uiBox last primary constraint");
|
|
|
|
}
|
|
|
|
|
2015-08-15 22:32:34 -05:00
|
|
|
// TODO do we still need to set hugging? I think we do for stretchy controls...
|
|
|
|
// TODO try unsetting spinbox intrinsics and seeing what happens
|
2016-05-06 19:34:02 -05:00
|
|
|
- (void)recreateConstraints
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
2016-05-06 18:21:41 -05:00
|
|
|
uiBox *b = self.b;
|
2015-08-15 22:32:34 -05:00
|
|
|
uintmax_t i, n;
|
|
|
|
BOOL hasStretchy;
|
2016-04-30 16:45:44 -05:00
|
|
|
NSView *firstStretchy = nil;
|
|
|
|
CGFloat padding;
|
|
|
|
NSView *prev, *next;
|
2016-05-02 14:47:48 -05:00
|
|
|
BOOL hasNoStretchyView;
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2016-04-30 17:07:36 -05:00
|
|
|
n = [b->children count];
|
|
|
|
if (n == 0)
|
2015-08-15 22:32:34 -05:00
|
|
|
return;
|
2016-04-30 16:45:44 -05:00
|
|
|
padding = 0;
|
|
|
|
if (b->padded)
|
|
|
|
padding = 8.0; // TODO named constant
|
2015-08-15 22:32:34 -05:00
|
|
|
|
|
|
|
[b->view removeConstraints:[b->view constraints]];
|
|
|
|
|
2016-04-30 16:45:44 -05:00
|
|
|
// first, attach the first view to the leading
|
2016-05-06 18:21:41 -05:00
|
|
|
prev = boxChildView(b, 0);
|
2016-04-30 16:45:44 -05:00
|
|
|
[b->view addConstraint:mkConstraint(prev, b->primaryStart,
|
|
|
|
NSLayoutRelationEqual,
|
|
|
|
b->view, b->primaryStart,
|
|
|
|
1, 0,
|
|
|
|
@"uiBox first primary constraint")];
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2016-04-30 16:45:44 -05:00
|
|
|
// next, assemble the views in the primary direction
|
|
|
|
// they all go in a straight line
|
|
|
|
// also figure out whether we have stretchy controls, and which is the first
|
|
|
|
if (isStretchy(b, 0)) {
|
|
|
|
hasStretchy = YES;
|
|
|
|
firstStretchy = prev;
|
|
|
|
} else
|
|
|
|
hasStretchy = NO;
|
|
|
|
for (i = 1; i < n; i++) {
|
2016-05-06 18:21:41 -05:00
|
|
|
next = boxChildView(b, i);
|
2016-04-30 16:45:44 -05:00
|
|
|
if (!hasStretchy && isStretchy(b, i)) {
|
2015-08-15 22:32:34 -05:00
|
|
|
hasStretchy = YES;
|
2016-04-30 16:45:44 -05:00
|
|
|
firstStretchy = next;
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
2016-04-30 16:45:44 -05:00
|
|
|
[b->view addConstraint:mkConstraint(next, b->primaryStart,
|
|
|
|
NSLayoutRelationEqual,
|
|
|
|
prev, b->primaryEnd,
|
|
|
|
1, padding,
|
|
|
|
@"uiBox later primary constraint")];
|
|
|
|
prev = next;
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2016-04-30 16:45:44 -05:00
|
|
|
// if there is a stretchy control, add the no-stretchy view
|
2016-05-06 19:34:02 -05:00
|
|
|
self.stretchy = hasStretchy;
|
|
|
|
NSLayoutRelation relation = NSLayoutRelationEqual;
|
|
|
|
if (!hasStretchy)
|
|
|
|
relation = NSLayoutRelationLessThanOrEqual;
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2016-04-30 16:45:44 -05:00
|
|
|
// and finally end the primary direction
|
2016-05-06 19:34:02 -05:00
|
|
|
b->view.last = [b->view mkLast:relation on:prev];
|
|
|
|
[b->view addConstraint:b->view.last];
|
2015-08-15 22:32:34 -05:00
|
|
|
|
|
|
|
// next: assemble the views in the secondary direction
|
|
|
|
// each of them will span the secondary direction
|
|
|
|
for (i = 0; i < n; i++) {
|
2016-05-06 18:21:41 -05:00
|
|
|
[b->view addConstraint:mkConstraint(boxChildView(b, i), b->secondaryStart,
|
2016-04-30 16:45:44 -05:00
|
|
|
NSLayoutRelationEqual,
|
|
|
|
b->view, b->secondaryStart,
|
|
|
|
1, 0,
|
|
|
|
@"uiBox start secondary constraint")];
|
2016-05-06 18:21:41 -05:00
|
|
|
[b->view addConstraint:mkConstraint(boxChildView(b, i), b->secondaryEnd,
|
2016-04-30 16:45:44 -05:00
|
|
|
NSLayoutRelationEqual,
|
|
|
|
b->view, b->secondaryEnd,
|
|
|
|
1, 0,
|
|
|
|
@"uiBox start secondary constraint")];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2016-04-30 16:45:44 -05:00
|
|
|
// finally, set sizes for stretchy controls
|
|
|
|
if (hasStretchy)
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
if (!isStretchy(b, i))
|
|
|
|
continue;
|
2016-05-06 18:21:41 -05:00
|
|
|
prev = boxChildView(b, i);
|
2016-04-30 16:45:44 -05:00
|
|
|
if (prev == firstStretchy)
|
|
|
|
continue;
|
|
|
|
[b->view addConstraint:mkConstraint(prev, b->primarySize,
|
|
|
|
NSLayoutRelationEqual,
|
|
|
|
firstStretchy, b->primarySize,
|
|
|
|
1, 0,
|
|
|
|
@"uiBox stretchy sizing")];
|
|
|
|
}
|
2015-08-17 18:11:35 -05:00
|
|
|
}
|
|
|
|
|
2016-05-06 19:34:02 -05:00
|
|
|
- (void)updateConstraints
|
|
|
|
{
|
|
|
|
[super updateConstraints];
|
|
|
|
self.willRelayout = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)inStretchyView
|
|
|
|
{
|
|
|
|
uiControl *c;
|
|
|
|
NSView *v;
|
|
|
|
|
|
|
|
c = uiControlParent(uiControl(self.b));
|
|
|
|
v = (NSView *) uiControlHandle(c);
|
|
|
|
if ([v isKindOfClass:[boxView class]]) {
|
|
|
|
boxView *bv = (boxView *) v;
|
|
|
|
uintmax_t i, n;
|
|
|
|
|
|
|
|
n = [bv.b->children count];
|
2016-05-06 19:55:33 -05:00
|
|
|
if (bv.b->vertical != self.b->vertical)
|
|
|
|
return YES;
|
2016-05-06 19:34:02 -05:00
|
|
|
for (i = 0; i < n; i++)
|
|
|
|
if (isStretchy(bv.b, i))
|
2016-05-06 19:55:33 -05:00
|
|
|
return YES;
|
|
|
|
return NO;
|
2016-05-06 19:34:02 -05:00
|
|
|
}
|
2016-05-06 19:55:33 -05:00
|
|
|
return YES;
|
2016-05-06 19:34:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)layout
|
|
|
|
{
|
|
|
|
[super layout];
|
|
|
|
if (!self.willRelayout) return;
|
|
|
|
self.willRelayout = NO;
|
2016-05-06 19:55:33 -05:00
|
|
|
if (!self.stretchy && ![self inStretchyView]) {
|
2016-05-06 19:34:02 -05:00
|
|
|
NSView *prev;
|
|
|
|
|
|
|
|
prev = [self.last firstItem];
|
|
|
|
[self removeConstraint:self.last];
|
|
|
|
self.last = [self mkLast:NSLayoutRelationEqual on:prev];
|
|
|
|
[self addConstraint:self.last];
|
|
|
|
[self updateConstraintsForSubtreeIfNeeded];
|
|
|
|
[super layout];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-06 18:21:41 -05:00
|
|
|
@end
|
|
|
|
|
2015-08-15 22:32:34 -05:00
|
|
|
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
|
|
|
|
{
|
|
|
|
NSView *childView;
|
|
|
|
|
|
|
|
childView = (NSView *) uiControlHandle(c);
|
|
|
|
[b->children addObject:[NSValue valueWithPointer:c]];
|
|
|
|
[b->stretchy addObject:[NSNumber numberWithInt:stretchy]];
|
|
|
|
|
2015-08-17 00:52:06 -05:00
|
|
|
uiControlSetParent(c, uiControl(b));
|
2016-04-25 12:38:17 -05:00
|
|
|
uiDarwinControlSetSuperview(uiDarwinControl(c), b->view);
|
2016-04-25 16:52:16 -05:00
|
|
|
uiDarwinControlSyncEnableState(uiDarwinControl(c), uiControlEnabledToUser(uiControl(b)));
|
2015-08-17 00:52:06 -05:00
|
|
|
|
2015-08-15 22:32:34 -05:00
|
|
|
// TODO save the old hugging priorities
|
|
|
|
// if a control is stretchy, it should not hug in the primary direction
|
|
|
|
// otherwise, it should *forcibly* hug
|
|
|
|
if (stretchy)
|
|
|
|
setHuggingPri(childView, NSLayoutPriorityDefaultLow, b->primaryOrientation);
|
|
|
|
else
|
|
|
|
// TODO will default high work?
|
2015-08-16 22:16:42 -05:00
|
|
|
setHuggingPri(childView, NSLayoutPriorityRequired, b->primaryOrientation);
|
2015-08-15 22:32:34 -05:00
|
|
|
// make sure controls don't hug their secondary direction so they fill the width of the view
|
2015-08-16 22:16:42 -05:00
|
|
|
setHuggingPri(childView, NSLayoutPriorityDefaultLow, b->secondaryOrientation);
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2016-05-06 19:34:02 -05:00
|
|
|
[b->view recreateConstraints];
|
2016-05-06 18:21:41 -05:00
|
|
|
[b->view setNeedsUpdateConstraints:YES];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void uiBoxDelete(uiBox *b, uintmax_t n)
|
|
|
|
{
|
|
|
|
NSValue *v;
|
|
|
|
uiControl *removed;
|
2015-08-17 00:52:06 -05:00
|
|
|
NSView *removedView;
|
2015-08-15 22:32:34 -05:00
|
|
|
|
|
|
|
v = (NSValue *) [b->children objectAtIndex:n];
|
|
|
|
removed = (uiControl *) [v pointerValue];
|
2015-08-17 00:52:06 -05:00
|
|
|
removedView = (NSView *) uiControlHandle(removed);
|
|
|
|
[removedView removeFromSuperview];
|
2015-08-15 22:32:34 -05:00
|
|
|
uiControlSetParent(removed, NULL);
|
|
|
|
[b->children removeObjectAtIndex:n];
|
|
|
|
[b->stretchy removeObjectAtIndex:n];
|
2016-05-06 19:34:02 -05:00
|
|
|
[b->view recreateConstraints];
|
2016-05-06 18:21:41 -05:00
|
|
|
[b->view setNeedsUpdateConstraints:YES];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
int uiBoxPadded(uiBox *b)
|
|
|
|
{
|
|
|
|
return b->padded;
|
|
|
|
}
|
|
|
|
|
2015-08-16 22:16:42 -05:00
|
|
|
void uiBoxSetPadded(uiBox *b, int padded)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
|
|
|
b->padded = padded;
|
2016-05-06 19:34:02 -05:00
|
|
|
[b->view recreateConstraints];
|
2016-05-06 18:21:41 -05:00
|
|
|
[b->view setNeedsUpdateConstraints:YES];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2015-08-16 22:08:00 -05:00
|
|
|
static uiBox *finishNewBox(BOOL vertical)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
|
|
|
uiBox *b;
|
|
|
|
|
2016-04-25 11:35:01 -05:00
|
|
|
uiDarwinNewControl(uiBox, b);
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2016-05-06 18:21:41 -05:00
|
|
|
b->view = [[boxView alloc] initWithFrame:NSZeroRect];
|
|
|
|
b->view.b = b;
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2015-08-17 00:41:04 -05:00
|
|
|
b->children = [NSMutableArray new];
|
|
|
|
b->stretchy = [NSMutableArray new];
|
2015-08-15 22:32:34 -05:00
|
|
|
|
|
|
|
b->vertical = vertical;
|
|
|
|
if (b->vertical) {
|
2016-04-30 16:45:44 -05:00
|
|
|
b->primaryStart = NSLayoutAttributeTop;
|
|
|
|
b->primaryEnd = NSLayoutAttributeBottom;
|
|
|
|
b->secondaryStart = NSLayoutAttributeLeading;
|
|
|
|
b->secondaryEnd = NSLayoutAttributeTrailing;
|
|
|
|
b->primarySize = NSLayoutAttributeHeight;
|
2015-08-15 22:32:34 -05:00
|
|
|
b->primaryOrientation = NSLayoutConstraintOrientationVertical;
|
|
|
|
b->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
|
|
|
} else {
|
2016-04-30 16:45:44 -05:00
|
|
|
b->primaryStart = NSLayoutAttributeLeading;
|
|
|
|
b->primaryEnd = NSLayoutAttributeTrailing;
|
|
|
|
b->secondaryStart = NSLayoutAttributeTop;
|
|
|
|
b->secondaryEnd = NSLayoutAttributeBottom;
|
|
|
|
b->primarySize = NSLayoutAttributeWidth;
|
2015-08-15 22:32:34 -05:00
|
|
|
b->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
|
|
|
b->secondaryOrientation = NSLayoutConstraintOrientationVertical;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
uiBox *uiNewHorizontalBox(void)
|
|
|
|
{
|
|
|
|
return finishNewBox(NO);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiBox *uiNewVerticalBox(void)
|
|
|
|
{
|
|
|
|
return finishNewBox(YES);
|
|
|
|
}
|