// 15 august 2015 #import "uipriv_darwin.h" struct uiBox { uiDarwinControl c; NSView *view; BOOL vertical; int padded; NSMutableArray *children; // []NSValue 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 uiBoxType, // type function 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]; } static void onDestroy(uiBox *b) { uintmax_t i; uiControl *child; NSView *childView; for (i = 0; i < [b->children count]; i++) { 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]; } // TODO container update state // TODO change i to n static NSString *viewName(uintmax_t i) { return [NSString stringWithFormat:@"view%ju", i]; } static NSString *widthMetricName(uintmax_t i) { return [NSString stringWithFormat:@"view%juwidth", i]; } static NSString *heightMetricName(uintmax_t i) { return [NSString stringWithFormat:@"view%juheight", i]; } 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]]; // first collect the views and their fitting sizes (for non-stretchy controls) // 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; NSView *childView; NSSize fittingSize; child = childAt(b, n); childView = (NSView *) uiControlHandle(child); [views setObject:childView forKey:viewName(n)]; 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:@"|"]; addConstraint(b->view, constraint, metrics, views); [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]; } 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]]; // 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? setHuggingPri(childView, NSLayoutPriorityRequired, b->primaryOrientation); // make sure controls don't hug their secondary direction so they fill the width of the view setHuggingPri(childView, NSLayoutPriorityDefaultLow, b->secondaryOrientation); uiControlSetParent(c, uiControl(b)); relayout(b); } void uiBoxDelete(uiBox *b, uintmax_t n) { NSValue *v; uiControl *removed; v = (NSValue *) [b->children objectAtIndex:n]; removed = (uiControl *) [v pointerValue]; uiControlSetParent(removed, NULL); [b->children removeObjectAtIndex:n]; [b->stretchy removeObjectAtIndex:n]; relayout(b); } int uiBoxPadded(uiBox *b) { return b->padded; } void uiBoxSetPadded(uiBox *b, int padded) { b->padded = padded; relayout(b); } static uiBox *finishNewBox(BOOL vertical) { uiBox *b; b = (uiBox *) uiNewControl(uiBoxType()); b->view = [[NSView alloc] initWithFrame:NSZeroRect]; b->children = [NSMutableArray alloc]; b->stretchy = [NSMutableArray alloc]; b->vertical = vertical; if (b->vertical) { b->primaryDirPrefix = @"V:"; b->secondaryDirPrefix = @"H:"; b->primaryOrientation = NSLayoutConstraintOrientationVertical; b->secondaryOrientation = NSLayoutConstraintOrientationHorizontal; } else { b->primaryDirPrefix = @"H:"; b->secondaryDirPrefix = @"V:"; b->primaryOrientation = NSLayoutConstraintOrientationHorizontal; b->secondaryOrientation = NSLayoutConstraintOrientationVertical; } b->noStretchyView = [[NSView alloc] initWithFrame:NSZeroRect]; [b->noStretchyView setTranslatesAutoresizingMaskIntoConstraints:NO]; setHuggingPri(b->noStretchyView, NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal); setHuggingPri(b->noStretchyView, NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical); uiDarwinFinishNewControl(b, uiBox); return b; } uiBox *uiNewHorizontalBox(void) { return finishNewBox(NO); } uiBox *uiNewVerticalBox(void) { return finishNewBox(YES); }