adwaita-swift/Sources/Adwaita/View/ViewSwitcher.swift
david-swift 04c77831b5
Some checks are pending
Deploy Docs / publish (push) Waiting to run
SwiftLint / SwiftLint (push) Waiting to run
Remove Core library
2025-10-30 21:36:13 +01:00

141 lines
4.7 KiB
Swift

//
// ViewSwitcher.swift
// Adwaita
//
// Created by david-swift on 03.01.24.
//
import CAdw
import LevenshteinTransformations
/// The view switcher widget.
public struct ViewSwitcher<Element>: AdwaitaWidget where Element: ViewSwitcherOption {
/// The selected element.
@Binding var selectedElement: Element
/// The elements.
var elements: [Element]
/// Whether the wide style is used, that means the icons and titles are on the same line.
var wide = false
/// Initialize a view switcher.
/// - Parameters:
/// - elements: The elements.
/// - selectedElement: The selected element.
public init(_ elements: [Element], selectedElement: Binding<Element>) {
self._selectedElement = selectedElement
self.elements = elements
}
/// 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 switcher = ViewStorage(adw_view_switcher_new()?.opaque())
let stack = ViewStorage(adw_view_stack_new()?.opaque())
adw_view_switcher_set_stack(switcher.opaquePointer, stack.opaquePointer)
updateSwitcher(switcher: switcher)
switcher.fields["stack"] = stack
return switcher
}
/// 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 {
if updateProperties {
updateSwitcher(switcher: storage)
}
}
/// Update a view switcher's style and selection.
/// - Parameter switcher: The view switcher.
func updateSwitcher(switcher: ViewStorage) {
let stack = switcher.fields["stack"] as? ViewStorage
stack?.notify(name: "visible-child") {
if let title = adw_view_stack_get_visible_child_name(stack?.opaquePointer),
let option = elements.first(where: { $0.title == .init(cString: title) }) {
selectedElement = option
}
}
if (switcher.previousState as? Self)?.wide != wide {
adw_view_switcher_set_policy(
switcher.opaquePointer,
wide ? ADW_VIEW_SWITCHER_POLICY_WIDE : ADW_VIEW_SWITCHER_POLICY_NARROW
)
}
let adwStack = adw_view_switcher_get_stack(switcher.opaquePointer)
let insert: (String) -> Void = { title in
if let element = elements.first(where: { $0.title == title }) {
adw_view_stack_add_titled_with_icon(
adwStack,
gtk_label_new(""),
element.title,
element.title,
element.icon.string
)
}
}
let remove: (Int) -> Void = { index in
if let element = elements[safe: index] {
let child = adw_view_stack_get_child_by_name(adwStack, element.title)
adw_view_stack_remove(adwStack, child)
}
}
((switcher.previousState as? Self)?.elements ?? []).map { $0.title }.transform(
to: elements.map { $0.title },
functions: .init { index in
remove(index)
} insert: { _, title in
insert(title)
}
)
adw_view_stack_set_visible_child_name(adwStack, selectedElement.title)
switcher.previousState = self
}
/// Set whether to use the wide design.
/// - Parameter wide: Whether to use the wide design.
/// - Returns: The view switcher.
public func wideDesign(_ wide: Bool = true) -> Self {
var newSelf = self
newSelf.wide = wide
return newSelf
}
}
extension ViewSwitcher where Element: CaseIterable {
/// Initialize a view switcher.
/// - Parameter selectedElement: The selected element.
public init(selectedElement: Binding<Element>) {
self._selectedElement = selectedElement
self.elements = .init(Element.allCases)
}
}
/// The protocol an element type for view switcher has to conform to.
public protocol ViewSwitcherOption {
/// The title displayed in the switcher and used for identification.
var title: String { get }
/// A symbolic representation in the view switcher.
var icon: Icon { get }
}