diff --git a/Sources/Model/User Interface/View/Property.swift b/Sources/Model/User Interface/View/Property.swift index a17682a..dc49a5b 100644 --- a/Sources/Model/User Interface/View/Property.swift +++ b/Sources/Model/User Interface/View/Property.swift @@ -58,6 +58,33 @@ 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. @@ -116,6 +143,23 @@ 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 { @@ -140,20 +184,67 @@ extension Widget { /// - type: The view render data type. /// /// This is the default implementation which requires the usage of ``Property``. - public func update(_ storage: ViewStorage, data: WidgetData, updateProperties: Bool, type: Data.Type) { - self.updateProperties(storage, updateProperties: updateProperties) + public func update( + _ storage: ViewStorage, + data: WidgetData, + updateProperties: Bool, + type: Data.Type + ) where Data: ViewRenderData { + self.updateProperties(storage, data: data, updateProperties: updateProperties, type: type) if updateProperties { storage.previousState = self } } + /// Initialize the properties wrapped with ``Property``. + /// - Parameters: + /// - storage: The view storage. + /// - data: Modify views before being updated. + /// - type: The view render data type. + public func initProperties( + _ storage: ViewStorage, + data: WidgetData, + type: Data.Type + ) where Data: ViewRenderData { + let mirror = Mirror(reflecting: self) + for property in mirror.children { + if let value = property.value as? any ViewPropertyProtocol { + let subview = value.wrappedValue.storage(data: data, type: type) + initViewProperty(value, view: subview, parent: storage) + storage.content[property.label ?? .mainContent] = [subview] + } + } + } + + /// Initialize the properties wrapped with ``ViewProperty``. + /// - Parameters: + /// - value: The property. + /// - view: The subview's view storage. + /// - parent: The parent's view storage. + func initViewProperty( + _ value: Property, + view: ViewStorage, + parent: ViewStorage + ) where Property: ViewPropertyProtocol { + if let view = view.pointer as? Property.ViewPointer, let pointer = parent.pointer as? Property.Pointer { + value.setView(pointer, view) + } + } + /// Update the properties wrapped with ``Property``. /// - Parameters: /// - storage: The storage to update. + /// - data: The widget data. /// - updateProperties: Whether to update the view's properties. - public func updateProperties(_ storage: ViewStorage, updateProperties: Bool) { + /// - type: The view render data type. + public func updateProperties( + _ storage: ViewStorage, + data: WidgetData, + updateProperties: Bool, + type: Data.Type + ) where Data: ViewRenderData { let mirror = Mirror(reflecting: self) - updateNotEquatable(mirror: mirror, storage: storage) + updateNotEquatable(mirror: mirror, storage: storage, data: data, updateProperties: updateProperties, type: type) guard updateProperties else { return } @@ -165,7 +256,16 @@ extension Widget { /// - Parameters: /// - mirror: A mirror of the widget. /// - storage: The view storage. - func updateNotEquatable(mirror: Mirror, storage: ViewStorage) { + /// - data: The widget data. + /// - updateProperties: Whether to update the properties. + /// - type: The view render data type. + func updateNotEquatable( + mirror: Mirror, + storage: ViewStorage, + data: WidgetData, + updateProperties: Bool, + type: Data.Type + ) where Data: ViewRenderData { for property in mirror.children { if let value = property.value as? any PropertyProtocol { if value.updateStrategy == .always || @@ -173,6 +273,10 @@ extension Widget { setProperty(property: value, storage: storage) } } + if let value = property.value as? any ViewPropertyProtocol, + let storage = storage.content[property.label ?? .mainContent]?.first { + value.wrappedValue.updateStorage(storage, data: data, updateProperties: updateProperties, type: type) + } } } diff --git a/Tests/DemoApp/DemoApp.swift b/Tests/DemoApp/DemoApp.swift index 3df2f03..9471803 100644 --- a/Tests/DemoApp/DemoApp.swift +++ b/Tests/DemoApp/DemoApp.swift @@ -41,9 +41,11 @@ struct DemoView: View { } else { Backend1.TestWidget3() } - Backend1.Button(model.test) { - Task { - app.addSceneElement("main") + Backend1.Wrapper { + Backend1.Button(model.test) { + Task { + app.addSceneElement("main") + } } } TestView()