diff --git a/Sources/Meta.docc/Tutorials/CreateBackend.md b/Sources/Meta.docc/Tutorials/CreateBackend.md index 1897624..80ae064 100644 --- a/Sources/Meta.docc/Tutorials/CreateBackend.md +++ b/Sources/Meta.docc/Tutorials/CreateBackend.md @@ -592,7 +592,12 @@ import TermKit public struct Frame: TermKitWidget { - @ViewProperty(set: { $0.addSubview($1) }, pointer: TermKit.Frame.self, subview: TermKit.View.self) + @ViewProperty( + set: { $0.addSubview($1) }, + pointer: TermKit.Frame.self, + subview: TermKit.View.self, + context: MainViewContext.self + ) var view: Body public init(@ViewBuilder content: @escaping () -> Body) { // Use the view builder @@ -607,7 +612,7 @@ public struct Frame: TermKitWidget { ``` -Define the type of the view context with the `subview` property. +Define the type of the view context with the `context` property. Remember not to use this property wrapper in the wrapper widget. diff --git a/Sources/Model/User Interface/View/Properties/Property.swift b/Sources/Model/User Interface/View/Properties/Property.swift index 719ebfe..b50a7a6 100644 --- a/Sources/Model/User Interface/View/Properties/Property.swift +++ b/Sources/Model/User Interface/View/Properties/Property.swift @@ -180,9 +180,7 @@ extension Widget { 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] + initViewProperty(value, data: data, parent: storage, label: property.label ?? .mainContent, type: type) } if let value = property.value as? any BindingPropertyProtocol { initBindingProperty(value, parent: storage) @@ -193,16 +191,26 @@ extension Widget { /// Initialize the properties wrapped with ``ViewProperty``. /// - Parameters: /// - value: The property. - /// - view: The subview's view storage. + /// - data: The widget data. /// - parent: The parent's view storage. - func initViewProperty( + /// - label: The view content label. + /// - type: The view context type of the parent view. + 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 { + data: WidgetData, + parent: ViewStorage, + label: String, + type: ParentContext.Type + ) where Property: ViewPropertyProtocol, ParentContext: ViewRenderData { + var data = data + if type != Property.ViewContext.self { + data = data.noModifiers + } + let subview = value.wrappedValue.storage(data: data, type: Property.ViewContext.self) + if let view = subview.pointer as? Property.ViewPointer, let pointer = parent.pointer as? Property.Pointer { value.setView(pointer, view) } + parent.content[label] = [subview] } /// Initialize a binding property. @@ -226,143 +234,6 @@ extension Widget { } } - /// Update the properties wrapped with ``Property``. - /// - Parameters: - /// - storage: The storage to update. - /// - data: The widget data. - /// - updateProperties: Whether to update the view's properties. - /// - 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, data: data, updateProperties: updateProperties, type: type) - guard updateProperties else { - return - } - updateAlwaysWhenStateUpdate(mirror: mirror, storage: storage) - updateEquatable(mirror: mirror, storage: storage) - } - - /// Update the properties which are not equatable and should always be updated (e.g. closures). - /// - Parameters: - /// - mirror: A mirror of the widget. - /// - storage: The view storage. - /// - 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 || - value.wrappedValue as? any Equatable == nil && value.updateStrategy != .alwaysWhenStateUpdate { - 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) - } - if let value = property.value as? any BindingPropertyProtocol { - setBindingProperty(property: value, storage: storage) - } - } - } - - /// Update the properties which should always be updated when a state property changed - /// (e.g. "regular" properties which are not equatable). - /// - Parameters: - /// - mirror: A mirror of the widget. - /// - storage: The view storage. - /// - /// Initialize the ``Property`` property wrapper with the ``UpdateStrategy/alwaysWhenStateUpdate``. - func updateAlwaysWhenStateUpdate(mirror: Mirror, storage: ViewStorage) { - for property in mirror.children { - if let value = property.value as? any PropertyProtocol { - if value.updateStrategy == .alwaysWhenStateUpdate { - setProperty(property: value, storage: storage) - } - } - } - } - - /// Update equatable properties (most properties). - /// - Parameters: - /// - mirror: A mirror of the widget. - /// - storage: The view storage. - func updateEquatable(mirror: Mirror, storage: ViewStorage) { - let previousState: Mirror.Children? = if let previousState = storage.previousState { - Mirror(reflecting: previousState).children - } else { - nil - } - for property in mirror.children { - if let value = property.value as? any PropertyProtocol, - value.updateStrategy == .automatic, - let wrappedValue = value.wrappedValue as? any Equatable { - var update = true - if let previous = previousState?.first(where: { previousProperty in - previousProperty.label == property.label - })?.value as? any PropertyProtocol, - equal(previous, wrappedValue) { - update = false - } - if update { - setProperty(property: value, storage: storage) - } - } - } - } - - /// Check whether a property is equal to a value. - /// - Parameters: - /// - property: The property. - /// - value: The value. - /// - Returns: Whether the property and value are equal. - func equal( - _ property: Property, - _ value: Value - ) -> Bool where Property: PropertyProtocol, Value: Equatable { - equal(property.wrappedValue, value) - } - - /// Check whether a value is equal to another value. - /// - Parameters: - /// - value1: The first value. - /// - value2: The second value. - /// - Returns: Whether the values are equal. - func equal( - _ value1: Value1, - _ value2: Value2 - ) -> Bool where Value2: Equatable { - if let value1 = value1 as? Value2 { - return value1 == value2 - } - return false - } - - /// Apply a property to the framework. - /// - Parameters: - /// - property: The property. - /// - storage: The view storage. - func setProperty(property: Property, storage: ViewStorage) where Property: PropertyProtocol { - if let optional = property.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil { - return - } - if let pointer = storage.pointer as? Property.Pointer { - property.setProperty(pointer, property.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 index dc3c0f6..a2071ae 100644 --- a/Sources/Model/User Interface/View/Properties/ViewProperty.swift +++ b/Sources/Model/User Interface/View/Properties/ViewProperty.swift @@ -10,7 +10,7 @@ /// 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 { +public struct ViewProperty: ViewPropertyProtocol where ViewContext: ViewRenderData { /// The wrapped value. public var wrappedValue: Body = [] @@ -22,10 +22,12 @@ public struct ViewProperty: ViewPropertyProtocol { /// - 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). + /// - context: The view render data type. public init( set setView: @escaping (Pointer, ViewPointer) -> Void, pointer: Pointer.Type, - subview: ViewPointer.Type + subview: ViewPointer.Type, + context: ViewContext.Type ) { self.setView = setView } @@ -41,6 +43,8 @@ protocol ViewPropertyProtocol { associatedtype Pointer /// The type of the view's content. associatedtype ViewPointer + /// The view render data type. + associatedtype ViewContext: ViewRenderData /// The wrapped value. var wrappedValue: Body { get } diff --git a/Sources/Model/User Interface/View/Properties/Widget+.swift b/Sources/Model/User Interface/View/Properties/Widget+.swift new file mode 100644 index 0000000..0ef2c26 --- /dev/null +++ b/Sources/Model/User Interface/View/Properties/Widget+.swift @@ -0,0 +1,175 @@ +// +// Property+.swift +// Meta +// +// Created by david-swift on 14.10.24. +// + +extension Widget { + + /// Update the properties wrapped with ``Property``. + /// - Parameters: + /// - storage: The storage to update. + /// - data: The widget data. + /// - updateProperties: Whether to update the view's properties. + /// - 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, data: data, updateProperties: updateProperties, type: type) + guard updateProperties else { + return + } + updateAlwaysWhenStateUpdate(mirror: mirror, storage: storage) + updateEquatable(mirror: mirror, storage: storage) + } + + /// Update the properties which are not equatable and should always be updated (e.g. closures). + /// - Parameters: + /// - mirror: A mirror of the widget. + /// - storage: The view storage. + /// - 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 || + value.wrappedValue as? any Equatable == nil && value.updateStrategy != .alwaysWhenStateUpdate { + setProperty(property: value, storage: storage) + } + } + if let value = property.value as? any ViewPropertyProtocol, + let storage = storage.content[property.label ?? .mainContent]?.first { + updateViewProperty( + value: value, + storage: storage, + data: data, + updateProperties: updateProperties, + type: type + ) + } + if let value = property.value as? any BindingPropertyProtocol { + setBindingProperty(property: value, storage: storage) + } + } + } + + /// Update a view property. + /// - Parameters: + /// - value: The property. + /// - storage: The view storage. + /// - data: The widget data. + /// - updateProperties: Whether to update the properties. + /// - type: The parent context type. + func updateViewProperty( + value: Property, + storage: ViewStorage, + data: WidgetData, + updateProperties: Bool, + type: ParentContext.Type + ) where Property: ViewPropertyProtocol, ParentContext: ViewRenderData { + var data = data + if type != Property.ViewContext.self { + data = data.noModifiers + } + value.wrappedValue + .updateStorage(storage, data: data, updateProperties: updateProperties, type: Property.ViewContext.self) + } + + /// Update the properties which should always be updated when a state property changed + /// (e.g. "regular" properties which are not equatable). + /// - Parameters: + /// - mirror: A mirror of the widget. + /// - storage: The view storage. + /// + /// Initialize the ``Property`` property wrapper with the ``UpdateStrategy/alwaysWhenStateUpdate``. + func updateAlwaysWhenStateUpdate(mirror: Mirror, storage: ViewStorage) { + for property in mirror.children { + if let value = property.value as? any PropertyProtocol { + if value.updateStrategy == .alwaysWhenStateUpdate { + setProperty(property: value, storage: storage) + } + } + } + } + + /// Update equatable properties (most properties). + /// - Parameters: + /// - mirror: A mirror of the widget. + /// - storage: The view storage. + func updateEquatable(mirror: Mirror, storage: ViewStorage) { + let previousState: Mirror.Children? = if let previousState = storage.previousState { + Mirror(reflecting: previousState).children + } else { + nil + } + for property in mirror.children { + if let value = property.value as? any PropertyProtocol, + value.updateStrategy == .automatic, + let wrappedValue = value.wrappedValue as? any Equatable { + var update = true + if let previous = previousState?.first(where: { previousProperty in + previousProperty.label == property.label + })?.value as? any PropertyProtocol, + equal(previous, wrappedValue) { + update = false + } + if update { + setProperty(property: value, storage: storage) + } + } + } + } + + /// Check whether a property is equal to a value. + /// - Parameters: + /// - property: The property. + /// - value: The value. + /// - Returns: Whether the property and value are equal. + func equal( + _ property: Property, + _ value: Value + ) -> Bool where Property: PropertyProtocol, Value: Equatable { + equal(property.wrappedValue, value) + } + + /// Check whether a value is equal to another value. + /// - Parameters: + /// - value1: The first value. + /// - value2: The second value. + /// - Returns: Whether the values are equal. + func equal( + _ value1: Value1, + _ value2: Value2 + ) -> Bool where Value2: Equatable { + if let value1 = value1 as? Value2 { + return value1 == value2 + } + return false + } + + /// Apply a property to the framework. + /// - Parameters: + /// - property: The property. + /// - storage: The view storage. + func setProperty(property: Property, storage: ViewStorage) where Property: PropertyProtocol { + if let optional = property.wrappedValue as? any OptionalProtocol, optional.optionalValue == nil { + return + } + if let pointer = storage.pointer as? Property.Pointer { + property.setProperty(pointer, property.wrappedValue, storage) + } + } + +}