Add navigation stack
Some checks failed
Deploy Docs / publish (push) Failing after 0s
SwiftLint / SwiftLint (push) Failing after 1s

This commit is contained in:
david-swift 2024-12-15 21:26:38 +01:00
parent 7f53c267d2
commit 8391997848
4 changed files with 149 additions and 35 deletions

View File

@ -30,10 +30,10 @@ public struct Label: SwiftUIWidget {
public static func view(properties: Self) -> some SwiftUI.View {
SwiftUI.Label {
SwiftUI.Text(properties.label)
SwiftUI.Spacer()
} icon: {
properties.icon.image
}
}
}

View File

@ -0,0 +1,120 @@
//
// NavigationStack.swift
// MacBackend
//
// Created by david-swift on 14.12.2024.
//
import SwiftUI
/// The navigation stack view.
public struct NavigationStack<Component>: SwiftUIWidget where Component: CustomStringConvertible {
/// The current navigation path.
@Meta.Binding var path: NavigationPath
/// The content.
var content: (Component) -> Body
/// The initial view.
var initialView: Body
/// The wrapped views.
public var wrappedViews: [String: Meta.AnyView] {
if let current = path.current {
_ = current
return [.mainContent: initialView, current.description: content(current)]
} else {
return [.mainContent: initialView]
}
}
/// Initialize the navigation stack.
/// - Parameters:
/// - path: The path.
/// - content: The content.
/// - initialView: The initial view.
public init(
path: Meta.Binding<NavigationPath>,
@Meta.ViewBuilder content: @escaping (Component) -> Body,
@Meta.ViewBuilder initialView: () -> Body
) {
self._path = path
self.content = content
self.initialView = initialView()
}
/// The navigation path.
public struct NavigationPath {
/// The path.
var path: [Component] = []
/// The current component.
var current: Component? { path.last }
/// The path as an array of strings.
var hashable: [String] {
get {
path.map { $0.description }
}
set {
if newValue.count < hashable.count {
_ = path.popLast()
}
}
}
/// Initialize a navigation stack.
public init() { }
/// Pop the last element.
public mutating func pop() {
_ = path.popLast()
}
/// Add a new element.
/// - Parameter component: The element.
public mutating func push(_ component: Component) {
path.append(component)
}
}
/// Receive the SwiftUI view.
/// - Parameter properties: The properties.
/// - Returns: The SwiftUI view.
public static func view(properties: Self) -> some SwiftUI.View {
SwiftUINavigationStack(path: properties.$path.swiftUI)
}
}
/// The SwiftUI navigation stack.
struct SwiftUINavigationStack<Component>: SwiftUI.View where Component: CustomStringConvertible {
/// The path.
@SwiftUI.Binding var path: NavigationPath<Component>
/// The views.
@SwiftUI.Environment(\.views)
var views
/// The view's body.
var body: some SwiftUI.View {
SwiftUI.NavigationStack(path: $path.hashable) {
let view = MacBackendView(.mainContent)
if path.current != nil {
view
.navigationDestination(for: String.self) { string in
SwiftUI.VStack {
MacBackendView(string)
.environment(\.views, views)
}
}
} else {
view
}
}
}
}
/// The navigation path.
public typealias NavigationPath<Component: CustomStringConvertible> = NavigationStack<Component>.NavigationPath

View File

@ -59,7 +59,6 @@ public struct MenuBar: MacSceneElement {
let scene = SceneStorage(id: id, pointer: app.mainMenu) { }
let data = WidgetData(sceneStorage: scene, appStorage: app)
let storage = MenuCollection { self.content }.getMenu(data: data, menu: app.mainMenu)
print(app.helpItem.submenu)
let appStorage = MenuCollection { self.app }.getMenu(data: data, menu: app.appItem.submenu)
let windowStorage = MenuCollection { self.window }.getMenu(data: data, menu: app.windowsItem.submenu)
let helpStorage = MenuCollection { self.help }.getMenu(data: data, menu: app.helpItem.submenu)

View File

@ -20,53 +20,38 @@ struct Demo: App {
@State(blockUpdates: true)
private var width = 500
@State(blockUpdates: true)
private var height = 400
private var height = 450
@State private var elements: [Element] = [.init()]
@State private var selectedElement: String?
@State private var alert = false
@State private var presentMenu: Signal = .init()
@State private var selection = "Hello"
@State private var path: NavigationPath<Navigation> = .init()
var scene: Scene {
Window(id: "secondary") { _ in
Text(selection)
.centeredToolbar {
Picker("Test", items: ["Hello", "World"], selection: $selection)
.segmented()
}
}
.frame(width: .constant(800), height: .constant(600))
Window("Main", id: "main") { _ in
NavigationSplitView {
List(elements, selection: $selectedElement) { element in
Label(element.id, icon: .system(name: "play.fill"))
}
} detail: {
VStack {
Button(selectedElement ?? "World") {
let element = Element()
elements.append(element)
selectedElement = element.id
NavigationStack(path: $path) { navigation in
Button(navigation.description) {
selectedElement = nil
}
Button(alert.description) {
presentMenu.signal()
}
.menu(present: presentMenu) {
MenuButton(alert.description) {
print("Hi")
} initialView: {
VStack {
Button(selectedElement ?? "World") {
let element = Element()
elements.append(element)
selectedElement = element.id
}
Button("Navigate") {
path.push(.test)
}
}
}
.alert("Hello", isPresented: $alert)
.cancelButton("Cancel") {
print("Cancel")
}
.destructiveButton("Destructive", default: true) {
print("Destructive")
}
.toolbar {
Button("World", icon: .system(name: "globe")) {
print("World")
.toolbar {
Button("World", icon: .system(name: "globe")) {
print("World")
}
}
}
}
@ -138,4 +123,14 @@ struct Element: Identifiable {
}
enum Navigation: CustomStringConvertible {
case test
var description: String {
"Test"
}
}
// swiftlint:enable missing_docs no_magic_numbers closure_body_length