diff --git a/README.md b/README.md index 8c69278..75ec11a 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,7 @@ It knows the following layers of UI: - An app is the entry point of the executable, containing the windows. - A scene element is a template for a container holding one or multiple views (e.g., a window). -- A view is a part of the actual UI inside a window, another view, or another renderable component. -- Custom renderable components can be used for more restrictive UI elements, such as menus. +- A view is a part of the actual UI inside a window, or another view. Detailed information can be found in the [docs](https://aparokshaui.github.io/meta/). diff --git a/Sources/Meta.docc/Meta.md b/Sources/Meta.docc/Meta.md index 051b009..0427ab3 100644 --- a/Sources/Meta.docc/Meta.md +++ b/Sources/Meta.docc/Meta.md @@ -14,8 +14,7 @@ It knows the following layers of UI: - An app is the entry point of the executable, containing the windows. - A scene element is a template for a container holding one or multiple views (e.g., a window). -- A view is a part of the actual UI inside a window, another view, or another renderable component. -- Custom renderable components can be used for more restrictive UI elements, such as menus. +- A view is a part of the actual UI inside a window, or another view. ## Topics diff --git a/Sources/Meta.docc/Principles/DeclarativeDesign.md b/Sources/Meta.docc/Principles/DeclarativeDesign.md index d3df40e..f68e1c2 100644 --- a/Sources/Meta.docc/Principles/DeclarativeDesign.md +++ b/Sources/Meta.docc/Principles/DeclarativeDesign.md @@ -77,7 +77,8 @@ Window { ## Elements of the User Interface -_Meta_ knows different levels of UI. The app is the entry point of the executable. It contains multiple scene elements (e.g., windows on desktop systems, but can also be, e.g., menu bars - simply anything "top-level"), which may contain other scene elements, views, or custom renderable elements (e.g., menus). All of those layers, except for the app layer, are defined using their own domain-specific language. +_Meta_ knows different levels of UI. The app is the entry point of the executable. It contains multiple scene elements (e.g., windows on desktop systems, but can also be, e.g., menu bars - simply anything "top-level"), which may contain other scene elements, or views. +All of those layers, except for the app layer, are defined using their own domain-specific language. The following code shows all of the available levels of UI for a typical desktop _backend_ (but all the elements are backend-specific): @@ -92,7 +93,7 @@ struct AwesomeApp: App { // The app (no DSL) Window("Awesome App") { // The view DSL ContentView() .padding(10) - Menu { // A DSL for custom renderable elements + Menu { // The view DSL Button("Hello") { print("Hello") } Button("World") { print("World") } } @@ -102,13 +103,11 @@ struct AwesomeApp: App { // The app (no DSL) } ``` -In the tutorial, you will get more familiar with the different levels of UI. - A domain-specific language in _Meta_ consists of the following definitions: -- A result builder translates the domain-specific language into an array (``ViewBuilder`` for views, ``SceneBuilder`` for scenes, ``Builder`` for custom renderable elements). -- A protocol for elements of the domain (``AnyView`` for views, ``SceneElement`` for scenes, ``Renderable`` for custom renderable elements). When constructing or updating a user interface, functions required by this protocol will be called. The array is "translated" into the actual UI. -- A storage object persists between updates and saves data concerning a UI element (``ViewStorage`` for views, ``SceneStorage`` for scenes, ``RenderableStorage`` for custom renderable elements). This is required as the UI elements' definitions in the DSL are re-rendered with each update. +- A result builder translates the domain-specific language into an array (``ViewBuilder`` for views, ``SceneBuilder`` for scenes). +- A protocol for elements of the domain (``AnyView`` for views, ``SceneElement`` for scenes). When constructing or updating a user interface, functions required by this protocol will be called. The array is "translated" into the actual UI. +- A storage object persists between updates and saves data concerning a UI element (``ViewStorage`` for views, ``SceneStorage`` for scenes). This is required as the UI elements' definitions in the DSL are re-rendered with each update. When creating a backend, you define platform-specific UI elements conforming to the protocol for this type of UI element and manage their "translation" into imperative code using the storage object. diff --git a/Sources/Meta.docc/Tutorials/CreateBackend.md b/Sources/Meta.docc/Tutorials/CreateBackend.md index ba0ae80..894d695 100644 --- a/Sources/Meta.docc/Tutorials/CreateBackend.md +++ b/Sources/Meta.docc/Tutorials/CreateBackend.md @@ -37,7 +37,8 @@ let package = Package( ## Backend-Specific Protocols -As mentioned in , the backend has to define a backend-specific widget and scene element type. +As mentioned in , the backend has to define a backend-specific scene element type. +Often, it is sensible to define a widget type for regular views. ```swift import Meta @@ -48,6 +49,8 @@ public protocol TermKitWidget: Widget { } ## The Wrapper Widget +In this section, the widget type for regular views will be extended so that it can be used for rendering. + With _Meta_, arrays of ``AnyView`` have to be able to be converted into a single widget. This allows definitions such as the following one: @@ -74,10 +77,10 @@ public struct VStack: Wrapper, TermKitWidget { self.content = content() } - public func container( + public func container( modifiers: [(any AnyView) -> any AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { let storages = content.storages(modifiers: modifiers, type: type) // Get the storages of child views if storages.count == 1 { return .init(storages[0].pointer, content: [.mainContent: storages]) @@ -94,12 +97,12 @@ public struct VStack: Wrapper, TermKitWidget { return .init(view, content: [.mainContent: storages]) // Save storages of child views in the parent's storage for view updates } - public func update( + public func update( _ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { guard let storages = storage.content[.mainContent] else { return } @@ -124,10 +127,26 @@ However, consider the following exceptions: - _Always_ update closures (such as the action of a button widget). They may contain reference to state which is updated whenever a view update takes place. - _Always_ update bindings. As one can see when looking at ``Binding/init(get:set:)``, they contain two closures which, in most cases, contain a reference to state. +### The Render Data Type + +Now, define a view render data type for the main views. + +```swift +public enum MainViewType: ViewRenderData { + + public typealias WidgetType = TermKitWidget + public typealias WrapperType = VStack + +} +``` + +It is possible to have multiple view render data types in one backend for different situations. +As an example, you could add another type for menus. + ## The App Storage An app storage object in the app definition determines which backend to use for rendering. -Therefore, it must contain information about the scene element and widget types, as well as the wrapper widget. +Therefore, it must contain information about the scene element. Additionally, the function for executing the app is defined on the object, allowing you to put the setup of the UI into the correct context. The quit funtion should terminate the app. @@ -139,15 +158,10 @@ import TermKit public class TermKitApp: AppStorage { public typealias SceneElementType = TermKitSceneElement - public typealias WidgetType = TermKitWidget - public typealias WrapperType = VStack - public var app: () -> any App public var storage: StandardAppStorage = .init() - public required init(id: String, app: @escaping () -> any App) { - self.app = app - } + public required init(id: String) { } public func run(setup: @escaping () -> Void) { Application.prepare() @@ -165,7 +179,7 @@ public class TermKitApp: AppStorage { ## Next Steps -Now, you can start implementing scene elements (windows or other "top-level containers"), views, and custom renderable elements. +Now, you can start implementing scene elements (windows or other "top-level containers"), and views. Remember following the instructions for correct updating above for all of the UI element types. If you still have questions, browse code in the [TermKitBackend repository](https://github.com/david-swift/TermKitBackend) or ask a question in the [discussions](https://github.com/AparokshaUI/Meta/discussions). Feedback on the documentation is appreciated! diff --git a/Sources/Model/Extensions/Array.swift b/Sources/Model/Extensions/Array.swift index a4f31d6..62813d5 100644 --- a/Sources/Model/Extensions/Array.swift +++ b/Sources/Model/Extensions/Array.swift @@ -17,10 +17,10 @@ extension Array: AnyView where Element == AnyView { /// - modifiers: Modify views before being updated. /// - type: The app storage type. /// - Returns: A widget. - public func widget( + public func widget( modifiers: [(AnyView) -> AnyView], - type: Storage.Type - ) -> Widget where Storage: AppStorage { + type: Data.Type + ) -> Widget where Data: ViewRenderData { if count == 1, let widget = self[safe: 0]?.widget(modifiers: modifiers, type: type) { return widget } else { @@ -30,7 +30,7 @@ extension Array: AnyView where Element == AnyView { modified[safe: index] = modifier(view) } } - return Storage.WrapperType { modified } + return Data.WrapperType { modified } } } @@ -40,12 +40,12 @@ extension Array: AnyView where Element == AnyView { /// - modifiers: Modify views before being updated. /// - updateProperties: Whether to update properties. /// - type: The type of the app storage. - public func update( + public func update( _ storages: [ViewStorage], modifiers: [(AnyView) -> AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { for (index, element) in filter({ $0.renderable(type: type, modifiers: modifiers) }).enumerated() { if let storage = storages[safe: index] { element @@ -60,10 +60,10 @@ extension Array: AnyView where Element == AnyView { /// - modifiers: Modify views before generating the storages. /// - type: The type of the app storage. /// - Returns: The storages. - public func storages( + public func storages( modifiers: [(AnyView) -> AnyView], - type: Storage.Type - ) -> [ViewStorage] where Storage: AppStorage { + type: Data.Type + ) -> [ViewStorage] where Data: ViewRenderData { compactMap { view in view.renderable(type: type, modifiers: modifiers) ? view.storage(modifiers: modifiers, type: type) : nil } @@ -114,44 +114,3 @@ extension Array where Element: Identifiable { } } - -extension Array where Element == Renderable { - - /// Update a collection of renderable elements. - /// - Parameters: - /// - storages: The collection of renderable storages. - /// - updateProperties: Whether to update properties. - /// - type: The type of the renderable element. - /// - fields: Additional information. - public func update( - _ storages: [RenderableStorage], - updateProperties: Bool, - type: RenderableType.Type, - fields: [String: Any] - ) { - for (index, element) in filter({ $0 as? RenderableType != nil }).enumerated() { - if let storage = storages[safe: index] { - element - .update(storage, updateProperties: updateProperties, type: type, fields: fields) - } - } - } - - /// Get the renderable storages of a collection of renderable elements. - /// - Parameters: - /// - type: The type of the renderable element. - /// - fields: Additional information. - /// - Returns: The storages. - public func storages( - type: RenderableType.Type, - fields: [String: Any] - ) -> [RenderableStorage] { - compactMap { element in - if element as? RenderableType != nil { - return element.container(type: type, fields: fields) - } - return nil - } - } - -} diff --git a/Sources/Model/User Interface/App/App.swift b/Sources/Model/User Interface/App/App.swift index 3f06841..f0305e4 100644 --- a/Sources/Model/User Interface/App/App.swift +++ b/Sources/Model/User Interface/App/App.swift @@ -48,7 +48,7 @@ extension App { public static func main() { let app = setupApp() app.app.run { - for element in app.scene { + for element in app.scene where element as? Storage.SceneElementType != nil { element.setupInitialContainers(app: app.app) } } @@ -60,7 +60,8 @@ extension App { /// To run the app, call the ``AppStorage/run(setup:)`` function. public static func setupApp() -> Self { var appInstance = self.init() - appInstance.app = Storage(id: appInstance.id) { appInstance } + appInstance.app = Storage(id: appInstance.id) + appInstance.app.storage.app = { appInstance } StateManager.addUpdateHandler { force in var updateProperties = force for property in appInstance.getState() { diff --git a/Sources/Model/User Interface/App/AppStorage.swift b/Sources/Model/User Interface/App/AppStorage.swift index 42b509e..cb7d503 100644 --- a/Sources/Model/User Interface/App/AppStorage.swift +++ b/Sources/Model/User Interface/App/AppStorage.swift @@ -10,22 +10,13 @@ public protocol AppStorage: AnyObject { /// The type of scene elements (which should be backend-specific). associatedtype SceneElementType - /// The type of widget elements (which should be backend-specific). - associatedtype WidgetType - /// The wrapper widget. - associatedtype WrapperType: Wrapper - - /// The scene. - var app: () -> any App { get } /// The scene storage. var storage: StandardAppStorage { get set } /// Initialize the app storage. - /// - Parameters: - /// - id: The app's identifier. - /// - app: Get the application. - init(id: String, app: @escaping () -> any App) + /// - Parameters id: The app's identifier. + init(id: String) /// Run the application. /// - Parameter setup: A closure that is expected to be executed right at the beginning. @@ -47,7 +38,7 @@ extension AppStorage { /// Add a new scene element with the content of the scene element with a certain id. /// - Parameter id: The element's id. public func addSceneElement(_ id: String) { - if let element = app().scene.last(where: { $0.id == id }) { + if let element = storage.app?().scene.last(where: { $0.id == id }) { let container = element.container(app: self) storage.sceneStorage.append(container) showSceneElement(id) diff --git a/Sources/Model/User Interface/App/StandardAppStorage.swift b/Sources/Model/User Interface/App/StandardAppStorage.swift index 150d8c7..da9dc32 100644 --- a/Sources/Model/User Interface/App/StandardAppStorage.swift +++ b/Sources/Model/User Interface/App/StandardAppStorage.swift @@ -14,6 +14,9 @@ public struct StandardAppStorage { /// The state storage. var stateStorage: [String: StateProtocol] = [:] + /// The scene. + var app: (() -> any App)? + /// Initialize the standard app storage. public init() { } diff --git a/Sources/Model/User Interface/Renderable/Builder.swift b/Sources/Model/User Interface/Renderable/Builder.swift deleted file mode 100644 index 238382a..0000000 --- a/Sources/Model/User Interface/Renderable/Builder.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// Builder.swift -// Meta -// -// Created by david-swift on 03.07.24. -// - -import Foundation - -/// The ``Builder`` is a result builder for custom data. -@resultBuilder -public enum Builder { - - /// A component used in the ``Builder``. - public enum Component { - - /// A renderable element as a component. - case element(_: RenderableType) - /// An array of components as a component. - case components(_: [Self]) - - } - - /// Build combined results from statement blocks. - /// - Parameter elements: The components. - /// - Returns: The components in a component. - public static func buildBlock(_ elements: Component...) -> Component { - .components(elements) - } - - /// Translate an element into a ``Builder/Component``. - /// - Parameter element: The element to translate. - /// - Returns: A component created from the element. - public static func buildExpression(_ element: RenderableType) -> Component { - .element(element) - } - - /// Translate an array of elements into a ``Builder/Component``. - /// - Parameter elements: The elements to translate. - /// - Returns: A component created from the element. - public static func buildExpression(_ elements: [RenderableType]) -> Component { - var components: [Component] = [] - for element in elements { - components.append(.element(element)) - } - return .components(components) - } - - /// Fetch a component. - /// - Parameter component: A component. - /// - Returns: The component. - public static func buildExpression(_ component: Component) -> Component { - component - } - - /// Convert a component to an array of elements. - /// - Parameter component: The component to convert. - /// - Returns: The generated array of elements. - public static func buildFinalResult(_ component: Component) -> [RenderableType] { - switch component { - case let .element(element): - return [element] - case let .components(components): - return components.flatMap { buildFinalResult($0) } - } - } - -} diff --git a/Sources/Model/User Interface/Renderable/Renderable.swift b/Sources/Model/User Interface/Renderable/Renderable.swift deleted file mode 100644 index 0c01102..0000000 --- a/Sources/Model/User Interface/Renderable/Renderable.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Renderable.swift -// Meta -// -// Created by david-swift on 03.07.24. -// - -/// A structure conforming to `Renderable` is a more limited UI type similar to a view, e.g. a menu. -public protocol Renderable { - - /// The view storage. - /// - Parameters: - /// - type: The type of the renderable elements. - /// - fields: More information. - func container(type: RenderableType.Type, fields: [String: Any]) -> RenderableStorage - - /// Update the stored content. - /// - Parameters: - /// - storage: The storage to update. - /// - updateProperties: Whether to update the properties. - /// - type: The type of the renderable elements. - /// - fields: - func update( - _ storage: RenderableStorage, - updateProperties: Bool, - type: RenderableType.Type, - fields: [String: Any] - ) - -} diff --git a/Sources/Model/User Interface/Renderable/RenderableStorage.swift b/Sources/Model/User Interface/Renderable/RenderableStorage.swift deleted file mode 100644 index a7b5181..0000000 --- a/Sources/Model/User Interface/Renderable/RenderableStorage.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// ViewStorage.swift -// Meta -// -// Created by david-swift on 26.05.24. -// - -/// Store a reference to a rendered element in a renderable storage. -public class RenderableStorage { - - /// The pointer. - /// - /// It can be a C pointer, a Swift class, or other information depending on the backend and renderable type. - public var pointer: Any? - /// Various properties of a widget. - public var fields: [String: Any] = [:] - /// Other renderable storage elements. - public var content: [String: [RenderableStorage]] = [:] - - /// The pointer as an opaque pointer, as this is may be needed with backends interoperating with C or C++. - public var opaquePointer: OpaquePointer? { - get { - pointer as? OpaquePointer - } - set { - pointer = newValue - } - } - - /// Initialize a renderable storage. - /// - Parameters: - /// - pointer: The pointer to the renderable element, its type depends on the backend. - /// - content: Other renderable storages. - public init(_ pointer: Any?, content: [String: [RenderableStorage]] = [:]) { - self.pointer = pointer - self.content = content - } - -} diff --git a/Sources/Model/User Interface/View/AnyView.swift b/Sources/Model/User Interface/View/AnyView.swift index db0ec41..4493215 100644 --- a/Sources/Model/User Interface/View/AnyView.swift +++ b/Sources/Model/User Interface/View/AnyView.swift @@ -29,12 +29,12 @@ extension AnyView { /// - modifiers: Modify views before being updated. /// - updateProperties: Whether to update properties. /// - type: The type of the app storage. - public func updateStorage( + public func updateStorage( _ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { widget(modifiers: modifiers, type: type) .update(storage, modifiers: modifiers, updateProperties: updateProperties, type: type) } @@ -44,31 +44,31 @@ extension AnyView { /// - modifiers: Modify views before being updated. /// - type: The widget types. /// - Returns: The storage. - public func storage( + public func storage( modifiers: [(AnyView) -> AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { widget(modifiers: modifiers, type: type).container(modifiers: modifiers, type: type) } /// Wrap the view into a widget. /// - Parameter modifiers: Modify views before being updated. /// - Returns: The widget. - func widget(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> Widget where Storage: AppStorage { + func widget(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> Widget where Data: ViewRenderData { let modified = getModified(modifiers: modifiers) if let peer = modified as? Widget { return peer } if let array = modified as? Body { - return Storage.WrapperType { array } + return Data.WrapperType { array } } - return Storage.WrapperType { viewContent.map { $0.getModified(modifiers: modifiers) } } + return Data.WrapperType { viewContent.map { $0.getModified(modifiers: modifiers) } } } /// Whether the view can be rendered in a certain environment. - func renderable(type: Storage.Type, modifiers: [(AnyView) -> AnyView]) -> Bool where Storage: AppStorage { + func renderable(type: Data.Type, modifiers: [(AnyView) -> AnyView]) -> Bool where Data: ViewRenderData { let result = getModified(modifiers: modifiers) - return result as? Storage.WidgetType != nil + return result as? Data.WidgetType != nil || result as? SimpleView != nil || result as? View != nil || result as? ConvenienceWidget != nil diff --git a/Sources/Model/User Interface/View/ViewRenderData.swift b/Sources/Model/User Interface/View/ViewRenderData.swift new file mode 100644 index 0000000..9458e0a --- /dev/null +++ b/Sources/Model/User Interface/View/ViewRenderData.swift @@ -0,0 +1,16 @@ +// +// ViewRenderData.swift +// Meta +// +// Created by david-swift on 13.07.24. +// + +/// Information about the widget and wrapper types. +public protocol ViewRenderData { + + /// The type of widget elements (which should be backend-specific). + associatedtype WidgetType + /// The wrapper widget. + associatedtype WrapperType: Wrapper + +} diff --git a/Sources/Model/User Interface/View/ViewStorage.swift b/Sources/Model/User Interface/View/ViewStorage.swift index 5260af1..7b6ef0a 100644 --- a/Sources/Model/User Interface/View/ViewStorage.swift +++ b/Sources/Model/User Interface/View/ViewStorage.swift @@ -14,8 +14,6 @@ public class ViewStorage { public var pointer: Any? /// The view's content for container widgets. public var content: [String: [ViewStorage]] - /// The view's renderable content. - public var renderableContent: [String: [RenderableStorage]] = [:] /// The view's state (used in `StateWrapper`). var state: [String: StateProtocol] = [:] /// Various properties of a widget. diff --git a/Sources/Model/User Interface/View/Widget.swift b/Sources/Model/User Interface/View/Widget.swift index 9396a27..dd83967 100644 --- a/Sources/Model/User Interface/View/Widget.swift +++ b/Sources/Model/User Interface/View/Widget.swift @@ -15,10 +15,10 @@ public protocol Widget: AnyView { /// - Parameters: /// - modifiers: Modify views before being updated. /// - type: The type of the app storage. - func container( + func container( modifiers: [(AnyView) -> AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData /// Update the stored content. /// - Parameters: @@ -26,12 +26,12 @@ public protocol Widget: AnyView { /// - modifiers: Modify views before being updated /// - updateProperties: Whether to update the view's properties. /// - type: The type of the app storage. - func update( + func update( _ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage + type: Data.Type + ) where Data: ViewRenderData } diff --git a/Sources/View/AppearObserver.swift b/Sources/View/AppearObserver.swift index 83f64a9..cd96c98 100644 --- a/Sources/View/AppearObserver.swift +++ b/Sources/View/AppearObserver.swift @@ -17,10 +17,10 @@ struct AppearObserver: ConvenienceWidget { /// - Parameters: /// - modifiers: Modify views before being updated. /// - type: The type of the app storage. - func container( + func container( modifiers: [(any AnyView) -> any AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { let storage = content.storage(modifiers: modifiers, type: type) modify(storage) return storage @@ -32,12 +32,12 @@ struct AppearObserver: ConvenienceWidget { /// - modifiers: Modify views before being updated /// - updateProperties: Whether to update the view's properties. /// - type: The type of the app storage. - func update( + func update( _ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type) } diff --git a/Sources/View/ContentModifier.swift b/Sources/View/ContentModifier.swift index 1abe1fa..03b946d 100644 --- a/Sources/View/ContentModifier.swift +++ b/Sources/View/ContentModifier.swift @@ -17,10 +17,10 @@ struct ContentModifier: ConvenienceWidget where Content: AnyView { /// - Parameters: /// - modifiers: Modify views before being updated. /// - type: The type of the app storage. - func container( + func container( modifiers: [(any AnyView) -> any AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { content.storage(modifiers: modifiers + [modifyView], type: type) } @@ -30,12 +30,12 @@ struct ContentModifier: ConvenienceWidget where Content: AnyView { /// - modifiers: Modify views before being updated /// - updateProperties: Whether to update the view's properties. /// - type: The type of the app storage. - func update( + func update( _ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { content .updateStorage(storage, modifiers: modifiers + [modifyView], updateProperties: updateProperties, type: type) } diff --git a/Sources/View/Freeze.swift b/Sources/View/Freeze.swift index 15b0ad9..0cdbe64 100644 --- a/Sources/View/Freeze.swift +++ b/Sources/View/Freeze.swift @@ -17,10 +17,10 @@ struct Freeze: ConvenienceWidget { /// - Parameters: /// - modifiers: Modify views before being updated. /// - type: The type of the app storage. - func container( + func container( modifiers: [(any AnyView) -> any AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { content.storage(modifiers: modifiers, type: type) } @@ -30,12 +30,12 @@ struct Freeze: ConvenienceWidget { /// - modifiers: Modify views before being updated /// - updateProperties: Whether to update the view's properties. /// - type: The type of the app storage. - func update( + func update( _ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { guard !freeze else { return } diff --git a/Sources/View/InspectorWrapper.swift b/Sources/View/InspectorWrapper.swift index 63d998c..1cef9f6 100644 --- a/Sources/View/InspectorWrapper.swift +++ b/Sources/View/InspectorWrapper.swift @@ -17,10 +17,10 @@ struct InspectorWrapper: ConvenienceWidget { /// - Parameters: /// - modifiers: Modify views before being updated. /// - type: The type of the app storage. - func container( + func container( modifiers: [(any AnyView) -> any AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { let storage = content.storage(modifiers: modifiers, type: type) modify(storage, true) return storage @@ -32,12 +32,12 @@ struct InspectorWrapper: ConvenienceWidget { /// - modifiers: Modify views before being updated /// - updateProperties: Whether to update the view's properties. /// - type: The type of the app storage. - func update( + func update( _ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type) modify(storage, updateProperties) } diff --git a/Sources/View/ModifierStopper.swift b/Sources/View/ModifierStopper.swift index 32834fe..38c9296 100644 --- a/Sources/View/ModifierStopper.swift +++ b/Sources/View/ModifierStopper.swift @@ -15,10 +15,10 @@ struct ModifierStopper: ConvenienceWidget { /// - Parameters: /// - modifiers: Modify views before being updated. /// - type: The type of the app storage. - func container( + func container( modifiers: [(any AnyView) -> any AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { content.storage(modifiers: [], type: type) } @@ -28,12 +28,12 @@ struct ModifierStopper: ConvenienceWidget { /// - modifiers: Modify views before being updated /// - updateProperties: Whether to update the view's properties. /// - type: The type of the app storage. - func update( + func update( _ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { content.updateStorage(storage, modifiers: [], updateProperties: updateProperties, type: type) } diff --git a/Sources/View/StateWrapper.swift b/Sources/View/StateWrapper.swift index f836c1a..9281ed5 100644 --- a/Sources/View/StateWrapper.swift +++ b/Sources/View/StateWrapper.swift @@ -36,12 +36,12 @@ struct StateWrapper: ConvenienceWidget { /// - modifiers: Modify views before being updated. /// - updateProperties: Whether to update properties. /// - type: The type of the app storage. - func update( + func update( _ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, - type: Storage.Type - ) where Storage: AppStorage { + type: Data.Type + ) where Data: ViewRenderData { var updateProperties = updateProperties for property in state { if let oldID = storage.state[property.key]?.id { @@ -64,10 +64,10 @@ struct StateWrapper: ConvenienceWidget { /// - modifiers: Modify views before being updated. /// - type: The type of the app storage. /// - Returns: The view storage. - func container( + func container( modifiers: [(AnyView) -> AnyView], - type: Storage.Type - ) -> ViewStorage where Storage: AppStorage { + type: Data.Type + ) -> ViewStorage where Data: ViewRenderData { let content = content().storage(modifiers: modifiers, type: type) let storage = ViewStorage(content.pointer, content: [.mainContent: [content]]) storage.state = state diff --git a/Tests/DemoApp/DemoApp.swift b/Tests/DemoApp/DemoApp.swift index f795cec..c02c203 100644 --- a/Tests/DemoApp/DemoApp.swift +++ b/Tests/DemoApp/DemoApp.swift @@ -69,10 +69,6 @@ struct TestView: View { Backend1.Button(test) { test = "\(Int.random(in: 1...10))" } - Backend1.Menu("Hi") { - Backend1.Menu("World") { - } - } } } diff --git a/Tests/SampleBackends/Backend1.swift b/Tests/SampleBackends/Backend1.swift index 6ba51fd..89de7b7 100644 --- a/Tests/SampleBackends/Backend1.swift +++ b/Tests/SampleBackends/Backend1.swift @@ -6,14 +6,14 @@ public enum Backend1 { public init() { } - public func container(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage { + public func container(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> ViewStorage where Data: ViewRenderData { print("Init test widget 1") let storage = ViewStorage(nil) storage.fields["test"] = 0 return storage } - public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) { + public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Data.Type) { print("Update test widget 1 (#\(storage.fields["test"] ?? ""))") storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1 } @@ -24,14 +24,14 @@ public enum Backend1 { public init() { } - public func container(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage { + public func container(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> ViewStorage where Data: ViewRenderData { print("Init test widget 3") let storage = ViewStorage(nil) storage.fields["test"] = 0 return storage } - public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) { + public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Data.Type) { print("Update test widget 3 (#\(storage.fields["test"] ?? ""))") storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1 } @@ -48,7 +48,7 @@ public enum Backend1 { self.action = action } - public func container(modifiers: [(any AnyView) -> any AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage { + public func container(modifiers: [(any AnyView) -> any AnyView], type: Data.Type) -> ViewStorage where Data: ViewRenderData { print("Init button") let storage = ViewStorage(nil) Task { @@ -59,7 +59,7 @@ public enum Backend1 { return storage } - public func update(_ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, type: Storage.Type) { + public func update(_ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, type: Data.Type) { storage.fields["action"] = action if updateProperties { print("Update button (label = \(label))") @@ -70,39 +70,6 @@ public enum Backend1 { } - public struct Menu: BackendWidget, MenuElement { - - var label: String - var content: MenuContent - - public init(_ label: String, @Builder content: @escaping () -> MenuContent) { - self.label = label - self.content = content() - } - - public func container(type: RenderableType.Type, fields: [String : Any]) -> RenderableStorage { - .init(nil) - } - - public func update(_ storage: RenderableStorage, updateProperties: Bool, type: RenderableType.Type, fields: [String : Any]) { - print("Update renderable") - } - - public func container(modifiers: [(any AnyView) -> any AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage { - let storage = ViewStorage(nil) - storage.renderableContent[.mainContent] = (content as [Renderable]).storages(type: MenuElement.self, fields: [:]) - return storage - } - - public func update(_ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, type: Storage.Type) { - guard updateProperties, let content = storage.renderableContent[.mainContent] else { - return - } - (self.content as [Renderable]).update(content, updateProperties: updateProperties, type: MenuElement.self, fields: [:]) - } - - } - public struct Window: BackendSceneElement { public var id: String @@ -123,7 +90,7 @@ public enum Backend1 { public func container(app: Storage) -> SceneStorage where Storage: AppStorage { print("Show \(id)") - let viewStorage = content.storage(modifiers: [], type: Storage.self) + let viewStorage = content.storage(modifiers: [], type: MainViewRenderData.self) return .init(id: id, pointer: nil, content: [.mainContent : [viewStorage]]) { print("Make visible") } @@ -134,7 +101,7 @@ public enum Backend1 { guard let viewStorage = storage.content[.mainContent]?.first else { return } - content.updateStorage(viewStorage, modifiers: [], updateProperties: updateProperties, type: Storage.self) + content.updateStorage(viewStorage, modifiers: [], updateProperties: updateProperties, type: MainViewRenderData.self) } } @@ -147,13 +114,13 @@ public enum Backend1 { self.content = content() } - public func container(modifiers: [(any Meta.AnyView) -> any Meta.AnyView], type: Storage.Type) -> Meta.ViewStorage where Storage : Meta.AppStorage { + public func container(modifiers: [(any Meta.AnyView) -> any Meta.AnyView], type: Data.Type) -> Meta.ViewStorage where Data: ViewRenderData { let storage = ViewStorage(nil) storage.content = [.mainContent: content.storages(modifiers: modifiers, type: type)] return storage } - public func update(_ storage: Meta.ViewStorage, modifiers: [(any Meta.AnyView) -> any Meta.AnyView], updateProperties: Bool, type: Storage.Type) where Storage : Meta.AppStorage { + public func update(_ storage: Meta.ViewStorage, modifiers: [(any Meta.AnyView) -> any Meta.AnyView], updateProperties: Bool, type: Data.Type) where Data: ViewRenderData { guard let storages = storage.content[.mainContent] else { return } @@ -162,26 +129,24 @@ public enum Backend1 { } + public struct MainViewRenderData: ViewRenderData { + + public typealias WidgetType = BackendWidget + public typealias WrapperType = Wrapper + + } + public protocol BackendWidget: Widget { } public protocol BackendSceneElement: SceneElement { } - public protocol MenuElement: Renderable { } - - public typealias MenuContent = [MenuElement] - public class Backend1App: AppStorage { public typealias SceneElementType = BackendSceneElement - public typealias WidgetType = BackendWidget - public typealias WrapperType = Wrapper - public var app: () -> any App public var storage: StandardAppStorage = .init() - public required init(id: String, app: @escaping () -> any App) { - self.app = app - } + public required init(id: String) { } public func run(setup: @escaping () -> Void) { setup() diff --git a/Tests/SampleBackends/Backend2.swift b/Tests/SampleBackends/Backend2.swift index b7081a3..dd7d05e 100644 --- a/Tests/SampleBackends/Backend2.swift +++ b/Tests/SampleBackends/Backend2.swift @@ -6,14 +6,14 @@ public enum Backend2 { public init() { } - public func container(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage { + public func container(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> ViewStorage where Data: ViewRenderData { print("Init test widget 2") let storage = ViewStorage(nil) storage.fields["test"] = 0 return storage } - public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) { + public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Data.Type) { print("Update test widget 2 (#\(storage.fields["test"] ?? ""))") storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1 } @@ -24,14 +24,14 @@ public enum Backend2 { public init() { } - public func container(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage { + public func container(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> ViewStorage where Data: ViewRenderData { print("Init test widget 4") let storage = ViewStorage(nil) storage.fields["test"] = 0 return storage } - public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) { + public func update(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Data.Type) { print("Update test widget 4 (#\(storage.fields["test"] ?? ""))") storage.fields["test"] = (storage.fields["test"] as? Int ?? 0) + 1 } @@ -46,13 +46,13 @@ public enum Backend2 { self.content = content() } - public func container(modifiers: [(any Meta.AnyView) -> any Meta.AnyView], type: Storage.Type) -> Meta.ViewStorage where Storage : Meta.AppStorage { + public func container(modifiers: [(any Meta.AnyView) -> any Meta.AnyView], type: Data.Type) -> Meta.ViewStorage where Data: ViewRenderData { let storage = ViewStorage(nil) storage.content = [.mainContent: content.storages(modifiers: modifiers, type: type)] return storage } - public func update(_ storage: Meta.ViewStorage, modifiers: [(any Meta.AnyView) -> any Meta.AnyView], updateProperties: Bool, type: Storage.Type) where Storage : Meta.AppStorage { + public func update(_ storage: Meta.ViewStorage, modifiers: [(any Meta.AnyView) -> any Meta.AnyView], updateProperties: Bool, type: Data.Type) where Data: ViewRenderData { guard let storages = storage.content[.mainContent] else { return } @@ -68,15 +68,10 @@ public enum Backend2 { public class Backend2App: AppStorage { public typealias SceneElementType = BackendSceneElement - public typealias WidgetType = BackendWidget - public typealias WrapperType = Wrapper - public var app: () -> any App public var storage: StandardAppStorage = .init() - public required init(id: String, app: @escaping () -> any App) { - self.app = app - } + public required init(id: String) { } public func run(setup: @escaping () -> Void) { setup()