Add support for custom renderable types
This commit is contained in:
parent
fedc845b3f
commit
700b953953
@ -32,8 +32,8 @@ It knows the following layers of UI:
|
|||||||
|
|
||||||
- An app is the entry point of the executable, containing the windows.
|
- 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 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 a menu.
|
- A view is a part of the actual UI inside a window, another view, or another renderable component.
|
||||||
- A menu is a list of buttons, other menus, and views. Certain views (such as menu buttons) allow menus to be used.
|
- Custom renderable components can be used for more restrictive UI elements, such as menus.
|
||||||
|
|
||||||
Detailed information can be found in the [docs](https://aparokshaui.github.io/meta/).
|
Detailed information can be found in the [docs](https://aparokshaui.github.io/meta/).
|
||||||
|
|
||||||
|
|||||||
@ -14,5 +14,5 @@ It knows the following layers of UI:
|
|||||||
|
|
||||||
- An app is the entry point of the executable, containing the windows.
|
- 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 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 a menu.
|
- A view is a part of the actual UI inside a window, another view, or another renderable component.
|
||||||
- A menu is a list of buttons, other menus, and views. Certain views (such as menu buttons) allow menus to be used.
|
- Custom renderable components can be used for more restrictive UI elements, such as menus.
|
||||||
|
|||||||
@ -36,18 +36,18 @@ extension Array: AnyView where Element == AnyView {
|
|||||||
|
|
||||||
/// Update a collection of views with a collection of view storages.
|
/// Update a collection of views with a collection of view storages.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - storage: The collection of view storages.
|
/// - storages: The collection of view storages.
|
||||||
/// - modifiers: Modify views before being updated.
|
/// - modifiers: Modify views before being updated.
|
||||||
/// - updateProperties: Whether to update properties.
|
/// - updateProperties: Whether to update properties.
|
||||||
/// - type: The type of the app storage.
|
/// - type: The type of the app storage.
|
||||||
public func update<Storage>(
|
public func update<Storage>(
|
||||||
_ storage: [ViewStorage],
|
_ storages: [ViewStorage],
|
||||||
modifiers: [(AnyView) -> AnyView],
|
modifiers: [(AnyView) -> AnyView],
|
||||||
updateProperties: Bool,
|
updateProperties: Bool,
|
||||||
type: Storage.Type
|
type: Storage.Type
|
||||||
) where Storage: AppStorage {
|
) where Storage: AppStorage {
|
||||||
for (index, element) in filter({ $0.renderable(type: type, modifiers: modifiers) }).enumerated() {
|
for (index, element) in filter({ $0.renderable(type: type, modifiers: modifiers) }).enumerated() {
|
||||||
if let storage = storage[safe: index] {
|
if let storage = storages[safe: index] {
|
||||||
element
|
element
|
||||||
.widget(modifiers: modifiers, type: type)
|
.widget(modifiers: modifiers, type: type)
|
||||||
.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
|
||||||
@ -114,3 +114,44 @@ extension Array where Element: Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Array where Element == Renderable {
|
||||||
|
|
||||||
|
/// Update a collection of renderable elements.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - storage: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
68
Sources/Model/User Interface/Renderable/Builder.swift
Normal file
68
Sources/Model/User Interface/Renderable/Builder.swift
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
//
|
||||||
|
// Builder.swift
|
||||||
|
// Meta
|
||||||
|
//
|
||||||
|
// Created by david-swift on 03.07.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// The ``Builder`` is a result builder for scenes.
|
||||||
|
@resultBuilder
|
||||||
|
public enum Builder<RenderableType> {
|
||||||
|
|
||||||
|
/// A component used in the ``MenuBuilder``.
|
||||||
|
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 components: The components.
|
||||||
|
/// - Returns: The components in a component.
|
||||||
|
public static func buildBlock(_ elements: Component...) -> Component {
|
||||||
|
.components(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translate an element into a ``MenuBuilder.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 ``MenuBuilder.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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
30
Sources/Model/User Interface/Renderable/Renderable.swift
Normal file
30
Sources/Model/User Interface/Renderable/Renderable.swift
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// 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]
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// ViewStorage.swift
|
||||||
|
// Meta
|
||||||
|
//
|
||||||
|
// Created by david-swift on 26.05.24.
|
||||||
|
//
|
||||||
|
|
||||||
|
/// Store a reference to a rendered view in a view 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] = [:]
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
/// - Parameter pointer: The pointer to the renderable element, its type depends on the backend.
|
||||||
|
public init(_ pointer: Any?, content: [String: [ViewStorage]] = [:]) {
|
||||||
|
self.pointer = pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -14,6 +14,8 @@ public class ViewStorage {
|
|||||||
public var pointer: Any?
|
public var pointer: Any?
|
||||||
/// The view's content for container widgets.
|
/// The view's content for container widgets.
|
||||||
public var content: [String: [ViewStorage]]
|
public var content: [String: [ViewStorage]]
|
||||||
|
/// The view's renderable content.
|
||||||
|
public var renderableContent: [String: [RenderableStorage]] = [:]
|
||||||
/// The view's state (used in `StateWrapper`).
|
/// The view's state (used in `StateWrapper`).
|
||||||
var state: [String: StateProtocol] = [:]
|
var state: [String: StateProtocol] = [:]
|
||||||
/// Various properties of a widget.
|
/// Various properties of a widget.
|
||||||
|
|||||||
@ -19,6 +19,7 @@ public protocol Widget: AnyView {
|
|||||||
modifiers: [(AnyView) -> AnyView],
|
modifiers: [(AnyView) -> AnyView],
|
||||||
type: Storage.Type
|
type: Storage.Type
|
||||||
) -> ViewStorage where Storage: AppStorage
|
) -> ViewStorage where Storage: AppStorage
|
||||||
|
|
||||||
/// Update the stored content.
|
/// Update the stored content.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - storage: The storage to update.
|
/// - storage: The storage to update.
|
||||||
|
|||||||
@ -71,6 +71,10 @@ struct TestView: View {
|
|||||||
test = "\(Int.random(in: 1...10))"
|
test = "\(Int.random(in: 1...10))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Backend1.Menu("Hi") {
|
||||||
|
Backend1.Menu("World") {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,6 +70,39 @@ 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 struct Window: BackendSceneElement {
|
||||||
|
|
||||||
public var id: String
|
public var id: String
|
||||||
@ -133,6 +166,10 @@ public enum Backend1 {
|
|||||||
|
|
||||||
public protocol BackendSceneElement: SceneElement { }
|
public protocol BackendSceneElement: SceneElement { }
|
||||||
|
|
||||||
|
public protocol MenuElement: Renderable { }
|
||||||
|
|
||||||
|
public typealias MenuContent = [MenuElement]
|
||||||
|
|
||||||
public class Backend1App: AppStorage {
|
public class Backend1App: AppStorage {
|
||||||
|
|
||||||
public typealias SceneElementType = BackendSceneElement
|
public typealias SceneElementType = BackendSceneElement
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user