adwaita-swift/Sources/Adwaita/View/NavigationView+.swift

150 lines
5.2 KiB
Swift

//
// NavigationView+.swift
// Adwaita
//
// Created by david-swift on 17.02.24.
//
import CAdw
extension NavigationView {
/// The ID for the component field in a content storage.
static var componentID: String { "component" }
/// Initialize a navigation view.
/// - Parameters:
/// - stack: The navigation stack for pushing and popping.
/// - initialTitle: The title of the initial view.
/// - content: The view for a path component.
/// - initialView: The view that is displayed when the path is empty.
public init<Component>(
_ stack: Binding<NavigationStack<Component>>,
_ initialTitle: String,
@ViewBuilder content: @escaping (Component) -> Body,
@ViewBuilder initialView: @escaping () -> Body
) {
self.init()
appearFunctions.append { storage, data in
let initialStorage = initialView().storage(data: data, type: AdwaitaMainView.self)
storage.fields[.mainContent] = [initialStorage]
adw_navigation_view_push(
storage.opaquePointer,
adw_navigation_page_new(initialStorage.opaquePointer?.cast(), initialTitle)
)
storage.connectSignal(name: "popped", id: "components", argCount: 1) { args in
guard var storages = storage.fields[.mainContent] as? [ViewStorage],
let arg = args.first as? UnsafeRawPointer else {
return
}
if storages.last?.opaquePointer == adw_navigation_page_get_child(OpaquePointer(arg).cast())?.opaque() {
storages.removeLast()
storage.fields[.mainContent] = storages
}
}
}
updateFunctions.append { [self] storage, data, updateProperties in
updateFunction(
storage: storage,
data: data,
stack: stack,
views: (content, initialView()),
updateProperties: updateProperties
)
}
}
/// A stack controls a navigation view.
public struct NavigationStack<Component> where Component: CustomStringConvertible {
/// The action to run at the next view update, if any.
var action: Action<Component>?
/// Initialize a navigation stack.
public init() { }
/// Remove the last item from the navigation view.
public mutating func pop() {
action = .pop
}
/// Add a new item to the navigation view.
/// - Parameter component: The component's value.
public mutating func push(_ component: Component) {
action = .push(component: component)
}
}
/// An action to run on a view update.
enum Action<Component> {
/// Remove the last item.
case pop
/// Add a new item.
case push(component: Component)
}
/// Update a navigation view.
/// - Parameters:
/// - storage: The view storage.
/// - modifiers: The view modifiers.
/// - stack: The navigation stack for pushing and popping.
/// - views: The views.
/// - updateProperties: Whether to update properties.
func updateFunction<Component>(
storage: ViewStorage,
data: WidgetData,
stack: Binding<NavigationStack<Component>>,
views: ((Component) -> Body, Body),
updateProperties: Bool
) {
guard var storages = storage.fields[.mainContent] as? [ViewStorage] else {
return
}
switch stack.wrappedValue.action {
case .pop:
if storages.count > 1 {
adw_navigation_view_pop(storage.opaquePointer)
storages.removeLast()
} else { print("Warning: removing the initial view is not allowed.") }
case let .push(component):
let contentStorage = views.0(component).storage(data: data, type: AdwaitaMainView.self)
contentStorage.fields[Self.componentID] = component
storages.append(contentStorage)
adw_navigation_view_push(
storage.opaquePointer,
adw_navigation_page_new(contentStorage.opaquePointer?.cast(), component.description)
)
default:
break
}
StateManager.blockUpdates = true; stack.wrappedValue.action = nil; StateManager.blockUpdates = false
storage.fields[.mainContent] = storages
for storage in storages {
if let component = storage.fields[Self.componentID] as? Component {
views.0(component)
.updateStorage(
storage,
data: data,
updateProperties: updateProperties,
type: AdwaitaMainView.self
)
} else {
views.1
.updateStorage(
storage,
data: data,
updateProperties: updateProperties,
type: AdwaitaMainView.self
)
}
}
}
}
/// A stack controls a navigation view.
public typealias NavigationStack = NavigationView.NavigationStack