Skip to content

Commit 6496921

Browse files
committed
Added Gtk support
Entry and DropDown are behaving weirdly (can't disable focusability completely) Focus Chain apparently doesn't like too many skipped targets
1 parent 295c05f commit 6496921

File tree

6 files changed

+168
-2
lines changed

6 files changed

+168
-2
lines changed

Examples/Bundler.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,8 @@ version = '0.1.0'
6464
identifier = 'dev.swiftcrossui.HoverExample'
6565
product = 'HoverExample'
6666
version = '0.1.0'
67+
68+
[apps.ControlFocusabilityTest]
69+
identifier = 'dev.swiftcrossui.ControlFocusabilityTest'
70+
product = 'ControlFocusabilityTest'
71+
version = '0.1.0'

Examples/Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ let package = Package(
7676
.executableTarget(
7777
name: "HoverExample",
7878
dependencies: exampleDependencies
79+
),
80+
.executableTarget(
81+
name: "ControlFocusabilityTest",
82+
dependencies: exampleDependencies
7983
)
8084
]
8185
)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import DefaultBackend
2+
import SwiftCrossUI
3+
4+
#if canImport(SwiftBundlerRuntime)
5+
import SwiftBundlerRuntime
6+
#endif
7+
8+
@main
9+
@HotReloadable
10+
struct ControlsFocusabilityApp: App {
11+
@State var count = 0
12+
@State var exampleButtonState = false
13+
@State var exampleSwitchState = false
14+
@State var exampleCheckboxState = false
15+
@State var sliderValue = 5.0
16+
@State var text = ""
17+
@State var flavor: String? = nil
18+
@State var enabled = true
19+
@State var isButtonFocusable = true
20+
@State var isToggleButtonFocusable = true
21+
@State var isToggleSwitchFocusable = true
22+
@State var isCheckboxFocusable = true
23+
@State var isSliderFocusable = true
24+
@State var isTextFieldFocusable = true
25+
@State var isPickerFocusable = true
26+
27+
var body: some Scene {
28+
WindowGroup("ControlsApp") {
29+
#hotReloadable {
30+
ScrollView {
31+
VStack(spacing: 30) {
32+
HStack {
33+
VStack {
34+
Text("Button")
35+
Button("Click me!") {
36+
count += 1
37+
}
38+
.focusable(isButtonFocusable)
39+
Text("Count: \(count)")
40+
}
41+
Toggle("focusable", active: $isButtonFocusable)
42+
.focusable(false)
43+
}
44+
.padding(.bottom, 20)
45+
46+
#if !canImport(UIKitBackend)
47+
HStack {
48+
VStack {
49+
Text("Toggle button")
50+
Toggle("Toggle me!", active: $exampleButtonState)
51+
.toggleStyle(.button)
52+
.focusable(isToggleButtonFocusable)
53+
Text("Currently enabled: \(exampleButtonState)")
54+
}
55+
Toggle("focusable", active: $isToggleButtonFocusable)
56+
.focusable(false)
57+
}
58+
.padding(.bottom, 20)
59+
#endif
60+
61+
HStack {
62+
VStack {
63+
Text("Toggle switch")
64+
Toggle("Toggle me:", active: $exampleSwitchState)
65+
.toggleStyle(.switch)
66+
.focusable(isToggleSwitchFocusable)
67+
Text("Currently enabled: \(exampleSwitchState)")
68+
}
69+
Toggle("focusable", active: $isToggleSwitchFocusable)
70+
.focusable(false)
71+
}
72+
73+
#if !canImport(UIKitBackend)
74+
HStack {
75+
VStack {
76+
Text("Checkbox")
77+
Toggle("Toggle me:", active: $exampleCheckboxState)
78+
.toggleStyle(.checkbox)
79+
.focusable(isCheckboxFocusable)
80+
Text("Currently enabled: \(exampleCheckboxState)")
81+
}
82+
Toggle("focusable", active: $isCheckboxFocusable)
83+
.focusable(false)
84+
}
85+
#endif
86+
87+
HStack {
88+
VStack {
89+
Text("Slider")
90+
Slider($sliderValue, minimum: 0, maximum: 10)
91+
.frame(maxWidth: 200)
92+
.focusable(isSliderFocusable)
93+
Text("Value: \(String(format: "%.02f", sliderValue))")
94+
}
95+
Toggle("focusable", active: $isSliderFocusable)
96+
.focusable(false)
97+
}
98+
HStack {
99+
VStack {
100+
Text("Text field")
101+
TextField("Text field", text: $text)
102+
.focusable(isTextFieldFocusable)
103+
Text("Value: \(text)")
104+
}
105+
Toggle("focusable", active: $isTextFieldFocusable)
106+
.focusable(false)
107+
}
108+
109+
HStack {
110+
VStack {
111+
Text("Drop down")
112+
HStack {
113+
Text("Flavor: ")
114+
Picker(of: ["Vanilla", "Chocolate", "Strawberry"], selection: $flavor)
115+
.focusable(isPickerFocusable)
116+
}
117+
Text("You chose: \(flavor ?? "Nothing yet!")")
118+
}
119+
Toggle("focusable", active: $isPickerFocusable)
120+
.focusable(false)
121+
}
122+
}
123+
.padding()
124+
.disabled(!enabled)
125+
126+
Toggle(enabled ? "Disable all" : "Enable all", active: $enabled)
127+
.padding()
128+
.focusable(false)
129+
}
130+
.frame(minHeight: 600)
131+
}
132+
133+
}.defaultSize(width: 400, height: 600)
134+
}
135+
}

Examples/Sources/ControlsExample/ControlsApp.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ struct ControlsApp: App {
7979
}
8080
Text("You chose: \(flavor ?? "Nothing yet!")")
8181
}
82-
}.padding().disabled(!enabled)
82+
}
83+
.padding()
84+
.disabled(!enabled)
8385
.focusable(isFocusable)
8486

8587
Toggle(enabled ? "Disable all" : "Enable all", active: $enabled)
@@ -93,6 +95,7 @@ struct ControlsApp: App {
9395
.padding()
9496
.focusable(isFocusable)
9597
}
98+
.frame(minHeight: 600)
9699
}
97100

98101
}.defaultSize(width: 400, height: 600)

Sources/Gtk/Widgets/Widget.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ open class Widget: GObject {
131131

132132
/// Set to -1 for no min height request
133133
@GObjectProperty(named: "height-request") public var minHeight: Int
134+
135+
/// Wether a Widget is focusable
136+
public var focusable: Bool {
137+
get {
138+
let result = gtk_widget_get_focusable(widgetPointer) != 0
139+
return result
140+
}
141+
set {
142+
gtk_widget_set_focusable(widgetPointer, newValue.toGBoolean())
143+
gtk_widget_set_focus_on_click(widgetPointer, newValue.toGBoolean())
144+
}
145+
}
134146

135147
/// Sets the name of the Gtk view for useful debugging in inspector (Ctrl+Shift+D)
136148
public func tag(as tag: String) {

Sources/GtkBackend/GtkBackend.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ public final class GtkBackend: AppBackend {
753753
// TODO: Update button label color using environment
754754
let button = button as! Gtk.Button
755755
button.sensitive = environment.isEnabled
756+
button.focusable = environment.isFocusable
756757
button.label = label
757758
button.clicked = { _ in action() }
758759
button.css.clear()
@@ -775,6 +776,7 @@ public final class GtkBackend: AppBackend {
775776
toggle.toggled = { widget in
776777
onChange(widget.active)
777778
}
779+
toggle.focusable = environment.isFocusable
778780
toggle.css.clear()
779781
// This is a control, but we set isControl to false anyway because isControl overrides
780782
// the button background and makes the on and off states of the toggle look identical.
@@ -796,6 +798,7 @@ public final class GtkBackend: AppBackend {
796798
) {
797799
let switchWidget = switchWidget as! Gtk.Switch
798800
switchWidget.sensitive = environment.isEnabled
801+
switchWidget.focusable = environment.isFocusable
799802
switchWidget.notifyActive = { widget, _ in
800803
onChange(widget.active)
801804
}
@@ -816,6 +819,7 @@ public final class GtkBackend: AppBackend {
816819
) {
817820
let checkboxWidget = checkboxWidget as! Gtk.CheckButton
818821
checkboxWidget.sensitive = environment.isEnabled
822+
checkboxWidget.focusable = environment.isFocusable
819823
checkboxWidget.notifyActive = { widget, _ in
820824
onChange(widget.active)
821825
}
@@ -844,6 +848,7 @@ public final class GtkBackend: AppBackend {
844848
slider.minimum = minimum
845849
slider.maximum = maximum
846850
slider.digits = decimalPlaces
851+
slider.focusable = environment.isFocusable
847852
slider.valueChanged = { widget in
848853
onChange(widget.value)
849854
}
@@ -873,7 +878,8 @@ public final class GtkBackend: AppBackend {
873878
textField.activate = { _ in
874879
onSubmit()
875880
}
876-
881+
textField.focusable = environment.isFocusable
882+
877883
textField.css.clear()
878884
textField.css.set(properties: Self.cssProperties(for: environment, isControl: true))
879885
}
@@ -929,6 +935,7 @@ public final class GtkBackend: AppBackend {
929935
) {
930936
let picker = picker as! DropDown
931937
picker.sensitive = environment.isEnabled
938+
picker.focusable = environment.isFocusable
932939

933940
// Check whether the options need to be updated or not (avoiding unnecessary updates is
934941
// required to prevent an infinite loop caused by the onChange handler)

0 commit comments

Comments
 (0)