Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f47727df52 | |||
| 2cab78dedc | |||
| 64c536b03c | |||
| ed46533740 | |||
| 0e2595e2d4 | |||
| d7b7c112cf | |||
|
|
3b2f8f926c | ||
|
|
ce9c5bf7d1 | ||
| 18f51ffb93 | |||
| 9f50e272f3 | |||
| 681a51110d | |||
| ee92f63f86 | |||
| a8ce63a67f | |||
| 99603193b9 | |||
| b12c02d391 |
@ -25,7 +25,7 @@ jobs:
|
||||
echo "<script>window.location.href += \"/documentation/meta\"</script><p>Please enable JavaScript to view the documentation <a href='/documentation/meta'>here</a>.</p>" > docs/index.html;
|
||||
sed -i '' 's/,2px/,10px/g' docs/css/index.*.css
|
||||
- name: Upload
|
||||
uses: wangyucode/sftp-upload-action@v2.0.2
|
||||
uses: wangyucode/sftp-upload-action@v2.0.4
|
||||
with:
|
||||
host: 'volans.uberspace.de'
|
||||
username: 'akforum'
|
||||
|
||||
14
CMakeLists.txt
Normal file
14
CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
project(Meta LANGUAGES Swift)
|
||||
|
||||
if(POLICY CMP0157)
|
||||
cmake_policy(SET CMP0157 NEW)
|
||||
endif()
|
||||
|
||||
set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
add_subdirectory(Sources)
|
||||
add_subdirectory(Tests)
|
||||
@ -24,17 +24,26 @@ let package = Package(
|
||||
targets: [
|
||||
.target(
|
||||
name: "Meta",
|
||||
path: "Sources"
|
||||
path: "Sources",
|
||||
exclude: [
|
||||
"CMakeLists.txt"
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "SampleBackends",
|
||||
dependencies: ["Meta"],
|
||||
path: "Tests/SampleBackends"
|
||||
path: "Tests/SampleBackends",
|
||||
exclude: [
|
||||
"CMakeLists.txt"
|
||||
]
|
||||
),
|
||||
.executableTarget(
|
||||
name: "DemoApp",
|
||||
dependencies: ["SampleBackends"],
|
||||
path: "Tests/DemoApp"
|
||||
path: "Tests/DemoApp",
|
||||
exclude: [
|
||||
"CMakeLists.txt"
|
||||
]
|
||||
)
|
||||
],
|
||||
swiftLanguageModes: [.v5]
|
||||
|
||||
26
Sources/CMakeLists.txt
Normal file
26
Sources/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
||||
file(GLOB META_SOURCES
|
||||
"Model/Data Flow/*.swift"
|
||||
|
||||
"Model/Extensions/*.swift"
|
||||
|
||||
"Model/User Interface/App/*.swift"
|
||||
"Model/User Interface/Scene/*.swift"
|
||||
"Model/User Interface/View/Properties/*.swift"
|
||||
"Model/User Interface/View/*.swift"
|
||||
|
||||
"View/*.swift"
|
||||
)
|
||||
|
||||
add_library(Meta ${META_SOURCES})
|
||||
|
||||
target_compile_options(Meta PUBLIC -enable-testing)
|
||||
|
||||
set_target_properties(Meta PROPERTIES
|
||||
Swift_LANGUAGE_VERSION 5
|
||||
)
|
||||
|
||||
install(TARGETS Meta
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
@ -373,9 +373,7 @@ For the ``EitherView``, it is again the initializer that has to match a requirem
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let view = TermKit.View()
|
||||
let storage = ViewStorage(view)
|
||||
update(storage, data: data, updateProperties: true, type: type)
|
||||
return storage
|
||||
return .init(view)
|
||||
}
|
||||
|
||||
// ...
|
||||
@ -386,6 +384,7 @@ Normally, you would initialize all your content storages in the container functi
|
||||
In this case, this does not work. If the either view is called via standard `if`/`else` syntax and the condition is `true`,
|
||||
we can access `view1`, but `view2` is empty (the actual view is not known). If `condition` is `false`, `view1` is empty and `view2` is known.
|
||||
Therefore, we have to wait with the initialization process until `condition` changes, which is why this is handled in the `update` function.
|
||||
Make sure to call the child view's `update` function after constructing a view in the parent view's `update` function.
|
||||
|
||||
```swift
|
||||
// ...
|
||||
@ -406,6 +405,7 @@ Therefore, we have to wait with the initialization process until `condition` cha
|
||||
view = content.pointer as? TermKit.View
|
||||
} else {
|
||||
let content = body.storage(data: data, type: type)
|
||||
body.update(content, data: data, updateProperties: true, type: type)
|
||||
storage.content[condition.description] = [content]
|
||||
view = content.pointer as? TermKit.View
|
||||
}
|
||||
|
||||
49
Sources/Model/Data Flow/Environment.swift
Normal file
49
Sources/Model/Data Flow/Environment.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// Environment.swift
|
||||
// Meta
|
||||
//
|
||||
// Created by david-swift on 23.10.24.
|
||||
//
|
||||
|
||||
/// A property wrapper for properties in a view that should be stored throughout view updates.
|
||||
@propertyWrapper
|
||||
public struct Environment<Value>: EnvironmentProtocol {
|
||||
|
||||
/// Access the environment value.
|
||||
public var wrappedValue: Value? {
|
||||
content.value as? Value
|
||||
}
|
||||
/// The value's identifier.
|
||||
var id: String
|
||||
/// The content.
|
||||
let content = EnvironmentContent()
|
||||
|
||||
// swiftlint:disable function_default_parameter_at_end
|
||||
/// Initialize a property representing an environment value in the view.
|
||||
/// - Parameters:
|
||||
/// - wrappedValue: The wrapped value.
|
||||
/// - id: The environment value's identifier.
|
||||
public init(wrappedValue: Value? = nil, _ id: String) {
|
||||
self.id = id
|
||||
}
|
||||
// swiftlint:enable function_default_parameter_at_end
|
||||
|
||||
}
|
||||
|
||||
/// An environment property's content.
|
||||
class EnvironmentContent {
|
||||
|
||||
/// The value.
|
||||
var value: Any?
|
||||
|
||||
}
|
||||
|
||||
/// The environment property protocol.
|
||||
protocol EnvironmentProtocol {
|
||||
|
||||
/// The content.
|
||||
var content: EnvironmentContent { get }
|
||||
/// The identifier.
|
||||
var id: String { get }
|
||||
|
||||
}
|
||||
@ -18,8 +18,10 @@ public struct State<Value>: StateProtocol {
|
||||
}
|
||||
nonmutating set {
|
||||
rawValue = newValue
|
||||
content.update = true
|
||||
StateManager.updateViews(force: forceUpdates)
|
||||
if !blockUpdates {
|
||||
content.update = true
|
||||
StateManager.updateViews(force: forceUpdates)
|
||||
}
|
||||
writeValue?(newValue)
|
||||
}
|
||||
}
|
||||
@ -47,7 +49,10 @@ public struct State<Value>: StateProtocol {
|
||||
}
|
||||
|
||||
/// Whether to force update the views when the value changes.
|
||||
var forceUpdates: Bool
|
||||
var forceUpdates = false
|
||||
|
||||
/// Whether to block updates.
|
||||
var blockUpdates = false
|
||||
|
||||
/// The closure for initializing the state property's value.
|
||||
var getInitialValue: () -> Value
|
||||
@ -67,21 +72,35 @@ public struct State<Value>: StateProtocol {
|
||||
self.forceUpdates = forceUpdates
|
||||
}
|
||||
|
||||
/// Initialize a property representing a state in the view with an autoclosure.
|
||||
/// - Parameters:
|
||||
/// - wrappedValue: The wrapped value.
|
||||
/// - blockUpdates: Whether updates to this state value should not update the UI.
|
||||
///
|
||||
/// This can be useful for storing data and reading this data on special occasions, e.g. on startup.
|
||||
public init(wrappedValue: @autoclosure @escaping () -> Value, blockUpdates: Bool) {
|
||||
getInitialValue = wrappedValue
|
||||
self.blockUpdates = blockUpdates
|
||||
}
|
||||
|
||||
/// Initialize a property representing a state in the view with an explicit closure.
|
||||
/// - Parameters:
|
||||
/// - wrappedValue: Get the wrapped value.
|
||||
/// - writeValue: Perform additional operations when the value changes.
|
||||
/// - forceUpdates: Whether to force update all available views when the property gets modified.
|
||||
/// - blockUpdates: Whether updates to this state value should not update the UI.
|
||||
///
|
||||
/// This initializer can be used to get data from the disk.
|
||||
/// This initializer can be used e.g. to get data from the disk.
|
||||
public init(
|
||||
wrappedValue: @escaping () -> Value,
|
||||
writeValue: ((Value) -> Void)? = nil,
|
||||
forceUpdates: Bool = false
|
||||
forceUpdates: Bool = false,
|
||||
blockUpdates: Bool = false
|
||||
) {
|
||||
getInitialValue = wrappedValue
|
||||
self.writeValue = writeValue
|
||||
self.forceUpdates = forceUpdates
|
||||
self.blockUpdates = blockUpdates
|
||||
}
|
||||
|
||||
/// Get the initial value.
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
//
|
||||
// OpaquePointer.swift
|
||||
// Meta
|
||||
//
|
||||
// Created by david-swift on 29.09.24.
|
||||
//
|
||||
|
||||
extension OpaquePointer: @retroactive @unchecked Sendable { }
|
||||
@ -45,6 +45,7 @@ extension App {
|
||||
for element in app.scene where element is Storage.SceneElementType {
|
||||
element.setupInitialContainers(app: app.app)
|
||||
}
|
||||
StateManager.updateViews(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,12 @@ extension AppStorage {
|
||||
/// Focus the scene element with a certain id (if supported). Create the element if it doesn't already exist.
|
||||
/// - Parameter id: The element's id.
|
||||
public func showSceneElement(_ id: String) {
|
||||
storage.sceneStorage.last { $0.id == id && !$0.destroy }?.show() ?? addSceneElement(id)
|
||||
guard let storage = storage.sceneStorage.last(where: { $0.id == id && !$0.destroy }) else {
|
||||
addSceneElement(id)
|
||||
StateManager.updateViews(force: true)
|
||||
return
|
||||
}
|
||||
storage.show()
|
||||
}
|
||||
|
||||
/// Add a new scene element with the content of the scene element with a certain id.
|
||||
@ -39,6 +44,7 @@ extension AppStorage {
|
||||
let container = element.container(app: self)
|
||||
storage.sceneStorage.append(container)
|
||||
showSceneElement(id)
|
||||
StateManager.updateViews(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
//
|
||||
|
||||
/// A view building conditional bodies.
|
||||
///
|
||||
/// Do not forget to call the update function after constructing a new UI.
|
||||
public protocol EitherView: AnyView {
|
||||
|
||||
/// Initialize the either view.
|
||||
|
||||
@ -143,7 +143,6 @@ extension Widget {
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storage = ViewStorage(initializeWidget())
|
||||
initProperties(storage, data: data, type: type)
|
||||
update(storage, data: data, updateProperties: true, type: type)
|
||||
return storage
|
||||
}
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@ extension View {
|
||||
|
||||
/// The view's content.
|
||||
public var viewContent: Body {
|
||||
[StateWrapper(content: { view }, state: getState())]
|
||||
[StateWrapper(content: { view }, state: getState(), environment: getEnvironmentVariables())]
|
||||
}
|
||||
|
||||
/// Get the state from the properties.
|
||||
@ -47,4 +47,16 @@ extension View {
|
||||
return state
|
||||
}
|
||||
|
||||
/// Get the environment properties.
|
||||
/// - Returns: The environment properties.
|
||||
func getEnvironmentVariables() -> [String: any EnvironmentProtocol] {
|
||||
var environment: [String: any EnvironmentProtocol] = [:]
|
||||
for property in Mirror(reflecting: self).children {
|
||||
if let label = property.label, let value = property.value as? any EnvironmentProtocol {
|
||||
environment[label] = value
|
||||
}
|
||||
}
|
||||
return environment
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
struct AppearObserver: ConvenienceWidget {
|
||||
|
||||
/// The custom code to edit the widget.
|
||||
var modify: (ViewStorage) -> Void
|
||||
var modify: (ViewStorage, WidgetData) -> Void
|
||||
/// The wrapped view.
|
||||
var content: AnyView
|
||||
|
||||
@ -23,7 +23,7 @@ struct AppearObserver: ConvenienceWidget {
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storage = content.storage(data: data, type: type)
|
||||
modify(storage)
|
||||
modify(storage, data)
|
||||
return storage
|
||||
}
|
||||
|
||||
@ -50,10 +50,17 @@ extension AnyView {
|
||||
/// Run a function on the widget when it appears for the first time.
|
||||
/// - Parameter closure: The function.
|
||||
/// - Returns: A view.
|
||||
public func inspectOnAppear(_ closure: @escaping (ViewStorage) -> Void) -> AnyView {
|
||||
public func inspectOnAppear(_ closure: @escaping (ViewStorage, WidgetData) -> Void) -> AnyView {
|
||||
AppearObserver(modify: closure, content: self)
|
||||
}
|
||||
|
||||
/// Run a function on the widget when it appears for the first time.
|
||||
/// - Parameter closure: The function.
|
||||
/// - Returns: A view.
|
||||
public func inspectOnAppear(_ closure: @escaping (ViewStorage) -> Void) -> AnyView {
|
||||
inspectOnAppear { storage, _ in closure(storage) }
|
||||
}
|
||||
|
||||
/// Run a function when the view appears for the first time.
|
||||
/// - Parameter closure: The function.
|
||||
/// - Returns: A view.
|
||||
|
||||
67
Sources/View/DataWrapper.swift
Normal file
67
Sources/View/DataWrapper.swift
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// StateWrapper.swift
|
||||
// Meta
|
||||
//
|
||||
// Created by david-swift on 09.06.24.
|
||||
//
|
||||
|
||||
/// Assign values to the environment.
|
||||
///
|
||||
/// Access the environment in views (``View``) via `@Environment`.
|
||||
struct DataWrapper: ConvenienceWidget {
|
||||
|
||||
/// The content.
|
||||
var content: Body
|
||||
/// The identifier for the new environment value.
|
||||
var label: String
|
||||
/// The environment value.
|
||||
var data: Any
|
||||
|
||||
/// Get a view storage.
|
||||
/// - Parameters:
|
||||
/// - data: Modify views before being updated.
|
||||
/// - type: The view render data type.
|
||||
/// - Returns: The view storage.
|
||||
func container<Data>(
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
content.storage(data: data.modify { $0.fields[label] = self.data }, type: type)
|
||||
}
|
||||
|
||||
/// Update a view storage.
|
||||
/// - Parameters:
|
||||
/// - storage: The view storage.
|
||||
/// - data: Modify views before being updated.
|
||||
/// - updateProperties: Whether to update properties.
|
||||
/// - type: The view render data type.
|
||||
/// - Returns: The view storage.
|
||||
func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
content
|
||||
.updateStorage(
|
||||
storage,
|
||||
data: data.modify { $0.fields[label] = self.data },
|
||||
updateProperties: updateProperties,
|
||||
type: type
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AnyView {
|
||||
|
||||
/// Assign a value to an environment label.
|
||||
/// - Parameters:
|
||||
/// - label: The environment label.
|
||||
/// - data: The value.
|
||||
/// - Returns: The view.
|
||||
public func environment(_ label: String, data: Any) -> AnyView {
|
||||
DataWrapper(content: [self], label: label, data: data)
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,7 +9,7 @@
|
||||
struct InspectorWrapper: ConvenienceWidget {
|
||||
|
||||
/// The custom code to edit the widget.
|
||||
var modify: (ViewStorage, Bool) -> Void
|
||||
var modify: (ViewStorage, WidgetData, Bool) -> Void
|
||||
/// The wrapped view.
|
||||
var content: AnyView
|
||||
|
||||
@ -22,9 +22,7 @@ struct InspectorWrapper: ConvenienceWidget {
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let storage = content.storage(data: data, type: type)
|
||||
modify(storage, true)
|
||||
return storage
|
||||
content.storage(data: data, type: type)
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
@ -40,7 +38,7 @@ struct InspectorWrapper: ConvenienceWidget {
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
|
||||
modify(storage, updateProperties)
|
||||
modify(storage, data, updateProperties)
|
||||
}
|
||||
|
||||
}
|
||||
@ -51,10 +49,17 @@ extension AnyView {
|
||||
/// Run a custom code accessing the view's storage when initializing and updating the view.
|
||||
/// - Parameter modify: Modify the storage. The boolean indicates whether state in parent views changed.
|
||||
/// - Returns: A view.
|
||||
public func inspect(_ modify: @escaping (ViewStorage, Bool) -> Void) -> AnyView {
|
||||
public func inspect(_ modify: @escaping (ViewStorage, WidgetData, Bool) -> Void) -> AnyView {
|
||||
InspectorWrapper(modify: modify, content: self)
|
||||
}
|
||||
|
||||
/// Run a custom code accessing the view's storage when initializing and updating the view.
|
||||
/// - Parameter modify: Modify the storage. The boolean indicates whether state in parent views changed.
|
||||
/// - Returns: A view.
|
||||
public func inspect(_ modify: @escaping (ViewStorage, Bool) -> Void) -> AnyView {
|
||||
inspect { storage, _, updateProperties in modify(storage, updateProperties) }
|
||||
}
|
||||
|
||||
/// Run a function when the view gets updated.
|
||||
/// - Parameter onUpdate: The function.
|
||||
/// - Returns: A view.
|
||||
|
||||
91
Sources/View/SafeWrapper.swift
Normal file
91
Sources/View/SafeWrapper.swift
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// SafeWrapper.swift
|
||||
// Meta
|
||||
//
|
||||
// Created by david-swift on 02.02.26.
|
||||
//
|
||||
|
||||
/// Wrap a widget but keep its pointer.
|
||||
struct SafeWrapper: ConvenienceWidget {
|
||||
|
||||
/// The custom code to edit the wrapper.
|
||||
/// The pointer is the one of the child widget.
|
||||
var modify: (ViewStorage, WidgetData, Bool) -> Void
|
||||
/// The wrapped view.
|
||||
var content: AnyView
|
||||
|
||||
/// The view storage.
|
||||
/// - Parameters:
|
||||
/// - data: Modify views before being updated.
|
||||
/// - type: The view render data type.
|
||||
/// - Returns: The view storage.
|
||||
func container<Data>(
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
let contentStorage = content.storage(data: data, type: type)
|
||||
return .init(contentStorage.pointer, content: [.mainContent: [contentStorage]])
|
||||
}
|
||||
|
||||
/// Update the stored content.
|
||||
/// - Parameters:
|
||||
/// - storage: The storage to update.
|
||||
/// - data: Modify views before being updated
|
||||
/// - updateProperties: Whether to update the view's properties.
|
||||
/// - type: The view render data type.
|
||||
func update<Data>(
|
||||
_ storage: ViewStorage,
|
||||
data: WidgetData,
|
||||
updateProperties: Bool,
|
||||
type: Data.Type
|
||||
) where Data: ViewRenderData {
|
||||
if let contentStorage = storage.content[.mainContent]?.first {
|
||||
content.updateStorage(contentStorage, data: data, updateProperties: updateProperties, type: type)
|
||||
}
|
||||
modify(storage, data, updateProperties)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Extend any view.
|
||||
extension AnyView {
|
||||
|
||||
/// Wrap a widget but keep its pointer.
|
||||
/// - Parameter modify: Modify the storage. The boolean indicates whether state in parent views changed.
|
||||
/// - Returns: A view.
|
||||
public func wrap(_ modify: @escaping (ViewStorage, WidgetData, Bool) -> Void) -> AnyView {
|
||||
SafeWrapper(modify: modify, content: self)
|
||||
}
|
||||
|
||||
/// Wrap a widget but keep its pointer.
|
||||
/// - Parameter modify: Modify the storage. The boolean indicates whether state in parent views changed.
|
||||
/// - Returns: A view.
|
||||
public func wrap(_ modify: @escaping (ViewStorage, Bool) -> Void) -> AnyView {
|
||||
wrap { storage, _, updateProperties in modify(storage, updateProperties) }
|
||||
}
|
||||
|
||||
/// A wrapper for generic simple modifiers.
|
||||
/// - Parameters:
|
||||
/// - properties: The properties will be stored. Do not change the layout throughout updates.
|
||||
/// - update: If properties change, run this function.
|
||||
/// - Returns: A view.
|
||||
public func wrapModifier(properties: [any Hashable], update: @escaping (ViewStorage) -> Void) -> AnyView {
|
||||
wrap { storage, _, updateProperties in
|
||||
guard updateProperties else {
|
||||
return
|
||||
}
|
||||
var shouldUpdate = false
|
||||
for (index, property) in properties.enumerated() {
|
||||
let update = {
|
||||
shouldUpdate = true
|
||||
storage.fields[index.description] = property
|
||||
}
|
||||
if let equatable = storage.fields[index.description] as? any Hashable {
|
||||
if property.hashValue != equatable.hashValue { update() }
|
||||
} else { update() }
|
||||
}
|
||||
if shouldUpdate { update(storage) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -12,6 +12,8 @@ struct StateWrapper: ConvenienceWidget {
|
||||
var content: () -> Body
|
||||
/// The state information (from properties with the `State` wrapper).
|
||||
var state: [String: StateProtocol] = [:]
|
||||
/// The environment properties.
|
||||
var environment: [String: any EnvironmentProtocol] = [:]
|
||||
|
||||
/// Initialize a `StateWrapper`.
|
||||
/// - Parameter content: The view content.
|
||||
@ -23,9 +25,15 @@ struct StateWrapper: ConvenienceWidget {
|
||||
/// - Parameters:
|
||||
/// - content: The view content.
|
||||
/// - state: The state information.
|
||||
init(content: @escaping () -> Body, state: [String: StateProtocol]) {
|
||||
/// - environment: The environment properties.
|
||||
init(
|
||||
content: @escaping () -> Body,
|
||||
state: [String: StateProtocol],
|
||||
environment: [String: any EnvironmentProtocol]
|
||||
) {
|
||||
self.content = content
|
||||
self.state = state
|
||||
self.environment = environment
|
||||
}
|
||||
|
||||
/// Update a view storage.
|
||||
@ -51,6 +59,7 @@ struct StateWrapper: ConvenienceWidget {
|
||||
property.value.content.update = false
|
||||
}
|
||||
}
|
||||
assignEnvironment(data: data)
|
||||
guard let storage = storage.content[.mainContent]?.first else {
|
||||
return
|
||||
}
|
||||
@ -66,6 +75,7 @@ struct StateWrapper: ConvenienceWidget {
|
||||
data: WidgetData,
|
||||
type: Data.Type
|
||||
) -> ViewStorage where Data: ViewRenderData {
|
||||
assignEnvironment(data: data)
|
||||
let content = content().storage(data: data, type: type)
|
||||
let storage = ViewStorage(content.pointer, content: [.mainContent: [content]])
|
||||
storage.state = state
|
||||
@ -75,4 +85,12 @@ struct StateWrapper: ConvenienceWidget {
|
||||
return storage
|
||||
}
|
||||
|
||||
/// Assign an environment value to the environment property.
|
||||
/// - Parameter data: The widget data.
|
||||
func assignEnvironment(data: WidgetData) {
|
||||
for property in environment {
|
||||
property.value.content.value = data.fields[property.value.id]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
2
Tests/CMakeLists.txt
Normal file
2
Tests/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_subdirectory(SampleBackends)
|
||||
add_subdirectory(DemoApp)
|
||||
13
Tests/DemoApp/CMakeLists.txt
Normal file
13
Tests/DemoApp/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
add_executable(DemoApp
|
||||
DemoApp.swift
|
||||
)
|
||||
|
||||
target_compile_options(DemoApp PUBLIC
|
||||
-parse-as-library
|
||||
)
|
||||
|
||||
target_link_libraries(DemoApp PRIVATE SampleBackends)
|
||||
|
||||
set_target_properties(DemoApp PROPERTIES
|
||||
Swift_LANGUAGE_VERSION 5
|
||||
)
|
||||
@ -23,6 +23,7 @@ struct DemoApp: App {
|
||||
var scene: Scene {
|
||||
Backend1.Window("main", spawn: 1) {
|
||||
DemoView(app: app)
|
||||
.environment("test", data: 5)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +32,8 @@ struct DemoApp: App {
|
||||
struct DemoView: View {
|
||||
|
||||
@State private var model = TestModel()
|
||||
@Environment("test")
|
||||
private var test: Int?
|
||||
var app: any AppStorage
|
||||
let condition = false
|
||||
|
||||
@ -46,6 +49,7 @@ struct DemoView: View {
|
||||
app.addSceneElement("main")
|
||||
}
|
||||
}
|
||||
.onAppear { print(test ?? 0) }
|
||||
}
|
||||
TestView()
|
||||
testContent
|
||||
|
||||
12
Tests/SampleBackends/CMakeLists.txt
Normal file
12
Tests/SampleBackends/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
add_library(SampleBackends
|
||||
Backend1.swift
|
||||
Backend2.swift
|
||||
)
|
||||
|
||||
target_link_libraries(SampleBackends
|
||||
PRIVATE Meta
|
||||
)
|
||||
|
||||
set_target_properties(SampleBackends PROPERTIES
|
||||
Swift_LANGUAGE_VERSION 5
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user