Explicitly set view context in view properties
This commit is contained in:
parent
cd214f2a7b
commit
443b942c46
@ -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.
|
||||
|
||||
|
@ -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<Property>(
|
||||
/// - label: The view content label.
|
||||
/// - type: The view context type of the parent view.
|
||||
func initViewProperty<Property, ParentContext>(
|
||||
_ 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<Data>(
|
||||
_ 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<Data>(
|
||||
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, Value>(
|
||||
_ 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, Value2>(
|
||||
_ 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: 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.
|
||||
|
@ -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<Pointer, ViewPointer>: ViewPropertyProtocol {
|
||||
public struct ViewProperty<Pointer, ViewPointer, ViewContext>: ViewPropertyProtocol where ViewContext: ViewRenderData {
|
||||
|
||||
/// The wrapped value.
|
||||
public var wrappedValue: Body = []
|
||||
@ -22,10 +22,12 @@ public struct ViewProperty<Pointer, ViewPointer>: 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 }
|
||||
|
175
Sources/Model/User Interface/View/Properties/Widget+.swift
Normal file
175
Sources/Model/User Interface/View/Properties/Widget+.swift
Normal file
@ -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<Data>(
|
||||
_ 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<Data>(
|
||||
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<Property, ParentContext>(
|
||||
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, Value>(
|
||||
_ 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, Value2>(
|
||||
_ 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: 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user