178 lines
5.4 KiB
Swift
178 lines
5.4 KiB
Swift
//
|
|
// SwiftUIWidget.swift
|
|
// MacBackend
|
|
//
|
|
// Created by david-swift on 25.11.2024.
|
|
//
|
|
|
|
import LevenshteinTransformations
|
|
import SwiftUI
|
|
|
|
/// Wrap a SwiftUI widget to be used inside a `MacBackend` view.
|
|
public protocol SwiftUIWidget: MacWidget {
|
|
|
|
/// The content SwiftUI view.
|
|
associatedtype Content: SwiftUI.View
|
|
|
|
/// The wrapped views.
|
|
var wrappedViews: [String: Meta.AnyView] { get }
|
|
|
|
/// Get the SwiftUI view.
|
|
/// - Parameter properties: The widget data.
|
|
/// - Returns: The SwiftUI view.
|
|
@SwiftUI.ViewBuilder
|
|
static func view(properties: Self) -> Content
|
|
|
|
}
|
|
|
|
extension SwiftUIWidget {
|
|
|
|
/// The wrapped views.
|
|
public var wrappedViews: [String: Meta.AnyView] {
|
|
[:]
|
|
}
|
|
|
|
/// The view storage.
|
|
/// - Parameters:
|
|
/// - data: 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 {
|
|
internalContainer(data: data, type: type)
|
|
}
|
|
|
|
/// The view storage.
|
|
/// - Parameters:
|
|
/// - data: Modify views before being updated.
|
|
/// - type: The view render data type.
|
|
/// - Returns: The view storage.
|
|
func internalContainer<Data>(
|
|
data: WidgetData,
|
|
type: Data.Type
|
|
) -> ViewStorage where Data: ViewRenderData {
|
|
let id = UUID().uuidString
|
|
let updater = SwiftUIUpdater.updater
|
|
updater.state[id] = self
|
|
let wrappedStorages = wrappedViews.reduce(into: [String: ViewStorage]()) { partialResult, element in
|
|
partialResult[element.key] = element.value.storage(data: data, type: type)
|
|
}
|
|
let storage: ViewStorage = .init(nil)
|
|
storage.fields["child-storages"] = wrappedStorages
|
|
let view = NSHostingView(
|
|
rootView: SwiftUIWrapperView(updater: updater, id: id, data: data) { value in
|
|
if let value = value as? Self {
|
|
Self.view(properties: value)
|
|
.environment(\.views, storage.fields["child-storages"] as? [String: ViewStorage])
|
|
}
|
|
}
|
|
)
|
|
storage.pointer = view
|
|
storage.fields["updater-id"] = id
|
|
return storage
|
|
}
|
|
|
|
/// Update the stored content.
|
|
/// - Parameters:
|
|
/// - storage: The storage to update.
|
|
/// - data: 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 {
|
|
internalUpdate(storage, data: data, updateProperties: updateProperties, type: type)
|
|
}
|
|
|
|
/// Update the stored content.
|
|
/// - Parameters:
|
|
/// - storage: The storage to update.
|
|
/// - data: Modify views before being updated
|
|
/// - updateProperties: Whether to update the view's properties.
|
|
/// - type: The view render data type.
|
|
func internalUpdate<Data>(
|
|
_ storage: ViewStorage,
|
|
data: WidgetData,
|
|
updateProperties: Bool,
|
|
type: Data.Type
|
|
) where Data: ViewRenderData {
|
|
if updateProperties, let id = storage.fields["updater-id"] as? String {
|
|
Task { @MainActor in
|
|
SwiftUIUpdater.updater.state[id] = self
|
|
}
|
|
}
|
|
var children = storage.fields["child-storages"] as? [String: ViewStorage] ?? [:]
|
|
for view in wrappedViews where !children.contains(where: { $0.key == view.key }) {
|
|
children[view.key] = view.value.storage(data: data, type: type)
|
|
}
|
|
for view in children where !wrappedViews.contains(where: { $0.key == view.key }) {
|
|
children[view.key] = nil
|
|
}
|
|
storage.fields["child-storages"] = children
|
|
for (key, storage) in children {
|
|
wrappedViews[key]?.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// A SwiftUI view which can be displayed and updated inside a `MacBackend` widget.
|
|
struct SwiftUIWrapperView<Content>: SwiftUI.View where Content: SwiftUI.View {
|
|
|
|
/// The updater observable object.
|
|
@ObservedObject var updater: SwiftUIUpdater
|
|
/// The identifier.
|
|
var id: String
|
|
/// The widget data.
|
|
var data: WidgetData
|
|
/// The wrapped view.
|
|
var view: (Any) -> Content
|
|
|
|
/// The SwiftUI view content.
|
|
var body: some SwiftUI.View {
|
|
if let state = updater.state[id] {
|
|
view(state)
|
|
}
|
|
}
|
|
|
|
/// Initialize the SwiftUI wrapper view.
|
|
/// - Parameters:
|
|
/// - updater: The updater observable object.
|
|
/// - id: The identifier.
|
|
/// - data: The widget data.
|
|
/// - view: The wrapped view.
|
|
init(updater: SwiftUIUpdater, id: String, data: WidgetData, @SwiftUI.ViewBuilder view: @escaping (Any) -> Content) {
|
|
self.updater = updater
|
|
self.id = id
|
|
self.view = view
|
|
self.data = data
|
|
}
|
|
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
|
|
/// The views environment value.
|
|
@Entry var views: [String: ViewStorage]?
|
|
|
|
}
|
|
|
|
/// The SwiftUI updater object.
|
|
class SwiftUIUpdater: ObservableObject {
|
|
|
|
/// The updater.
|
|
static var updater: SwiftUIUpdater = .init()
|
|
|
|
/// The state for SwiftUI views.
|
|
@Published var state: [String: Any] = [:]
|
|
|
|
/// Initialize an updater.
|
|
init() { }
|
|
|
|
}
|