Remove custom renderable elements (use views)

This commit is contained in:
david-swift 2024-07-13 11:46:13 +02:00
parent e133b7b8b7
commit 93546224f2
24 changed files with 150 additions and 352 deletions

View File

@ -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/).

View File

@ -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

View File

@ -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 <doc:CreateBackend> 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.

View File

@ -37,7 +37,8 @@ let package = Package(
## Backend-Specific Protocols
As mentioned in <doc:Backends>, the backend has to define a backend-specific widget and scene element type.
As mentioned in <doc:Backends>, 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<Storage>(
public func container<Data>(
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<Storage>(
public func update<Data>(
_ 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!

View File

@ -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<Storage>(
public func widget<Data>(
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<Storage>(
public func update<Data>(
_ 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<Storage>(
public func storages<Data>(
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<RenderableType>(
_ 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<RenderableType>(
type: RenderableType.Type,
fields: [String: Any]
) -> [RenderableStorage] {
compactMap { element in
if element as? RenderableType != nil {
return element.container(type: type, fields: fields)
}
return nil
}
}
}

View File

@ -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() {

View File

@ -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)

View File

@ -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() { }

View File

@ -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<RenderableType> {
/// 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) }
}
}
}

View File

@ -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<RenderableType>(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<RenderableType>(
_ storage: RenderableStorage,
updateProperties: Bool,
type: RenderableType.Type,
fields: [String: Any]
)
}

View File

@ -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
}
}

View File

@ -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<Storage>(
public func updateStorage<Data>(
_ 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<Storage>(
public func storage<Data>(
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<Storage>(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> Widget where Storage: AppStorage {
func widget<Data>(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<Storage>(type: Storage.Type, modifiers: [(AnyView) -> AnyView]) -> Bool where Storage: AppStorage {
func renderable<Data>(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

View File

@ -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
}

View File

@ -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.

View File

@ -15,10 +15,10 @@ public protocol Widget: AnyView {
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the app storage.
func container<Storage>(
func container<Data>(
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<Storage>(
func update<Data>(
_ storage: ViewStorage,
modifiers: [(AnyView) -> AnyView],
updateProperties: Bool,
type: Storage.Type
) where Storage: AppStorage
type: Data.Type
) where Data: ViewRenderData
}

View File

@ -17,10 +17,10 @@ struct AppearObserver: ConvenienceWidget {
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the app storage.
func container<Storage>(
func container<Data>(
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<Storage>(
func update<Data>(
_ 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)
}

View File

@ -17,10 +17,10 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the app storage.
func container<Storage>(
func container<Data>(
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<Content>: 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<Storage>(
func update<Data>(
_ 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)
}

View File

@ -17,10 +17,10 @@ struct Freeze: ConvenienceWidget {
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the app storage.
func container<Storage>(
func container<Data>(
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<Storage>(
func update<Data>(
_ 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
}

View File

@ -17,10 +17,10 @@ struct InspectorWrapper: ConvenienceWidget {
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the app storage.
func container<Storage>(
func container<Data>(
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<Storage>(
func update<Data>(
_ 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)
}

View File

@ -15,10 +15,10 @@ struct ModifierStopper: ConvenienceWidget {
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - type: The type of the app storage.
func container<Storage>(
func container<Data>(
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<Storage>(
func update<Data>(
_ 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)
}

View File

@ -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<Storage>(
func update<Data>(
_ 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<Storage>(
func container<Data>(
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

View File

@ -69,10 +69,6 @@ struct TestView: View {
Backend1.Button(test) {
test = "\(Int.random(in: 1...10))"
}
Backend1.Menu("Hi") {
Backend1.Menu("World") {
}
}
}
}

View File

@ -6,14 +6,14 @@ public enum Backend1 {
public init() { }
public func container<Storage>(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage {
public func container<Data>(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>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) {
public func update<Data>(_ 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<Storage>(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage {
public func container<Data>(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>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) {
public func update<Data>(_ 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<Storage>(modifiers: [(any AnyView) -> any AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage {
public func container<Data>(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>(_ storage: ViewStorage, modifiers: [(any AnyView) -> any AnyView], updateProperties: Bool, type: Storage.Type) {
public func update<Data>(_ 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<MenuElement> content: @escaping () -> MenuContent) {
self.label = label
self.content = content()
}
public func container<RenderableType>(type: RenderableType.Type, fields: [String : Any]) -> RenderableStorage {
.init(nil)
}
public func update<RenderableType>(_ storage: RenderableStorage, updateProperties: Bool, type: RenderableType.Type, fields: [String : Any]) {
print("Update renderable")
}
public func container<Storage>(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>(_ 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<Storage>(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<Storage>(modifiers: [(any Meta.AnyView) -> any Meta.AnyView], type: Storage.Type) -> Meta.ViewStorage where Storage : Meta.AppStorage {
public func container<Data>(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>(_ storage: Meta.ViewStorage, modifiers: [(any Meta.AnyView) -> any Meta.AnyView], updateProperties: Bool, type: Storage.Type) where Storage : Meta.AppStorage {
public func update<Data>(_ 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()

View File

@ -6,14 +6,14 @@ public enum Backend2 {
public init() { }
public func container<Storage>(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage {
public func container<Data>(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>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) {
public func update<Data>(_ 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<Storage>(modifiers: [(AnyView) -> AnyView], type: Storage.Type) -> ViewStorage where Storage: AppStorage {
public func container<Data>(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>(_ storage: ViewStorage, modifiers: [(AnyView) -> AnyView], updateProperties: Bool, type: Storage.Type) {
public func update<Data>(_ 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<Storage>(modifiers: [(any Meta.AnyView) -> any Meta.AnyView], type: Storage.Type) -> Meta.ViewStorage where Storage : Meta.AppStorage {
public func container<Data>(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>(_ storage: Meta.ViewStorage, modifiers: [(any Meta.AnyView) -> any Meta.AnyView], updateProperties: Bool, type: Storage.Type) where Storage : Meta.AppStorage {
public func update<Data>(_ 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()