Skip to content

Commit cdb29a4

Browse files
committed
Create experimental interactive terminal backend (using TermKit)
1 parent 2a97750 commit cdb29a4

File tree

5 files changed

+196
-2
lines changed

5 files changed

+196
-2
lines changed

.github/workflows/swift-linux.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ jobs:
1616
sudo apt update && \
1717
sudo apt install -y libgtk-4-dev clang
1818
- name: Build
19-
run: swift build -v
19+
run: |
20+
swift build SwiftCrossUI && \
21+
swift build GtkBackend && \
22+
swift build CounterExample && \
23+
swift build RandomNumberGeneratorExample && \
24+
swift build WindowPropertiesExample && \
25+
swift build GreetingGeneratorExample && \
26+
swift build FileViewerExample && \
27+
swift build NavigationExample && \
28+
swift build SplitExample && \
29+
swift build GtkCodeGen && \
30+
swift build GtkExample
2031
- name: Test
2132
run: swift test

.github/workflows/swift-macos.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ jobs:
1717
- name: Patch libffi
1818
run: sed -i '' 's/-I..includedir.//g' /usr/local/Homebrew/Library/Homebrew/os/mac/pkgconfig/13/libffi.pc
1919
- name: Build
20-
run: swift build -v
20+
run: |
21+
swift build SwiftCrossUI && \
22+
swift build GtkBackend && \
23+
swift build CounterExample && \
24+
swift build RandomNumberGeneratorExample && \
25+
swift build WindowPropertiesExample && \
26+
swift build GreetingGeneratorExample && \
27+
swift build FileViewerExample && \
28+
swift build NavigationExample && \
29+
swift build SplitExample && \
30+
swift build GtkCodeGen && \
31+
swift build GtkExample
2132
- name: Test
2233
run: swift test

Package.resolved

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,22 @@ if checkSDL2Installed() {
141141
)
142142
}
143143

144+
// TODO: Conditionally include TermKit backend
145+
conditionalTargets.append(
146+
.target(
147+
name: "CursesBackend",
148+
dependencies: ["SwiftCrossUI", "TermKit"]
149+
)
150+
)
151+
backendTargets.append("CursesBackend")
152+
exampleDependencies.append("CursesBackend")
153+
dependencies.append(
154+
.package(
155+
url: "https://github.com/migueldeicaza/TermKit",
156+
revision: "3bce85d1bafbbb0336b3b7b7e905c35754cb9adf"
157+
)
158+
)
159+
144160
let package = Package(
145161
name: "swift-cross-ui",
146162
platforms: [.macOS(.v10_15)],
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import Foundation
2+
import SwiftCrossUI
3+
import TermKit
4+
5+
public struct CursesBackend: AppBackend {
6+
public typealias Widget = TermKit.View
7+
8+
public init(appIdentifier: String) {}
9+
10+
public func run<AppRoot: App>(
11+
_ app: AppRoot,
12+
_ setViewGraph: @escaping (ViewGraph<AppRoot>) -> Void
13+
) where AppRoot.Backend == Self {
14+
let viewGraph = ViewGraph(for: app, backend: self)
15+
setViewGraph(viewGraph)
16+
17+
Application.prepare()
18+
let root = RootView()
19+
root.addSubview(viewGraph.rootNode.widget)
20+
Application.top.addSubview(root)
21+
Application.run()
22+
}
23+
24+
public func runInMainThread(action: @escaping () -> Void) {
25+
DispatchQueue.main.async {
26+
action()
27+
}
28+
}
29+
30+
public func show(_ widget: Widget) {
31+
widget.setNeedsDisplay()
32+
}
33+
34+
public func createVStack(spacing: Int) -> Widget {
35+
return View()
36+
}
37+
38+
public func addChild(_ child: Widget, toVStack container: Widget) {
39+
// TODO: Properly calculate layout
40+
child.y = Pos.at(container.subviews.count)
41+
container.addSubview(child)
42+
}
43+
44+
public func setSpacing(ofVStack container: Widget, to spacing: Int) {}
45+
46+
public func createHStack(spacing: Int) -> Widget {
47+
return View()
48+
}
49+
50+
public func addChild(_ child: Widget, toHStack container: Widget) {
51+
// TODO: Properly calculate layout
52+
child.y = Pos.at(container.subviews.count)
53+
container.addSubview(child)
54+
}
55+
56+
public func setSpacing(ofHStack container: Widget, to spacing: Int) {}
57+
58+
public func createTextView(content: String, shouldWrap: Bool) -> Widget {
59+
let label = Label(content)
60+
label.width = Dim.fill()
61+
return label
62+
}
63+
64+
public func setContent(ofTextView textView: Widget, to content: String) {
65+
let label = textView as! Label
66+
label.text = content
67+
}
68+
69+
public func setWrap(ofTextView textView: Widget, to shouldWrap: Bool) {}
70+
71+
public func createButton(label: String, action: @escaping () -> Void) -> Widget {
72+
let button = TermKit.Button(label, clicked: action)
73+
button.height = Dim.sized(1)
74+
return button
75+
}
76+
77+
public func setLabel(ofButton button: Widget, to label: String) {
78+
(button as! TermKit.Button).text = label
79+
}
80+
81+
public func setAction(ofButton button: Widget, to action: @escaping () -> Void) {
82+
(button as! TermKit.Button).clicked = { _ in
83+
action()
84+
}
85+
}
86+
87+
// TODO: Properly implement padding container. Perhaps use a conversion factor to
88+
// convert the pixel values to 'characters' of padding
89+
public func createPaddingContainer(for child: Widget) -> Widget {
90+
return child
91+
}
92+
93+
public func getChild(ofPaddingContainer container: Widget) -> Widget {
94+
return container
95+
}
96+
97+
public func setPadding(
98+
ofPaddingContainer container: Widget,
99+
top: Int,
100+
bottom: Int,
101+
leading: Int,
102+
trailing: Int
103+
) {}
104+
}
105+
106+
class RootView: TermKit.View {
107+
override func processKey(event: KeyEvent) -> Bool {
108+
if super.processKey(event: event) {
109+
return true
110+
}
111+
112+
switch event.key {
113+
case .controlC, .esc:
114+
Application.requestStop()
115+
return true
116+
default:
117+
return false
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)