Remove custom renderable elements (use views)
This commit is contained in:
parent
e133b7b8b7
commit
93546224f2
@ -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/).
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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!
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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() { }
|
||||
|
||||
|
||||
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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]
|
||||
)
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
16
Sources/Model/User Interface/View/ViewRenderData.swift
Normal file
16
Sources/Model/User Interface/View/ViewRenderData.swift
Normal 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
|
||||
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -69,10 +69,6 @@ struct TestView: View {
|
||||
Backend1.Button(test) {
|
||||
test = "\(Int.random(in: 1...10))"
|
||||
}
|
||||
Backend1.Menu("Hi") {
|
||||
Backend1.Menu("World") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user