Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3e1894e80 | |||
| 682947abb0 | |||
| ba6a46f793 | |||
| 9a3760b24c | |||
| ebb68009ee | |||
| cd4e4dff9d | |||
| bd6c9d6f6b | |||
| d6a8f180ef | |||
| 80c430c546 | |||
| bb1d946b4a | |||
| 1a5cc43ef1 | |||
| 4b2127c55c | |||
| bc03112d2d |
@ -1,4 +1,4 @@
|
|||||||
// swift-tools-version: 5.9
|
// swift-tools-version: 6.0
|
||||||
//
|
//
|
||||||
// Package.swift
|
// Package.swift
|
||||||
// Meta
|
// Meta
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
/// ```
|
/// ```
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
@dynamicMemberLookup
|
@dynamicMemberLookup
|
||||||
public struct Binding<Value> {
|
public struct Binding<Value>: Sendable where Value: Sendable {
|
||||||
|
|
||||||
/// The value.
|
/// The value.
|
||||||
public var wrappedValue: Value {
|
public var wrappedValue: Value {
|
||||||
@ -64,11 +64,11 @@ public struct Binding<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The closure for getting the value.
|
/// The closure for getting the value.
|
||||||
private let getValue: () -> Value
|
private let getValue: @Sendable () -> Value
|
||||||
/// The closure for settings the value.
|
/// The closure for settings the value.
|
||||||
private let setValue: (Value) -> Void
|
private let setValue: @Sendable (Value) -> Void
|
||||||
/// Handlers observing whether the binding changes.
|
/// Handlers observing whether the binding changes.
|
||||||
private var handlers: [(Value) -> Void] = []
|
private var handlers: [@Sendable (Value) -> Void] = []
|
||||||
|
|
||||||
/// Get a property of any content of a `Binding` as a `Binding`.
|
/// Get a property of any content of a `Binding` as a `Binding`.
|
||||||
/// - Parameter keyPath: The path to the member.
|
/// - Parameter keyPath: The path to the member.
|
||||||
@ -85,7 +85,7 @@ public struct Binding<Value> {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - get: The closure for getting the value.
|
/// - get: The closure for getting the value.
|
||||||
/// - set: The closure for setting the value.
|
/// - set: The closure for setting the value.
|
||||||
public init(get: @escaping () -> Value, set: @escaping (Value) -> Void) {
|
public init(get: @Sendable @escaping () -> Value, set: @Sendable @escaping (Value) -> Void) {
|
||||||
self.getValue = get
|
self.getValue = get
|
||||||
self.setValue = set
|
self.setValue = set
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ public struct Binding<Value> {
|
|||||||
/// Observe whether data is changed over this binding.
|
/// Observe whether data is changed over this binding.
|
||||||
/// - Parameter handler: The handler.
|
/// - Parameter handler: The handler.
|
||||||
/// - Returns: The binding.
|
/// - Returns: The binding.
|
||||||
public func onSet(_ handler: @escaping (Value) -> Void) -> Self {
|
public func onSet(_ handler: @Sendable @escaping (Value) -> Void) -> Self {
|
||||||
var newSelf = self
|
var newSelf = self
|
||||||
newSelf.handlers.append(handler)
|
newSelf.handlers.append(handler)
|
||||||
return newSelf
|
return newSelf
|
||||||
@ -113,7 +113,7 @@ public struct Binding<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extend bindings.
|
/// Extend bindings.
|
||||||
extension Binding where Value: MutableCollection {
|
extension Binding where Value: MutableCollection, Value.Index: Sendable, Value.Element: Sendable {
|
||||||
|
|
||||||
/// Get a child at a certain index of the array as a binding.
|
/// Get a child at a certain index of the array as a binding.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@ -136,7 +136,8 @@ extension Binding where Value: MutableCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extend bindings.
|
/// Extend bindings.
|
||||||
extension Binding where Value: MutableCollection, Value.Element: Identifiable {
|
extension Binding
|
||||||
|
where Value: MutableCollection, Value.Element: Identifiable, Value.Index: Sendable, Value.Element: Sendable {
|
||||||
|
|
||||||
/// Get a child of the array with a certain id as a binding.
|
/// Get a child of the array with a certain id as a binding.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
|||||||
@ -58,7 +58,7 @@ public protocol Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Data about a model's state value.
|
/// Data about a model's state value.
|
||||||
public struct ModelData {
|
public struct ModelData: Sendable {
|
||||||
|
|
||||||
/// The state value's identifier.
|
/// The state value's identifier.
|
||||||
var storage: StateContent.Storage
|
var storage: StateContent.Storage
|
||||||
@ -67,23 +67,8 @@ public struct ModelData {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extend the model.
|
|
||||||
extension Model {
|
extension Model {
|
||||||
|
|
||||||
/// Get the value as a binding.
|
|
||||||
public var binding: Binding<Self> {
|
|
||||||
.init {
|
|
||||||
getModel()
|
|
||||||
} set: { newValue in
|
|
||||||
guard let data = model else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data.storage.value = newValue
|
|
||||||
data.storage.update = true
|
|
||||||
StateManager.updateViews(force: data.force)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the model up.
|
/// Set the model up.
|
||||||
///
|
///
|
||||||
/// At the point this function gets called, the model data is available.
|
/// At the point this function gets called, the model data is available.
|
||||||
@ -100,7 +85,9 @@ extension Model {
|
|||||||
setModel(&model)
|
setModel(&model)
|
||||||
data.storage.value = model
|
data.storage.value = model
|
||||||
data.storage.update = true
|
data.storage.update = true
|
||||||
StateManager.updateViews(force: data.force)
|
Task {
|
||||||
|
await StateManager.updateViews(force: data.force)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current version of the model.
|
/// Get the current version of the model.
|
||||||
@ -116,3 +103,23 @@ extension Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Model where Self: Sendable {
|
||||||
|
|
||||||
|
/// Get the value as a binding.
|
||||||
|
public var binding: Binding<Self> {
|
||||||
|
.init {
|
||||||
|
getModel()
|
||||||
|
} set: { newValue in
|
||||||
|
guard let data = model else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.storage.value = newValue
|
||||||
|
data.storage.update = true
|
||||||
|
Task {
|
||||||
|
await StateManager.updateViews(force: data.force)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -8,12 +8,14 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// A type that signalizes an action.
|
/// A type that signalizes an action.
|
||||||
public struct Signal {
|
public struct Signal: Model, Sendable {
|
||||||
|
|
||||||
/// An action is signalized by toggling a boolean to `true` and back to `false`.
|
/// An action is signalized by toggling a boolean to `true` and back to `false`.
|
||||||
@State var boolean = false
|
var boolean = false
|
||||||
/// A signal has a unique identifier.
|
/// A signal has a unique identifier.
|
||||||
public let id: UUID = .init()
|
public let id: UUID = .init()
|
||||||
|
/// The model data.
|
||||||
|
public var model: ModelData?
|
||||||
|
|
||||||
/// Whether the action has caused an update.
|
/// Whether the action has caused an update.
|
||||||
public var update: Bool { boolean }
|
public var update: Bool { boolean }
|
||||||
@ -23,7 +25,11 @@ public struct Signal {
|
|||||||
|
|
||||||
/// Activate a signal.
|
/// Activate a signal.
|
||||||
public func signal() {
|
public func signal() {
|
||||||
boolean = true
|
setModel { $0.boolean = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destroy a signal.
|
||||||
|
mutating func destroySignal() {
|
||||||
boolean = false
|
boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import Foundation
|
|||||||
|
|
||||||
/// A property wrapper for properties in a view that should be stored throughout view updates.
|
/// A property wrapper for properties in a view that should be stored throughout view updates.
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct State<Value>: StateProtocol {
|
public struct State<Value>: StateProtocol, Sendable where Value: Sendable {
|
||||||
|
|
||||||
/// Access the stored value. This updates the views when being changed.
|
/// Access the stored value. This updates the views when being changed.
|
||||||
public var wrappedValue: Value {
|
public var wrappedValue: Value {
|
||||||
@ -19,7 +19,9 @@ public struct State<Value>: StateProtocol {
|
|||||||
nonmutating set {
|
nonmutating set {
|
||||||
rawValue = newValue
|
rawValue = newValue
|
||||||
content.update = true
|
content.update = true
|
||||||
StateManager.updateViews(force: forceUpdates)
|
Task {
|
||||||
|
await StateManager.updateViews(force: forceUpdates)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +51,7 @@ public struct State<Value>: StateProtocol {
|
|||||||
var forceUpdates: Bool
|
var forceUpdates: Bool
|
||||||
|
|
||||||
/// The closure for initializing the state property's value.
|
/// The closure for initializing the state property's value.
|
||||||
var getInitialValue: () -> Value
|
var getInitialValue: @Sendable () -> Value
|
||||||
|
|
||||||
/// The content.
|
/// The content.
|
||||||
let content: StateContent = .init()
|
let content: StateContent = .init()
|
||||||
@ -59,7 +61,7 @@ public struct State<Value>: StateProtocol {
|
|||||||
/// - wrappedValue: The wrapped value.
|
/// - wrappedValue: The wrapped value.
|
||||||
/// - id: An explicit identifier.
|
/// - id: An explicit identifier.
|
||||||
/// - forceUpdates: Whether to force update all available views when the property gets modified.
|
/// - forceUpdates: Whether to force update all available views when the property gets modified.
|
||||||
public init(wrappedValue: @autoclosure @escaping () -> Value, forceUpdates: Bool = false) {
|
public init(wrappedValue: @Sendable @autoclosure @escaping () -> Value, forceUpdates: Bool = false) {
|
||||||
getInitialValue = wrappedValue
|
getInitialValue = wrappedValue
|
||||||
self.forceUpdates = forceUpdates
|
self.forceUpdates = forceUpdates
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// A class storing the state's content.
|
/// A class storing the state's content.
|
||||||
class StateContent {
|
final class StateContent: @unchecked Sendable {
|
||||||
|
|
||||||
/// The storage.
|
/// The storage.
|
||||||
var storage: Storage?
|
var storage: Storage?
|
||||||
@ -39,7 +39,7 @@ class StateContent {
|
|||||||
init() { }
|
init() { }
|
||||||
|
|
||||||
/// A class storing the value.
|
/// A class storing the value.
|
||||||
class Storage {
|
class Storage: @unchecked Sendable {
|
||||||
|
|
||||||
/// The stored value.
|
/// The stored value.
|
||||||
var value: Any
|
var value: Any
|
||||||
|
|||||||
@ -8,31 +8,52 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// This type manages view updates.
|
/// This type manages view updates.
|
||||||
public enum StateManager {
|
@globalActor
|
||||||
|
public actor StateManager {
|
||||||
|
|
||||||
/// Whether to block updates in general.
|
/// Whether to block updates in general.
|
||||||
public static var blockUpdates = false
|
@StateManager static var blockAllUpdates = false
|
||||||
/// The application identifier.
|
/// The application identifier.
|
||||||
static var appID: String?
|
static var appID: String?
|
||||||
/// The functions handling view updates.
|
/// The functions handling view updates.
|
||||||
static var updateHandlers: [(Bool) -> Void] = []
|
static var updateHandlers: [@Sendable (Bool) async -> Void] = []
|
||||||
|
/// The shared instance of the actor.
|
||||||
|
public static let shared = StateManager()
|
||||||
|
|
||||||
/// Update all of the views.
|
/// Update all of the views.
|
||||||
/// - Parameter force: Whether to force all views to update.
|
/// - Parameter force: Whether to force all views to update.
|
||||||
///
|
///
|
||||||
/// Nothing happens if ``StateManager/blockUpdates`` is true.
|
/// Nothing happens if ``StateManager/blockUpdates`` is true.
|
||||||
public static func updateViews(force: Bool = false) {
|
public static func updateViews(force: Bool = false) async {
|
||||||
if !blockUpdates {
|
if await !blockAllUpdates {
|
||||||
for handler in updateHandlers {
|
for handler in updateHandlers {
|
||||||
handler(force)
|
Task {
|
||||||
|
await handler(force)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a handler that is called when the user interface should update.
|
/// Add a handler that is called when the user interface should update.
|
||||||
/// - Parameter handler: The handler. The parameter defines whether the whole UI should be force updated.
|
/// - Parameter handler: The handler. The parameter defines whether the whole UI should be force updated.
|
||||||
static func addUpdateHandler(handler: @escaping (Bool) -> Void) {
|
static func addUpdateHandler(handler: @Sendable @escaping (Bool) async -> Void) {
|
||||||
updateHandlers.append(handler)
|
updateHandlers.append(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Block all updates.
|
||||||
|
///
|
||||||
|
/// The user interface will not respond to changes.
|
||||||
|
@StateManager
|
||||||
|
public static func blockUpdates() {
|
||||||
|
blockAllUpdates = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unblock all updates.
|
||||||
|
///
|
||||||
|
/// The user interface will respond to changes.
|
||||||
|
@StateManager
|
||||||
|
public static func unblockUpdates() {
|
||||||
|
blockAllUpdates = false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// An interface for accessing `State` without specifying the generic type.
|
/// An interface for accessing `State` without specifying the generic type.
|
||||||
protocol StateProtocol {
|
protocol StateProtocol: Sendable {
|
||||||
|
|
||||||
/// The state content.
|
/// The state content.
|
||||||
var content: StateContent { get }
|
var content: StateContent { get }
|
||||||
|
|||||||
@ -44,10 +44,10 @@ extension Array: AnyView where Element == AnyView {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
for (index, element) in filter({ $0.renderable(type: type, data: data) }).enumerated() {
|
for (index, element) in filter({ $0.renderable(type: type, data: data) }).enumerated() {
|
||||||
if let storage = storages[safe: index] {
|
if let storage = storages[safe: index] {
|
||||||
element
|
await element
|
||||||
.widget(data: data, type: type)
|
.widget(data: data, type: type)
|
||||||
.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
@ -62,9 +62,9 @@ extension Array: AnyView where Element == AnyView {
|
|||||||
public func storages<Data>(
|
public func storages<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> [ViewStorage] where Data: ViewRenderData {
|
) async -> [ViewStorage] where Data: ViewRenderData {
|
||||||
compactMap { view in
|
await compactMap { view in
|
||||||
view.renderable(type: type, data: data) ? view.storage(data: data, type: type) : nil
|
await view.renderable(type: type, data: data) ? view.storage(data: data, type: type) : nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +96,78 @@ extension Array {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the first element of the sequence that satisfies the given predicate.
|
||||||
|
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
|
||||||
|
/// and returns a Boolean value indicating whether the element is a match.
|
||||||
|
/// - Returns: The first element of the sequence that satisfies `predicate`,
|
||||||
|
/// or `nil` if there is no element that satisfies `predicate`.
|
||||||
|
public func first(where predicate: (Element) async throws -> Bool) async rethrows -> Element? {
|
||||||
|
for element in self {
|
||||||
|
let matches = try await predicate(element)
|
||||||
|
if matches {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index of the first element of the sequence that satisfies the given predicate.
|
||||||
|
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
|
||||||
|
/// and returns a Boolean value indicating whether the element is a match.
|
||||||
|
/// - Returns: The index of the first element of the sequence that satisfies `predicate`,
|
||||||
|
/// or `nil` if there is no element that satisfies `predicate`.
|
||||||
|
public func firstIndex(where predicate: (Element) async throws -> Bool) async rethrows -> Int? {
|
||||||
|
for (index, element) in enumerated() {
|
||||||
|
let matches = try await predicate(element)
|
||||||
|
if matches {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the last element of the sequence that satisfies the given predicate.
|
||||||
|
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
|
||||||
|
/// and returns a Boolean value indicating whether the element is a match.
|
||||||
|
/// - Returns: The last element of the sequence that satisfies `predicate`,
|
||||||
|
/// or `nil` if there is no element that satisfies `predicate`.
|
||||||
|
public func last(where predicate: (Element) async throws -> Bool) async rethrows -> Element? {
|
||||||
|
try await reversed().first(where: predicate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate.
|
||||||
|
/// - Parameter predicate: A closure that takes an element of the sequence as its argument
|
||||||
|
/// and returns a Boolean value indicating whether the element is a match.
|
||||||
|
/// - Returns: Whether the sequence contains an element that satisfies `predicate`.
|
||||||
|
public func contains(where predicate: (Element) async throws -> Bool) async rethrows -> Bool {
|
||||||
|
try await first(where: predicate) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an array containing the results of mapping the given closure over the sequence’s elements.
|
||||||
|
/// Remove elements that are `nil`.
|
||||||
|
/// - Parameter transform: Transforms each element.
|
||||||
|
/// - Returns: The result.
|
||||||
|
public func compactMap<ElementOfResult>(
|
||||||
|
_ transform: (Element) async throws -> ElementOfResult?
|
||||||
|
) async rethrows -> [ElementOfResult] {
|
||||||
|
var result: [ElementOfResult] = []
|
||||||
|
for element in self {
|
||||||
|
if let element = try await transform(element) {
|
||||||
|
result.append(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an array containing the results of mapping the given closure over the sequence’s elements.
|
||||||
|
/// - Parameter transform: Transforms each element.
|
||||||
|
/// - Returns: The result.
|
||||||
|
public func map<ElementOfResult>(
|
||||||
|
_ transform: (Element) async throws -> ElementOfResult
|
||||||
|
) async rethrows -> [ElementOfResult] {
|
||||||
|
try await compactMap { try await transform($0) }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extend arrays.
|
/// Extend arrays.
|
||||||
|
|||||||
8
Sources/Model/Extensions/KeyPath.swift
Normal file
8
Sources/Model/Extensions/KeyPath.swift
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//
|
||||||
|
// KeyPath.swift
|
||||||
|
// Meta
|
||||||
|
//
|
||||||
|
// Created by david-swift on 18.09.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
extension KeyPath: @retroactive @unchecked Sendable { }
|
||||||
8
Sources/Model/Extensions/OpaquePointer.swift
Normal file
8
Sources/Model/Extensions/OpaquePointer.swift
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//
|
||||||
|
// OpaquePointer.swift
|
||||||
|
// Meta
|
||||||
|
//
|
||||||
|
// Created by david-swift on 29.09.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
extension OpaquePointer: @retroactive @unchecked Sendable { }
|
||||||
@ -21,7 +21,7 @@
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
public protocol App {
|
public protocol App: Sendable {
|
||||||
|
|
||||||
/// The app storage type.
|
/// The app storage type.
|
||||||
associatedtype Storage: AppStorage
|
associatedtype Storage: AppStorage
|
||||||
@ -60,26 +60,30 @@ extension App {
|
|||||||
public static func setupApp() -> Self {
|
public static func setupApp() -> Self {
|
||||||
var appInstance = self.init()
|
var appInstance = self.init()
|
||||||
appInstance.app = Storage(id: appInstance.id)
|
appInstance.app = Storage(id: appInstance.id)
|
||||||
appInstance.app.storage.app = { appInstance }
|
StateManager.addUpdateHandler { [appInstance] force in
|
||||||
StateManager.addUpdateHandler { force in
|
|
||||||
let updateProperties = force || appInstance.getState().contains { $0.value.content.update }
|
let updateProperties = force || appInstance.getState().contains { $0.value.content.update }
|
||||||
var removeIndices: [Int] = []
|
var removeIndices: [Int] = []
|
||||||
for (index, element) in appInstance.app.storage.sceneStorage.enumerated() {
|
for (index, element) in await appInstance.app.storage.sceneStorage.enumerated() {
|
||||||
if element.destroy {
|
if await element.destroy {
|
||||||
removeIndices.insert(index, at: 0)
|
removeIndices.insert(index, at: 0)
|
||||||
} else if let scene = appInstance.scene.first(
|
} else if let scene = await appInstance.scene.first(
|
||||||
where: { $0.id == element.id }
|
where: { await $0.id == element.id }
|
||||||
) as? Storage.SceneElementType as? SceneElement {
|
) as? Storage.SceneElementType as? SceneElement {
|
||||||
scene.update(element, app: appInstance.app, updateProperties: updateProperties)
|
scene.update(element, app: appInstance.app, updateProperties: updateProperties)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index in removeIndices {
|
for index in removeIndices {
|
||||||
appInstance.app.storage.sceneStorage.remove(at: index)
|
await appInstance.app.modifyStandardAppStorage { $0.sceneStorage.remove(at: index) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StateManager.appID = appInstance.id
|
StateManager.appID = appInstance.id
|
||||||
|
Task { [appInstance] in
|
||||||
let state = appInstance.getState()
|
let state = appInstance.getState()
|
||||||
appInstance.app.storage.stateStorage = state
|
await appInstance.app.modifyStandardAppStorage { storage in
|
||||||
|
storage.stateStorage = state
|
||||||
|
storage.app = { appInstance }
|
||||||
|
}
|
||||||
|
}
|
||||||
return appInstance
|
return appInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// The app storage protocol.
|
/// The app storage protocol.
|
||||||
public protocol AppStorage: AnyObject {
|
public protocol AppStorage: Actor, Sendable {
|
||||||
|
|
||||||
/// The type of scene elements (which should be backend-specific).
|
/// The type of scene elements (which should be backend-specific).
|
||||||
associatedtype SceneElementType
|
associatedtype SceneElementType
|
||||||
@ -20,10 +20,28 @@ public protocol AppStorage: AnyObject {
|
|||||||
|
|
||||||
/// Run the application.
|
/// Run the application.
|
||||||
/// - Parameter setup: A closure that is expected to be executed right at the beginning.
|
/// - Parameter setup: A closure that is expected to be executed right at the beginning.
|
||||||
func run(setup: @escaping () -> Void)
|
nonisolated func run(setup: @Sendable @escaping () -> Void)
|
||||||
|
|
||||||
/// Terminate the application.
|
/// Terminate the application.
|
||||||
func quit()
|
nonisolated func quit()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppStorage {
|
||||||
|
|
||||||
|
/// Modify the app storage.
|
||||||
|
/// - Parameter modify: The modifications.
|
||||||
|
func modifyStandardAppStorage(_ modify: (inout StandardAppStorage) -> Void) {
|
||||||
|
var copy = storage
|
||||||
|
modify(©)
|
||||||
|
self.storage = copy
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a scene.
|
||||||
|
/// - Parameter scene: The scene.
|
||||||
|
public func appendScene(_ scene: SceneStorage) {
|
||||||
|
modifyStandardAppStorage { $0.sceneStorage.append(scene) }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,13 +50,28 @@ extension AppStorage {
|
|||||||
|
|
||||||
/// Focus the scene element with a certain id (if supported). Create the element if it doesn't already exist.
|
/// Focus the scene element with a certain id (if supported). Create the element if it doesn't already exist.
|
||||||
/// - Parameter id: The element's id.
|
/// - Parameter id: The element's id.
|
||||||
public func showSceneElement(_ id: String) {
|
nonisolated public func showSceneElement(_ id: String) {
|
||||||
storage.sceneStorage.last { $0.id == id && !$0.destroy }?.show() ?? addSceneElement(id)
|
Task {
|
||||||
|
await storage.sceneStorage
|
||||||
|
.last { scene in
|
||||||
|
let destroy = await scene.destroy
|
||||||
|
return await scene.id == id && !destroy
|
||||||
|
}?
|
||||||
|
.show() ?? addSceneElement(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new scene element with the content of the scene element with a certain id.
|
/// Add a new scene element with the content of the scene element with a certain id.
|
||||||
/// - Parameter id: The element's id.
|
/// - Parameter id: The element's id.
|
||||||
public func addSceneElement(_ id: String) {
|
nonisolated public func addSceneElement(_ id: String) {
|
||||||
|
Task {
|
||||||
|
await internalAddSceneElement(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new scene element with the content of the scene element with a certain id.
|
||||||
|
/// - Parameter id: The element's id.
|
||||||
|
func internalAddSceneElement(_ id: String) {
|
||||||
if let element = storage.app?().scene.last(where: { $0.id == id }) {
|
if let element = storage.app?().scene.last(where: { $0.id == id }) {
|
||||||
let container = element.container(app: self)
|
let container = element.container(app: self)
|
||||||
storage.sceneStorage.append(container)
|
storage.sceneStorage.append(container)
|
||||||
@ -48,13 +81,13 @@ extension AppStorage {
|
|||||||
|
|
||||||
/// Focus the window with a certain id (if supported). Create the window if it doesn't already exist.
|
/// Focus the window with a certain id (if supported). Create the window if it doesn't already exist.
|
||||||
/// - Parameter id: The window's id.
|
/// - Parameter id: The window's id.
|
||||||
public func showWindow(_ id: String) {
|
nonisolated public func showWindow(_ id: String) {
|
||||||
showSceneElement(id)
|
showSceneElement(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new window with the content of the window template with a certain id.
|
/// Add a new window with the content of the window template with a certain id.
|
||||||
/// - Parameter id: The window template's id.
|
/// - Parameter id: The window template's id.
|
||||||
public func addWindow(_ id: String) {
|
nonisolated public func addWindow(_ id: String) {
|
||||||
addSceneElement(id)
|
addSceneElement(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// The app storage protocol.
|
/// The app storage protocol.
|
||||||
public struct StandardAppStorage {
|
public struct StandardAppStorage: Sendable {
|
||||||
|
|
||||||
/// The scene storage.
|
/// The scene storage.
|
||||||
public var sceneStorage: [SceneStorage] = []
|
public var sceneStorage: [SceneStorage] = []
|
||||||
@ -15,7 +15,7 @@ public struct StandardAppStorage {
|
|||||||
var stateStorage: [String: StateProtocol] = [:]
|
var stateStorage: [String: StateProtocol] = [:]
|
||||||
|
|
||||||
/// The scene.
|
/// The scene.
|
||||||
var app: (() -> any App)?
|
var app: (@Sendable () -> any App)?
|
||||||
|
|
||||||
/// Initialize the standard app storage.
|
/// Initialize the standard app storage.
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// A structure conforming to `SceneElement` can be added to an app's `scene` property.
|
/// A structure conforming to `SceneElement` can be added to an app's `scene` property.
|
||||||
public protocol SceneElement {
|
public protocol SceneElement: Sendable {
|
||||||
|
|
||||||
/// The window type's identifier.
|
/// The window type's identifier.
|
||||||
var id: String { get }
|
var id: String { get }
|
||||||
|
|||||||
@ -6,22 +6,22 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// Store a reference to a rendered scene element in a view storage.
|
/// Store a reference to a rendered scene element in a view storage.
|
||||||
public class SceneStorage {
|
public actor SceneStorage {
|
||||||
|
|
||||||
/// The scene element's identifier.
|
/// The scene element's identifier.
|
||||||
public var id: String
|
public var id: String
|
||||||
/// The pointer.
|
/// The pointer.
|
||||||
///
|
///
|
||||||
/// It can be a C pointer, a Swift class, or other information depending on the backend.
|
/// It can be a C pointer, a Swift class, or other information depending on the backend.
|
||||||
public var pointer: Any?
|
public var pointer: Sendable?
|
||||||
/// The scene element's view content.
|
/// The scene element's view content.
|
||||||
public var content: [String: [ViewStorage]]
|
var content: [String: [ViewStorage]]
|
||||||
/// Various properties of a scene element.
|
/// Various properties of a scene element.
|
||||||
public var fields: [String: Any] = [:]
|
var fields: [String: Sendable] = [:]
|
||||||
/// Whether the reference to the window should disappear in the next update.
|
/// Whether the reference to the window should disappear in the next update.
|
||||||
public var destroy = false
|
public var destroy = false
|
||||||
/// Show the scene element (including moving into the foreground, if possible).
|
/// Show the scene element (including moving into the foreground, if possible).
|
||||||
public var show: () -> Void
|
public var show: @Sendable () -> Void
|
||||||
/// The previous state of the scene element.
|
/// The previous state of the scene element.
|
||||||
public var previousState: SceneElement?
|
public var previousState: SceneElement?
|
||||||
|
|
||||||
@ -43,9 +43,9 @@ public class SceneStorage {
|
|||||||
/// - show: Function called when the scene element should be displayed.
|
/// - show: Function called when the scene element should be displayed.
|
||||||
public init(
|
public init(
|
||||||
id: String,
|
id: String,
|
||||||
pointer: Any?,
|
pointer: Sendable?,
|
||||||
content: [String: [ViewStorage]] = [:],
|
content: [String: [ViewStorage]] = [:],
|
||||||
show: @escaping () -> Void
|
show: @Sendable @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.pointer = pointer
|
self.pointer = pointer
|
||||||
@ -53,4 +53,65 @@ public class SceneStorage {
|
|||||||
self.show = show
|
self.show = show
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the pointer.
|
||||||
|
/// - Parameter value: The new pointer.
|
||||||
|
public func setPointer(_ value: Sendable?) {
|
||||||
|
pointer = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the element of a certain field.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: The key.
|
||||||
|
/// - value: The field.
|
||||||
|
public func setField(key: String, value: Sendable) {
|
||||||
|
fields[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a certain field.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: The key.
|
||||||
|
public func removeField(key: String) {
|
||||||
|
fields.removeValue(forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the element of a certain field.
|
||||||
|
/// - Parameter key: The key.
|
||||||
|
/// - Returns: The field.
|
||||||
|
public func getField(key: String) -> Sendable? {
|
||||||
|
fields[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the content elements under a certain key.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: The key.
|
||||||
|
/// - value: The content elements.
|
||||||
|
public func setContent(key: String, value: [ViewStorage]) {
|
||||||
|
content[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the content elements under a certain key.
|
||||||
|
/// - Parameter key: The key.
|
||||||
|
/// - Returns: The content elements.
|
||||||
|
public func getContent(key: String) -> [ViewStorage] {
|
||||||
|
content[key] ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the previous state.
|
||||||
|
/// - Parameter state: The state.
|
||||||
|
public func setPreviousState(_ state: SceneElement?) {
|
||||||
|
previousState = state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the scene will be destroyed.
|
||||||
|
/// - Parameter destroy: Whether the scene will be destroyed.
|
||||||
|
public func setDestroy(_ destroy: Bool) {
|
||||||
|
self.destroy = destroy
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the closure which shows the scene.
|
||||||
|
/// - Parameter show: The closure.
|
||||||
|
public func setShow(_ show: @Sendable @escaping () -> Void) {
|
||||||
|
self.show = show
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// The view type used for any form of a view.
|
/// The view type used for any form of a view.
|
||||||
public protocol AnyView {
|
public protocol AnyView: Sendable {
|
||||||
|
|
||||||
/// The view's content.
|
/// The view's content.
|
||||||
@ViewBuilder var viewContent: Body { get }
|
@ViewBuilder var viewContent: Body { get }
|
||||||
@ -38,8 +38,8 @@ extension AnyView {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
widget(data: data, type: type)
|
await widget(data: data, type: type)
|
||||||
.update(storage, data: data, updateProperties: updateProperties, type: type)
|
.update(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +51,8 @@ extension AnyView {
|
|||||||
public func storage<Data>(
|
public func storage<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
widget(data: data, type: type).container(data: data, type: type)
|
await widget(data: data, type: type).container(data: data, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrap the view into a widget.
|
/// Wrap the view into a widget.
|
||||||
|
|||||||
@ -10,14 +10,14 @@
|
|||||||
/// This will be used if you do not provide a custom ``Widget/update(_:data:updateProperties:type:)`` method
|
/// This will be used if you do not provide a custom ``Widget/update(_:data:updateProperties:type:)`` method
|
||||||
/// or call the ``Widget/updateProperties(_:updateProperties:)`` method in your custom update method.
|
/// or call the ``Widget/updateProperties(_:updateProperties:)`` method in your custom update method.
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
|
public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol where Value: Sendable {
|
||||||
|
|
||||||
/// The wrapped binding.
|
/// The wrapped binding.
|
||||||
public var wrappedValue: Binding<Value>
|
public var wrappedValue: Binding<Value>
|
||||||
/// Observe the UI element.
|
/// Observe the UI element.
|
||||||
var observe: (Pointer, Binding<Value>, ViewStorage) -> Void
|
var observe: @Sendable (Pointer, Binding<Value>, ViewStorage) async -> Void
|
||||||
/// Set the UI element's property.
|
/// Set the UI element's property.
|
||||||
var setValue: (Pointer, Value, ViewStorage) -> Void
|
var setValue: @Sendable (Pointer, Value, ViewStorage) async -> Void
|
||||||
|
|
||||||
/// Initialize a property.
|
/// Initialize a property.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@ -27,8 +27,8 @@ public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
|
|||||||
/// - pointer: The type of the pointer.
|
/// - pointer: The type of the pointer.
|
||||||
public init(
|
public init(
|
||||||
wrappedValue: Binding<Value>,
|
wrappedValue: Binding<Value>,
|
||||||
observe: @escaping (Pointer, Binding<Value>, ViewStorage) -> Void,
|
observe: @Sendable @escaping (Pointer, Binding<Value>, ViewStorage) async -> Void,
|
||||||
set setValue: @escaping (Pointer, Value, ViewStorage) -> Void,
|
set setValue: @Sendable @escaping (Pointer, Value, ViewStorage) async -> Void,
|
||||||
pointer: Pointer.Type
|
pointer: Pointer.Type
|
||||||
) {
|
) {
|
||||||
self.wrappedValue = wrappedValue
|
self.wrappedValue = wrappedValue
|
||||||
@ -44,14 +44,14 @@ public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
|
|||||||
/// - pointer: The type of the pointer.
|
/// - pointer: The type of the pointer.
|
||||||
public init(
|
public init(
|
||||||
wrappedValue: Binding<Value>,
|
wrappedValue: Binding<Value>,
|
||||||
observe: @escaping (Pointer, Binding<Value>) -> Void,
|
observe: @Sendable @escaping (Pointer, Binding<Value>) async -> Void,
|
||||||
set setValue: @escaping (Pointer, Value) -> Void,
|
set setValue: @Sendable @escaping (Pointer, Value) async -> Void,
|
||||||
pointer: Pointer.Type
|
pointer: Pointer.Type
|
||||||
) {
|
) {
|
||||||
self.init(
|
self.init(
|
||||||
wrappedValue: wrappedValue,
|
wrappedValue: wrappedValue,
|
||||||
observe: { pointer, value, _ in observe(pointer, value) },
|
observe: { pointer, value, _ in await observe(pointer, value) },
|
||||||
set: { pointer, value, _ in setValue(pointer, value) },
|
set: { pointer, value, _ in await setValue(pointer, value) },
|
||||||
pointer: pointer
|
pointer: pointer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -59,19 +59,19 @@ public struct BindingProperty<Value, Pointer>: BindingPropertyProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The binding property protocol.
|
/// The binding property protocol.
|
||||||
protocol BindingPropertyProtocol {
|
protocol BindingPropertyProtocol: Sendable {
|
||||||
|
|
||||||
/// The binding's wrapped value.
|
/// The binding's wrapped value.
|
||||||
associatedtype Value
|
associatedtype Value: Sendable
|
||||||
/// The storage's pointer.
|
/// The storage's pointer.
|
||||||
associatedtype Pointer
|
associatedtype Pointer
|
||||||
|
|
||||||
/// The wrapped value.
|
/// The wrapped value.
|
||||||
var wrappedValue: Binding<Value> { get }
|
var wrappedValue: Binding<Value> { get }
|
||||||
/// Observe the property.
|
/// Observe the property.
|
||||||
var observe: (Pointer, Binding<Value>, ViewStorage) -> Void { get }
|
var observe: @Sendable (Pointer, Binding<Value>, ViewStorage) async -> Void { get }
|
||||||
/// Set the property.
|
/// Set the property.
|
||||||
var setValue: (Pointer, Value, ViewStorage) -> Void { get }
|
var setValue: @Sendable (Pointer, Value, ViewStorage) async -> Void { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,12 +84,12 @@ extension Widget {
|
|||||||
func setBindingProperty<Property>(
|
func setBindingProperty<Property>(
|
||||||
property: Property,
|
property: Property,
|
||||||
storage: ViewStorage
|
storage: ViewStorage
|
||||||
) where Property: BindingPropertyProtocol {
|
) async where Property: BindingPropertyProtocol {
|
||||||
if let optional = property.wrappedValue.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil {
|
if let optional = property.wrappedValue.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let pointer = storage.pointer as? Property.Pointer {
|
if let pointer = await storage.pointer as? Property.Pointer {
|
||||||
property.setValue(pointer, property.wrappedValue.wrappedValue, storage)
|
await property.setValue(pointer, property.wrappedValue.wrappedValue, storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,10 +10,10 @@
|
|||||||
/// This will be used if you do not provide a custom ``Widget/update(_:data:updateProperties:type:)`` method
|
/// This will be used if you do not provide a custom ``Widget/update(_:data:updateProperties:type:)`` method
|
||||||
/// or call the ``Widget/updateProperties(_:updateProperties:)`` method in your custom update method.
|
/// or call the ``Widget/updateProperties(_:updateProperties:)`` method in your custom update method.
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct Property<Value, Pointer>: PropertyProtocol {
|
public struct Property<Value, Pointer>: PropertyProtocol where Value: Sendable {
|
||||||
|
|
||||||
/// The function applying the property to the UI.
|
/// The function applying the property to the UI.
|
||||||
public var setProperty: (Pointer, Value, ViewStorage) -> Void
|
public var setProperty: @Sendable (Pointer, Value, ViewStorage) async -> Void
|
||||||
/// The wrapped value.
|
/// The wrapped value.
|
||||||
public var wrappedValue: Value
|
public var wrappedValue: Value
|
||||||
/// The update strategy.
|
/// The update strategy.
|
||||||
@ -27,7 +27,7 @@ public struct Property<Value, Pointer>: PropertyProtocol {
|
|||||||
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
||||||
public init(
|
public init(
|
||||||
wrappedValue: Value,
|
wrappedValue: Value,
|
||||||
set setProperty: @escaping (Pointer, Value, ViewStorage) -> Void,
|
set setProperty: @Sendable @escaping (Pointer, Value, ViewStorage) async -> Void,
|
||||||
pointer: Pointer.Type,
|
pointer: Pointer.Type,
|
||||||
updateStrategy: UpdateStrategy = .automatic
|
updateStrategy: UpdateStrategy = .automatic
|
||||||
) {
|
) {
|
||||||
@ -44,13 +44,13 @@ public struct Property<Value, Pointer>: PropertyProtocol {
|
|||||||
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
||||||
public init(
|
public init(
|
||||||
wrappedValue: Value,
|
wrappedValue: Value,
|
||||||
set setProperty: @escaping (Pointer, Value) -> Void,
|
set setProperty: @Sendable @escaping (Pointer, Value) async -> Void,
|
||||||
pointer: Pointer.Type,
|
pointer: Pointer.Type,
|
||||||
updateStrategy: UpdateStrategy = .automatic
|
updateStrategy: UpdateStrategy = .automatic
|
||||||
) {
|
) {
|
||||||
self.init(
|
self.init(
|
||||||
wrappedValue: wrappedValue,
|
wrappedValue: wrappedValue,
|
||||||
set: { pointer, value, _ in setProperty(pointer, value) },
|
set: { pointer, value, _ in await setProperty(pointer, value) },
|
||||||
pointer: pointer,
|
pointer: pointer,
|
||||||
updateStrategy: updateStrategy
|
updateStrategy: updateStrategy
|
||||||
)
|
)
|
||||||
@ -66,13 +66,13 @@ extension Property where Value: OptionalProtocol {
|
|||||||
/// - pointer: The type of the pointer.
|
/// - pointer: The type of the pointer.
|
||||||
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
||||||
public init(
|
public init(
|
||||||
set setProperty: @escaping (Pointer, Value.Wrapped, ViewStorage) -> Void,
|
set setProperty: @Sendable @escaping (Pointer, Value.Wrapped, ViewStorage) async -> Void,
|
||||||
pointer: Pointer.Type,
|
pointer: Pointer.Type,
|
||||||
updateStrategy: UpdateStrategy = .automatic
|
updateStrategy: UpdateStrategy = .automatic
|
||||||
) {
|
) {
|
||||||
self.setProperty = { pointer, value, storage in
|
self.setProperty = { pointer, value, storage in
|
||||||
if let value = value.optionalValue {
|
if let value = value.optionalValue {
|
||||||
setProperty(pointer, value, storage)
|
await setProperty(pointer, value, storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wrappedValue = nil
|
wrappedValue = nil
|
||||||
@ -86,12 +86,12 @@ extension Property where Value: OptionalProtocol {
|
|||||||
/// - pointer: The type of the pointer.
|
/// - pointer: The type of the pointer.
|
||||||
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
/// - updateStrategy: The update strategy, this should be ``UpdateStrategy/automatic`` in most cases.
|
||||||
public init(
|
public init(
|
||||||
set setProperty: @escaping (Pointer, Value.Wrapped) -> Void,
|
set setProperty: @Sendable @escaping (Pointer, Value.Wrapped) async -> Void,
|
||||||
pointer: Pointer.Type,
|
pointer: Pointer.Type,
|
||||||
updateStrategy: UpdateStrategy = .automatic
|
updateStrategy: UpdateStrategy = .automatic
|
||||||
) {
|
) {
|
||||||
self.init(
|
self.init(
|
||||||
set: { pointer, value, _ in setProperty(pointer, value) },
|
set: { pointer, value, _ in await setProperty(pointer, value) },
|
||||||
pointer: pointer,
|
pointer: pointer,
|
||||||
updateStrategy: updateStrategy
|
updateStrategy: updateStrategy
|
||||||
)
|
)
|
||||||
@ -100,7 +100,7 @@ extension Property where Value: OptionalProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The property protocol.
|
/// The property protocol.
|
||||||
protocol PropertyProtocol {
|
protocol PropertyProtocol: Sendable {
|
||||||
|
|
||||||
/// The type of the wrapped value.
|
/// The type of the wrapped value.
|
||||||
associatedtype Value
|
associatedtype Value
|
||||||
@ -110,14 +110,14 @@ protocol PropertyProtocol {
|
|||||||
/// The wrapped value.
|
/// The wrapped value.
|
||||||
var wrappedValue: Value { get }
|
var wrappedValue: Value { get }
|
||||||
/// Set the property.
|
/// Set the property.
|
||||||
var setProperty: (Pointer, Value, ViewStorage) -> Void { get }
|
var setProperty: @Sendable (Pointer, Value, ViewStorage) async -> Void { get }
|
||||||
/// The update strategy.
|
/// The update strategy.
|
||||||
var updateStrategy: UpdateStrategy { get }
|
var updateStrategy: UpdateStrategy { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The update strategy for properties.
|
/// The update strategy for properties.
|
||||||
public enum UpdateStrategy {
|
public enum UpdateStrategy: Sendable {
|
||||||
|
|
||||||
/// If equatable, update only when the value changed.
|
/// If equatable, update only when the value changed.
|
||||||
/// If not equatable, this is equivalent to ``UpdateStrategy/always``.
|
/// If not equatable, this is equivalent to ``UpdateStrategy/always``.
|
||||||
@ -140,10 +140,10 @@ extension Widget {
|
|||||||
public func container<Data>(
|
public func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
let storage = ViewStorage(initializeWidget())
|
let storage = ViewStorage(initializeWidget())
|
||||||
initProperties(storage, data: data, type: type)
|
await initProperties(storage, data: data, type: type)
|
||||||
update(storage, data: data, updateProperties: true, type: type)
|
await update(storage, data: data, updateProperties: true, type: type)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,10 +160,10 @@ extension Widget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
self.updateProperties(storage, data: data, updateProperties: updateProperties, type: type)
|
await self.updateProperties(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
if updateProperties {
|
if updateProperties {
|
||||||
storage.previousState = self
|
await storage.setPreviousState(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,16 +176,16 @@ extension Widget {
|
|||||||
_ storage: ViewStorage,
|
_ storage: ViewStorage,
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
let mirror = Mirror(reflecting: self)
|
let mirror = Mirror(reflecting: self)
|
||||||
for property in mirror.children {
|
for property in mirror.children {
|
||||||
if let value = property.value as? any ViewPropertyProtocol {
|
if let value = property.value as? any ViewPropertyProtocol {
|
||||||
let subview = value.wrappedValue.storage(data: data, type: type)
|
let subview = await value.wrappedValue.storage(data: data, type: type)
|
||||||
initViewProperty(value, view: subview, parent: storage)
|
await initViewProperty(value, view: subview, parent: storage)
|
||||||
storage.content[property.label ?? .mainContent] = [subview]
|
await storage.setContent(key: property.label ?? .mainContent, value: [subview])
|
||||||
}
|
}
|
||||||
if let value = property.value as? any BindingPropertyProtocol {
|
if let value = property.value as? any BindingPropertyProtocol {
|
||||||
initBindingProperty(value, parent: storage)
|
await initBindingProperty(value, parent: storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,9 +199,10 @@ extension Widget {
|
|||||||
_ value: Property,
|
_ value: Property,
|
||||||
view: ViewStorage,
|
view: ViewStorage,
|
||||||
parent: ViewStorage
|
parent: ViewStorage
|
||||||
) where Property: ViewPropertyProtocol {
|
) async where Property: ViewPropertyProtocol {
|
||||||
if let view = view.pointer as? Property.ViewPointer, let pointer = parent.pointer as? Property.Pointer {
|
if let view = await view.pointer as? Property.ViewPointer,
|
||||||
value.setView(pointer, view)
|
let pointer = await parent.pointer as? Property.Pointer {
|
||||||
|
await value.setView(pointer, view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,9 +210,12 @@ extension Widget {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - value: The property.
|
/// - value: The property.
|
||||||
/// - parent: The view storage.
|
/// - parent: The view storage.
|
||||||
func initBindingProperty<Property>(_ value: Property, parent: ViewStorage) where Property: BindingPropertyProtocol {
|
func initBindingProperty<Property>(
|
||||||
if let view = parent.pointer as? Property.Pointer {
|
_ value: Property,
|
||||||
value.observe(
|
parent: ViewStorage
|
||||||
|
) async where Property: BindingPropertyProtocol {
|
||||||
|
if let view = await parent.pointer as? Property.Pointer {
|
||||||
|
await value.observe(
|
||||||
view,
|
view,
|
||||||
.init {
|
.init {
|
||||||
value.wrappedValue.wrappedValue
|
value.wrappedValue.wrappedValue
|
||||||
@ -237,14 +241,20 @@ extension Widget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
let mirror = Mirror(reflecting: self)
|
let mirror = Mirror(reflecting: self)
|
||||||
updateNotEquatable(mirror: mirror, storage: storage, data: data, updateProperties: updateProperties, type: type)
|
await updateNotEquatable(
|
||||||
|
mirror: mirror,
|
||||||
|
storage: storage,
|
||||||
|
data: data,
|
||||||
|
updateProperties: updateProperties,
|
||||||
|
type: type
|
||||||
|
)
|
||||||
guard updateProperties else {
|
guard updateProperties else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
updateAlwaysWhenStateUpdate(mirror: mirror, storage: storage)
|
await updateAlwaysWhenStateUpdate(mirror: mirror, storage: storage)
|
||||||
updateEquatable(mirror: mirror, storage: storage)
|
await updateEquatable(mirror: mirror, storage: storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the properties which are not equatable and should always be updated (e.g. closures).
|
/// Update the properties which are not equatable and should always be updated (e.g. closures).
|
||||||
@ -260,20 +270,21 @@ extension Widget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
for property in mirror.children {
|
for property in mirror.children {
|
||||||
if let value = property.value as? any PropertyProtocol {
|
if let value = property.value as? any PropertyProtocol {
|
||||||
if value.updateStrategy == .always ||
|
if value.updateStrategy == .always ||
|
||||||
value.wrappedValue as? any Equatable == nil && value.updateStrategy != .alwaysWhenStateUpdate {
|
value.wrappedValue as? any Equatable == nil && value.updateStrategy != .alwaysWhenStateUpdate {
|
||||||
setProperty(property: value, storage: storage)
|
await setProperty(property: value, storage: storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let value = property.value as? any ViewPropertyProtocol,
|
if let value = property.value as? any ViewPropertyProtocol,
|
||||||
let storage = storage.content[property.label ?? .mainContent]?.first {
|
let storage = await storage.getContent(key: property.label ?? .mainContent).first {
|
||||||
value.wrappedValue.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
await value.wrappedValue
|
||||||
|
.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
if let value = property.value as? any BindingPropertyProtocol {
|
if let value = property.value as? any BindingPropertyProtocol {
|
||||||
setBindingProperty(property: value, storage: storage)
|
await setBindingProperty(property: value, storage: storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,11 +296,11 @@ extension Widget {
|
|||||||
/// - storage: The view storage.
|
/// - storage: The view storage.
|
||||||
///
|
///
|
||||||
/// Initialize the ``Property`` property wrapper with the ``UpdateStrategy/alwaysWhenStateUpdate``.
|
/// Initialize the ``Property`` property wrapper with the ``UpdateStrategy/alwaysWhenStateUpdate``.
|
||||||
func updateAlwaysWhenStateUpdate(mirror: Mirror, storage: ViewStorage) {
|
func updateAlwaysWhenStateUpdate(mirror: Mirror, storage: ViewStorage) async {
|
||||||
for property in mirror.children {
|
for property in mirror.children {
|
||||||
if let value = property.value as? any PropertyProtocol {
|
if let value = property.value as? any PropertyProtocol {
|
||||||
if value.updateStrategy == .alwaysWhenStateUpdate {
|
if value.updateStrategy == .alwaysWhenStateUpdate {
|
||||||
setProperty(property: value, storage: storage)
|
await setProperty(property: value, storage: storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,8 +310,8 @@ extension Widget {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - mirror: A mirror of the widget.
|
/// - mirror: A mirror of the widget.
|
||||||
/// - storage: The view storage.
|
/// - storage: The view storage.
|
||||||
func updateEquatable(mirror: Mirror, storage: ViewStorage) {
|
func updateEquatable(mirror: Mirror, storage: ViewStorage) async {
|
||||||
let previousState: Mirror.Children? = if let previousState = storage.previousState {
|
let previousState: Mirror.Children? = if let previousState = await storage.previousState {
|
||||||
Mirror(reflecting: previousState).children
|
Mirror(reflecting: previousState).children
|
||||||
} else {
|
} else {
|
||||||
nil
|
nil
|
||||||
@ -317,7 +328,7 @@ extension Widget {
|
|||||||
update = false
|
update = false
|
||||||
}
|
}
|
||||||
if update {
|
if update {
|
||||||
setProperty(property: value, storage: storage)
|
await setProperty(property: value, storage: storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -354,12 +365,12 @@ extension Widget {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - property: The property.
|
/// - property: The property.
|
||||||
/// - storage: The view storage.
|
/// - storage: The view storage.
|
||||||
func setProperty<Property>(property: Property, storage: ViewStorage) where Property: PropertyProtocol {
|
func setProperty<Property>(property: Property, storage: ViewStorage) async where Property: PropertyProtocol {
|
||||||
if let optional = property.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil {
|
if let optional = property.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let pointer = storage.pointer as? Property.Pointer {
|
if let pointer = await storage.pointer as? Property.Pointer {
|
||||||
property.setProperty(pointer, property.wrappedValue, storage)
|
await property.setProperty(pointer, property.wrappedValue, storage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ public struct ViewProperty<Pointer, ViewPointer>: ViewPropertyProtocol {
|
|||||||
/// The wrapped value.
|
/// The wrapped value.
|
||||||
public var wrappedValue: Body = []
|
public var wrappedValue: Body = []
|
||||||
/// Set the view.
|
/// Set the view.
|
||||||
var setView: (Pointer, ViewPointer) -> Void
|
var setView: @Sendable (Pointer, ViewPointer) async -> Void
|
||||||
|
|
||||||
/// Initialize a property.
|
/// Initialize a property.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@ -23,7 +23,7 @@ public struct ViewProperty<Pointer, ViewPointer>: ViewPropertyProtocol {
|
|||||||
/// - pointer: The pointer type of the parent view (usually a concrete view type).
|
/// - pointer: The pointer type of the parent view (usually a concrete view type).
|
||||||
/// - subview: The pointer type of the child view (usually a protocol, view class, or similar).
|
/// - subview: The pointer type of the child view (usually a protocol, view class, or similar).
|
||||||
public init(
|
public init(
|
||||||
set setView: @escaping (Pointer, ViewPointer) -> Void,
|
set setView: @Sendable @escaping (Pointer, ViewPointer) async -> Void,
|
||||||
pointer: Pointer.Type,
|
pointer: Pointer.Type,
|
||||||
subview: ViewPointer.Type
|
subview: ViewPointer.Type
|
||||||
) {
|
) {
|
||||||
@ -35,7 +35,7 @@ public struct ViewProperty<Pointer, ViewPointer>: ViewPropertyProtocol {
|
|||||||
/// The view property protocol.
|
/// The view property protocol.
|
||||||
///
|
///
|
||||||
/// Do not use for wrapper widgets.
|
/// Do not use for wrapper widgets.
|
||||||
protocol ViewPropertyProtocol {
|
protocol ViewPropertyProtocol: Sendable {
|
||||||
|
|
||||||
/// The type of the view's pointer.
|
/// The type of the view's pointer.
|
||||||
associatedtype Pointer
|
associatedtype Pointer
|
||||||
@ -45,6 +45,6 @@ protocol ViewPropertyProtocol {
|
|||||||
/// The wrapped value.
|
/// The wrapped value.
|
||||||
var wrappedValue: Body { get }
|
var wrappedValue: Body { get }
|
||||||
/// Set the view.
|
/// Set the view.
|
||||||
var setView: (Pointer, ViewPointer) -> Void { get }
|
var setView: @Sendable (Pointer, ViewPointer) async -> Void { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,18 +6,18 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// Store a reference to a rendered view in a view storage.
|
/// Store a reference to a rendered view in a view storage.
|
||||||
public class ViewStorage {
|
public actor ViewStorage: Sendable {
|
||||||
|
|
||||||
/// The pointer.
|
/// The pointer.
|
||||||
///
|
///
|
||||||
/// It can be a C pointer, a Swift class, or other information depending on the backend.
|
/// It can be a C pointer, a Swift class, or other information depending on the backend.
|
||||||
public var pointer: Any?
|
public var pointer: Sendable?
|
||||||
/// The view's content for container widgets.
|
/// The view's content for container widgets.
|
||||||
public var content: [String: [ViewStorage]]
|
var content: [String: [ViewStorage]]
|
||||||
/// The view's state (used in `StateWrapper`).
|
/// The view's state (used in `StateWrapper`).
|
||||||
var state: [String: StateProtocol] = [:]
|
var state: [String: StateProtocol] = [:]
|
||||||
/// Various properties of a widget.
|
/// Various properties of a widget.
|
||||||
public var fields: [String: Any] = [:]
|
var fields: [String: Sendable] = [:]
|
||||||
/// The previous state of the widget.
|
/// The previous state of the widget.
|
||||||
public var previousState: Widget?
|
public var previousState: Widget?
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ public class ViewStorage {
|
|||||||
/// - pointer: The pointer to the widget, its type depends on the backend.
|
/// - pointer: The pointer to the widget, its type depends on the backend.
|
||||||
/// - content: The view's content for container widgets.
|
/// - content: The view's content for container widgets.
|
||||||
public init(
|
public init(
|
||||||
_ pointer: Any?,
|
_ pointer: Sendable?,
|
||||||
content: [String: [ViewStorage]] = [:],
|
content: [String: [ViewStorage]] = [:],
|
||||||
state: Widget? = nil
|
state: Widget? = nil
|
||||||
) {
|
) {
|
||||||
@ -45,4 +45,88 @@ public class ViewStorage {
|
|||||||
self.previousState = state
|
self.previousState = state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize a view storage.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - pointer: The opaque pointer.
|
||||||
|
/// - content: The view's content for container widgets.
|
||||||
|
public init(
|
||||||
|
_ pointer: OpaquePointer?,
|
||||||
|
content: [String: [ViewStorage]] = [:],
|
||||||
|
state: Widget? = nil
|
||||||
|
) {
|
||||||
|
self.pointer = pointer
|
||||||
|
self.content = content
|
||||||
|
self.previousState = state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the state under a certain key.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: The key.
|
||||||
|
/// - value: The state.
|
||||||
|
func setState(key: String, value: StateProtocol) {
|
||||||
|
state[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the state.
|
||||||
|
/// - Parameter state: The state.
|
||||||
|
func setState(_ state: [String: StateProtocol]) {
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the state under a certain key.
|
||||||
|
/// - Parameter key: The key.
|
||||||
|
/// - Returns: The state.
|
||||||
|
func getState(key: String) -> StateProtocol? {
|
||||||
|
state[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the pointer.
|
||||||
|
/// - Parameter value: The new pointer.
|
||||||
|
public func setPointer(_ value: Sendable?) {
|
||||||
|
pointer = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the element of a certain field.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: The key.
|
||||||
|
/// - value: The field.
|
||||||
|
public func setField(key: String, value: Sendable) {
|
||||||
|
fields[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove a certain field.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: The key.
|
||||||
|
public func removeField(key: String) {
|
||||||
|
fields.removeValue(forKey: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the element of a certain field.
|
||||||
|
/// - Parameter key: The key.
|
||||||
|
/// - Returns: The field.
|
||||||
|
public func getField(key: String) -> Sendable? {
|
||||||
|
fields[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the content elements under a certain key.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - key: The key.
|
||||||
|
/// - value: The content elements.
|
||||||
|
public func setContent(key: String, value: [ViewStorage]) {
|
||||||
|
content[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the content elements under a certain key.
|
||||||
|
/// - Parameter key: The key.
|
||||||
|
/// - Returns: The content elements.
|
||||||
|
public func getContent(key: String) -> [ViewStorage] {
|
||||||
|
content[key] ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the previous state.
|
||||||
|
/// - Parameter state: The state.
|
||||||
|
public func setPreviousState(_ state: Widget?) {
|
||||||
|
previousState = state
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ public protocol Widget: AnyView {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData
|
) async -> ViewStorage where Data: ViewRenderData
|
||||||
|
|
||||||
/// Update the stored content.
|
/// Update the stored content.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@ -32,14 +32,14 @@ public protocol Widget: AnyView {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData
|
) async where Data: ViewRenderData
|
||||||
|
|
||||||
/// Get the widget.
|
/// Get the widget.
|
||||||
/// - Returns: The widget.
|
/// - Returns: The widget.
|
||||||
///
|
///
|
||||||
/// Define this function only if you do not define ``Widget/container(data:type:)``.
|
/// Define this function only if you do not define ``Widget/container(data:type:)``.
|
||||||
/// Otherwise, it will not have an effect.
|
/// Otherwise, it will not have an effect.
|
||||||
func initializeWidget() -> Any
|
func initializeWidget() -> Sendable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ extension Widget {
|
|||||||
|
|
||||||
/// Print a warning if the widget does not set this function but it gets accessed.
|
/// Print a warning if the widget does not set this function but it gets accessed.
|
||||||
/// - Returns: A dummy pointer.
|
/// - Returns: A dummy pointer.
|
||||||
public func initializeWidget() -> Any {
|
public func initializeWidget() -> Sendable {
|
||||||
print("Warning: Define initialize widget function or container function for \(Self.self)")
|
print("Warning: Define initialize widget function or container function for \(Self.self)")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,16 +6,16 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
/// Data passed to widgets when initializing or updating the container.
|
/// Data passed to widgets when initializing or updating the container.
|
||||||
public struct WidgetData {
|
public struct WidgetData: Sendable {
|
||||||
|
|
||||||
/// The view modifiers.
|
/// The view modifiers.
|
||||||
public var modifiers: [(AnyView) -> AnyView] = []
|
public var modifiers: [@Sendable (AnyView) -> AnyView] = []
|
||||||
/// The scene storage of the parent scene element.
|
/// The scene storage of the parent scene element.
|
||||||
public var sceneStorage: SceneStorage
|
public var sceneStorage: SceneStorage
|
||||||
/// The app storage of the parent app.
|
/// The app storage of the parent app.
|
||||||
public var appStorage: any AppStorage
|
public var appStorage: any AppStorage
|
||||||
/// Fields for custom data.
|
/// Fields for custom data.
|
||||||
public var fields: [String: Any] = [:]
|
public var fields: [String: Sendable] = [:]
|
||||||
|
|
||||||
/// Modify the data so that there are no modifiers.
|
/// Modify the data so that there are no modifiers.
|
||||||
public var noModifiers: Self {
|
public var noModifiers: Self {
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
struct AppearObserver: ConvenienceWidget {
|
struct AppearObserver: ConvenienceWidget {
|
||||||
|
|
||||||
/// The custom code to edit the widget.
|
/// The custom code to edit the widget.
|
||||||
var modify: (ViewStorage) -> Void
|
var modify: @Sendable (ViewStorage) async -> Void
|
||||||
/// The wrapped view.
|
/// The wrapped view.
|
||||||
var content: AnyView
|
var content: AnyView
|
||||||
|
|
||||||
@ -21,9 +21,9 @@ struct AppearObserver: ConvenienceWidget {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
let storage = content.storage(data: data, type: type)
|
let storage = await content.storage(data: data, type: type)
|
||||||
modify(storage)
|
await modify(storage)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,8 +38,8 @@ struct AppearObserver: ConvenienceWidget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -50,15 +50,15 @@ extension AnyView {
|
|||||||
/// Run a function on the widget when it appears for the first time.
|
/// Run a function on the widget when it appears for the first time.
|
||||||
/// - Parameter closure: The function.
|
/// - Parameter closure: The function.
|
||||||
/// - Returns: A view.
|
/// - Returns: A view.
|
||||||
public func inspectOnAppear(_ closure: @escaping (ViewStorage) -> Void) -> AnyView {
|
public func inspectOnAppear(_ closure: @Sendable @escaping (ViewStorage) async -> Void) -> AnyView {
|
||||||
AppearObserver(modify: closure, content: self)
|
AppearObserver(modify: closure, content: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a function when the view appears for the first time.
|
/// Run a function when the view appears for the first time.
|
||||||
/// - Parameter closure: The function.
|
/// - Parameter closure: The function.
|
||||||
/// - Returns: A view.
|
/// - Returns: A view.
|
||||||
public func onAppear(_ closure: @escaping () -> Void) -> AnyView {
|
public func onAppear(_ closure: @Sendable @escaping () async -> Void) -> AnyView {
|
||||||
inspectOnAppear { _ in closure() }
|
inspectOnAppear { _ in await closure() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {
|
|||||||
/// The wrapped view.
|
/// The wrapped view.
|
||||||
var content: AnyView
|
var content: AnyView
|
||||||
/// The closure for the modification.
|
/// The closure for the modification.
|
||||||
var modify: (Content) -> AnyView
|
var modify: @Sendable (Content) -> AnyView
|
||||||
|
|
||||||
/// The view storage.
|
/// The view storage.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@ -21,8 +21,8 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
content.storage(data: data.modify { $0.modifiers += [modifyView] }, type: type)
|
await content.storage(data: data.modify { $0.modifiers += [modifyView] }, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the stored content.
|
/// Update the stored content.
|
||||||
@ -36,8 +36,8 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
content
|
await content
|
||||||
.updateStorage(
|
.updateStorage(
|
||||||
storage,
|
storage,
|
||||||
data: data.modify { $0.modifiers += [modifyView] },
|
data: data.modify { $0.modifiers += [modifyView] },
|
||||||
@ -68,7 +68,7 @@ extension AnyView {
|
|||||||
/// - Returns: A view.
|
/// - Returns: A view.
|
||||||
public func modifyContent<Content>(
|
public func modifyContent<Content>(
|
||||||
_ type: Content.Type,
|
_ type: Content.Type,
|
||||||
modify: @escaping (Content) -> AnyView
|
modify: @Sendable @escaping (Content) -> AnyView
|
||||||
) -> AnyView where Content: AnyView {
|
) -> AnyView where Content: AnyView {
|
||||||
ContentModifier(content: self, modify: modify)
|
ContentModifier(content: self, modify: modify)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,9 +23,9 @@ struct DummyEitherView: ConvenienceWidget {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
|
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
|
||||||
let storage = content.storage(data: data, type: type)
|
let storage = await content.storage(data: data, type: type)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,9 +40,9 @@ struct DummyEitherView: ConvenienceWidget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
|
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
|
||||||
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,8 +21,8 @@ struct Freeze: ConvenienceWidget {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
content.storage(data: data, type: type)
|
await content.storage(data: data, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the stored content.
|
/// Update the stored content.
|
||||||
@ -36,11 +36,11 @@ struct Freeze: ConvenienceWidget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
guard !freeze else {
|
guard !freeze else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
struct InspectorWrapper: ConvenienceWidget {
|
struct InspectorWrapper: ConvenienceWidget {
|
||||||
|
|
||||||
/// The custom code to edit the widget.
|
/// The custom code to edit the widget.
|
||||||
var modify: (ViewStorage, Bool) -> Void
|
var modify: @Sendable (ViewStorage, Bool) async -> Void
|
||||||
/// The wrapped view.
|
/// The wrapped view.
|
||||||
var content: AnyView
|
var content: AnyView
|
||||||
|
|
||||||
@ -21,9 +21,9 @@ struct InspectorWrapper: ConvenienceWidget {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
let storage = content.storage(data: data, type: type)
|
let storage = await content.storage(data: data, type: type)
|
||||||
modify(storage, true)
|
await modify(storage, true)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +38,9 @@ struct InspectorWrapper: ConvenienceWidget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
await content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
modify(storage, updateProperties)
|
await modify(storage, updateProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -51,15 +51,15 @@ extension AnyView {
|
|||||||
/// Run a custom code accessing the view's storage when initializing and updating the view.
|
/// Run a custom code accessing the view's storage when initializing and updating the view.
|
||||||
/// - Parameter modify: Modify the storage. The boolean indicates whether state in parent views changed.
|
/// - Parameter modify: Modify the storage. The boolean indicates whether state in parent views changed.
|
||||||
/// - Returns: A view.
|
/// - Returns: A view.
|
||||||
public func inspect(_ modify: @escaping (ViewStorage, Bool) -> Void) -> AnyView {
|
public func inspect(_ modify: @Sendable @escaping (ViewStorage, Bool) async -> Void) -> AnyView {
|
||||||
InspectorWrapper(modify: modify, content: self)
|
InspectorWrapper(modify: modify, content: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a function when the view gets updated.
|
/// Run a function when the view gets updated.
|
||||||
/// - Parameter onUpdate: The function.
|
/// - Parameter onUpdate: The function.
|
||||||
/// - Returns: A view.
|
/// - Returns: A view.
|
||||||
public func onUpdate(_ onUpdate: @escaping () -> Void) -> AnyView {
|
public func onUpdate(_ onUpdate: @Sendable @escaping () async -> Void) -> AnyView {
|
||||||
inspect { _, _ in onUpdate() }
|
inspect { _, _ in await onUpdate() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,8 @@ struct ModifierStopper: ConvenienceWidget {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
content.storage(data: data.noModifiers, type: type)
|
await content.storage(data: data.noModifiers, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the stored content.
|
/// Update the stored content.
|
||||||
@ -34,8 +34,8 @@ struct ModifierStopper: ConvenienceWidget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
content.updateStorage(storage, data: data.noModifiers, updateProperties: updateProperties, type: type)
|
await content.updateStorage(storage, data: data.noModifiers, updateProperties: updateProperties, type: type)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,13 +9,13 @@
|
|||||||
struct StateWrapper: ConvenienceWidget {
|
struct StateWrapper: ConvenienceWidget {
|
||||||
|
|
||||||
/// The content.
|
/// The content.
|
||||||
var content: () -> Body
|
var content: @Sendable () -> Body
|
||||||
/// The state information (from properties with the `State` wrapper).
|
/// The state information (from properties with the `State` wrapper).
|
||||||
var state: [String: StateProtocol] = [:]
|
var state: [String: StateProtocol] = [:]
|
||||||
|
|
||||||
/// Initialize a `StateWrapper`.
|
/// Initialize a `StateWrapper`.
|
||||||
/// - Parameter content: The view content.
|
/// - Parameter content: The view content.
|
||||||
init(@ViewBuilder content: @escaping () -> Body) {
|
init(@ViewBuilder content: @Sendable @escaping () -> Body) {
|
||||||
self.content = content
|
self.content = content
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ struct StateWrapper: ConvenienceWidget {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - content: The view content.
|
/// - content: The view content.
|
||||||
/// - state: The state information.
|
/// - state: The state information.
|
||||||
init(content: @escaping () -> Body, state: [String: StateProtocol]) {
|
init(content: @Sendable @escaping () -> Body, state: [String: StateProtocol]) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
@ -40,10 +40,10 @@ struct StateWrapper: ConvenienceWidget {
|
|||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) where Data: ViewRenderData {
|
) async where Data: ViewRenderData {
|
||||||
var updateProperties = updateProperties
|
var updateProperties = updateProperties
|
||||||
for property in state {
|
for property in state {
|
||||||
if let storage = storage.state[property.key]?.content.storage {
|
if let storage = await storage.getState(key: property.key)?.content.storage {
|
||||||
property.value.content.storage = storage
|
property.value.content.storage = storage
|
||||||
}
|
}
|
||||||
if property.value.content.update {
|
if property.value.content.update {
|
||||||
@ -51,10 +51,16 @@ struct StateWrapper: ConvenienceWidget {
|
|||||||
property.value.content.update = false
|
property.value.content.update = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard let storage = storage.content[.mainContent]?.first else {
|
guard let storage = await storage.getContent(key: .mainContent).first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content().updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
await content().updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||||
|
for property in state {
|
||||||
|
if var value = property.value.content.value as? Signal, value.update {
|
||||||
|
value.destroySignal()
|
||||||
|
property.value.content.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a view storage.
|
/// Get a view storage.
|
||||||
@ -65,10 +71,10 @@ struct StateWrapper: ConvenienceWidget {
|
|||||||
func container<Data>(
|
func container<Data>(
|
||||||
data: WidgetData,
|
data: WidgetData,
|
||||||
type: Data.Type
|
type: Data.Type
|
||||||
) -> ViewStorage where Data: ViewRenderData {
|
) async -> ViewStorage where Data: ViewRenderData {
|
||||||
let content = content().storage(data: data, type: type)
|
let content = await content().storage(data: data, type: type)
|
||||||
let storage = ViewStorage(content.pointer, content: [.mainContent: [content]])
|
let storage = ViewStorage(await content.pointer, content: [.mainContent: [content]])
|
||||||
storage.state = state
|
await storage.setState(state)
|
||||||
for element in state {
|
for element in state {
|
||||||
element.value.setup()
|
element.value.setup()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
@testable import Meta
|
import Meta
|
||||||
import SampleBackends
|
import SampleBackends
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@ -73,7 +73,7 @@ struct TestView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestModel: Model {
|
struct TestModel: Sendable, Model {
|
||||||
|
|
||||||
var test = "Label"
|
var test = "Label"
|
||||||
|
|
||||||
|
|||||||
@ -6,16 +6,16 @@ public enum Backend1 {
|
|||||||
|
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
|
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
|
||||||
print("Init test widget 1")
|
print("Init test widget 1")
|
||||||
let storage = ViewStorage(nil)
|
let storage = ViewStorage(nil)
|
||||||
storage.fields["test"] = 0
|
await storage.setField(key: "test", value: 0)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
|
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
|
||||||
print("Update test widget 1 (#\(storage.fields["test"] ?? ""))")
|
print("Update test widget 1 (#\(await storage.getField(key: "test") ?? ""))")
|
||||||
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
|
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -24,16 +24,16 @@ public enum Backend1 {
|
|||||||
|
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
|
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
|
||||||
print("Init test widget 3")
|
print("Init test widget 3")
|
||||||
let storage = ViewStorage(nil)
|
let storage = ViewStorage(nil)
|
||||||
storage.fields["test"] = 0
|
await storage.setField(key: "test", value: 0)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
|
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
|
||||||
print("Update test widget 3 (#\(storage.fields["test"] ?? ""))")
|
print("Update test widget 3 (#\(await storage.getField(key: "test") ?? ""))")
|
||||||
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
|
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -42,28 +42,28 @@ public enum Backend1 {
|
|||||||
|
|
||||||
@Property(set: { print("Update button (label = \($1))") }, pointer: Any.self)
|
@Property(set: { print("Update button (label = \($1))") }, pointer: Any.self)
|
||||||
var label = ""
|
var label = ""
|
||||||
@Property(set: { $2.fields["action"] = $1 }, pointer: Any.self)
|
@Property(set: { $2.setField(key: "action", value: $1) }, pointer: Any.self)
|
||||||
var action: () -> Void = { }
|
var action: @Sendable () -> Void = { }
|
||||||
|
|
||||||
public init(_ label: String, action: @escaping () -> Void) {
|
public init(_ label: String, action: @Sendable @escaping () -> Void) {
|
||||||
self.label = label
|
self.label = label
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
|
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
|
||||||
print("Init button")
|
print("Init button")
|
||||||
let storage = ViewStorage(nil)
|
let storage = ViewStorage(nil)
|
||||||
Task {
|
Task {
|
||||||
try await Task.sleep(nanoseconds: 1_000_000_000)
|
try await Task.sleep(nanoseconds: 1_000_000_000)
|
||||||
(storage.fields["action"] as? () -> Void)?()
|
(await storage.getField(key: "action") as? @Sendable () -> Void)?()
|
||||||
}
|
}
|
||||||
storage.fields["action"] = action
|
await storage.setField(key: "action", value: action)
|
||||||
storage.previousState = self
|
await storage.setPreviousState(self)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Window: BackendSceneElement {
|
public struct Window: BackendSceneElement, Sendable {
|
||||||
|
|
||||||
public var id: String
|
public var id: String
|
||||||
var spawn: Int
|
var spawn: Int
|
||||||
@ -77,7 +77,9 @@ public enum Backend1 {
|
|||||||
|
|
||||||
public func setupInitialContainers<Storage>(app: Storage) where Storage: AppStorage {
|
public func setupInitialContainers<Storage>(app: Storage) where Storage: AppStorage {
|
||||||
for _ in 0..<spawn {
|
for _ in 0..<spawn {
|
||||||
app.storage.sceneStorage.append(container(app: app))
|
Task {
|
||||||
|
await app.appendScene(container(app: app))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,17 +88,21 @@ public enum Backend1 {
|
|||||||
let storage = SceneStorage(id: id, pointer: nil) {
|
let storage = SceneStorage(id: id, pointer: nil) {
|
||||||
print("Make visible")
|
print("Make visible")
|
||||||
}
|
}
|
||||||
let viewStorage = content.storage(data: .init(sceneStorage: storage, appStorage: app), type: MainViewRenderData.self)
|
Task {
|
||||||
storage.content[.mainContent] = [viewStorage]
|
let viewStorage = await content.storage(data: .init(sceneStorage: storage, appStorage: app), type: MainViewRenderData.self)
|
||||||
|
await storage.setContent(key: .mainContent, value: [viewStorage])
|
||||||
|
}
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update<Storage>(_ storage: SceneStorage, app: Storage, updateProperties: Bool) where Storage: AppStorage {
|
public func update<Storage>(_ storage: SceneStorage, app: Storage, updateProperties: Bool) where Storage: AppStorage {
|
||||||
|
Task {
|
||||||
print("Update \(id)")
|
print("Update \(id)")
|
||||||
guard let viewStorage = storage.content[.mainContent]?.first else {
|
guard let viewStorage = await storage.getContent(key: .mainContent).first else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content.updateStorage(viewStorage, data: .init(sceneStorage: storage, appStorage: app), updateProperties: updateProperties, type: MainViewRenderData.self)
|
await content.updateStorage(viewStorage, data: .init(sceneStorage: storage, appStorage: app), updateProperties: updateProperties, type: MainViewRenderData.self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -109,17 +115,15 @@ public enum Backend1 {
|
|||||||
self.content = content()
|
self.content = content()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func container<Data>(data: WidgetData, type: Data.Type) -> Meta.ViewStorage where Data: ViewRenderData {
|
public func container<Data>(data: WidgetData, type: Data.Type) async -> Meta.ViewStorage where Data: ViewRenderData {
|
||||||
let storage = ViewStorage(nil)
|
let storage = ViewStorage(nil)
|
||||||
storage.content = [.mainContent: content.storages(data: data, type: type)]
|
await storage.setContent(key: .mainContent, value: content.storages(data: data, type: type))
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) where Data: ViewRenderData {
|
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async where Data: ViewRenderData {
|
||||||
guard let storages = storage.content[.mainContent] else {
|
let storages = await storage.getContent(key: .mainContent)
|
||||||
return
|
await content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
|
||||||
content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -150,19 +154,19 @@ public enum Backend1 {
|
|||||||
|
|
||||||
public protocol BackendSceneElement: SceneElement { }
|
public protocol BackendSceneElement: SceneElement { }
|
||||||
|
|
||||||
public class Backend1App: AppStorage {
|
public actor Backend1App: AppStorage {
|
||||||
|
|
||||||
public typealias SceneElementType = BackendSceneElement
|
public typealias SceneElementType = BackendSceneElement
|
||||||
|
|
||||||
public var storage: StandardAppStorage = .init()
|
public var storage: StandardAppStorage = .init()
|
||||||
|
|
||||||
public required init(id: String) { }
|
public init(id: String) { }
|
||||||
|
|
||||||
public func run(setup: @escaping () -> Void) {
|
nonisolated public func run(setup: @escaping () -> Void) {
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func quit() {
|
nonisolated public func quit() {
|
||||||
fatalError("Quit")
|
fatalError("Quit")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,16 +6,16 @@ public enum Backend2 {
|
|||||||
|
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
|
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
|
||||||
print("Init test widget 2")
|
print("Init test widget 2")
|
||||||
let storage = ViewStorage(nil)
|
let storage = ViewStorage(nil)
|
||||||
storage.fields["test"] = 0
|
await storage.setField(key: "test", value: 0)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
|
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
|
||||||
print("Update test widget 2 (#\(storage.fields["test"] ?? ""))")
|
print("Update test widget 2 (#\(await storage.getField(key: "test") ?? ""))")
|
||||||
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
|
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -24,16 +24,16 @@ public enum Backend2 {
|
|||||||
|
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
public func container<Data>(data: WidgetData, type: Data.Type) -> ViewStorage where Data: ViewRenderData {
|
public func container<Data>(data: WidgetData, type: Data.Type) async -> ViewStorage where Data: ViewRenderData {
|
||||||
print("Init test widget 4")
|
print("Init test widget 4")
|
||||||
let storage = ViewStorage(nil)
|
let storage = ViewStorage(nil)
|
||||||
storage.fields["test"] = 0
|
await storage.setField(key: "test", value: 0)
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) {
|
public func update<Data>(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async {
|
||||||
print("Update test widget 4 (#\(storage.fields["test"] ?? ""))")
|
print("Update test widget 4 (#\(await storage.getField(key: "test") ?? ""))")
|
||||||
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
|
await storage.setField(key: "test", value: (storage.getField(key: "test") as? Int ?? 0) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -46,17 +46,15 @@ public enum Backend2 {
|
|||||||
self.content = content()
|
self.content = content()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func container<Data>(data: WidgetData, type: Data.Type) -> Meta.ViewStorage where Data: ViewRenderData {
|
public func container<Data>(data: WidgetData, type: Data.Type) async -> Meta.ViewStorage where Data: ViewRenderData {
|
||||||
let storage = ViewStorage(nil)
|
let storage = ViewStorage(nil)
|
||||||
storage.content = [.mainContent: content.storages(data: data, type: type)]
|
await storage.setContent(key: .mainContent, value: content.storages(data: data, type: type))
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) where Data: ViewRenderData {
|
public func update<Data>(_ storage: Meta.ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) async where Data: ViewRenderData {
|
||||||
guard let storages = storage.content[.mainContent] else {
|
let storages = await storage.getContent(key: .mainContent)
|
||||||
return
|
await content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
||||||
}
|
|
||||||
content.update(storages, data: data, updateProperties: updateProperties, type: type)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -65,19 +63,19 @@ public enum Backend2 {
|
|||||||
|
|
||||||
public protocol BackendSceneElement: SceneElement { }
|
public protocol BackendSceneElement: SceneElement { }
|
||||||
|
|
||||||
public class Backend2App: AppStorage {
|
public actor Backend2App: AppStorage{
|
||||||
|
|
||||||
public typealias SceneElementType = BackendSceneElement
|
public typealias SceneElementType = BackendSceneElement
|
||||||
|
|
||||||
public var storage: StandardAppStorage = .init()
|
public var storage: StandardAppStorage = .init()
|
||||||
|
|
||||||
public required init(id: String) { }
|
public init(id: String) { }
|
||||||
|
|
||||||
public func run(setup: @escaping () -> Void) {
|
nonisolated public func run(setup: @escaping () -> Void) {
|
||||||
setup()
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func quit() {
|
nonisolated public func quit() {
|
||||||
fatalError("Quit")
|
fatalError("Quit")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user