Skip to content

Commit f9194b1

Browse files
committed
Update UI
1 parent 495bef2 commit f9194b1

File tree

8 files changed

+226
-44
lines changed

8 files changed

+226
-44
lines changed

Sources/GateEngine/UI/GameViewController.swift

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,13 @@ public final class GameView: View {
6363
self.pendingBackgroundColor = newValue
6464
}
6565
case .offScreen:
66-
_renderTarget?.clearColor = newValue ?? .clear
66+
if let _renderTarget {
67+
_renderTarget.clearColor = newValue ?? .clear
68+
}else{
69+
self.pendingBackgroundColor = newValue
70+
}
6771
}
68-
super.backgroundColor = nil
72+
super.backgroundColor = newValue
6973
}
7074
}
7175

@@ -194,7 +198,6 @@ public final class GameView: View {
194198
if let pendingBackgroundColor {
195199
if window != nil {
196200
self.backgroundColor = pendingBackgroundColor
197-
self.pendingBackgroundColor = nil
198201
}
199202
}
200203
}else{
@@ -203,6 +206,7 @@ public final class GameView: View {
203206
_renderTarget = RenderTarget(backgroundColor: self.backgroundColor ?? .clear)
204207
Game.shared.attributes.remove(.renderingIsPermitted)
205208
}
209+
self.pendingBackgroundColor = nil
206210
}
207211
}
208212
extension GameView {
@@ -229,6 +233,11 @@ open class GameViewController: ViewController {
229233
@usableFromInline
230234
internal let context = ECSContext()
231235

236+
@inlinable @inline(__always)
237+
public var gameView: GameView {
238+
return unsafeDowncast(self.view, to: GameView.self)
239+
}
240+
232241
final public override func loadView() {
233242
self.view = GameView()
234243
Task {
@@ -300,6 +309,58 @@ open class GameViewController: ViewController {
300309
}
301310
}
302311

312+
extension GameView {
313+
/**
314+
Move a 3D point into this view's coordinate space.
315+
316+
- returns: A 2D position representing the location of a 3D object in this view's bounds.
317+
*/
318+
public func convert(_ position: Position3, from camera: Camera) -> Position2 {
319+
let size = self.bounds.size
320+
let matricies = camera.matricies(withViewportSize: size * self.interfaceScale)
321+
var position = position * matricies.viewProjection()
322+
position.x /= position.z
323+
position.y /= position.z
324+
325+
position.x = size.width * (position.x + 1) / 2
326+
position.y = size.height * (1.0 - ((position.y + 1) / 2))
327+
328+
// position.x /= self.interfaceScale
329+
// position.y /= self.interfaceScale
330+
331+
return Position2(position.x, position.y)
332+
}
333+
334+
/**
335+
Move a 2D point into a 3D space.
336+
337+
- returns: A Ray3D representing the location of a 2D point located on the view. The ray's direction is toward the 3D space accounting for perspective distortion.
338+
*/
339+
public func convert(_ position: Position2, to camera: Camera) -> Ray3D {
340+
switch camera.fieldOfView {
341+
case .perspective(let fieldOfView):
342+
let size = self.bounds.size
343+
let halfSize = size / 2
344+
let aspectRatio = size.aspectRatio
345+
346+
let inverseView = camera.matricies(withViewportSize: size * interfaceScale).view.inverse
347+
let halfFOV = tan(fieldOfView.rawValueAsRadians * 0.5)
348+
let near = camera.clippingPlane.near
349+
let far = camera.clippingPlane.far
350+
351+
let dx = halfFOV * (position.x / halfSize.width - 1.0) * aspectRatio
352+
let dy = halfFOV * (1.0 - position.y / halfSize.height)
353+
354+
let p1 = Position3(dx * near, dy * near, near) * inverseView
355+
let p2 = Position3(dx * far, dy * far, far) * inverseView
356+
357+
return Ray3D(from: p1, toward: p2)
358+
case .orthographic(_):
359+
fatalError("Not implemented")
360+
}
361+
}
362+
}
363+
303364
extension GameViewController {
304365
@_transparent
305366
public var entities: ContiguousArray<Entity> {

Sources/GateEngine/UI/ImageView.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,6 @@ open class ImageView: View {
3737
super.init()
3838
}
3939

40-
open override func updateLayoutConstraints() {
41-
let size = contentSize()
42-
self.layoutConstraints.removeAllHorizontalSizeConstraints()
43-
self.layoutConstraints.removeAllVerticalSizeConstraints()
44-
self.widthAnchor.constrain(to: size.width)
45-
self.heightAnchor.constrain(to: size.height)
46-
}
47-
4840
public override func contentSize() -> Size2 {
4941
if let subRect {
5042
return subRect.size

Sources/GateEngine/UI/Layout.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,13 @@ public struct Layout {
346346
}
347347
}
348348

349+
if computed == nil {
350+
let contentWidth = view.contentSize().width
351+
if contentWidth > 0 {
352+
computed = Value<Layout.Horizontal, Layout.Size>.Computed(value: contentWidth)
353+
}
354+
}
355+
349356
view.layoutConstraints.resolvedFrame.width.currentlyBeingResolved = false
350357
if let computed {
351358
view.layoutConstraints.resolvedFrame.width.computed = computed
@@ -441,6 +448,13 @@ public struct Layout {
441448
}
442449
}
443450

451+
if computed == nil {
452+
let contentHeight = view.contentSize().height
453+
if contentHeight > 0 {
454+
computed = Value<Layout.Vertical, Layout.Size>.Computed(value: contentHeight)
455+
}
456+
}
457+
444458
view.layoutConstraints.resolvedFrame.height.currentlyBeingResolved = false
445459
if let computed {
446460
view.layoutConstraints.resolvedFrame.height.computed = computed

Sources/GateEngine/UI/ScrollView.swift

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,39 @@ open class ScrollView: View {
4141
animationAccumulator += deltaTime
4242
let factor: Float = .minimum(1, animationAccumulator / animationDuration)
4343
self.offset.interpolate(to: destinationOffset, .linear(factor))
44+
45+
let contentSize: Size2 = self.contentSize
46+
if contentSize.width > self.bounds.width, destinationOffset.x < -(contentSize.width - self.bounds.width) {
47+
destinationOffset.x = -(contentSize.width - self.bounds.width)
48+
}else if destinationOffset.x > 0 {
49+
destinationOffset.x = 0
50+
}
51+
if contentSize.height > self.bounds.height, destinationOffset.y < -(contentSize.height - self.bounds.height) {
52+
destinationOffset.y = -(contentSize.height - self.bounds.height)
53+
}else if destinationOffset.y > 0 {
54+
destinationOffset.y = 0
55+
}
56+
}
57+
58+
var contentSize: Size2 {
59+
var maxY: Float = 0
60+
var maxX: Float = 0
61+
for subview in self.contentView.subviews {
62+
let subviewMaxX = subview.frame.maxX
63+
if subviewMaxX > maxX {
64+
maxX = subviewMaxX
65+
}
66+
let subviewMaxY = subview.frame.maxY
67+
if subviewMaxY > maxY {
68+
maxY = subviewMaxY
69+
}
70+
}
71+
return Size2(maxX, maxY)
4472
}
4573

46-
override init(size: Size2? = nil) {
74+
override public init(size: Size2? = nil) {
4775
super.init(size: size)
76+
self.clipToBounds = true
4877
self.addSubview(contentView)
4978
}
5079

@@ -57,19 +86,15 @@ open class ScrollView: View {
5786
self.contentView.bottomAnchor.constrain(offset.x, from: self.bottomAnchor)
5887
self.contentView.trailingAnchor.constrain(to: self.trailingAnchor)
5988
}
60-
61-
open override func canBeHit() -> Bool {
62-
return true
63-
}
64-
89+
6590
open override func scrolled(_ delta: Position2, isPlatformGeneratedMomentum isMomentum: Bool) {
6691
super.scrolled(delta, isPlatformGeneratedMomentum: isMomentum)
6792
if isMomentum == false {
6893
if scrollDirection.contains(.horizontal) {
69-
destinationOffset.x = delta.x
94+
destinationOffset.x += delta.x
7095
}
7196
if scrollDirection.contains(.vertical) {
72-
destinationOffset.y = delta.y
97+
destinationOffset.y += delta.y
7398
}
7499
}
75100
}

Sources/GateEngine/UI/SplitViewController.swift

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,20 +85,46 @@ public final class SplitView: View {
8585
dividerControl.isEnabled = self.canResizeSidebar
8686
}
8787
}
88-
public var dividerXOffset: Float = 300 {
88+
public var sidebarWidth: Float = 300 {
8989
didSet {
9090
self.setNeedsUpdateConstraints()
9191
}
9292
}
9393

94+
public var canResizeDetail: Bool = true {
95+
didSet {
96+
dividerControl2.isEnabled = self.canResizeDetail
97+
}
98+
}
99+
public var detailWidth: Float = 300 {
100+
didSet {
101+
self.setNeedsUpdateConstraints()
102+
}
103+
}
104+
105+
weak var sidebarView: View? = nil
106+
weak var contentView: View? = nil
107+
weak var detailView: View? = nil
108+
94109
let dividerControl: SplitViewDividerControl = SplitViewDividerControl()
95-
public func addSidebarView(_ sidebarView: View, contentView: View) {
110+
let dividerControl2: SplitViewDividerControl = SplitViewDividerControl()
111+
112+
public func addSidebarView(_ sidebarView: View, contentView: View, detailView: View?) {
96113
for subview in subviews {
97114
subview.removeFromSuperview()
98115
}
116+
self.sidebarView = sidebarView
99117
self.addSubview(sidebarView)
118+
self.contentView = contentView
100119
self.addSubview(contentView)
120+
if let detailView {
121+
self.detailView = detailView
122+
self.addSubview(detailView)
123+
}
101124
self.addSubview(dividerControl)
125+
if detailView != nil {
126+
self.addSubview(dividerControl2)
127+
}
102128
}
103129

104130
public override init(size: Size2? = nil) {
@@ -115,7 +141,7 @@ public final class SplitView: View {
115141
if dividerControl.isDragging {
116142
if cursor.button(.button1).isPressed {
117143
if let new = cursor.locationInView(self)?.x {
118-
dividerXOffset = new
144+
sidebarWidth = new
119145
}
120146
}
121147
}
@@ -128,19 +154,41 @@ public final class SplitView: View {
128154
dividerControl.topAnchor.constrain(to: self.topAnchor)
129155
dividerControl.bottomAnchor.constrain(to: self.bottomAnchor)
130156
dividerControl.widthAnchor.constrain(to: 8)
131-
dividerControl.leadingAnchor.constrain(dividerXOffset - 4, from: self.leadingAnchor, priority: .high)
157+
dividerControl.leadingAnchor.constrain(sidebarWidth - 4, from: self.leadingAnchor, priority: .high)
158+
159+
dividerControl2.layoutConstraints.removeAllConstraints()
160+
if detailView != nil {
161+
dividerControl2.topAnchor.constrain(to: self.topAnchor)
162+
dividerControl2.bottomAnchor.constrain(to: self.bottomAnchor)
163+
dividerControl2.widthAnchor.constrain(to: 8)
164+
dividerControl2.leadingAnchor.constrain(-detailWidth - 4, from: self.trailingAnchor, priority: .high)
165+
}
132166

133167
self.subviews[0].layoutConstraints.removeAllConstraints()
134168
self.subviews[0].topAnchor.constrain(to: self.topAnchor)
135169
self.subviews[0].leadingAnchor.constrain(to: self.leadingAnchor)
136170
self.subviews[0].bottomAnchor.constrain(to: self.bottomAnchor)
137-
self.subviews[0].widthAnchor.constrain(to: dividerXOffset)
171+
self.subviews[0].widthAnchor.constrain(to: sidebarWidth)
138172

139-
self.subviews[1].layoutConstraints.removeAllConstraints()
140-
self.subviews[1].topAnchor.constrain(to: self.topAnchor)
141-
self.subviews[1].leadingAnchor.constrain(dividerXOffset + 1, from: self.leadingAnchor)
142-
self.subviews[1].bottomAnchor.constrain(to: self.bottomAnchor)
143-
self.subviews[1].trailingAnchor.constrain(to: self.trailingAnchor)
173+
if detailView != nil {
174+
self.subviews[1].layoutConstraints.removeAllConstraints()
175+
self.subviews[1].topAnchor.constrain(to: self.topAnchor)
176+
self.subviews[1].leadingAnchor.constrain(sidebarWidth + 1, from: self.leadingAnchor)
177+
self.subviews[1].bottomAnchor.constrain(to: self.bottomAnchor)
178+
self.subviews[1].trailingAnchor.constrain(-detailWidth, from: self.trailingAnchor)
179+
180+
self.subviews[2].layoutConstraints.removeAllConstraints()
181+
self.subviews[2].topAnchor.constrain(to: self.topAnchor)
182+
self.subviews[2].leadingAnchor.constrain(-detailWidth, from: self.trailingAnchor)
183+
self.subviews[2].bottomAnchor.constrain(to: self.bottomAnchor)
184+
self.subviews[2].widthAnchor.constrain(to: detailWidth)
185+
}else{
186+
self.subviews[1].layoutConstraints.removeAllConstraints()
187+
self.subviews[1].topAnchor.constrain(to: self.topAnchor)
188+
self.subviews[1].leadingAnchor.constrain(sidebarWidth + 1, from: self.leadingAnchor)
189+
self.subviews[1].bottomAnchor.constrain(to: self.bottomAnchor)
190+
self.subviews[1].trailingAnchor.constrain(to: self.trailingAnchor)
191+
}
144192
}
145193
}
146194

@@ -154,16 +202,32 @@ open class SplitViewController: ViewController {
154202

155203
open override func viewDidLoad() {
156204
super.viewDidLoad()
157-
self.splitView.addSidebarView(children[0].view, contentView: children[1].view)
205+
if detailViewController != nil {
206+
self.splitView.addSidebarView(children[0].view, contentView: children[1].view, detailView: children[2].view)
207+
}else{
208+
self.splitView.addSidebarView(children[0].view, contentView: children[1].view, detailView: nil)
209+
}
158210
}
159211

160212
public var splitView: SplitView {
161213
return self.view as! SplitView
162214
}
163215

164-
public init(sideBar: ViewController, content: ViewController) {
216+
weak var sidebarViewController: ViewController? = nil
217+
weak var contentViewController: ViewController? = nil
218+
weak var detailViewController: ViewController? = nil
219+
220+
public init(sideBar: ViewController, content: ViewController, detail: ViewController? = nil) {
165221
super.init()
166222
self.addChildViewController(sideBar)
223+
self.sidebarViewController = sideBar
224+
167225
self.addChildViewController(content)
226+
self.contentViewController = content
227+
228+
if let detail {
229+
self.detailViewController = detail
230+
self.addChildViewController(detail)
231+
}
168232
}
169233
}

Sources/GateEngine/UI/StackView.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,27 @@ public final class StackView: View {
4949
self.needsUpdateConstraints = true
5050
}
5151

52+
public override func contentSize() -> Size2 {
53+
switch axis {
54+
case .horizontal:
55+
return Size2(
56+
width: subviews.last?.frame.maxX ?? 0,
57+
height: (subviews.sorted(by: {$0.bounds.height > $1.bounds.height}).first?.bounds.height ?? 0) + (spacing * Float(subviews.count - 1))
58+
)
59+
case .vertical:
60+
return Size2(
61+
width: (subviews.sorted(by: {$0.bounds.width > $1.bounds.width}).first?.bounds.width ?? 0) + (spacing * Float(subviews.count - 1)),
62+
height: subviews.last?.frame.maxY ?? 0
63+
)
64+
}
65+
}
66+
5267
public override func updateLayoutConstraints() {
68+
if subviews.isEmpty {
69+
self.widthAnchor.constrain(to: 0)
70+
self.heightAnchor.constrain(to: 0)
71+
return
72+
}
5373
switch axis {
5474
case .horizontal:
5575
switch distribution {
@@ -63,8 +83,7 @@ public final class StackView: View {
6383
}else{
6484
subView.leadingAnchor.constrain(spacing, from: previousView.trailingAnchor)
6585
}
66-
subView.topAnchor.constrain(to: self.topAnchor)
67-
subView.bottomAnchor.constrain(to: self.bottomAnchor)
86+
subView.centerYAnchor.constrain(to: self.centerYAnchor)
6887
previousView = subView
6988
}
7089
subviews.last?.trailingAnchor.constrain(to: self.trailingAnchor)

0 commit comments

Comments
 (0)