Skip to content

Commit 0700409

Browse files
committed
Implement start of new layout system (only AppKit works so far, and all views disabled other than text, button, and stacks)
1 parent c707db4 commit 0700409

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2833
-2035
lines changed

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 78 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ extension App {
88
public struct AppKitBackend: AppBackend {
99
public typealias Window = NSWindow
1010

11+
public static let font = NSFont.systemFont(ofSize: 12)
12+
1113
public enum Widget {
1214
case view(NSView)
1315
case viewController(NSViewController)
@@ -89,57 +91,97 @@ public struct AppKitBackend: AppBackend {
8991

9092
public func show(widget: Widget) {}
9193

92-
public func createTextView() -> Widget {
93-
return .view(NSTextField(wrappingLabelWithString: ""))
94+
class NSContainerView: NSView {
95+
var children: [NSView] = []
96+
97+
override func addSubview(_ view: NSView) {
98+
children.append(view)
99+
super.addSubview(view)
100+
}
101+
102+
func removeSubview(_ view: NSView) {
103+
children.removeAll { other in
104+
view === other
105+
}
106+
view.removeFromSuperview()
107+
}
94108
}
95109

96-
public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) {
97-
// TODO: Implement text wrap handling
98-
(textView.view as! NSTextField).stringValue = content
110+
public func createContainer() -> Widget {
111+
let container = NSContainerView()
112+
container.translatesAutoresizingMaskIntoConstraints = false
113+
return .view(container)
99114
}
100115

101-
public func createVStack() -> Widget {
102-
let view = NSStackView()
103-
view.orientation = .vertical
104-
view.alignment = .centerX
105-
view.setHuggingPriority(.defaultLow, for: .vertical)
106-
return .view(view)
116+
public func addChild(_ child: Widget, to container: Widget) {
117+
container.view.addSubview(child.view)
118+
child.view.translatesAutoresizingMaskIntoConstraints = false
107119
}
108120

109-
public func setChildren(_ children: [Widget], ofVStack container: Widget) {
110-
let stack = container.view as! NSStackView
111-
for child in children {
112-
stack.addView(child.view, in: .center)
113-
}
114-
if let first = children.first, children.count == 1 {
115-
first.view.pinEdges(to: container.view)
121+
public func setPosition(ofChildAt index: Int, in container: Widget, to position: SIMD2<Int>) {
122+
let container = container.view as! NSContainerView
123+
guard container.children.indices.contains(index) else {
124+
// TODO: Create proper logging system.
125+
print("warning: Attempted to set position of non-existent container child")
126+
return
116127
}
128+
129+
// TODO: Cache contraints so that they can be updated multiple times (at the moment
130+
// setting a position or size multiple times just creates conflicting constraints)
131+
let child = container.children[index]
132+
child.leftAnchor.constraint(
133+
equalTo: container.leftAnchor,
134+
constant: CGFloat(position.x)
135+
).isActive = true
136+
child.topAnchor.constraint(
137+
equalTo: container.topAnchor,
138+
constant: CGFloat(position.y)
139+
).isActive = true
140+
}
141+
142+
public func removeChild(_ child: Widget, from container: Widget) {
143+
let container = container.view as! NSContainerView
144+
container.removeSubview(child.view)
145+
}
146+
147+
public func naturalSize(of widget: Widget) -> SIMD2<Int> {
148+
let size = widget.view.intrinsicContentSize
149+
return SIMD2(
150+
Int(size.width),
151+
Int(size.height)
152+
)
117153
}
118154

119-
public func setSpacing(ofVStack widget: Widget, to spacing: Int) {
120-
(widget.view as! NSStackView).spacing = CGFloat(spacing)
155+
public func setSize(of widget: Widget, to size: SIMD2<Int>) {
156+
widget.view.widthAnchor.constraint(equalToConstant: Double(size.x)).isActive = true
157+
widget.view.heightAnchor.constraint(equalToConstant: Double(size.y)).isActive = true
121158
}
122159

123-
public func createHStack() -> Widget {
124-
let view = NSStackView()
125-
view.orientation = .horizontal
126-
view.alignment = .centerY
127-
view.setHuggingPriority(.defaultLow, for: .horizontal)
128-
return .view(view)
160+
public func size(of text: String, in proposedFrame: SIMD2<Int>) -> SIMD2<Int> {
161+
let proposedSize = NSSize(
162+
width: CGFloat(proposedFrame.x),
163+
height: CGFloat(proposedFrame.y)
164+
)
165+
let rect = NSString(string: text).boundingRect(
166+
with: proposedSize,
167+
options: [.usesLineFragmentOrigin],
168+
attributes: [.font: Self.font]
169+
)
170+
return SIMD2(
171+
Int(rect.size.width.rounded(.awayFromZero)),
172+
Int(rect.size.height.rounded(.awayFromZero))
173+
)
129174
}
130175

131-
public func setChildren(_ children: [Widget], ofHStack container: Widget) {
132-
let stack = container.view as! NSStackView
133-
for child in children {
134-
stack.addView(child.view, in: .center)
135-
}
136-
if let first = children.first, children.count == 1 {
137-
first.view.pinEdges(to: container.view)
138-
}
176+
public func createTextView() -> Widget {
177+
let textView = NSTextField(wrappingLabelWithString: "")
178+
textView.font = Self.font
179+
return .view(textView)
139180
}
140181

141-
public func setSpacing(ofHStack widget: Widget, to spacing: Int) {
142-
(widget.view as! NSStackView).spacing = CGFloat(spacing)
182+
public func updateTextView(_ textView: Widget, content: String, shouldWrap: Bool) {
183+
// TODO: Implement text wrap handling
184+
(textView.view as! NSTextField).stringValue = content
143185
}
144186

145187
public func createButton() -> Widget {
@@ -154,42 +196,6 @@ public struct AppKitBackend: AppBackend {
154196
}
155197
}
156198

157-
public func createPaddingContainer(for child: Widget) -> Widget {
158-
let paddingContainer = NSStackView()
159-
paddingContainer.addView(child.view, in: .center)
160-
paddingContainer.orientation = .vertical
161-
paddingContainer.alignment = .centerX
162-
return .view(paddingContainer)
163-
}
164-
165-
public func getChild(ofPaddingContainer container: Widget) -> Widget {
166-
return .view((container.view as! NSStackView).views[0])
167-
}
168-
169-
public func setPadding(
170-
ofPaddingContainer container: Widget,
171-
top: Int,
172-
bottom: Int,
173-
leading: Int,
174-
trailing: Int
175-
) {
176-
let view = container.view as! NSStackView
177-
view.edgeInsets.top = CGFloat(top)
178-
view.edgeInsets.bottom = CGFloat(bottom)
179-
view.edgeInsets.left = CGFloat(leading)
180-
view.edgeInsets.right = CGFloat(trailing)
181-
}
182-
183-
public func createSpacer() -> Widget {
184-
return .view(NSView())
185-
}
186-
187-
public func updateSpacer(
188-
_ spacer: Widget, expandHorizontally: Bool, expandVertically: Bool, minSize: Int
189-
) {
190-
// TODO: Update spacer
191-
}
192-
193199
public func createSwitch() -> Widget {
194200
return .view(NSSwitch())
195201
}
@@ -227,50 +233,6 @@ public struct AppKitBackend: AppBackend {
227233
toggle.state = state ? .on : .off
228234
}
229235

230-
public func getInheritedOrientation(of widget: Widget) -> InheritedOrientation? {
231-
guard let superview = widget.view.superview else {
232-
return nil
233-
}
234-
235-
if let stack = superview as? NSStackView {
236-
switch stack.orientation {
237-
case .horizontal:
238-
return .horizontal
239-
case .vertical:
240-
return .vertical
241-
default:
242-
break
243-
}
244-
}
245-
246-
return getInheritedOrientation(of: .view(superview))
247-
}
248-
249-
public func createSingleChildContainer() -> Widget {
250-
let container = NSSingleChildView()
251-
container.orientation = .vertical
252-
container.alignment = .centerX
253-
return .view(container)
254-
}
255-
256-
public func setChild(ofSingleChildContainer container: Widget, to widget: Widget?) {
257-
let container = container.view as! NSSingleChildView
258-
for child in container.arrangedSubviews {
259-
container.removeView(child)
260-
}
261-
if let widget = widget {
262-
container.addView(widget.view, in: .center)
263-
widget.view.pinEdges(to: container)
264-
container.zeroHeightConstraint?.isActive = false
265-
container.zeroWidthConstraint?.isActive = false
266-
} else {
267-
container.zeroHeightConstraint = container.heightAnchor.constraint(equalToConstant: 0)
268-
container.zeroWidthConstraint = container.widthAnchor.constraint(equalToConstant: 0)
269-
container.zeroHeightConstraint?.isActive = true
270-
container.zeroWidthConstraint?.isActive = true
271-
}
272-
}
273-
274236
public func createSlider() -> Widget {
275237
return .view(NSSlider())
276238
}
@@ -375,22 +337,6 @@ public struct AppKitBackend: AppBackend {
375337
return .view(scrollView)
376338
}
377339

378-
public func createLayoutTransparentStack() -> Widget {
379-
return .view(NSStackView())
380-
}
381-
382-
public func updateLayoutTransparentStack(_ container: Widget) {
383-
let stack = container.view as! NSStackView
384-
// Inherit orientation of nearest oriented parent (defaulting to vertical)
385-
stack.orientation =
386-
getInheritedOrientation(of: .view(stack)) == .horizontal ? .horizontal : .vertical
387-
}
388-
389-
public func addChild(_ child: Widget, toLayoutTransparentStack container: Widget) {
390-
let stack = container.view as! NSStackView
391-
stack.addView(child.view, in: .top)
392-
}
393-
394340
public func createSplitView(leadingChild: Widget, trailingChild: Widget) -> Widget {
395341
let splitViewController = NSSplitViewController()
396342

@@ -409,19 +355,6 @@ public struct AppKitBackend: AppBackend {
409355
return .viewController(splitViewController)
410356
}
411357

412-
public func createFrameContainer(for child: Widget) -> Widget {
413-
return child
414-
}
415-
416-
public func updateFrameContainer(_ container: Widget, minWidth: Int, minHeight: Int) {
417-
container.view.widthAnchor
418-
.constraint(greaterThanOrEqualToConstant: CGFloat(minWidth))
419-
.isActive = true
420-
container.view.heightAnchor
421-
.constraint(greaterThanOrEqualToConstant: CGFloat(minHeight))
422-
.isActive = true
423-
}
424-
425358
public func createImageView(filePath: String) -> Widget {
426359
let imageView = NSImageView()
427360
imageView.image = NSImage(contentsOfFile: filePath)

0 commit comments

Comments
 (0)