diff --git a/Sources/View/AppearObserver.swift b/Sources/View/AppearObserver.swift new file mode 100644 index 0000000..a86d408 --- /dev/null +++ b/Sources/View/AppearObserver.swift @@ -0,0 +1,74 @@ +// +// AppearObserver.swift +// Meta +// +// Created by david-swift on 29.06.24. +// + +/// Run a custom code accessing the view's storage when initializing the view. +struct AppearObserver: ConvenienceWidget { + + /// The custom code to edit the widget. + var modify: (ViewStorage) -> Void + /// The wrapped view. + var content: AnyView + + /// The debug tree parameters. + var debugTreeParameters: [(String, value: CustomStringConvertible)] { + [ + ("modify", value: "(ViewStorage) -> Void") + ] + } + + /// The debug tree's content. + var debugTreeContent: [(String, body: Body)] { + [("content", body: [content])] + } + + /// The view storage. + /// - Parameters: + /// - modifiers: Modify views before being updated. + /// - type: The type of the widgets. + func container(modifiers: [(any AnyView) -> any AnyView], type: WidgetType.Type) -> ViewStorage { + let storage = content.storage(modifiers: modifiers, type: type) + modify(storage) + return .init(nil, content: [.mainContent: [storage]]) + } + + /// Update the stored content. + /// - Parameters: + /// - storage: The storage to update. + /// - modifiers: Modify views before being updated + /// - updateProperties: Whether to update the view's properties. + /// - type: The type of the widgets. + func update( + _ storage: ViewStorage, + modifiers: [(any AnyView) -> any AnyView], + updateProperties: Bool, + type: WidgetType.Type + ) { + guard let storage = storage.content[.mainContent]?.first else { + return + } + content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type) + } + +} + +extension AnyView { + + /// Run a function on the widget when it appears for the first time. + /// - Parameter closure: The function. + /// - Returns: A view. + public func inspectOnAppear(_ closure: @escaping (ViewStorage) -> Void) -> AnyView { + AppearObserver(modify: closure, content: self) + } + + /// Run a function when the view appears for the first time. + /// - Parameter closure: The function. + /// - Returns: A view. + public func onAppear(_ closure: @escaping () -> Void) -> AnyView { + inspectOnAppear { _ in closure() } + } + +} diff --git a/Sources/View/ContentModifier.swift b/Sources/View/ContentModifier.swift new file mode 100644 index 0000000..e125af9 --- /dev/null +++ b/Sources/View/ContentModifier.swift @@ -0,0 +1,79 @@ +// +// ContentModifier.swift +// Meta +// +// Created by david-swift on 29.06.24. +// + +/// A widget which replaces views of a specific type in its content. +struct ContentModifier: ConvenienceWidget where Content: AnyView { + + /// The wrapped view. + var content: AnyView + /// The closure for the modification. + var modify: (Content) -> AnyView + + /// The debug tree parameters. + var debugTreeParameters: [(String, value: CustomStringConvertible)] { + [("modify", value: "(Content) -> AnyView")] + } + + /// The debug tree's content. + var debugTreeContent: [(String, body: Body)] { + [("content", body: [content])] + } + + /// The view storage. + /// - Parameters: + /// - modifiers: Modify views before being updated. + /// - type: The type of the widgets. + func container(modifiers: [(any AnyView) -> any AnyView], type: WidgetType.Type) -> ViewStorage { + .init(nil, content: [.mainContent: [content.storage(modifiers: modifiers + [modifyView], type: type)]]) + } + + /// Update the stored content. + /// - Parameters: + /// - storage: The storage to update. + /// - modifiers: Modify views before being updated + /// - updateProperties: Whether to update the view's properties. + /// - type: The type of the widgets. + func update( + _ storage: ViewStorage, + modifiers: [(any AnyView) -> any AnyView], + updateProperties: Bool, + type: WidgetType.Type + ) { + guard let storage = storage.content[.mainContent]?.first else { + return + } + content + .updateStorage(storage, modifiers: modifiers + [modifyView], updateProperties: updateProperties, type: type) + } + + /// Apply the modifier to a view. + /// - Parameter view: The view. + func modifyView(_ view: AnyView) -> AnyView { + if let view = view as? Content { + return modify(view).stopModifiers() + } else { + return view + } + } + +} + +extension AnyView { + + /// Replace every occurrence of a certain view type in the content. + /// - Parameters: + /// - type: The view type. + /// - modify: Modify the view. + /// - Returns: A view. + public func modifyContent( + _ type: Content.Type, + modify: @escaping (Content) -> AnyView + ) -> AnyView where Content: AnyView { + ContentModifier(content: self, modify: modify) + } + +} diff --git a/Sources/View/Freeze.swift b/Sources/View/Freeze.swift new file mode 100644 index 0000000..5b61613 --- /dev/null +++ b/Sources/View/Freeze.swift @@ -0,0 +1,65 @@ +// +// Freeze.swift +// Meta +// +// Created by david-swift on 29.06.24. +// + +/// State whether to update the child views or not. +struct Freeze: ConvenienceWidget { + + /// Whether not to update the child view. + var freeze: Bool + /// The wrapped view. + var content: AnyView + + /// The debug tree parameters. + var debugTreeParameters: [(String, value: CustomStringConvertible)] { + [ + ("freeze", value: freeze) + ] + } + + /// The debug tree's content. + var debugTreeContent: [(String, body: Body)] { + [("content", body: [content])] + } + + /// The view storage. + /// - Parameters: + /// - modifiers: Modify views before being updated. + /// - type: The type of the widgets. + func container(modifiers: [(any AnyView) -> any AnyView], type: WidgetType.Type) -> ViewStorage { + .init(nil, content: [.mainContent: [content.storage(modifiers: modifiers, type: type)]]) + } + + /// Update the stored content. + /// - Parameters: + /// - storage: The storage to update. + /// - modifiers: Modify views before being updated + /// - updateProperties: Whether to update the view's properties. + /// - type: The type of the widgets. + func update( + _ storage: ViewStorage, + modifiers: [(any AnyView) -> any AnyView], + updateProperties: Bool, + type: WidgetType.Type + ) { + guard !freeze, let storage = storage.content[.mainContent]?.first else { + return + } + content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type) + } + +} + +extension AnyView { + + /// Prevent a view from being updated. + /// - Parameter freeze: Whether to freeze the view. + /// - Returns: A view. + public func freeze(_ freeze: Bool = true) -> AnyView { + Freeze(freeze: freeze, content: self) + } + +} diff --git a/Sources/View/InspectorWrapper.swift b/Sources/View/InspectorWrapper.swift new file mode 100644 index 0000000..5ab0c87 --- /dev/null +++ b/Sources/View/InspectorWrapper.swift @@ -0,0 +1,75 @@ +// +// InspectorWrapper.swift +// Meta +// +// Created by david-swift on 29.06.24. +// + +/// Run a custom code accessing the view's storage when initializing and updating the view. +struct InspectorWrapper: ConvenienceWidget { + + /// The custom code to edit the widget. + var modify: (ViewStorage) -> Void + /// The wrapped view. + var content: AnyView + + /// The debug tree parameters. + var debugTreeParameters: [(String, value: CustomStringConvertible)] { + [ + ("modify", value: "(ViewStorage) -> Void") + ] + } + + /// The debug tree's content. + var debugTreeContent: [(String, body: Body)] { + [("content", body: [content])] + } + + /// The view storage. + /// - Parameters: + /// - modifiers: Modify views before being updated. + /// - type: The type of the widgets. + func container(modifiers: [(any AnyView) -> any AnyView], type: WidgetType.Type) -> ViewStorage { + let storage = content.storage(modifiers: modifiers, type: type) + modify(storage) + return .init(nil, content: [.mainContent: [storage]]) + } + + /// Update the stored content. + /// - Parameters: + /// - storage: The storage to update. + /// - modifiers: Modify views before being updated + /// - updateProperties: Whether to update the view's properties. + /// - type: The type of the widgets. + func update( + _ storage: ViewStorage, + modifiers: [(any AnyView) -> any AnyView], + updateProperties: Bool, + type: WidgetType.Type + ) { + guard let storage = storage.content[.mainContent]?.first else { + return + } + content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type) + modify(storage) + } + +} + +extension AnyView { + + /// Run a custom code accessing the view's storage when initializing and updating the view. + /// - Parameter modify: Modify the storage. + /// - Returns: A view. + public func inspect(_ modify: @escaping (ViewStorage) -> Void) -> AnyView { + InspectorWrapper(modify: modify, content: self) + } + + /// Run a function when the view gets updated. + /// - Parameter onUpdate: The function. + /// - Returns: A view. + public func onUpdate(_ onUpdate: @escaping () -> Void) -> AnyView { + inspect { _ in onUpdate() } + } + +} diff --git a/Sources/View/ModifierStopper.swift b/Sources/View/ModifierStopper.swift new file mode 100644 index 0000000..a1bdc26 --- /dev/null +++ b/Sources/View/ModifierStopper.swift @@ -0,0 +1,60 @@ +// +// ModifierStopper.swift +// Meta +// +// Created by david-swift on 29.06.24. +// + +/// Remove all of the content modifiers for the wrapped views. +struct ModifierStopper: ConvenienceWidget { + + /// The wrapped view. + var content: AnyView + + /// The debug tree parameters. + var debugTreeParameters: [(String, value: CustomStringConvertible)] { + [] + } + + /// The debug tree's content. + var debugTreeContent: [(String, body: Body)] { + [("content", body: [content])] + } + + /// The view storage. + /// - Parameters: + /// - modifiers: Modify views before being updated. + /// - type: The type of the widgets. + func container(modifiers: [(any AnyView) -> any AnyView], type: WidgetType.Type) -> ViewStorage { + .init(nil, content: [.mainContent: [content.storage(modifiers: [], type: type)]]) + } + + /// Update the stored content. + /// - Parameters: + /// - storage: The storage to update. + /// - modifiers: Modify views before being updated + /// - updateProperties: Whether to update the view's properties. + /// - type: The type of the widgets. + func update( + _ storage: ViewStorage, + modifiers: [(any AnyView) -> any AnyView], + updateProperties: Bool, + type: WidgetType.Type + ) { + guard let storage = storage.content[.mainContent]?.first else { + return + } + content.updateStorage(storage, modifiers: [], updateProperties: updateProperties, type: type) + } + +} + +extension AnyView { + + /// Remove all of the content modifiers for the wrapped views. + /// - Returns: A view. + public func stopModifiers() -> AnyView { + ModifierStopper(content: self) + } + +} diff --git a/Tests/DemoApp/DemoApp.swift b/Tests/DemoApp/DemoApp.swift index a7bea6b..4e2212b 100644 --- a/Tests/DemoApp/DemoApp.swift +++ b/Tests/DemoApp/DemoApp.swift @@ -58,23 +58,20 @@ struct DemoApp { static func main() { let backendType = Backend1.BackendWidget.self - let modifiers: [(AnyView) -> AnyView] = [ - { $0 as? Backend2.TestWidget2 != nil ? [Backend1.TestWidget1()] : $0 } - ] - print(DemoView().getDebugTree(parameters: true, type: backendType, modifiers: modifiers)) - let storage = DemoView().storage(modifiers: modifiers, type: backendType) + print(DemoView().getDebugTree(parameters: true, type: backendType, modifiers: [])) + let storage = DemoView().storage(modifiers: [], type: backendType) for round in 0...2 { print("#\(round)") - DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: true, type: backendType) + DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: backendType) } StateManager.addUpdateHandler { _ in - DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: false, type: backendType) + DemoView().updateStorage(storage, modifiers: [], updateProperties: false, type: backendType) } sleep(2) - DemoView().updateStorage(storage, modifiers: modifiers, updateProperties: true, type: backendType) + DemoView().updateStorage(storage, modifiers: [], updateProperties: true, type: backendType) } }