2015-08-15 22:32:34 -05:00
|
|
|
// 15 august 2015
|
|
|
|
#import "uipriv_darwin.h"
|
|
|
|
|
2015-08-17 00:58:23 -05:00
|
|
|
// TODOs:
|
2015-08-17 11:15:32 -05:00
|
|
|
// - page 2 isn't growable - I think this is because the button in the tab isn't stretchy
|
2015-08-22 13:31:12 -05:00
|
|
|
// - tab on page 2 is glitched
|
|
|
|
// - separators on page 4 have variable padding after them
|
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
|
2015-12-09 08:05:28 -06:00
|
|
|
// - wrong padding between controls in the queuemaintest and the control gallery...
|
2015-08-17 00:58:23 -05:00
|
|
|
|
2015-08-15 22:32:34 -05:00
|
|
|
struct uiBox {
|
|
|
|
uiDarwinControl c;
|
|
|
|
NSView *view;
|
|
|
|
BOOL vertical;
|
|
|
|
int padded;
|
|
|
|
NSMutableArray *children; // []NSValue<uiControl *>
|
|
|
|
NSMutableArray *stretchy; // []NSNumber
|
|
|
|
// this view is made stretchy if there are no stretchy views
|
|
|
|
NSView *noStretchyView;
|
|
|
|
NSString *primaryDirPrefix;
|
|
|
|
NSString *secondaryDirPrefix;
|
|
|
|
NSLayoutConstraintOrientation primaryOrientation;
|
|
|
|
NSLayoutConstraintOrientation secondaryOrientation;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void onDestroy(uiBox *);
|
|
|
|
|
|
|
|
uiDarwinDefineControlWithOnDestroy(
|
|
|
|
uiBox, // type name
|
|
|
|
view, // handle
|
|
|
|
onDestroy(this); // on destroy
|
|
|
|
)
|
|
|
|
|
|
|
|
static uiControl *childAt(uiBox *b, uintmax_t n)
|
|
|
|
{
|
|
|
|
NSValue *v;
|
|
|
|
|
|
|
|
v = (NSValue *) [b->children objectAtIndex:n];
|
|
|
|
return (uiControl *) [v pointerValue];
|
|
|
|
}
|
|
|
|
|
2015-08-16 22:16:42 -05:00
|
|
|
static void onDestroy(uiBox *b)
|
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);
|
|
|
|
}
|
|
|
|
if ([b->noStretchyView superview] != nil)
|
|
|
|
[b->noStretchyView removeFromSuperview];
|
|
|
|
[b->noStretchyView release];
|
|
|
|
[b->children release];
|
|
|
|
[b->stretchy release];
|
|
|
|
}
|
|
|
|
|
2015-08-21 00:09:07 -05:00
|
|
|
static void boxContainerUpdateState(uiControl *c)
|
|
|
|
{
|
|
|
|
uiBox *b = uiBox(c);
|
|
|
|
NSUInteger i;
|
|
|
|
|
|
|
|
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]);
|
|
|
|
controlUpdateState(child);
|
|
|
|
}
|
|
|
|
}
|
2015-08-15 22:32:34 -05:00
|
|
|
|
2015-08-20 18:40:23 -05:00
|
|
|
static NSString *viewName(uintmax_t n)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
2015-08-20 18:40:23 -05:00
|
|
|
return [NSString stringWithFormat:@"view%ju", n];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2015-08-20 18:40:23 -05:00
|
|
|
static NSString *widthMetricName(uintmax_t n)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
2015-08-20 18:40:23 -05:00
|
|
|
return [NSString stringWithFormat:@"view%juwidth", n];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
2015-08-20 18:40:23 -05:00
|
|
|
static NSString *heightMetricName(uintmax_t n)
|
2015-08-15 22:32:34 -05:00
|
|
|
{
|
2015-08-20 18:40:23 -05:00
|
|
|
return [NSString stringWithFormat:@"view%juheight", n];
|
2015-08-15 22:32:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static int isStretchy(uiBox *b, uintmax_t n)
|
|
|
|
{
|
|
|
|
NSNumber *num;
|
|
|
|
|
|
|
|
num = (NSNumber *) [b->stretchy objectAtIndex:n];
|
|
|
|
return [num intValue];
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO do we still need to set hugging? I think we do for stretchy controls...
|
|
|
|
// TODO try unsetting spinbox intrinsics and seeing what happens
|
|
|
|
static void relayout(uiBox *b)
|
|
|
|
{
|
|
|
|
NSMutableDictionary *metrics;
|
|
|
|
NSMutableDictionary *views;
|
|
|
|
uintmax_t i, n;
|
|
|
|
BOOL hasStretchy;
|
|
|
|
uintmax_t firstStretchy;
|
|
|
|
NSMutableString *constraint;
|
|
|
|
|
|
|
|
if ([b->children count] == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[b->view removeConstraints:[b->view constraints]];
|
|
|
|
|
2015-08-18 19:15:09 -05:00
|
|
|
// first lay out all children, collect the views and their fitting sizes (for non-stretchy controls)
|
2015-08-15 22:32:34 -05:00
|
|
|
// also figure out which is the first stretchy control, if any
|
|
|
|
metrics = [NSMutableDictionary new];
|
|
|
|
views = [NSMutableDictionary new];
|
|
|
|
hasStretchy = NO;
|
|
|
|
n = 0;
|
|
|
|
while (n < [b->children count]) {
|
|
|
|
uiControl *child;
|
2015-08-18 19:15:09 -05:00
|
|
|
uiDarwinControl *cc;
|
2015-08-15 22:32:34 -05:00
|
|
|
NSView *childView;
|
|
|
|
NSSize fittingSize;
|
|
|
|
|
|
|
|
child = childAt(b, n);
|
2015-08-18 19:15:09 -05:00
|
|
|
cc = uiDarwinControl(child);
|
2015-08-15 22:32:34 -05:00
|
|
|
childView = (NSView *) uiControlHandle(child);
|
|
|
|
[views setObject:childView forKey:viewName(n)];
|
2015-08-18 19:15:09 -05:00
|
|
|
(*(cc->Relayout))(cc);
|
2015-08-15 22:32:34 -05:00
|
|
|
fittingSize = fittingAlignmentSize(childView);
|
|
|
|
[metrics setObject:[NSNumber numberWithDouble:fittingSize.width]
|
|
|
|
forKey:widthMetricName(n)];
|
|
|
|
[metrics setObject:[NSNumber numberWithDouble:fittingSize.height]
|
|
|
|
forKey:heightMetricName(n)];
|
|
|
|
if (!hasStretchy && isStretchy(b, n)) {
|
|
|
|
hasStretchy = YES;
|
|
|
|
firstStretchy = n;
|
|
|
|
}
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there are no stretchy controls, we must add the no-stretchy view
|
|
|
|
// if there are, we must remove it
|
|
|
|
if (!hasStretchy) {
|
|
|
|
if ([b->noStretchyView superview] == nil)
|
|
|
|
[b->view addSubview:b->noStretchyView];
|
|
|
|
[views setObject:b->noStretchyView forKey:@"noStretchyView"];
|
|
|
|
} else {
|
|
|
|
if ([b->noStretchyView superview] != nil)
|
|
|
|
[b->noStretchyView removeFromSuperview];
|
|
|
|
}
|
|
|
|
|
|
|
|
// next, assemble the views in the primary direction
|
|
|
|
// they all go in a straight line
|
|
|
|
constraint = [NSMutableString new];
|
|
|
|
[constraint appendString:b->primaryDirPrefix];
|
|
|
|
[constraint appendString:@"|"];
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
if (b->padded && i != 0)
|
|
|
|
[constraint appendString:@"-"];
|
|
|
|
[constraint appendString:@"["];
|
|
|
|
[constraint appendString:viewName(i)];
|
|
|
|
// implement multiple stretchiness properly
|
|
|
|
if (isStretchy(b, i) && i != firstStretchy) {
|
|
|
|
[constraint appendString:@"(=="];
|
|
|
|
[constraint appendString:viewName(firstStretchy)];
|
|
|
|
[constraint appendString:@")"];
|
|
|
|
}
|
|
|
|
// if the control is not stretchy, restrict it to the fitting size
|
|
|
|
if (!isStretchy(b, i)) {
|
|
|
|
[constraint appendString:@"(=="];
|
|
|
|
if (b->vertical)
|
|
|
|
[constraint appendString:heightMetricName(i)];
|
|
|
|
else
|
|
|
|
[constraint appendString:widthMetricName(i)];
|
|
|
|
[constraint appendString:@")"];
|
|
|
|
}
|
|
|
|
[constraint appendString:@"]"];
|
|
|
|
}
|
|
|
|
if (!hasStretchy)
|
|
|
|
// don't space between the last control and the no-stretchy view
|
|
|
|
[constraint appendString:@"[noStretchyView]"];
|
|
|
|
[constraint appendString:@"|"];
|
2015-08-16 22:16:42 -05:00
|
|
|
addConstraint(b->view, constraint, metrics, views);
|
2015-08-15 22:32:34 -05:00
|
|
|
[constraint release];
|
|
|
|
|
|
|
|
// next: assemble the views in the secondary direction
|
|
|
|
// each of them will span the secondary direction
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
constraint = [NSMutableString new];
|
|
|
|
[constraint appendString:b->secondaryDirPrefix];
|
|
|
|
[constraint appendString:@"|["];
|
|
|
|
[constraint appendString:viewName(i)];
|
|
|
|
[constraint appendString:@"]|"];
|
|
|
|
addConstraint(b->view, constraint, nil, views);
|
|
|
|
[constraint release];
|
|
|
|
}
|
|
|
|
if (!hasStretchy) { // and again to the no-stretchy view
|
|
|
|
constraint = [NSMutableString new];
|
|
|
|
[constraint appendString:b->secondaryDirPrefix];
|
|
|
|
[constraint appendString:@"|[noStretchyView]|"];
|
|
|
|
addConstraint(b->view, constraint, nil, views);
|
|
|
|
[constraint release];
|
|
|
|
}
|
|
|
|
|
|
|
|
[metrics release];
|
|
|
|
[views release];
|
2015-08-17 18:11:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static void boxRelayout(uiDarwinControl *c)
|
|
|
|
{
|
|
|
|
relayout(uiBox(c));
|
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));
|
|
|
|
[b->view addSubview:childView];
|
|
|
|
|
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
|
|
|
|
|
|
|
relayout(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
|
|
|
relayout(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
relayout(b);
|
|
|
|
}
|
|
|
|
|
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-24 15:03:13 -05:00
|
|
|
b = (uiBox *) uiNewControl(uiBox);
|
2015-08-15 22:32:34 -05:00
|
|
|
|
|
|
|
b->view = [[NSView alloc] initWithFrame:NSZeroRect];
|
|
|
|
|
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) {
|
2015-08-16 22:16:42 -05:00
|
|
|
b->primaryDirPrefix = @"V:";
|
|
|
|
b->secondaryDirPrefix = @"H:";
|
2015-08-15 22:32:34 -05:00
|
|
|
b->primaryOrientation = NSLayoutConstraintOrientationVertical;
|
|
|
|
b->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
|
|
|
} else {
|
2015-08-16 22:16:42 -05:00
|
|
|
b->primaryDirPrefix = @"H:";
|
|
|
|
b->secondaryDirPrefix = @"V:";
|
2015-08-15 22:32:34 -05:00
|
|
|
b->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
|
|
|
b->secondaryOrientation = NSLayoutConstraintOrientationVertical;
|
|
|
|
}
|
|
|
|
|
|
|
|
b->noStretchyView = [[NSView alloc] initWithFrame:NSZeroRect];
|
|
|
|
[b->noStretchyView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
2015-08-16 22:16:42 -05:00
|
|
|
setHuggingPri(b->noStretchyView, NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
|
|
|
|
setHuggingPri(b->noStretchyView, NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);
|
2015-08-15 22:32:34 -05:00
|
|
|
|
|
|
|
uiDarwinFinishNewControl(b, uiBox);
|
2015-08-21 00:09:07 -05:00
|
|
|
uiControl(b)->ContainerUpdateState = boxContainerUpdateState;
|
2015-08-17 18:11:35 -05:00
|
|
|
uiDarwinControl(b)->Relayout = boxRelayout;
|
2015-08-15 22:32:34 -05:00
|
|
|
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
uiBox *uiNewHorizontalBox(void)
|
|
|
|
{
|
|
|
|
return finishNewBox(NO);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiBox *uiNewVerticalBox(void)
|
|
|
|
{
|
|
|
|
return finishNewBox(YES);
|
|
|
|
}
|