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.
|
||||
- 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 menu is a list of buttons, other menus, and views. Certain views (such as menu buttons) allow menus to be used.
|
||||
- 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.
|
||||
|
||||
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.
|
||||
- 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 menu is a list of buttons, other menus, and views. Certain views (such as menu buttons) allow menus to be used.
|
||||
- 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.
|
||||
|
||||
@ -36,18 +36,18 @@ extension Array: AnyView where Element == AnyView {
|
||||
|
||||
/// Update a collection of views with a collection of view storages.
|
||||
/// - Parameters:
|
||||
/// - storage: The collection of view storages.
|
||||
/// - storages: The collection of view storages.
|
||||
/// - modifiers: Modify views before being updated.
|
||||
/// - updateProperties: Whether to update properties.
|
||||
/// - type: The type of the app storage.
|
||||
public func update<Storage>(
|
||||
_ storage: [ViewStorage],
|
||||
_ storages: [ViewStorage],
|
||||
modifiers: [(AnyView) -> AnyView],
|
||||
updateProperties: Bool,
|
||||
type: Storage.Type
|
||||
) where Storage: AppStorage {
|
||||
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
|
||||
.widget(modifiers: modifiers, 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?
|
||||
/// 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.
|
||||
|
||||
@ -19,6 +19,7 @@ public protocol Widget: AnyView {
|
||||
modifiers: [(AnyView) -> AnyView],
|
||||
type: Storage.Type
|
||||
) -> ViewStorage where Storage: AppStorage
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
|
||||
@ -71,6 +71,10 @@ struct TestView: View {
|
||||
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 var id: String
|
||||
@ -133,6 +166,10 @@ public enum Backend1 {
|
||||
|
||||
public protocol BackendSceneElement: SceneElement { }
|
||||
|
||||
public protocol MenuElement: Renderable { }
|
||||
|
||||
public typealias MenuContent = [MenuElement]
|
||||
|
||||
public class Backend1App: AppStorage {
|
||||
|
||||
public typealias SceneElementType = BackendSceneElement
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user