Add support for toggle groups
Some checks are pending
Deploy Docs / publish (push) Waiting to run
SwiftLint / SwiftLint (push) Waiting to run

This commit is contained in:
david-swift 2025-11-03 22:28:28 +01:00
parent 1418d1333c
commit 90e8c78163
54 changed files with 403 additions and 50 deletions

View File

@ -2,7 +2,7 @@
// ActionRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// AspectFrame.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Avatar.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Banner.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Bin.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Box.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Button.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ButtonContent.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Carousel.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// CenterBox.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// CheckButton.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Clamp.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ComboRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// DropDown.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Entry.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// EntryRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ExpanderRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Fixed.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// FlowBox.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// HeaderBar.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Image.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Label.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// LevelBar.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// LinkButton.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ListBox.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Menu.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// NavigationView.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Overlay.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// OverlaySplitView.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// PasswordEntryRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Picture.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Popover.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// PreferencesGroup.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// PreferencesPage.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// PreferencesRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ProgressBar.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ScrolledWindow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// SearchBar.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// SearchEntry.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Separator.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// SpinRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// Spinner.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// SplitButton.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// StatusPage.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// SwitchRow.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ToastOverlay.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// ToggleButton.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -0,0 +1,190 @@
//
// ToggleGroup.swift
// Adwaita
//
// Created by auto-generation on 03.11.25.
//
import CAdw
import LevenshteinTransformations
/// A group of exclusive toggles.
///
///
///
/// `AdwToggleGroup` presents a set of exclusive toggles, represented as
/// `Toggle` objects. Each toggle can display an icon, a label, an icon
/// and a label, or a custom child.
///
/// Toggles are indexed by their position, with the first toggle being equivalent
/// to 0, and so on. Use the ``active(_:)`` to get that position.
///
/// Toggles can also have optional names, set via the ``name(_:)``
/// property. The name of the active toggle can be accessed via the
/// ``activeName(_:)`` property.
///
/// `AdwToggle` objects can be retrieved via their index or name, using
/// `ToggleGroup.get_toggle` or `ToggleGroup.get_toggle_by_name`
/// respectively. `AdwToggleGroup` also provides a `Gtk.SelectionModel` of
/// its toggles via the ``toggles(_:)`` property.
///
/// `AdwToggleGroup` is orientable, and the toggles can be displayed horizontally
/// or vertically. This is mostly useful for icon-only toggles.
///
/// Use the ``homogeneous(_:)`` property to make the toggles take
/// the same size, and the ``canShrink(_:)`` to control whether
/// the toggles can ellipsize.
///
/// Example of an `AdwToggleGroup` UI definition:
///
/// ```xml
/// <object class="AdwToggleGroup"><property name="active-name">picture</property><child><object class="AdwToggle"><property name="icon-name">camera-photo-symbolic</property><property name="tooltip" translatable="yes">Picture Mode</property><property name="name">picture</property></object></child><child><object class="AdwToggle"><property name="icon-name">camera-video-symbolic</property><property name="tooltip" translatable="yes">Recording Mode</property><property name="name">recording</property></object></child></object>
/// ```
///
/// See also: `InlineViewSwitcher`.
///
///
public struct ToggleGroup: AdwaitaWidget {
/// Additional update functions for type extensions.
var updateFunctions: [(ViewStorage, WidgetData, Bool) -> Void] = []
/// Additional appear functions for type extensions.
var appearFunctions: [(ViewStorage, WidgetData) -> Void] = []
/// The index of the active toggle.
///
/// Setting the index to a larger value than the number of toggles in the group
/// unsets the current active toggle.
///
/// If no toggle is active, the property will be set to
/// `Gtk.INVALID_LIST_POSITION`.
var active: Binding<UInt>?
/// The name of the active toggle.
///
/// The name can be set via ``name(_:)``. If the currently active
/// toggle doesn't have a name, the property will be set to `NULL`.
///
/// Set it to `NULL` to unset the current active toggle.
var activeName: Binding<String>?
/// Whether the toggles can be smaller than the natural size of their contents.
///
/// If set to `true`, the toggle labels will ellipsize.
///
/// See ``canShrink(_:)``.
var canShrink: Bool?
/// Whether all toggles take the same size.
var homogeneous: Bool?
/// The number of toggles within the group.
var nToggles: Binding<UInt>?
/// Initialize `ToggleGroup`.
public init() {
}
/// The view storage.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The view render data type.
/// - Returns: The view storage.
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
let storage = ViewStorage(adw_toggle_group_new()?.opaque())
for function in appearFunctions {
function(storage, data)
}
update(storage, data: data, updateProperties: true, type: type)
return storage
}
/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The view render data type.
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) where Data: ViewRenderData {
storage.modify { widget in
storage.notify(name: "active") {
let newValue = UInt(adw_toggle_group_get_active(storage.opaquePointer))
if let active, newValue != active.wrappedValue {
active.wrappedValue = newValue
}
}
storage.notify(name: "active-name") {
let newValue = String(cString: adw_toggle_group_get_active_name(storage.opaquePointer))
if let activeName, newValue != activeName.wrappedValue {
activeName.wrappedValue = newValue
}
}
storage.notify(name: "n-toggles") {
let newValue = UInt(adw_toggle_group_get_n_toggles(storage.opaquePointer))
if let nToggles, newValue != nToggles.wrappedValue {
nToggles.wrappedValue = newValue
}
}
if let active, updateProperties, (UInt(adw_toggle_group_get_active(storage.opaquePointer))) != active.wrappedValue {
adw_toggle_group_set_active(storage.opaquePointer, active.wrappedValue.cInt)
}
if let activeName, updateProperties, (String(cString: adw_toggle_group_get_active_name(storage.opaquePointer))) != activeName.wrappedValue {
adw_toggle_group_set_active_name(storage.opaquePointer, activeName.wrappedValue)
}
if let canShrink, updateProperties, (storage.previousState as? Self)?.canShrink != canShrink {
adw_toggle_group_set_can_shrink(widget, canShrink.cBool)
}
if let homogeneous, updateProperties, (storage.previousState as? Self)?.homogeneous != homogeneous {
adw_toggle_group_set_homogeneous(widget, homogeneous.cBool)
}
}
for function in updateFunctions {
function(storage, data, updateProperties)
}
if updateProperties {
storage.previousState = self
}
}
/// The index of the active toggle.
///
/// Setting the index to a larger value than the number of toggles in the group
/// unsets the current active toggle.
///
/// If no toggle is active, the property will be set to
/// `Gtk.INVALID_LIST_POSITION`.
public func active(_ active: Binding<UInt>?) -> Self {
modify { $0.active = active }
}
/// The name of the active toggle.
///
/// The name can be set via ``name(_:)``. If the currently active
/// toggle doesn't have a name, the property will be set to `NULL`.
///
/// Set it to `NULL` to unset the current active toggle.
public func activeName(_ activeName: Binding<String>?) -> Self {
modify { $0.activeName = activeName }
}
/// Whether the toggles can be smaller than the natural size of their contents.
///
/// If set to `true`, the toggle labels will ellipsize.
///
/// See ``canShrink(_:)``.
public func canShrink(_ canShrink: Bool? = true) -> Self {
modify { $0.canShrink = canShrink }
}
/// Whether all toggles take the same size.
public func homogeneous(_ homogeneous: Bool? = true) -> Self {
modify { $0.homogeneous = homogeneous }
}
/// The number of toggles within the group.
public func nToggles(_ nToggles: Binding<UInt>?) -> Self {
modify { $0.nToggles = nToggles }
}
}

View File

@ -2,7 +2,7 @@
// ToolbarView.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -2,7 +2,7 @@
// WindowTitle.swift
// Adwaita
//
// Created by auto-generation on 31.10.25.
// Created by auto-generation on 03.11.25.
//
import CAdw

View File

@ -0,0 +1,105 @@
//
// ToggleGroup+.swift
// Adwaita
//
// Created by david-swift on 03.11.25.
//
import CAdw
import LevenshteinTransformations
extension ToggleGroup {
/// The identifier for the values.
static var values: String { "values" }
/// The identifier for the toggles.
static var toggle: String { "Toggle::" }
/// Initialize a toggle group.
/// - Parameters:
/// - selection: The selected value.
/// - values: The available values.
public init<Element>(
selection: Binding<Element.ID>,
values: [Element]
) where Element: ToggleGroupItem {
self.init()
appearFunctions.append { storage, _ in
storage.notify(name: "active-name", id: "init") {
if let name = adw_toggle_group_get_active_name(storage.opaquePointer),
let values = storage.fields[Self.values] as? [Element],
let value = values.first(where: { $0.id.description == String(cString: name) }) {
selection.wrappedValue = value.id
}
}
Self.updateContent(
storage: storage,
selection: selection.wrappedValue,
values: values,
updateProperties: true
)
}
updateFunctions.append { storage, _, updateProperties in
Self.updateContent(
storage: storage,
selection: selection.wrappedValue,
values: values,
updateProperties: updateProperties
)
}
}
/// Update the combo row's content.
/// - Parameters:
/// - storage: The view storage.
/// - values: The elements.
/// - updateProperties: Whether to update the properties.
static func updateContent<Element>(
storage: ViewStorage,
selection: Element.ID,
values: [Element],
updateProperties: Bool
) where Element: ToggleGroupItem {
guard updateProperties else {
return
}
let old = storage.fields[Self.values] as? [Element] ?? []
old.identifiableTransform(
to: values,
functions: .init { index in
if let id = old[safe: index]?.id.description,
let toggle = storage.fields[Self.toggle + id] as? OpaquePointer {
adw_toggle_group_remove(storage.opaquePointer, toggle)
}
} insert: { _, element in
let toggle = adw_toggle_new()
adw_toggle_set_name(toggle, element.id.description)
if element.showLabel {
adw_toggle_set_label(toggle, element.id.description)
} else {
adw_toggle_set_tooltip(toggle, element.id.description)
}
if let icon = element.icon {
adw_toggle_set_icon_name(toggle, icon.string)
}
storage.fields[Self.toggle + element.id.description] = toggle
adw_toggle_group_add(storage.opaquePointer, toggle)
}
)
storage.fields[Self.values] = values
adw_toggle_group_set_active_name(storage.opaquePointer, selection.description)
}
}
/// An item of a toggle group.
public protocol ToggleGroupItem: Identifiable where Self.ID: CustomStringConvertible {
/// The item's icon.
var icon: Icon? { get }
/// Whether to show the label in the UI (the identifier's string conversion).
///
/// Otherwise, it will be used as the tooltip.
var showLabel: Bool { get }
}

View File

@ -35,6 +35,7 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
case textEditor
case splitView
case symbol
case toggleGroup
var id: Self {
self
@ -56,6 +57,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
return "Text Editor"
case .splitView:
return "Split View"
case .toggleGroup:
return "Toggle Group"
default:
return rawValue.capitalized
}
@ -118,6 +121,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
return "A split view whose panes are ajustable in size"
case .symbol:
return "Display a symbolic image"
case .toggleGroup:
return "Select an item"
}
}
@ -171,6 +176,8 @@ enum Page: String, Identifiable, CaseIterable, Codable, CustomStringConvertible
SplitViewDemo()
case .symbol:
SymbolDemo()
case .toggleGroup:
ToggleGroupDemo()
}
}
// swiftlint:enable cyclomatic_complexity

View File

@ -0,0 +1,46 @@
//
// ToggleGroupDemo.swift
// Adwaita
//
// Created by david-swift on 03.11.25.
//
// swiftlint:disable missing_docs no_magic_numbers
import Adwaita
import Foundation
struct ToggleGroupDemo: View {
@State private var selection: Subview = .view1
var view: Body {
ToggleGroup(selection: $selection, values: Subview.allCases)
.padding()
VStack {
Text(selection.rawValue)
.padding()
.padding(50, .vertical)
}
.card()
.padding()
}
enum Subview: String, ToggleGroupItem, CaseIterable, CustomStringConvertible {
case view1 = "View 1"
case view2 = "View 2"
var id: Self { self }
var description: String { rawValue }
var icon: Icon? { nil }
var showLabel: Bool { true }
}
}
// swiftlint:enable missing_docs no_magic_numbers

View File

@ -162,7 +162,12 @@ struct GenerationConfiguration {
class: "NavigationView",
excludeProperties: ["navigation-stack", "visible-page"]
),
.init(class: "Spinner")
.init(class: "Spinner"),
.init(
class: "ToggleGroup",
bindings: [.init(property: "active"), .init(property: "active-name"), .init(property: "n-toggles")],
excludeProperties: ["toggles"]
)
]
/// The Gtk widgets.