Skip to content

Commit 7e334ed

Browse files
committed
Introduce ToggleStyle.checkbox (no UIKitBackend impl yet)
1 parent e0569bd commit 7e334ed

File tree

14 files changed

+519
-9
lines changed

14 files changed

+519
-9
lines changed

Examples/Sources/ControlsExample/ControlsApp.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ struct ControlsApp: App {
1111
@State var count = 0
1212
@State var exampleButtonState = false
1313
@State var exampleSwitchState = false
14+
@State var exampleCheckboxState = false
1415
@State var sliderValue = 5.0
1516
@State var text = ""
1617
@State var flavor: String? = nil
@@ -44,6 +45,13 @@ struct ControlsApp: App {
4445
Text("Currently enabled: \(exampleSwitchState)")
4546
}
4647

48+
VStack {
49+
Text("Checkbox")
50+
Toggle("Toggle me:", active: $exampleCheckboxState)
51+
.toggleStyle(.checkbox)
52+
Text("Currently enabled: \(exampleCheckboxState)")
53+
}
54+
4755
VStack {
4856
Text("Slider")
4957
Slider($sliderValue, minimum: 0, maximum: 10)

Sources/AppKitBackend/AppKitBackend.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,28 @@ public final class AppKitBackend: AppBackend {
572572
toggle.state = state ? .on : .off
573573
}
574574

575+
public func createCheckbox() -> Widget {
576+
NSButton(checkboxWithTitle: "", target: nil, action: nil)
577+
}
578+
579+
public func updateCheckbox(
580+
_ checkbox: Widget,
581+
environment: EnvironmentValues,
582+
onChange: @escaping (Bool) -> Void
583+
) {
584+
let checkbox = checkbox as! NSButton
585+
checkbox.isEnabled = environment.isEnabled
586+
checkbox.onAction = { toggle in
587+
let checkbox = toggle as! NSButton
588+
onChange(checkbox.state == .on)
589+
}
590+
}
591+
592+
public func setState(ofCheckbox checkbox: Widget, to state: Bool) {
593+
let toggle = checkbox as! NSButton
594+
toggle.state = state ? .on : .off
595+
}
596+
575597
public func createSlider() -> Widget {
576598
return NSSlider()
577599
}
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import CGtk
2+
3+
/// Places a label next to an indicator.
4+
///
5+
/// <picture><source srcset="check-button-dark.png" media="(prefers-color-scheme: dark)"><img alt="Example GtkCheckButtons" src="check-button.png"></picture>
6+
///
7+
/// A `GtkCheckButton` is created by calling either [ctor@Gtk.CheckButton.new]
8+
/// or [ctor@Gtk.CheckButton.new_with_label].
9+
///
10+
/// The state of a `GtkCheckButton` can be set specifically using
11+
/// [method@Gtk.CheckButton.set_active], and retrieved using
12+
/// [method@Gtk.CheckButton.get_active].
13+
///
14+
/// # Inconsistent state
15+
///
16+
/// In addition to "on" and "off", check buttons can be an
17+
/// "in between" state that is neither on nor off. This can be used
18+
/// e.g. when the user has selected a range of elements (such as some
19+
/// text or spreadsheet cells) that are affected by a check button,
20+
/// and the current values in that range are inconsistent.
21+
///
22+
/// To set a `GtkCheckButton` to inconsistent state, use
23+
/// [method@Gtk.CheckButton.set_inconsistent].
24+
///
25+
/// # Grouping
26+
///
27+
/// Check buttons can be grouped together, to form mutually exclusive
28+
/// groups - only one of the buttons can be toggled at a time, and toggling
29+
/// another one will switch the currently toggled one off.
30+
///
31+
/// Grouped check buttons use a different indicator, and are commonly referred
32+
/// to as *radio buttons*.
33+
///
34+
/// <picture><source srcset="radio-button-dark.png" media="(prefers-color-scheme: dark)"><img alt="Example GtkRadioButtons" src="radio-button.png"></picture>
35+
///
36+
/// To add a `GtkCheckButton` to a group, use [method@Gtk.CheckButton.set_group].
37+
///
38+
/// When the code must keep track of the state of a group of radio buttons, it
39+
/// is recommended to keep track of such state through a stateful
40+
/// `GAction` with a target for each button. Using the `toggled` signals to keep
41+
/// track of the group changes and state is discouraged.
42+
///
43+
/// # Shortcuts and Gestures
44+
///
45+
/// `GtkCheckButton` supports the following keyboard shortcuts:
46+
///
47+
/// - <kbd>␣</kbd> or <kbd>Enter</kbd> activates the button.
48+
///
49+
/// # CSS nodes
50+
///
51+
/// ```
52+
/// checkbutton[.text-button][.grouped]
53+
/// ├── check
54+
/// ╰── [label]
55+
/// ```
56+
///
57+
/// A `GtkCheckButton` has a main node with name checkbutton. If the
58+
/// [property@Gtk.CheckButton:label] or [property@Gtk.CheckButton:child]
59+
/// properties are set, it contains a child widget. The indicator node
60+
/// is named check when no group is set, and radio if the checkbutton
61+
/// is grouped together with other checkbuttons.
62+
///
63+
/// # Accessibility
64+
///
65+
/// `GtkCheckButton` uses the [enum@Gtk.AccessibleRole.checkbox] role.
66+
open class CheckButton: Widget, Actionable {
67+
/// Creates a new `GtkCheckButton`.
68+
public convenience init() {
69+
self.init(
70+
gtk_check_button_new()
71+
)
72+
}
73+
74+
/// Creates a new `GtkCheckButton` with the given text.
75+
public convenience init(label: String) {
76+
self.init(
77+
gtk_check_button_new_with_label(label)
78+
)
79+
}
80+
81+
/// Creates a new `GtkCheckButton` with the given text and a mnemonic.
82+
public convenience init(mnemonic label: String) {
83+
self.init(
84+
gtk_check_button_new_with_mnemonic(label)
85+
)
86+
}
87+
88+
override func didMoveToParent() {
89+
super.didMoveToParent()
90+
91+
addSignal(name: "activate") { [weak self] () in
92+
guard let self = self else { return }
93+
self.activate?(self)
94+
}
95+
96+
addSignal(name: "toggled") { [weak self] () in
97+
guard let self = self else { return }
98+
self.toggled?(self)
99+
}
100+
101+
let handler2:
102+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
103+
{ _, value1, data in
104+
SignalBox1<OpaquePointer>.run(data, value1)
105+
}
106+
107+
addSignal(name: "notify::active", handler: gCallback(handler2)) {
108+
[weak self] (param0: OpaquePointer) in
109+
guard let self = self else { return }
110+
self.notifyActive?(self, param0)
111+
}
112+
113+
let handler3:
114+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
115+
{ _, value1, data in
116+
SignalBox1<OpaquePointer>.run(data, value1)
117+
}
118+
119+
addSignal(name: "notify::child", handler: gCallback(handler3)) {
120+
[weak self] (param0: OpaquePointer) in
121+
guard let self = self else { return }
122+
self.notifyChild?(self, param0)
123+
}
124+
125+
let handler4:
126+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
127+
{ _, value1, data in
128+
SignalBox1<OpaquePointer>.run(data, value1)
129+
}
130+
131+
addSignal(name: "notify::group", handler: gCallback(handler4)) {
132+
[weak self] (param0: OpaquePointer) in
133+
guard let self = self else { return }
134+
self.notifyGroup?(self, param0)
135+
}
136+
137+
let handler5:
138+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
139+
{ _, value1, data in
140+
SignalBox1<OpaquePointer>.run(data, value1)
141+
}
142+
143+
addSignal(name: "notify::inconsistent", handler: gCallback(handler5)) {
144+
[weak self] (param0: OpaquePointer) in
145+
guard let self = self else { return }
146+
self.notifyInconsistent?(self, param0)
147+
}
148+
149+
let handler6:
150+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
151+
{ _, value1, data in
152+
SignalBox1<OpaquePointer>.run(data, value1)
153+
}
154+
155+
addSignal(name: "notify::label", handler: gCallback(handler6)) {
156+
[weak self] (param0: OpaquePointer) in
157+
guard let self = self else { return }
158+
self.notifyLabel?(self, param0)
159+
}
160+
161+
let handler7:
162+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
163+
{ _, value1, data in
164+
SignalBox1<OpaquePointer>.run(data, value1)
165+
}
166+
167+
addSignal(name: "notify::use-underline", handler: gCallback(handler7)) {
168+
[weak self] (param0: OpaquePointer) in
169+
guard let self = self else { return }
170+
self.notifyUseUnderline?(self, param0)
171+
}
172+
173+
let handler8:
174+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
175+
{ _, value1, data in
176+
SignalBox1<OpaquePointer>.run(data, value1)
177+
}
178+
179+
addSignal(name: "notify::action-name", handler: gCallback(handler8)) {
180+
[weak self] (param0: OpaquePointer) in
181+
guard let self = self else { return }
182+
self.notifyActionName?(self, param0)
183+
}
184+
185+
let handler9:
186+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
187+
{ _, value1, data in
188+
SignalBox1<OpaquePointer>.run(data, value1)
189+
}
190+
191+
addSignal(name: "notify::action-target", handler: gCallback(handler9)) {
192+
[weak self] (param0: OpaquePointer) in
193+
guard let self = self else { return }
194+
self.notifyActionTarget?(self, param0)
195+
}
196+
}
197+
198+
/// If the check button is active.
199+
///
200+
/// Setting `active` to %TRUE will add the `:checked:` state to both
201+
/// the check button and the indicator CSS node.
202+
@GObjectProperty(named: "active") public var active: Bool
203+
204+
/// If the check button is in an “in between” state.
205+
///
206+
/// The inconsistent state only affects visual appearance,
207+
/// not the semantics of the button.
208+
@GObjectProperty(named: "inconsistent") public var inconsistent: Bool
209+
210+
/// Text of the label inside the check button, if it contains a label widget.
211+
@GObjectProperty(named: "label") public var label: String?
212+
213+
/// If set, an underline in the text indicates that the following
214+
/// character is to be used as mnemonic.
215+
@GObjectProperty(named: "use-underline") public var useUnderline: Bool
216+
217+
/// The name of the action with which this widget should be associated.
218+
@GObjectProperty(named: "action-name") public var actionName: String?
219+
220+
/// Emitted to when the check button is activated.
221+
///
222+
/// The `::activate` signal on `GtkCheckButton` is an action signal and
223+
/// emitting it causes the button to animate press then release.
224+
///
225+
/// Applications should never connect to this signal, but use the
226+
/// [signal@Gtk.CheckButton::toggled] signal.
227+
///
228+
/// The default bindings for this signal are all forms of the
229+
/// <kbd>␣</kbd> and <kbd>Enter</kbd> keys.
230+
public var activate: ((CheckButton) -> Void)?
231+
232+
/// Emitted when the buttons's [property@Gtk.CheckButton:active]
233+
/// property changes.
234+
public var toggled: ((CheckButton) -> Void)?
235+
236+
public var notifyActive: ((CheckButton, OpaquePointer) -> Void)?
237+
238+
public var notifyChild: ((CheckButton, OpaquePointer) -> Void)?
239+
240+
public var notifyGroup: ((CheckButton, OpaquePointer) -> Void)?
241+
242+
public var notifyInconsistent: ((CheckButton, OpaquePointer) -> Void)?
243+
244+
public var notifyLabel: ((CheckButton, OpaquePointer) -> Void)?
245+
246+
public var notifyUseUnderline: ((CheckButton, OpaquePointer) -> Void)?
247+
248+
public var notifyActionName: ((CheckButton, OpaquePointer) -> Void)?
249+
250+
public var notifyActionTarget: ((CheckButton, OpaquePointer) -> Void)?
251+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import CGtk3
2+
3+
/// A #GtkCheckButton places a discrete #GtkToggleButton next to a widget,
4+
/// (usually a #GtkLabel). See the section on #GtkToggleButton widgets for
5+
/// more information about toggle/check buttons.
6+
///
7+
/// The important signal ( #GtkToggleButton::toggled ) is also inherited from
8+
/// #GtkToggleButton.
9+
///
10+
/// # CSS nodes
11+
///
12+
/// |[<!-- language="plain" -->
13+
/// checkbutton
14+
/// ├── check
15+
/// ╰── <child>
16+
/// ]|
17+
///
18+
/// A GtkCheckButton with indicator (see gtk_toggle_button_set_mode()) has a
19+
/// main CSS node with name checkbutton and a subnode with name check.
20+
///
21+
/// |[<!-- language="plain" -->
22+
/// button.check
23+
/// ├── check
24+
/// ╰── <child>
25+
/// ]|
26+
///
27+
/// A GtkCheckButton without indicator changes the name of its main node
28+
/// to button and adds a .check style class to it. The subnode is invisible
29+
/// in this case.
30+
open class CheckButton: ToggleButton {
31+
/// Creates a new #GtkCheckButton.
32+
public convenience init() {
33+
self.init(
34+
gtk_check_button_new()
35+
)
36+
}
37+
38+
/// Creates a new #GtkCheckButton with a #GtkLabel to the right of it.
39+
public convenience init(label: String) {
40+
self.init(
41+
gtk_check_button_new_with_label(label)
42+
)
43+
}
44+
45+
/// Creates a new #GtkCheckButton containing a label. The label
46+
/// will be created using gtk_label_new_with_mnemonic(), so underscores
47+
/// in @label indicate the mnemonic for the check button.
48+
public convenience init(mnemonic label: String) {
49+
self.init(
50+
gtk_check_button_new_with_mnemonic(label)
51+
)
52+
}
53+
54+
}

Sources/Gtk3/Widgets/ToggleButton.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import CGtk3
22

3-
public class ToggleButton: Button {
3+
open class ToggleButton: Button {
44
public convenience init() {
55
self.init(gtk_toggle_button_new())
66
}
@@ -20,9 +20,23 @@ public class ToggleButton: Button {
2020
guard let self = self else { return }
2121
self.toggled?(self)
2222
}
23+
24+
let handler:
25+
@convention(c) (UnsafeMutableRawPointer, OpaquePointer, UnsafeMutableRawPointer) -> Void =
26+
{ _, value1, data in
27+
SignalBox1<OpaquePointer>.run(data, value1)
28+
}
29+
30+
addSignal(name: "notify::active", handler: gCallback(handler)) {
31+
[weak self] (param0: OpaquePointer) in
32+
guard let self = self else { return }
33+
self.notifyActive?(self, param0)
34+
}
2335
}
2436

25-
@GObjectProperty(named: "active") public var active: Bool
37+
@GObjectProperty(named: "active") open var active: Bool
38+
39+
open var toggled: ((ToggleButton) -> Void)?
2640

27-
public var toggled: ((ToggleButton) -> Void)?
41+
public var notifyActive: ((ToggleButton, OpaquePointer) -> Void)?
2842
}

0 commit comments

Comments
 (0)