From 6289e518e536b4d91ea09bd25c757012172d95d9 Mon Sep 17 00:00:00 2001 From: david-swift Date: Mon, 16 Sep 2024 22:33:44 +0200 Subject: [PATCH] Add binding properties --- .../View/Properties/BindingProperty.swift | 76 +++++++++++++++++++ .../View/{ => Properties}/Property.swift | 76 ++++++++----------- .../View/Properties/ViewProperty.swift | 50 ++++++++++++ 3 files changed, 158 insertions(+), 44 deletions(-) create mode 100644 Sources/Model/User Interface/View/Properties/BindingProperty.swift rename Sources/Model/User Interface/View/{ => Properties}/Property.swift (90%) create mode 100644 Sources/Model/User Interface/View/Properties/ViewProperty.swift diff --git a/Sources/Model/User Interface/View/Properties/BindingProperty.swift b/Sources/Model/User Interface/View/Properties/BindingProperty.swift new file mode 100644 index 0000000..efb64c4 --- /dev/null +++ b/Sources/Model/User Interface/View/Properties/BindingProperty.swift @@ -0,0 +1,76 @@ +// +// Property.swift +// Meta +// +// Created by david-swift on 16.09.24. +// + +/// Assign an observing and updating closure to a widget's binding property. +/// +/// 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. +@propertyWrapper +public struct BindingProperty: BindingPropertyProtocol { + + /// The wrapped binding. + public var wrappedValue: Binding + /// Observe the UI element. + var observe: (Pointer, Binding, ViewStorage) -> Void + /// Set the UI element's property. + var setValue: (Pointer, Value, ViewStorage) -> Void + + /// Initialize a property. + /// - Parameters: + /// - wrappedValue: The wrapped value. + /// - getProperty: Get the property from the UI. + /// - setProperty: The function applying the property to the UI. + /// - pointer: The type of the pointer. + public init( + wrappedValue: Binding, + observe: @escaping (Pointer, Binding, ViewStorage) -> Void, + set setValue: @escaping (Pointer, Value, ViewStorage) -> Void, + pointer: Pointer.Type + ) { + self.wrappedValue = wrappedValue + self.observe = observe + self.setValue = setValue + } + + /// Initialize a property. + /// - Parameters: + /// - wrappedValue: The wrapped value. + /// - getProperty: Get the property from the UI. + /// - setProperty: The function applying the property to the UI. + /// - pointer: The type of the pointer. + public init( + wrappedValue: Binding, + observe: @escaping (Pointer, Binding) -> Void, + set setValue: @escaping (Pointer, Value) -> Void, + pointer: Pointer.Type + ) { + self.init( + wrappedValue: wrappedValue, + observe: { pointer, value, _ in observe(pointer, value) }, + set: { pointer, value, _ in setValue(pointer, value) }, + pointer: pointer + ) + } + +} + +/// The binding property protocol. +protocol BindingPropertyProtocol { + + /// The binding's wrapped value. + associatedtype Value + /// The storage's pointer. + associatedtype Pointer + + /// The wrapped value. + var wrappedValue: Binding { get } + /// Observe the property. + var observe: (Pointer, Binding, ViewStorage) -> Void { get } + /// Set the property. + var setValue: (Pointer, Value, ViewStorage) -> Void { get } + +} diff --git a/Sources/Model/User Interface/View/Property.swift b/Sources/Model/User Interface/View/Properties/Property.swift similarity index 90% rename from Sources/Model/User Interface/View/Property.swift rename to Sources/Model/User Interface/View/Properties/Property.swift index ddea3ac..a99a8d3 100644 --- a/Sources/Model/User Interface/View/Property.swift +++ b/Sources/Model/User Interface/View/Properties/Property.swift @@ -58,33 +58,6 @@ public struct Property: PropertyProtocol { } -/// Assign an updating closure to a widget's property. -/// -/// 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. -@propertyWrapper -public struct ViewProperty: ViewPropertyProtocol { - - /// The wrapped value. - public var wrappedValue: Body = [] - /// Set the view. - var setView: (Pointer, ViewPointer) -> Void - - /// Initialize a property. - /// - Parameters: - /// - setView: Set the view. - /// - 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). - public init( - set setView: @escaping (Pointer, ViewPointer) -> Void, - pointer: Pointer.Type, - subview: ViewPointer.Type - ) { - self.setView = setView - } - -} - extension Property where Value: OptionalProtocol { /// Initialize a property. @@ -143,23 +116,6 @@ protocol PropertyProtocol { } -/// The view property protocol. -/// -/// Do not use for wrapper widgets. -protocol ViewPropertyProtocol { - - /// The type of the view's pointer. - associatedtype Pointer - /// The type of the view's content. - associatedtype ViewPointer - - /// The wrapped value. - var wrappedValue: Body { get } - /// Set the view. - var setView: (Pointer, ViewPointer) -> Void { get } - -} - /// The update strategy for properties. public enum UpdateStrategy { @@ -228,6 +184,9 @@ extension Widget { initViewProperty(value, view: subview, parent: storage) storage.content[property.label ?? .mainContent] = [subview] } + if let value = property.value as? any BindingPropertyProtocol { + initBindingProperty(value, parent: storage) + } } } @@ -246,6 +205,16 @@ extension Widget { } } + /// Initialize a binding property. + /// - Parameters: + /// - value: The property. + /// - parent: The view storage. + func initBindingProperty(_ value: Property, parent: ViewStorage) where Property: BindingPropertyProtocol { + if let view = parent.pointer as? Property.Pointer { + value.observe(view, value.wrappedValue, parent) + } + } + /// Update the properties wrapped with ``Property``. /// - Parameters: /// - storage: The storage to update. @@ -292,6 +261,9 @@ extension Widget { let storage = storage.content[property.label ?? .mainContent]?.first { value.wrappedValue.updateStorage(storage, data: data, updateProperties: updateProperties, type: type) } + if let value = property.value as? any BindingPropertyProtocol { + setBindingProperty(property: value, storage: storage) + } } } @@ -368,6 +340,22 @@ extension Widget { } } + /// Apply a binding property to the framework. + /// - Parameters: + /// - property: The property. + /// - storage: The view storage. + func setBindingProperty( + property: Property, + storage: ViewStorage + ) where Property: BindingPropertyProtocol { + if let optional = property.wrappedValue.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil { + return + } + if let pointer = storage.pointer as? Property.Pointer { + property.setValue(pointer, property.wrappedValue.wrappedValue, storage) + } + } + } /// A protocol for values which can be optional. diff --git a/Sources/Model/User Interface/View/Properties/ViewProperty.swift b/Sources/Model/User Interface/View/Properties/ViewProperty.swift new file mode 100644 index 0000000..dc3c0f6 --- /dev/null +++ b/Sources/Model/User Interface/View/Properties/ViewProperty.swift @@ -0,0 +1,50 @@ +// +// Property.swift +// Meta +// +// Created by david-swift on 16.09.24. +// + +/// Assign an updating closure to a widget's property. +/// +/// 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. +@propertyWrapper +public struct ViewProperty: ViewPropertyProtocol { + + /// The wrapped value. + public var wrappedValue: Body = [] + /// Set the view. + var setView: (Pointer, ViewPointer) -> Void + + /// Initialize a property. + /// - Parameters: + /// - setView: Set the view. + /// - 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). + public init( + set setView: @escaping (Pointer, ViewPointer) -> Void, + pointer: Pointer.Type, + subview: ViewPointer.Type + ) { + self.setView = setView + } + +} + +/// The view property protocol. +/// +/// Do not use for wrapper widgets. +protocol ViewPropertyProtocol { + + /// The type of the view's pointer. + associatedtype Pointer + /// The type of the view's content. + associatedtype ViewPointer + + /// The wrapped value. + var wrappedValue: Body { get } + /// Set the view. + var setView: (Pointer, ViewPointer) -> Void { get } + +}