import AppKit import PlaygroundSupport import SwiftUI let nibFile = NSNib.Name("MyView") var topLevelObjects : NSArray? Bundle.main.loadNibNamed(nibFile, owner:nil, topLevelObjects: &topLevelObjects) let views = (topLevelObjects as! Array<Any>).filter { $0 is NSView } // Present the view in Playground PlaygroundPage.current.liveView = views[0] as! NSView ///Just model protocol AxisProtocol { var name: String {get} var bounds: ClosedRange<Double> {get} } struct Axis: AxisProtocol { var name: String var bounds: ClosedRange<Double> } struct Coordinate<Axis:AxisProtocol>:CustomStringConvertible { var axis: Axis var at: Double var description: String { return "\(axis.name): \(String(format: "%.2f", at))" } } // There is a one manager which keeps multidimensional coordiantes class CoordinatesManager { @Published var currentCoordinates: [ Coordinate<Axis> ] init (at: [Coordinate<Axis>]) { self.currentCoordinates = at } } // Originally class which interpolates values in multidimensional space class ItsCompliacated { func getSomeValue(at: [Coordinate<Axis>]) -> NSPoint { //Counting this value is rather comlicated thing... return NSPoint(x: Double.random(in: 0...500), y: Double.random(in: 0...500)) } } // Glyph. something like VariableFonts, or MultipleMaster.dimensions could be width, weight, contrast, x-height, *****_angle... many, many more class Glyph: ObservableObject { @Published var manager: CoordinatesManager { didSet { print (manager.currentCoordinates) } } var elements: [ ItsCompliacated ] = [] var path: Path { var result = Path() if elements.count > 1 { result.addLines(elements.map({$0.getSomeValue(at: manager.currentCoordinates)})) } return result } init (elements:[ItsCompliacated], manager:CoordinatesManager) { self.manager = manager self.elements = elements } } let width = Axis(name: "width", bounds: 0...1000) let weight = Axis(name: "weight", bounds: 0...1000) let contrast = Axis(name: "contrast", bounds: 0...1000) let cWidth = Coordinate(axis: width, at: 100) let cWeight = Coordinate(axis: weight, at: 200) let cContrast = Coordinate(axis: contrast, at: 200) let manager = CoordinatesManager(at: [cWidth, cWeight, cContrast]) let glyph = Glyph(elements: Array(repeating: ItsCompliacated(), count: 5), manager: manager) public struct NavigatorView: View { @EnvironmentObject var glyph: Glyph public var body: some View { ZStack { GlyphShape(glyph: glyph).stroke() } } } public struct CoordinateView : View { @Binding var coordinate: Coordinate<Axis> public var body : some View { VStack (alignment: .leading, spacing: 0 ) { HStack(alignment: .top, spacing: 0) { Text(coordinate.axis.name).font(.system(size: 8)) Spacer(minLength: 5) Text("\(String(format: "%.2f", coordinate.at))").font(.system(size: 8)) } Slider(value: $coordinate.at, in: coordinate.axis.bounds) .controlSize(.mini) .labelsHidden() .padding(EdgeInsets(top: -8, leading: 0, bottom: -10, trailing: 0)) } } } public struct CoordinatesView : View { @Binding var coordinates: [Coordinate<Axis>] public var body: some View { VStack (alignment: .leading, spacing: 0 ) { List ((0..<coordinates.count), id:\.self) { coordinateIndex in CoordinateView(coordinate: self.$coordinates[coordinateIndex]) } } } } struct GlyphShape: Shape { @ObservedObject var glyph: Glyph func path(in rect: CGRect) -> Path { let glyphPath = glyph.path print ("MAKING SHAPE \(glyphPath)") return glyphPath } } struct ContentView: View { @EnvironmentObject var glyph: Glyph var body: some View { VStack { NavigatorView() .frame(width: 500, height: 500, alignment: .top) CoordinatesView(coordinates: $glyph.manager.currentCoordinates) .frame(minWidth: 500, idealWidth: 500, maxWidth: 500, minHeight: 300, idealHeight: CGFloat(glyph.manager.currentCoordinates.count * 50), maxHeight: 500, alignment: .bottom) //.frame(minWidth: .none, idealWidth: 500, maxWidth: .none, minHeight: .none, idealHeight: .none, maxHeight: .none, alignment: .bottom) } } } PlaygroundPage.current.setLiveView(ContentView().environmentObject(glyph))