Skip to content

Commit c93de28

Browse files
authored
Merge pull request #99 from lukepistrol/status-bar
Basic implementation of the status bar (non interactive)
2 parents 8203209 + 45e7102 commit c93de28

File tree

5 files changed

+307
-2
lines changed

5 files changed

+307
-2
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
287776EF27E3515300D46668 /* TabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776EE27E3515300D46668 /* TabBarItem.swift */; };
2525
289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289978EC27E4E97E00BB0357 /* FileIconStyle.swift */; };
2626
28B0A19827E385C300B73177 /* SideBarToolbarTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B0A19727E385C300B73177 /* SideBarToolbarTop.swift */; };
27+
28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */ = {isa = PBXBuildFile; productRef = 28CE5E9F27E6493D0065D29C /* StatusBar */; };
2728
28FFE1BF27E3A441001939DB /* SideBarToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FFE1BE27E3A441001939DB /* SideBarToolbarBottom.swift */; };
2829
2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468438427DC76E200F8E88E /* AppDelegate.swift */; };
2930
345F667527DF6C180069BD69 /* FileTabRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345F667427DF6C180069BD69 /* FileTabRow.swift */; };
@@ -107,6 +108,7 @@
107108
5C403B8F27E20F8000788241 /* WorkspaceClient in Frameworks */,
108109
B65E614627E6765D00255275 /* Introspect in Frameworks */,
109110
D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */,
111+
28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */,
110112
5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */,
111113
);
112114
runOnlyForDeploymentPostprocessing = 0;
@@ -302,6 +304,7 @@
302304
5CF38A5D27E48E6C0096A0F7 /* CodeFile */,
303305
D70F5E2B27E4E8CF004EE4B9 /* WelcomeModule */,
304306
B65E614527E6765D00255275 /* Introspect */,
307+
28CE5E9F27E6493D0065D29C /* StatusBar */,
305308
);
306309
productName = CodeEdit;
307310
productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */;
@@ -867,6 +870,10 @@
867870
/* End XCRemoteSwiftPackageReference section */
868871

869872
/* Begin XCSwiftPackageProductDependency section */
873+
28CE5E9F27E6493D0065D29C /* StatusBar */ = {
874+
isa = XCSwiftPackageProductDependency;
875+
productName = StatusBar;
876+
};
870877
5C403B8E27E20F8000788241 /* WorkspaceClient */ = {
871878
isa = XCSwiftPackageProductDependency;
872879
productName = WorkspaceClient;

CodeEdit/SideBar/SideBarItem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import SwiftUI
99
import WorkspaceClient
1010
import CodeFile
11+
import StatusBar
1112

1213
struct SideBarItem: View {
1314

@@ -40,6 +41,9 @@ struct SideBarItem: View {
4041
BreadcrumbsView(item, workspace: workspace)
4142
}
4243
}
44+
.safeAreaInset(edge: .bottom) {
45+
StatusBarView()
46+
}
4347
} else {
4448
Text("CodeEdit cannot open this file because its file type is not supported.")
4549
}
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
//
2+
// StatusBar.swift
3+
//
4+
//
5+
// Created by Lukas Pistrol on 19.03.22.
6+
//
7+
8+
import SwiftUI
9+
10+
@available(macOS 12, *)
11+
public struct StatusBarView: View {
12+
13+
@ObservedObject private var model: StatusBarModel
14+
15+
public init() {
16+
self.model = .init()
17+
}
18+
19+
public var body: some View {
20+
VStack(spacing: 0) {
21+
bar
22+
if model.isExpanded {
23+
terminal
24+
}
25+
}
26+
// removes weird light gray bar above when in light mode
27+
.padding(.top, -8) // (comment out to make it look normal in preview)
28+
}
29+
30+
private var dragGesture: some Gesture {
31+
DragGesture()
32+
.onChanged { value in
33+
let newHeight = max(0, min(height - value.translation.height, 500))
34+
if newHeight-1 > height || newHeight+1 < height {
35+
height = newHeight
36+
}
37+
model.isExpanded = height < 1 ? false : true
38+
}
39+
}
40+
41+
private var bar: some View {
42+
ZStack {
43+
Rectangle()
44+
.foregroundStyle(.bar)
45+
HStack(spacing: 14) {
46+
HStack(spacing: 8) {
47+
labelButton(model.errorCount.formatted(), image: "xmark.octagon")
48+
labelButton(model.warningCount.formatted(), image: "exclamationmark.triangle")
49+
}
50+
branchPicker
51+
reloadButton
52+
Spacer()
53+
cursorLocationLabel
54+
indentSelector
55+
encodingSelector
56+
lineEndSelector
57+
expandButton
58+
}
59+
.padding(.horizontal, 10)
60+
}
61+
.overlay(alignment: .top) {
62+
Divider()
63+
}
64+
.frame(height: 32)
65+
.gesture(dragGesture)
66+
.onHover { hovering in
67+
if hovering {
68+
NSCursor.resizeUpDown.push()
69+
} else {
70+
NSCursor.pop()
71+
}
72+
}
73+
}
74+
75+
@State private var height: Double = 300
76+
77+
private var terminal: some View {
78+
Rectangle()
79+
.foregroundColor(Color(red: 0.163, green: 0.163, blue: 0.188, opacity: 1.000))
80+
.frame(minHeight: 0, idealHeight: height, maxHeight: height)
81+
}
82+
83+
private func labelButton(_ text: String, image: String) -> some View {
84+
Button {
85+
// show errors/warnings
86+
} label: {
87+
HStack(spacing: 4) {
88+
Image(systemName: image)
89+
.font(.headline)
90+
Text(text)
91+
}
92+
}
93+
.buttonStyle(.borderless)
94+
.foregroundStyle(.primary)
95+
.onHover { hovering in
96+
if hovering {
97+
NSCursor.pointingHand.push()
98+
} else {
99+
NSCursor.pop()
100+
}
101+
}
102+
}
103+
104+
private var branchPicker: some View {
105+
Menu(model.branches[model.selectedBranch]) {
106+
ForEach(model.branches.indices, id: \.self) { branch in
107+
Button { model.selectedBranch = branch } label: {
108+
Text(model.branches[branch])
109+
// checkout branch
110+
}
111+
}
112+
}
113+
.menuStyle(.borderlessButton)
114+
.fixedSize()
115+
.onHover { hovering in
116+
if hovering {
117+
NSCursor.pointingHand.push()
118+
} else {
119+
NSCursor.pop()
120+
}
121+
}
122+
}
123+
124+
private var reloadButton: some View {
125+
// Temporary
126+
Button {
127+
model.isReloading = true
128+
// Just for looks for now. In future we'll call a function like
129+
// `reloadFileStatus()` here which will set/unset `reloading`
130+
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
131+
self.model.isReloading = false
132+
}
133+
} label: {
134+
Image(systemName: "arrow.triangle.2.circlepath")
135+
.imageScale(.large)
136+
.rotationEffect(.degrees(model.isReloading ? 360 : 0))
137+
.animation(animation, value: model.isReloading)
138+
.opacity(model.isReloading ? 1 : 0)
139+
// A bit of a hacky solution to prevent spinning counterclockwise once `reloading` changes to `false`
140+
.overlay {
141+
Image(systemName: "arrow.triangle.2.circlepath")
142+
.imageScale(.large)
143+
.opacity(model.isReloading ? 0 : 1)
144+
}
145+
146+
}
147+
.buttonStyle(.borderless)
148+
.foregroundStyle(.primary)
149+
.onHover { hovering in
150+
if hovering {
151+
NSCursor.pointingHand.push()
152+
} else {
153+
NSCursor.pop()
154+
}
155+
}
156+
}
157+
158+
// Temporary
159+
private var animation: Animation {
160+
// 10x speed when not reloading to make invisible ccw spin go fast in case button is pressed multiple times.
161+
.linear.speed(model.isReloading ? 0.5 : 10)
162+
}
163+
164+
private var cursorLocationLabel: some View {
165+
Text("Ln \(model.currentLine), Col \(model.currentCol)")
166+
.foregroundStyle(.primary)
167+
.onHover { hovering in
168+
if hovering {
169+
NSCursor.pointingHand.push()
170+
} else {
171+
NSCursor.pop()
172+
}
173+
}
174+
}
175+
176+
private var indentSelector: some View {
177+
Menu("2 Spaces") {
178+
// 2 spaces, 4 spaces, ...
179+
}
180+
.menuStyle(.borderlessButton)
181+
.fixedSize()
182+
.onHover { hovering in
183+
if hovering {
184+
NSCursor.pointingHand.push()
185+
} else {
186+
NSCursor.pop()
187+
}
188+
}
189+
}
190+
191+
private var encodingSelector: some View {
192+
Menu("UTF 8") {
193+
// UTF 8, ASCII, ...
194+
}
195+
.menuStyle(.borderlessButton)
196+
.fixedSize()
197+
.onHover { hovering in
198+
if hovering {
199+
NSCursor.pointingHand.push()
200+
} else {
201+
NSCursor.pop()
202+
}
203+
}
204+
}
205+
206+
private var lineEndSelector: some View {
207+
Menu("LF") {
208+
// LF, CRLF
209+
}
210+
.menuStyle(.borderlessButton)
211+
.fixedSize()
212+
.onHover { hovering in
213+
if hovering {
214+
NSCursor.pointingHand.push()
215+
} else {
216+
NSCursor.pop()
217+
}
218+
}
219+
}
220+
221+
private var expandButton: some View {
222+
Button {
223+
model.isExpanded.toggle()
224+
if model.isExpanded && height < 1 {
225+
height = 300
226+
}
227+
// Show/hide terminal window
228+
} label: {
229+
Image(systemName: "rectangle.bottomthird.inset.filled")
230+
.imageScale(.large)
231+
}
232+
.tint(model.isExpanded ? .accentColor : .primary)
233+
.buttonStyle(.borderless)
234+
.onHover { hovering in
235+
if hovering {
236+
NSCursor.pointingHand.push()
237+
} else {
238+
NSCursor.pop()
239+
}
240+
}
241+
}
242+
}
243+
244+
@available(macOS 12, *)
245+
struct SwiftUIView_Previews: PreviewProvider {
246+
static var previews: some View {
247+
ZStack(alignment: .bottom) {
248+
Color.white
249+
StatusBarView()
250+
.previewLayout(.fixed(width: 1.336, height: 500.0))
251+
.preferredColorScheme(.light)
252+
}
253+
}
254+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// StatusBarModel.swift
3+
//
4+
//
5+
// Created by Lukas Pistrol on 20.03.22.
6+
//
7+
8+
import Foundation
9+
10+
public class StatusBarModel: ObservableObject {
11+
12+
// TODO: Implement logic for updating values
13+
@Published public var errorCount: Int = 0 // Implementation missing
14+
@Published public var warningCount: Int = 0 // Implementation missing
15+
16+
@Published public var branches: [String] = ["main"] // Implementation missing
17+
@Published public var selectedBranch: Int = 0 // Implementation missing
18+
19+
@Published public var isReloading: Bool = false // Implementation missing
20+
21+
@Published public var currentLine: Int = 1 // Implementation missing
22+
@Published public var currentCol: Int = 1 // Implementation missing
23+
24+
@Published public var isExpanded: Bool = false // Implementation missing
25+
26+
// TODO: Add @Published vars for indentation, encoding, linebreak
27+
28+
public init() {}
29+
}

CodeEditModules/Package.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ let package = Package(
1717
name: "CodeFile",
1818
targets: ["CodeFile"]
1919
),
20-
.library(name: "WelcomeModule", targets: ["WelcomeModule"])
20+
.library(
21+
name: "WelcomeModule",
22+
targets: ["WelcomeModule"]
23+
),
24+
.library(
25+
name: "StatusBar",
26+
targets: ["StatusBar"]
27+
)
2128
],
2229
dependencies: [
2330
.package(
@@ -71,6 +78,10 @@ let package = Package(
7178
"SnapshotTesting"
7279
],
7380
path: "Modules/WelcomeModule/Tests"
74-
)
81+
),
82+
.target(
83+
name: "StatusBar",
84+
path: "Modules/StatusBar/src"
85+
)
7586
]
7687
)

0 commit comments

Comments
 (0)