Skip to content

Basic implementation of the status bar (non interactive) #99

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Mar 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
287776EF27E3515300D46668 /* TabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776EE27E3515300D46668 /* TabBarItem.swift */; };
289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 289978EC27E4E97E00BB0357 /* FileIconStyle.swift */; };
28B0A19827E385C300B73177 /* SideBarToolbarTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B0A19727E385C300B73177 /* SideBarToolbarTop.swift */; };
28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */ = {isa = PBXBuildFile; productRef = 28CE5E9F27E6493D0065D29C /* StatusBar */; };
28FFE1BF27E3A441001939DB /* SideBarToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FFE1BE27E3A441001939DB /* SideBarToolbarBottom.swift */; };
2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468438427DC76E200F8E88E /* AppDelegate.swift */; };
345F667527DF6C180069BD69 /* FileTabRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345F667427DF6C180069BD69 /* FileTabRow.swift */; };
Expand Down Expand Up @@ -107,6 +108,7 @@
5C403B8F27E20F8000788241 /* WorkspaceClient in Frameworks */,
B65E614627E6765D00255275 /* Introspect in Frameworks */,
D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */,
28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */,
5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -302,6 +304,7 @@
5CF38A5D27E48E6C0096A0F7 /* CodeFile */,
D70F5E2B27E4E8CF004EE4B9 /* WelcomeModule */,
B65E614527E6765D00255275 /* Introspect */,
28CE5E9F27E6493D0065D29C /* StatusBar */,
);
productName = CodeEdit;
productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */;
Expand Down Expand Up @@ -867,6 +870,10 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
28CE5E9F27E6493D0065D29C /* StatusBar */ = {
isa = XCSwiftPackageProductDependency;
productName = StatusBar;
};
5C403B8E27E20F8000788241 /* WorkspaceClient */ = {
isa = XCSwiftPackageProductDependency;
productName = WorkspaceClient;
Expand Down
4 changes: 4 additions & 0 deletions CodeEdit/SideBar/SideBarItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import SwiftUI
import WorkspaceClient
import CodeFile
import StatusBar

struct SideBarItem: View {

Expand Down Expand Up @@ -40,6 +41,9 @@ struct SideBarItem: View {
BreadcrumbsView(item, workspace: workspace)
}
}
.safeAreaInset(edge: .bottom) {
StatusBarView()
}
} else {
Text("CodeEdit cannot open this file because its file type is not supported.")
}
Expand Down
254 changes: 254 additions & 0 deletions CodeEditModules/Modules/StatusBar/src/StatusBar.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
//
// StatusBar.swift
//
//
// Created by Lukas Pistrol on 19.03.22.
//

import SwiftUI

@available(macOS 12, *)
public struct StatusBarView: View {

@ObservedObject private var model: StatusBarModel

public init() {
self.model = .init()
}

public var body: some View {
VStack(spacing: 0) {
bar
if model.isExpanded {
terminal
}
}
// removes weird light gray bar above when in light mode
.padding(.top, -8) // (comment out to make it look normal in preview)
}

private var dragGesture: some Gesture {
DragGesture()
.onChanged { value in
let newHeight = max(0, min(height - value.translation.height, 500))
if newHeight-1 > height || newHeight+1 < height {
height = newHeight
}
model.isExpanded = height < 1 ? false : true
}
}

private var bar: some View {
ZStack {
Rectangle()
.foregroundStyle(.bar)
HStack(spacing: 14) {
HStack(spacing: 8) {
labelButton(model.errorCount.formatted(), image: "xmark.octagon")
labelButton(model.warningCount.formatted(), image: "exclamationmark.triangle")
}
branchPicker
reloadButton
Spacer()
cursorLocationLabel
indentSelector
encodingSelector
lineEndSelector
expandButton
}
.padding(.horizontal, 10)
}
.overlay(alignment: .top) {
Divider()
}
.frame(height: 32)
.gesture(dragGesture)
.onHover { hovering in
if hovering {
NSCursor.resizeUpDown.push()
} else {
NSCursor.pop()
}
}
}

@State private var height: Double = 300

private var terminal: some View {
Rectangle()
.foregroundColor(Color(red: 0.163, green: 0.163, blue: 0.188, opacity: 1.000))
.frame(minHeight: 0, idealHeight: height, maxHeight: height)
}

private func labelButton(_ text: String, image: String) -> some View {
Button {
// show errors/warnings
} label: {
HStack(spacing: 4) {
Image(systemName: image)
.font(.headline)
Text(text)
}
}
.buttonStyle(.borderless)
.foregroundStyle(.primary)
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

private var branchPicker: some View {
Menu(model.branches[model.selectedBranch]) {
ForEach(model.branches.indices, id: \.self) { branch in
Button { model.selectedBranch = branch } label: {
Text(model.branches[branch])
// checkout branch
}
}
}
.menuStyle(.borderlessButton)
.fixedSize()
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

private var reloadButton: some View {
// Temporary
Button {
model.isReloading = true
// Just for looks for now. In future we'll call a function like
// `reloadFileStatus()` here which will set/unset `reloading`
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.model.isReloading = false
}
} label: {
Image(systemName: "arrow.triangle.2.circlepath")
.imageScale(.large)
.rotationEffect(.degrees(model.isReloading ? 360 : 0))
.animation(animation, value: model.isReloading)
.opacity(model.isReloading ? 1 : 0)
// A bit of a hacky solution to prevent spinning counterclockwise once `reloading` changes to `false`
.overlay {
Image(systemName: "arrow.triangle.2.circlepath")
.imageScale(.large)
.opacity(model.isReloading ? 0 : 1)
}

}
.buttonStyle(.borderless)
.foregroundStyle(.primary)
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

// Temporary
private var animation: Animation {
// 10x speed when not reloading to make invisible ccw spin go fast in case button is pressed multiple times.
.linear.speed(model.isReloading ? 0.5 : 10)
}

private var cursorLocationLabel: some View {
Text("Ln \(model.currentLine), Col \(model.currentCol)")
.foregroundStyle(.primary)
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

private var indentSelector: some View {
Menu("2 Spaces") {
// 2 spaces, 4 spaces, ...
}
.menuStyle(.borderlessButton)
.fixedSize()
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

private var encodingSelector: some View {
Menu("UTF 8") {
// UTF 8, ASCII, ...
}
.menuStyle(.borderlessButton)
.fixedSize()
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

private var lineEndSelector: some View {
Menu("LF") {
// LF, CRLF
}
.menuStyle(.borderlessButton)
.fixedSize()
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}

private var expandButton: some View {
Button {
model.isExpanded.toggle()
if model.isExpanded && height < 1 {
height = 300
}
// Show/hide terminal window
} label: {
Image(systemName: "rectangle.bottomthird.inset.filled")
.imageScale(.large)
}
.tint(model.isExpanded ? .accentColor : .primary)
.buttonStyle(.borderless)
.onHover { hovering in
if hovering {
NSCursor.pointingHand.push()
} else {
NSCursor.pop()
}
}
}
}

@available(macOS 12, *)
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
ZStack(alignment: .bottom) {
Color.white
StatusBarView()
.previewLayout(.fixed(width: 1.336, height: 500.0))
.preferredColorScheme(.light)
}
}
}
29 changes: 29 additions & 0 deletions CodeEditModules/Modules/StatusBar/src/StatusBarModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// StatusBarModel.swift
//
//
// Created by Lukas Pistrol on 20.03.22.
//

import Foundation

public class StatusBarModel: ObservableObject {

// TODO: Implement logic for updating values
@Published public var errorCount: Int = 0 // Implementation missing
@Published public var warningCount: Int = 0 // Implementation missing

@Published public var branches: [String] = ["main"] // Implementation missing
@Published public var selectedBranch: Int = 0 // Implementation missing

@Published public var isReloading: Bool = false // Implementation missing

@Published public var currentLine: Int = 1 // Implementation missing
@Published public var currentCol: Int = 1 // Implementation missing

@Published public var isExpanded: Bool = false // Implementation missing

// TODO: Add @Published vars for indentation, encoding, linebreak

public init() {}
}
15 changes: 13 additions & 2 deletions CodeEditModules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ let package = Package(
name: "CodeFile",
targets: ["CodeFile"]
),
.library(name: "WelcomeModule", targets: ["WelcomeModule"])
.library(
name: "WelcomeModule",
targets: ["WelcomeModule"]
),
.library(
name: "StatusBar",
targets: ["StatusBar"]
)
],
dependencies: [
.package(
Expand Down Expand Up @@ -71,6 +78,10 @@ let package = Package(
"SnapshotTesting"
],
path: "Modules/WelcomeModule/Tests"
)
),
.target(
name: "StatusBar",
path: "Modules/StatusBar/src"
)
]
)