Skip to content

Commit

Permalink
Create experimental interactive terminal backend (using TermKit)
Browse files Browse the repository at this point in the history
  • Loading branch information
stackotter committed Aug 24, 2023
1 parent 2a97750 commit cdb29a4
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 2 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/swift-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ jobs:
sudo apt update && \
sudo apt install -y libgtk-4-dev clang
- name: Build
run: swift build -v
run: |
swift build SwiftCrossUI && \
swift build GtkBackend && \
swift build CounterExample && \
swift build RandomNumberGeneratorExample && \
swift build WindowPropertiesExample && \
swift build GreetingGeneratorExample && \
swift build FileViewerExample && \
swift build NavigationExample && \
swift build SplitExample && \
swift build GtkCodeGen && \
swift build GtkExample
- name: Test
run: swift test
13 changes: 12 additions & 1 deletion .github/workflows/swift-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ jobs:
- name: Patch libffi
run: sed -i '' 's/-I..includedir.//g' /usr/local/Homebrew/Library/Homebrew/os/mac/pkgconfig/13/libffi.pc
- name: Build
run: swift build -v
run: |
swift build SwiftCrossUI && \
swift build GtkBackend && \
swift build CounterExample && \
swift build RandomNumberGeneratorExample && \
swift build WindowPropertiesExample && \
swift build GreetingGeneratorExample && \
swift build FileViewerExample && \
swift build NavigationExample && \
swift build SplitExample && \
swift build GtkCodeGen && \
swift build GtkExample
- name: Test
run: swift test
36 changes: 36 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
"version": null
}
},
{
"package": "OpenCombine",
"repositoryURL": "https://github.com/OpenCombine/OpenCombine.git",
"state": {
"branch": null,
"revision": "8576f0d579b27020beccbccc3ea6844f3ddfc2c2",
"version": "0.14.0"
}
},
{
"package": "Qlift",
"repositoryURL": "https://github.com/Longhanks/qlift",
Expand Down Expand Up @@ -55,6 +64,33 @@
"version": "508.0.1"
}
},
{
"package": "SwiftTerm",
"repositoryURL": "https://github.com/migueldeicaza/SwiftTerm.git",
"state": {
"branch": null,
"revision": "116b795e4de2324d4a1b1bb2db02141294bc9229",
"version": "1.2.4"
}
},
{
"package": "TermKit",
"repositoryURL": "https://github.com/migueldeicaza/TermKit",
"state": {
"branch": null,
"revision": "3bce85d1bafbbb0336b3b7b7e905c35754cb9adf",
"version": null
}
},
{
"package": "TextBufferKit",
"repositoryURL": "https://github.com/migueldeicaza/TextBufferKit.git",
"state": {
"branch": null,
"revision": "7f3ed5b1d7288de34ad853544d802647be11cfcf",
"version": "0.3.0"
}
},
{
"package": "XMLCoder",
"repositoryURL": "https://github.com/CoreOffice/XMLCoder",
Expand Down
16 changes: 16 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ if checkSDL2Installed() {
)
}

// TODO: Conditionally include TermKit backend
conditionalTargets.append(
.target(
name: "CursesBackend",
dependencies: ["SwiftCrossUI", "TermKit"]
)
)
backendTargets.append("CursesBackend")
exampleDependencies.append("CursesBackend")
dependencies.append(
.package(
url: "https://github.com/migueldeicaza/TermKit",
revision: "3bce85d1bafbbb0336b3b7b7e905c35754cb9adf"
)
)

let package = Package(
name: "swift-cross-ui",
platforms: [.macOS(.v10_15)],
Expand Down
120 changes: 120 additions & 0 deletions Sources/CursesBackend/CursesBackend.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import Foundation
import SwiftCrossUI
import TermKit

public struct CursesBackend: AppBackend {
public typealias Widget = TermKit.View

public init(appIdentifier: String) {}

public func run<AppRoot: App>(
_ app: AppRoot,
_ setViewGraph: @escaping (ViewGraph<AppRoot>) -> Void
) where AppRoot.Backend == Self {
let viewGraph = ViewGraph(for: app, backend: self)
setViewGraph(viewGraph)

Application.prepare()
let root = RootView()
root.addSubview(viewGraph.rootNode.widget)
Application.top.addSubview(root)
Application.run()
}

public func runInMainThread(action: @escaping () -> Void) {
DispatchQueue.main.async {
action()
}
}

public func show(_ widget: Widget) {
widget.setNeedsDisplay()
}

public func createVStack(spacing: Int) -> Widget {
return View()
}

public func addChild(_ child: Widget, toVStack container: Widget) {
// TODO: Properly calculate layout

Check warning on line 39 in Sources/CursesBackend/CursesBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Properly calculate layout) (todo)
child.y = Pos.at(container.subviews.count)
container.addSubview(child)
}

public func setSpacing(ofVStack container: Widget, to spacing: Int) {}

public func createHStack(spacing: Int) -> Widget {
return View()
}

public func addChild(_ child: Widget, toHStack container: Widget) {
// TODO: Properly calculate layout

Check warning on line 51 in Sources/CursesBackend/CursesBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Properly calculate layout) (todo)
child.y = Pos.at(container.subviews.count)
container.addSubview(child)
}

public func setSpacing(ofHStack container: Widget, to spacing: Int) {}

public func createTextView(content: String, shouldWrap: Bool) -> Widget {
let label = Label(content)
label.width = Dim.fill()
return label
}

public func setContent(ofTextView textView: Widget, to content: String) {
let label = textView as! Label

Check failure on line 65 in Sources/CursesBackend/CursesBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Force Cast Violation: Force casts should be avoided (force_cast)
label.text = content
}

public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) {}

public func createButton(label: String, action: @escaping () -> Void) -> Widget {
let button = TermKit.Button(label, clicked: action)
button.height = Dim.sized(1)
return button
}

public func setLabel(ofButton button: Widget, to label: String) {
(button as! TermKit.Button).text = label

Check failure on line 78 in Sources/CursesBackend/CursesBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Force Cast Violation: Force casts should be avoided (force_cast)
}

public func setAction(ofButton button: Widget, to action: @escaping () -> Void) {
(button as! TermKit.Button).clicked = { _ in

Check failure on line 82 in Sources/CursesBackend/CursesBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Force Cast Violation: Force casts should be avoided (force_cast)
action()
}
}

// TODO: Properly implement padding container. Perhaps use a conversion factor to

Check warning on line 87 in Sources/CursesBackend/CursesBackend.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Todo Violation: TODOs should be resolved (Properly implement padding con...) (todo)
// convert the pixel values to 'characters' of padding
public func createPaddingContainer(for child: Widget) -> Widget {
return child
}

public func getChild(ofPaddingContainer container: Widget) -> Widget {
return container
}

public func setPadding(
ofPaddingContainer container: Widget,
top: Int,
bottom: Int,
leading: Int,
trailing: Int
) {}
}

class RootView: TermKit.View {
override func processKey(event: KeyEvent) -> Bool {
if super.processKey(event: event) {
return true
}

switch event.key {
case .controlC, .esc:
Application.requestStop()
return true
default:
return false
}
}
}

0 comments on commit cdb29a4

Please sign in to comment.