Implement view updating system

This commit is contained in:
david-swift 2024-06-17 09:11:20 +02:00
parent 3c17404dc6
commit c15839de3b
9 changed files with 82 additions and 89 deletions

View File

@ -15,9 +15,9 @@ extension Array: AnyView where Element == AnyView {
/// Get the debug tree for an array of views. /// Get the debug tree for an array of views.
/// - Parameter parameters: Whether the widget parameters should be visible in the tree. /// - Parameter parameters: Whether the widget parameters should be visible in the tree.
/// - Returns: The tree. /// - Returns: The tree.
public func getBodyDebugTree<ViewType>(parameters: Bool, type: ViewType.Type) -> String { public func getBodyDebugTree<WidgetType>(parameters: Bool, type: WidgetType.Type) -> String {
var description = "" var description = ""
for view in self where view as? ViewType != nil { for view in self where view.renderable(type: type) {
let viewDescription: String let viewDescription: String
if let widget = view as? Widget { if let widget = view as? Widget {
viewDescription = widget.getViewDescription(parameters: parameters, type: type) viewDescription = widget.getViewDescription(parameters: parameters, type: type)
@ -45,7 +45,6 @@ extension Array: AnyView where Element == AnyView {
modified[safe: index] = modifier(view) modified[safe: index] = modifier(view)
} }
} }
// TODO: Is wrapper correct choice?
return Wrapper { modified } return Wrapper { modified }
} }
} }
@ -55,12 +54,13 @@ extension Array: AnyView where Element == AnyView {
/// - storage: The collection of view storages. /// - storage: The collection of view storages.
/// - modifiers: Modify views before being updated. /// - modifiers: Modify views before being updated.
/// - updateProperties: Whether to update properties. /// - updateProperties: Whether to update properties.
public func update(_ storage: [ViewStorage], modifiers: [(AnyView) -> AnyView], updateProperties: Bool) { /// - type: The type of the widgets.
for (index, element) in enumerated() { public func update<WidgetType>(_ storage: [ViewStorage], modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type) {
for (index, element) in enumerated() where element.renderable(type: type) {
if let storage = storage[safe: index] { if let storage = storage[safe: index] {
element element
.widget(modifiers: modifiers) .widget(modifiers: modifiers)
.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties) .updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
} }
} }
} }

View File

@ -18,7 +18,7 @@ extension AnyView {
/// Get the view's debug tree. /// Get the view's debug tree.
/// - Parameter parameters: Whether the widget parameters should be included in the debug tree. /// - Parameter parameters: Whether the widget parameters should be included in the debug tree.
/// - Returns: A textual description. /// - Returns: A textual description.
public func getDebugTree<ViewType>(parameters: Bool, type: ViewType.Type) -> String { public func getDebugTree<WidgetType>(parameters: Bool, type: WidgetType.Type) -> String {
if let body = self as? Body { if let body = self as? Body {
return body.getBodyDebugTree(parameters: parameters, type: type) return body.getBodyDebugTree(parameters: parameters, type: type)
} }
@ -42,21 +42,24 @@ extension AnyView {
/// - storage: The storage. /// - storage: The storage.
/// - modifiers: Modify views before being updated. /// - modifiers: Modify views before being updated.
/// - updateProperties: Whether to update properties. /// - updateProperties: Whether to update properties.
public func updateStorage(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool) { /// - type: The type of the widgets.
public func updateStorage<WidgetType>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type) {
let modified = getModified(modifiers: modifiers) let modified = getModified(modifiers: modifiers)
if let widget = modified as? Widget { if let widget = modified as? Widget {
widget.update(storage, modifiers: modifiers, updateProperties: updateProperties) widget.update(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
} else { } else {
Wrapper { viewContent } Wrapper { viewContent }
.update(storage, modifiers: modifiers, updateProperties: updateProperties) .update(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
} }
} }
/// Get a storage. /// Get a storage.
/// - Parameter modifiers: Modify views before being updated. /// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The widget types.
/// - Returns: The storage. /// - Returns: The storage.
public func storage(modifiers: [(AnyView) -> AnyView]) -> ViewStorage { public func storage<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
widget(modifiers: modifiers).container(modifiers: modifiers) widget(modifiers: modifiers).container(modifiers: modifiers, type: type)
} }
/// Wrap the view into a widget. /// Wrap the view into a widget.
@ -70,6 +73,11 @@ extension AnyView {
return Wrapper { viewContent } return Wrapper { viewContent }
} }
/// Whether the view can be rendered in a certain environment.
func renderable<WidgetType>(type: WidgetType.Type) -> Bool {
self as? WidgetType != nil || self as? SimpleView != nil || self as? View != nil
}
} }
/// `Body` is an array of views. /// `Body` is an array of views.

View File

@ -53,40 +53,6 @@ public enum ViewBuilder {
component component
} }
// TODO: Add support for optionals, building either
/*
/// Enables support for `if` statements without an `else`.
/// - Parameter component: An optional component.
/// - Returns: A nonoptional component.
public static func buildOptional(_ component: Component?) -> Component {
.element(
Bin()
.child {
if let component {
buildFinalResult(component)
} else {
[]
}
}
.visible(component != nil)
)
}
/// Enables support for `if`-`else` and `switch` statements.
/// - Parameter component: A component.
/// - Returns: The component.
public static func buildEither(first component: Component) -> Component {
.element(ViewStack(id: true) { _ in buildFinalResult(component) })
}
/// Enables support for `if`-`else` and `switch` statements.
/// - Parameter component: A component.
/// - Returns: The component.
public static func buildEither(second component: Component) -> Component {
.element(ViewStack(id: false) { _ in buildFinalResult(component) })
}
*/
/// Convert a component to an array of elements. /// Convert a component to an array of elements.
/// - Parameter component: The component to convert. /// - Parameter component: The component to convert.
/// - Returns: The generated array of elements. /// - Returns: The generated array of elements.

View File

@ -13,14 +13,17 @@ public protocol Widget: AnyView {
/// The debug tree's content. /// The debug tree's content.
var debugTreeContent: [(String, body: Body)] { get } var debugTreeContent: [(String, body: Body)] { get }
/// The view storage. /// The view storage.
/// - Parameter modifiers: Modify views before being updated. /// - Parameters:
func container(modifiers: [(AnyView) -> AnyView]) -> ViewStorage /// - modifiers: Modify views before being updated.
/// - type: The type of the widgets.
func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage
/// Update the stored content. /// Update the stored content.
/// - Parameters: /// - Parameters:
/// - storage: The storage to update. /// - storage: The storage to update.
/// - modifiers: Modify views before being updated /// - modifiers: Modify views before being updated
/// - updateProperties: Whether to update the view's properties. /// - updateProperties: Whether to update the view's properties.
func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool) /// - type: The type of the widgets.
func update<WidgetType>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type)
} }
@ -30,7 +33,7 @@ extension Widget {
public var viewContent: Body { [] } public var viewContent: Body { [] }
/// A description of the view. /// A description of the view.
public func getViewDescription<ViewType>(parameters: Bool, type: ViewType.Type) -> String { public func getViewDescription<WidgetType>(parameters: Bool, type: WidgetType.Type) -> String {
var content = "" var content = ""
for element in debugTreeContent { for element in debugTreeContent {
if content.isEmpty { if content.isEmpty {

View File

@ -50,7 +50,8 @@ public struct StateWrapper: Widget {
/// - storage: The view storage. /// - storage: The view storage.
/// - modifiers: Modify views before being updated. /// - modifiers: Modify views before being updated.
/// - updateProperties: Whether to update properties. /// - updateProperties: Whether to update properties.
public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool) { /// - type: The type of the widgets.
public func update<WidgetType>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type) {
var updateProperties = storage.fields[updateID] as? Bool ?? false var updateProperties = storage.fields[updateID] as? Bool ?? false
storage.fields[updateID] = false storage.fields[updateID] = false
for property in state { for property in state {
@ -65,15 +66,17 @@ public struct StateWrapper: Widget {
if let storage = storage.content[.mainContent]?.first { if let storage = storage.content[.mainContent]?.first {
content() content()
.widget(modifiers: modifiers) .widget(modifiers: modifiers)
.update(storage, modifiers: modifiers, updateProperties: updateProperties) .update(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
} }
} }
/// Get a view storage. /// Get a view storage.
/// - Parameter modifiers: Modify views before being updated. /// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the widgets.
/// - Returns: The view storage. /// - Returns: The view storage.
public func container(modifiers: [(AnyView) -> AnyView]) -> ViewStorage { public func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
let content = content().widget(modifiers: modifiers).container(modifiers: modifiers) let content = content().storage(modifiers: modifiers, type: type)
let storage = ViewStorage(content.pointer, content: [.mainContent: [content]]) let storage = ViewStorage(content.pointer, content: [.mainContent: [content]])
storage.state = state storage.state = state
observe(storage: storage) observe(storage: storage)

View File

@ -32,20 +32,21 @@ public struct Wrapper: Widget {
/// - storage: The view storage. /// - storage: The view storage.
/// - modifiers: Modify views before being updated. /// - modifiers: Modify views before being updated.
/// - updateProperties: Whether to update properties. /// - updateProperties: Whether to update properties.
public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool) { /// - type: The widget types.
if let storage = storage.content[.mainContent]?.first { public func update<WidgetType>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type) {
content guard let storages = storage.content[.mainContent] else {
.widget(modifiers: modifiers) return
.update(storage, modifiers: modifiers, updateProperties: updateProperties)
} }
content.update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type)
} }
/// Get a view storage. /// Get a view storage.
/// - Parameter modifiers: Modify views before being updated. /// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the widgets.
/// - Returns: The view storage. /// - Returns: The view storage.
public func container(modifiers: [(AnyView) -> AnyView]) -> ViewStorage { public func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
let content = content.widget(modifiers: modifiers).container(modifiers: modifiers) ViewStorage(nil, content: [.mainContent: content.map { $0.storage(modifiers: [], type: type) }])
return .init(content.pointer, content: [.mainContent: [content]])
} }
} }

View File

@ -4,17 +4,31 @@ import SampleBackends
struct DemoView: SimpleView { struct DemoView: SimpleView {
var view: Body { var view: Body {
Backend1.TestWidget() Backend1.TestWidget1()
TestView()
testContent testContent
} }
@ViewBuilder @ViewBuilder
var testContent: Body { var testContent: Body {
Backend2.TestWidget() Backend2.TestWidget2()
Backend1.TestWidget() Backend1.TestWidget1()
} }
} }
print(DemoView().getDebugTree(parameters: true, type: Backend1.BackendView.self)) struct TestView: SimpleView {
print(DemoView().getDebugTree(parameters: true, type: Backend2.BackendView.self))
var view: Body {
[]
}
}
print(DemoView().getDebugTree(parameters: true, type: Backend1.BackendWidget.self))
print(DemoView().getDebugTree(parameters: true, type: Backend2.BackendWidget.self))
let storage = DemoView().storage(modifiers: [], type: Backend1.BackendWidget.self)
for _ in 0...2 {
DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: Backend2.BackendWidget.self)
}

View File

@ -2,33 +2,32 @@ import Meta
public enum Backend1 { public enum Backend1 {
public struct TestWidget: BackendWidget { public struct TestWidget1: BackendWidget {
public init() { } public init() { }
public var debugTreeContent: [(String, body: Body)] { public var debugTreeContent: [(String, body: Body)] {
[] []
} }
public var debugTreeParameters: [(String, value: CustomStringConvertible)] { public var debugTreeParameters: [(String, value: CustomStringConvertible)] {
[] []
} }
public func container(modifiers: [(AnyView) -> AnyView]) -> ViewStorage { public func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
print("Init Content") print("Init test widget 1")
let storage = ViewStorage(nil) let storage = ViewStorage(nil)
storage.fields["test"] = 0 storage.fields["test"] = 0
return storage return storage
} }
public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool) { public func update<WidgetType>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type) {
storage.fields["test"] = storage.fields["tests"] as? Int ?? 0 + 1 print("Update test widget 1 (#\(storage.fields["test"] ?? ""))")
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
} }
} }
public protocol BackendView: AnyView { } public protocol BackendWidget: Widget { }
public protocol BackendWidget: BackendView, Widget { }
} }

View File

@ -2,7 +2,7 @@ import Meta
public enum Backend2 { public enum Backend2 {
public struct TestWidget: BackendWidget { public struct TestWidget2: BackendWidget {
public init() { } public init() { }
@ -14,21 +14,20 @@ public enum Backend2 {
[] []
} }
public func container(modifiers: [(AnyView) -> AnyView]) -> ViewStorage { public func container<WidgetType>(modifiers: [(AnyView) -> AnyView], type: WidgetType.Type) -> ViewStorage {
print("Init Content") print("Init test widget 2")
let storage = ViewStorage(nil) let storage = ViewStorage(nil)
storage.fields["test"] = 0 storage.fields["test"] = 0
return storage return storage
} }
public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool) { public func update<WidgetType>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: WidgetType.Type) {
storage.fields["test"] = storage.fields["tests"] as? Int ?? 0 + 1 print("Update test widget 2 (#\(storage.fields["test"] ?? ""))")
storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1
} }
} }
public protocol BackendView: AnyView { } public protocol BackendWidget: Widget { }
public protocol BackendWidget: BackendView, Widget { }
} }