Skip to content

Commit 66cb50e

Browse files
committed
Render the gallery catalog in the desktop rig
Ports the gallery screens against the core and drives the desktop window from ContentView, with a main-thread scheduler for async state.
1 parent 5fba084 commit 66cb50e

14 files changed

Lines changed: 573 additions & 14 deletions

Sources/SwiftUIDesktopDemo/DesktopDemo.swift

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,11 @@
66
// pushed through the bridge into the rig's Compose window.
77
//
88

9+
import Foundation
910
import AndroidSwiftUICore
1011
import AndroidSwiftUIBridge
1112
import SwiftJava
1213

13-
struct CounterDemo: View {
14-
15-
@State private var count = 0
16-
17-
var body: some View {
18-
VStack(spacing: 12) {
19-
Text("Count: \(count)")
20-
Button("Increment") { count += 1 }
21-
Toggle("Feature flag", isOn: .constant(true))
22-
}
23-
}
24-
}
25-
2614
@JavaClass("com.pureswift.swiftui.desktop.SwiftRuntime")
2715
open class SwiftRuntime: JavaObject {
2816
}
@@ -33,7 +21,11 @@ extension SwiftRuntime {
3321
@JavaMethod
3422
func start(_ store: TreeStore?) {
3523
guard let store else { return }
36-
let runtime = BridgeRuntime(root: CounterDemo(), store: store)
24+
// marshal re-renders onto the main thread; async state (e.g. a List
25+
// refresh Task) writes off-thread and JNI object creation must run here
26+
let runtime = BridgeRuntime(root: ContentView(), store: store) { block in
27+
DispatchQueue.main.async { block() }
28+
}
3729
runtime.start()
3830
}
3931
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import AndroidSwiftUICore
2+
3+
/// Button initializers and actions.
4+
struct ButtonScreen: View {
5+
6+
@State
7+
private var tapCount = 0
8+
9+
var body: some View {
10+
VStack(spacing: 16) {
11+
Text(verbatim: "Taps: \(tapCount)")
12+
Button("Title initializer") {
13+
tapCount += 1
14+
}
15+
Button(action: { tapCount += 1 }) {
16+
Text("Label closure initializer")
17+
}
18+
Button(action: { tapCount += 1 }) {
19+
Image("globe")
20+
}
21+
Button("Reset") {
22+
tapCount = 0
23+
}
24+
}
25+
}
26+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import AndroidSwiftUICore
2+
3+
/// Gallery of playground screens, navigated with a `NavigationStack`. Each row
4+
/// pushes a feature screen; `List`-based navigation returns with lazy
5+
/// containers in the next step.
6+
struct ContentView: View {
7+
8+
var body: some View {
9+
NavigationStack {
10+
ScrollView {
11+
VStack(spacing: 0) {
12+
NavigationLink("Text", destination: TextScreen())
13+
NavigationLink("Buttons", destination: ButtonScreen())
14+
NavigationLink("Stacks", destination: StacksScreen())
15+
NavigationLink("List", destination: ListScreen())
16+
NavigationLink("Grid", destination: GridScreen())
17+
NavigationLink("State", destination: StateScreen())
18+
NavigationLink("Controls", destination: ControlsScreen())
19+
NavigationLink("Modifiers", destination: ModifierScreen())
20+
NavigationLink("Observation", destination: ObservationScreen())
21+
NavigationLink("Navigation", destination: NavigationScreen())
22+
NavigationLink("Sheets", destination: SheetScreen())
23+
NavigationLink("Tabs", destination: TabScreen())
24+
}
25+
}
26+
.navigationTitle("Gallery")
27+
}
28+
}
29+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import AndroidSwiftUICore
2+
3+
/// Form controls and their state bindings.
4+
struct ControlsScreen: View {
5+
6+
@State
7+
private var isOn = false
8+
9+
@State
10+
private var progress = 0.25
11+
12+
@State
13+
private var sliderValue = 0.5
14+
15+
@State
16+
private var name = ""
17+
18+
@State
19+
private var fruit = "Apple"
20+
21+
var body: some View {
22+
ScrollView {
23+
VStack(spacing: 24) {
24+
Text("Toggle")
25+
Toggle("Enabled", isOn: $isOn)
26+
Text(isOn ? "Toggle is on" : "Toggle is off")
27+
Divider()
28+
Text("Indeterminate progress")
29+
ProgressView()
30+
Divider()
31+
Text("Determinate progress")
32+
ProgressView(value: progress)
33+
Text("\(Int(progress * 100))%")
34+
Button("Advance") {
35+
progress = progress >= 1 ? 0 : progress + 0.25
36+
}
37+
Divider()
38+
Text("Slider")
39+
Slider(value: $sliderValue, in: 0...1)
40+
Text("Value: \(Int(sliderValue * 100))%")
41+
Divider()
42+
Text("Text field")
43+
TextField("Name", text: $name)
44+
Text(name.isEmpty ? "Nothing typed yet" : "Hello, \(name)")
45+
Divider()
46+
Text("Picker")
47+
Picker("Fruit", selection: $fruit) {
48+
Text("Apple").tag("Apple")
49+
Text("Banana").tag("Banana")
50+
Text("Cherry").tag("Cherry")
51+
}
52+
Text("Selected: \(fruit)")
53+
}
54+
}
55+
}
56+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import AndroidSwiftUICore
2+
3+
/// Lazy grids.
4+
struct GridScreen: View {
5+
6+
var body: some View {
7+
VStack(spacing: 16) {
8+
Text("Three fixed columns")
9+
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]) {
10+
ForEach(1...9, id: \.self) { number in
11+
Text("\(number)")
12+
.padding()
13+
.background(Color.blue)
14+
}
15+
}
16+
Divider()
17+
Text("Adaptive columns")
18+
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))]) {
19+
ForEach(1...8, id: \.self) { number in
20+
Text("Item \(number)")
21+
.padding()
22+
.background(Color.orange)
23+
}
24+
}
25+
Divider()
26+
Text("Two rows, horizontal")
27+
LazyHGrid(rows: [GridItem(.flexible()), GridItem(.flexible())]) {
28+
ForEach(1...8, id: \.self) { number in
29+
Text("Cell \(number)")
30+
.padding()
31+
.background(Color.green)
32+
}
33+
}
34+
.frame(height: 160)
35+
}
36+
}
37+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import AndroidSwiftUICore
2+
3+
/// A lazy list with pull to refresh. Rows are evaluated on demand, so the
4+
/// large-list variant scrolls without materializing every row up front.
5+
struct ListScreen: View {
6+
7+
@State
8+
private var items = (1...30).map(ListItem.init)
9+
10+
var body: some View {
11+
List(items) { item in
12+
Text(item.title)
13+
}
14+
.refreshable {
15+
await addItem()
16+
}
17+
}
18+
19+
private func addItem() async {
20+
try? await Task.sleep(for: .seconds(1))
21+
items.insert(ListItem(id: items.count + 1), at: 0)
22+
}
23+
}
24+
25+
struct ListItem: Identifiable {
26+
27+
let id: Int
28+
29+
var title: String {
30+
"Row \(id)"
31+
}
32+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import AndroidSwiftUICore
2+
3+
/// Layout and effect modifiers.
4+
struct ModifierScreen: View {
5+
6+
var body: some View {
7+
ScrollView {
8+
VStack(spacing: 24) {
9+
ModifierSection(title: "Padding") {
10+
Text("Padded")
11+
.padding()
12+
.background(Color.blue)
13+
}
14+
ModifierSection(title: "Frame") {
15+
Text("Fixed 200x60")
16+
.frame(width: 200, height: 60)
17+
.background(Color.green)
18+
}
19+
ModifierSection(title: "Background") {
20+
Text("Colored background")
21+
.padding()
22+
.background(Color.orange)
23+
}
24+
ModifierSection(title: "Clip + corner radius") {
25+
Text("Rounded")
26+
.padding()
27+
.background(Color.purple)
28+
.cornerRadius(16)
29+
}
30+
ModifierSection(title: "Offset") {
31+
Text("Shifted")
32+
.offset(x: 30, y: 0)
33+
.background(Color.red)
34+
}
35+
ModifierSection(title: "Rotation") {
36+
Text("Rotated")
37+
.background(Color.yellow)
38+
.rotationEffect(.degrees(15))
39+
}
40+
ModifierSection(title: "Scale") {
41+
Text("Scaled")
42+
.background(Color.pink)
43+
.scaleEffect(1.5)
44+
}
45+
}
46+
}
47+
}
48+
}
49+
50+
struct ModifierSection<Content: View>: View {
51+
52+
let title: String
53+
54+
let content: Content
55+
56+
init(title: String, @ViewBuilder content: () -> Content) {
57+
self.title = title
58+
self.content = content()
59+
}
60+
61+
var body: some View {
62+
VStack(spacing: 8) {
63+
Text(title)
64+
content
65+
Divider()
66+
}
67+
}
68+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import AndroidSwiftUICore
2+
3+
/// Classic and value-based navigation, plus the dismiss environment action.
4+
struct NavigationScreen: View {
5+
6+
@Environment(\.dismiss)
7+
private var dismiss
8+
9+
@State
10+
private var showsAlert = false
11+
12+
var body: some View {
13+
VStack(spacing: 16) {
14+
Button("Show alert") { showsAlert = true }
15+
Text("Value-based navigation")
16+
NavigationLink("Push value 1", value: 1)
17+
NavigationLink(value: 2) {
18+
Text("Push value 2")
19+
}
20+
Divider()
21+
Text("Classic navigation")
22+
NavigationLink("Push destination view", destination: ClassicDestination())
23+
Divider()
24+
Button("Pop with dismiss") {
25+
dismiss()
26+
}
27+
}
28+
.navigationDestination(for: Int.self) { value in
29+
ValueDestination(value: value)
30+
}
31+
.alert("An alert", isPresented: $showsAlert, message: "This is an alert message.", buttons: [
32+
AlertButton("Cancel", role: .cancel),
33+
AlertButton("OK"),
34+
])
35+
.navigationTitle("Navigation")
36+
}
37+
}
38+
39+
struct ValueDestination: View {
40+
41+
let value: Int
42+
43+
@Environment(\.dismiss)
44+
private var dismiss
45+
46+
var body: some View {
47+
VStack(spacing: 16) {
48+
Text(verbatim: "Destination for value \(value)")
49+
if value < 3 {
50+
NavigationLink("Push value \(value + 1)", value: value + 1)
51+
}
52+
Button("Pop with dismiss") {
53+
dismiss()
54+
}
55+
}
56+
.navigationTitle("Value \(value)")
57+
}
58+
}
59+
60+
struct ClassicDestination: View {
61+
62+
@Environment(\.dismiss)
63+
private var dismiss
64+
65+
var body: some View {
66+
VStack(spacing: 16) {
67+
Text("Classic destination")
68+
Button("Pop with dismiss") {
69+
dismiss()
70+
}
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)