176 lines
5.1 KiB
Swift
176 lines
5.1 KiB
Swift
|
// 7 august 2015
|
||
|
import Cocoa
|
||
|
|
||
|
struct BoxControl {
|
||
|
var c: Control
|
||
|
var stretchy: Bool
|
||
|
var horzHuggingPri: NSLayoutPriority
|
||
|
var vertHuggingPri: NSLayoutPriority
|
||
|
}
|
||
|
|
||
|
class Box : NSView, Control {
|
||
|
private var controls: [BoxControl]
|
||
|
private var parent: Control?
|
||
|
private var vertical: Bool
|
||
|
private var padded: Bool
|
||
|
|
||
|
private var primaryDirPrefix: String
|
||
|
private var secondaryDirPrefix: String
|
||
|
private var primaryOrientation: NSLayoutConstraintOrientation
|
||
|
private var secondaryOrientation: NSLayoutConstraintOrientation
|
||
|
|
||
|
// we implement a lack of stretchy controls by adding a stretchy view at the end of the view list when we assemble layouts
|
||
|
// this is that view
|
||
|
private var noStretchyView: NSView
|
||
|
|
||
|
init(vertical: Bool, padded: Bool) {
|
||
|
self.controls = []
|
||
|
self.parent = nil
|
||
|
self.vertical = vertical
|
||
|
self.padded = padded
|
||
|
|
||
|
self.primaryDirPrefix = "H:"
|
||
|
self.secondaryDirPrefix = "V:"
|
||
|
self.primaryOrientation = NSLayoutConstraintOrientation.Horizontal
|
||
|
self.secondaryOrientation = NSLayoutConstraintOrientation.Vertical
|
||
|
if self.vertical {
|
||
|
self.primaryDirPrefix = "V:"
|
||
|
self.secondaryDirPrefix = "H:"
|
||
|
self.primaryOrientation = NSLayoutConstraintOrientation.Vertical
|
||
|
self.secondaryOrientation = NSLayoutConstraintOrientation.Horizontal
|
||
|
}
|
||
|
|
||
|
self.noStretchyView = NSView(frame: NSZeroRect)
|
||
|
self.noStretchyView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
// make the view stretchy in both directions
|
||
|
// you can tell this is correct by synthesizing an Add() in your head; see below
|
||
|
setHorzHuggingPri(self.noStretchyView, myNSLayoutPriorityDefaultLow)
|
||
|
setVertHuggingPri(self.noStretchyView, myNSLayoutPriorityDefaultLow)
|
||
|
|
||
|
super.init(frame: NSZeroRect)
|
||
|
self.translatesAutoresizingMaskIntoConstraints = false
|
||
|
}
|
||
|
|
||
|
required init?(coder: NSCoder) {
|
||
|
fatalError("can't use this constructor, sorry")
|
||
|
}
|
||
|
|
||
|
func Add(control: Control, _ stretchy: Bool) {
|
||
|
var c: BoxControl
|
||
|
|
||
|
var view = control.View()
|
||
|
c = BoxControl(
|
||
|
c: control,
|
||
|
stretchy: stretchy,
|
||
|
horzHuggingPri: horzHuggingPri(view),
|
||
|
vertHuggingPri: vertHuggingPri(view))
|
||
|
self.addSubview(view)
|
||
|
self.controls.append(c)
|
||
|
|
||
|
// if a control is stretchy, it should not hug in the primary direction
|
||
|
// otherwise, it should *forcibly* hug
|
||
|
if c.stretchy {
|
||
|
setHuggingPri(view, myNSLayoutPriorityDefaultLow, self.primaryOrientation)
|
||
|
} else {
|
||
|
// TODO will default high work?
|
||
|
setHuggingPri(view, myNSLayoutPriorityRequired, self.primaryOrientation)
|
||
|
}
|
||
|
|
||
|
// make sure controls don't hug their secondary direction so they fill the width of the view
|
||
|
setHuggingPri(view, myNSLayoutPriorityDefaultLow, self.secondaryOrientation)
|
||
|
|
||
|
self.relayout()
|
||
|
}
|
||
|
|
||
|
func View() -> NSView {
|
||
|
return self
|
||
|
}
|
||
|
|
||
|
func SetParent(p: Control) {
|
||
|
self.parent = p
|
||
|
}
|
||
|
|
||
|
// TODO do we still need to set hugging? I think we do for stretchy controls...
|
||
|
// TODO try unsetting spinbox intrinsics and seeing what happens
|
||
|
private func relayout() {
|
||
|
var constraint: String
|
||
|
|
||
|
if self.controls.count == 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
self.removeConstraints(self.constraints)
|
||
|
|
||
|
// first collect the views
|
||
|
var views = [String: NSView]()
|
||
|
var n = 0
|
||
|
var firstStretchy = -1
|
||
|
var metrics = [String: CGFloat]()
|
||
|
for c in self.controls {
|
||
|
views["view\(n)"] = c.c.View()
|
||
|
var s = fittingAlignmentSize(c.c.View())
|
||
|
metrics["view\(n)width"] = s.width
|
||
|
metrics["view\(n)height"] = s.height
|
||
|
if firstStretchy == -1 && c.stretchy {
|
||
|
firstStretchy = n
|
||
|
}
|
||
|
n++
|
||
|
}
|
||
|
|
||
|
// if there are no stretchy controls, we must add the no-stretchy view
|
||
|
// if there are, we must remove it
|
||
|
if firstStretchy == -1 {
|
||
|
if self.noStretchyView.superview == nil {
|
||
|
self.addSubview(self.noStretchyView)
|
||
|
}
|
||
|
views["noStretchyView"] = self.noStretchyView
|
||
|
} else {
|
||
|
if self.noStretchyView.superview != nil {
|
||
|
self.noStretchyView.removeFromSuperview()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// next, assemble the views in the primary direction
|
||
|
// they all go in a straight line
|
||
|
constraint = "\(self.primaryDirPrefix)|"
|
||
|
for i in 0..<n {
|
||
|
if self.padded && i != 0 {
|
||
|
constraint += "-"
|
||
|
}
|
||
|
constraint += "[view\(i)"
|
||
|
// implement multiple stretchiness properly
|
||
|
if self.controls[i].stretchy && i != firstStretchy {
|
||
|
constraint += "(==view\(firstStretchy))"
|
||
|
}
|
||
|
// if the control is not stretchy, restrict it to the fitting size
|
||
|
if !self.controls[i].stretchy {
|
||
|
if self.vertical {
|
||
|
constraint += "(==view\(i)height)"
|
||
|
} else {
|
||
|
constraint += "(==view\(i)width)"
|
||
|
}
|
||
|
}
|
||
|
constraint += "]"
|
||
|
}
|
||
|
if firstStretchy == -1 { // don't space between the last control and the no-stretchy view
|
||
|
constraint += "[noStretchyView]"
|
||
|
}
|
||
|
constraint += "|"
|
||
|
var constraints = mkconstraints(constraint, metrics, views)
|
||
|
self.addConstraints(constraints)
|
||
|
|
||
|
// next: assemble the views in the secondary direction
|
||
|
// each of them will span the secondary direction
|
||
|
for i in 0..<n {
|
||
|
constraint = "\(self.secondaryDirPrefix)|[view\(i)]|"
|
||
|
var constraints = mkconstraints(constraint, nil, views)
|
||
|
self.addConstraints(constraints)
|
||
|
}
|
||
|
if firstStretchy == -1 { // and again to the no-stretchy view
|
||
|
constraint = "\(self.secondaryDirPrefix)|[noStretchyView]|"
|
||
|
var constraints = mkconstraints(constraint, nil, views)
|
||
|
self.addConstraints(constraints)
|
||
|
}
|
||
|
}
|
||
|
}
|