adwaita-swift/Sources/Adwaita/View/ToggleGroup+.swift
david-swift 2072c16040
All checks were successful
SwiftLint / SwiftLint (push) Successful in 7s
Deploy Docs / publish (push) Successful in 1h49m51s
Add support for keypaths where sensible #74
2026-02-04 16:36:51 +01:00

137 lines
4.8 KiB
Swift

//
// 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(
selection: selection,
values: values,
id: \.id,
label: \.id.description,
icon: \.icon,
showLabel: \.showLabel
)
}
/// Initialize a toggle group.
/// - Parameters:
/// - selection: The selected value.
/// - values: The available values.
/// - id: The path to the identifier.
/// - label: The path to the label.
/// - icon: The path to the icon.
/// - showLabel: The path to the boolean that defines whether to show an element's label.
public init<Element, Identifier>(
selection: Binding<Identifier>,
values: [Element],
id: KeyPath<Element, Identifier>,
label: KeyPath<Element, String>,
icon: KeyPath<Element, Icon?>? = nil,
showLabel: KeyPath<Element, Bool>? = nil
) where Identifier: Equatable {
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 identifier = values
.map({ $0[keyPath: label] }).first(where: { $0.description == String(cString: name) }),
let value = values.first(where: { $0[keyPath: label] == identifier }) {
selection.wrappedValue = value[keyPath: id]
}
}
}
updateFunctions.append { storage, _, updateProperties in
Self.updateContent(
storage: storage,
selection: selection.wrappedValue,
values: values,
id: id,
label: label,
icon: icon,
showLabel: showLabel,
updateProperties: updateProperties
)
}
}
// swiftlint:disable function_parameter_count
/// Update the combo row's content.
static func updateContent<Element, Identifier>(
storage: ViewStorage,
selection: Identifier,
values: [Element],
id: KeyPath<Element, Identifier>,
label: KeyPath<Element, String>,
icon: KeyPath<Element, Icon?>?,
showLabel: KeyPath<Element, Bool>?,
updateProperties: Bool
) where Identifier: Equatable {
guard updateProperties else {
return
}
let old = storage.fields[Self.values] as? [Element] ?? []
old.transform(
to: values,
id: id,
functions: .init { index in
if let id = old[safe: index]?[keyPath: label],
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[keyPath: label])
if let showLabel, !element[keyPath: showLabel] {
adw_toggle_set_tooltip(toggle, element[keyPath: label])
} else {
adw_toggle_set_label(toggle, element[keyPath: label])
}
if let icon, let icon = element[keyPath: icon] {
adw_toggle_set_icon_name(toggle, icon.string)
}
storage.fields[Self.toggle + element[keyPath: label]] = toggle
adw_toggle_group_add(storage.opaquePointer, toggle)
}
)
storage.fields[Self.values] = values
if let selection = values.first(where: { $0[keyPath: id] == selection }) {
adw_toggle_group_set_active_name(storage.opaquePointer, selection[keyPath: label])
}
}
// swiftlint:enable function_parameter_count
}
/// 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 }
}