Skip to content
This repository has been archived by the owner on Oct 17, 2024. It is now read-only.

Commit

Permalink
Make widget data more flexible
Browse files Browse the repository at this point in the history
  • Loading branch information
david-swift committed Aug 15, 2024
1 parent 7b26dce commit 4e20539
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 103 deletions.
10 changes: 5 additions & 5 deletions Sources/Meta.docc/Tutorials/CreateBackend.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public struct VStack: Wrapper, TermKitWidget {
}

public func container<Data>(
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let storages = content.storages(modifiers: modifiers, type: type) // Get the storages of child views
let storages = content.storages(data: data, type: type) // Get the storages of child views
if storages.count == 1 {
return .init(storages[0].pointer, content: [.mainContent: storages])
}
Expand All @@ -99,14 +99,14 @@ public struct VStack: Wrapper, TermKitWidget {

public func update<Data>(
_ storage: ViewStorage,
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
guard let storages = storage.content[.mainContent] else {
return
}
content.update(storages, modifiers: modifiers, updateProperties: updateProperties, type: type) // Update the storages of child views
content.update(storages, data: data, updateProperties: updateProperties, type: type) // Update the storages of child views
}

}
Expand All @@ -123,7 +123,7 @@ It indicates that a state variable (see <doc:StateConcept>) of an ancestor view
If state doesn't change, it is impossible for the UI to change.
However, consider the following exceptions:

- _Always_ update view content (using ``AnyView/updateStorage(_:modifiers:updateProperties:type:)`` or ``Swift/Array/storages(modifiers:type:)``). Child views may contain own state.
- _Always_ update view content (using ``AnyView/updateStorage(_:data:updateProperties:type:)`` or ``Swift/Array/storages(data:type:)``). Child views may contain own state.
- _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.

Expand Down
24 changes: 12 additions & 12 deletions Sources/Model/Extensions/Array.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ extension Array: AnyView where Element == AnyView {

/// Get a widget from a collection of views.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - type: The app storage type.
/// - Returns: A widget.
public func widget<Data>(
modifiers: [(AnyView) -> AnyView],
data: WidgetData,
type: Data.Type
) -> Widget where Data: ViewRenderData {
if count == 1, let widget = self[safe: 0]?.widget(modifiers: modifiers, type: type) {
if count == 1, let widget = self[safe: 0]?.widget(data: data, type: type) {
return widget
} else {
var modified = self
for (index, view) in modified.enumerated() {
modified[safe: index] = view.getModified(modifiers: modifiers, type: type)
modified[safe: index] = view.getModified(data: data, type: type)
}
return Data.WrapperType { modified }
}
Expand All @@ -35,35 +35,35 @@ extension Array: AnyView where Element == AnyView {
/// Update a collection of views with a collection of view storages.
/// - Parameters:
/// - storages: The collection of view storages.
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - updateProperties: Whether to update properties.
/// - type: The type of the app storage.
public func update<Data>(
_ storages: [ViewStorage],
modifiers: [(AnyView) -> AnyView],
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
for (index, element) in filter({ $0.renderable(type: type, modifiers: modifiers) }).enumerated() {
for (index, element) in filter({ $0.renderable(type: type, data: data) }).enumerated() {
if let storage = storages[safe: index] {
element
.widget(modifiers: modifiers, type: type)
.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
.widget(data: data, type: type)
.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}
}
}

/// Get the view storages of a collection of views.
/// - Parameters:
/// - modifiers: Modify views before generating the storages.
/// - data: Modify views before generating the storages.
/// - type: The type of the app storage.
/// - Returns: The storages.
public func storages<Data>(
modifiers: [(AnyView) -> AnyView],
data: WidgetData,
type: Data.Type
) -> [ViewStorage] where Data: ViewRenderData {
compactMap { view in
view.renderable(type: type, modifiers: modifiers) ? view.storage(modifiers: modifiers, type: type) : nil
view.renderable(type: type, data: data) ? view.storage(data: data, type: type) : nil
}
}

Expand Down
30 changes: 15 additions & 15 deletions Sources/Model/User Interface/View/AnyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public protocol AnyView {

extension AnyView {

Check failure on line 16 in Sources/Model/User Interface/View/AnyView.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Missing Docs Violation: internal declarations should be documented (missing_docs)

func getModified<Data>(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> AnyView where Data: ViewRenderData {
func getModified<Data>(data: WidgetData, type: Data.Type) -> AnyView where Data: ViewRenderData {
var modified: AnyView = self
for modifier in modifiers {
for modifier in data.modifiers {
modified = modifier(modified)
}
return modified
Expand All @@ -26,48 +26,48 @@ extension AnyView {
/// Update a storage to a view.
/// - Parameters:
/// - storage: The storage.
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - updateProperties: Whether to update properties.
/// - type: The type of the app storage.
public func updateStorage<Data>(
_ storage: ViewStorage,
modifiers: [(AnyView) -> AnyView],
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
widget(modifiers: modifiers, type: type)
.update(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
widget(data: data, type: type)
.update(storage, data: data, updateProperties: updateProperties, type: type)
}

/// Get a storage.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - type: The widget types.
/// - Returns: The storage.
public func storage<Data>(
modifiers: [(AnyView) -> AnyView],
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
widget(modifiers: modifiers, type: type).container(modifiers: modifiers, type: type)
widget(data: data, type: type).container(data: data, type: type)
}

/// Wrap the view into a widget.
/// - Parameter modifiers: Modify views before being updated.
/// - Parameter data: Modify views before being updated.
/// - Returns: The widget.
func widget<Data>(modifiers: [(AnyView) -> AnyView], type: Data.Type) -> Widget where Data: ViewRenderData {
let modified = getModified(modifiers: modifiers, type: type)
func widget<Data>(data: WidgetData, type: Data.Type) -> Widget where Data: ViewRenderData {
let modified = getModified(data: data, type: type)
if let peer = modified as? Widget {
return peer
}
if let array = modified as? Body {
return Data.WrapperType { array }
}
return Data.WrapperType { viewContent.map { $0.getModified(modifiers: modifiers, type: type) } }
return Data.WrapperType { viewContent.map { $0.getModified(data: data, type: type) } }
}

/// Whether the view can be rendered in a certain environment.
func renderable<Data>(type: Data.Type, modifiers: [(AnyView) -> AnyView]) -> Bool where Data: ViewRenderData {
let result = getModified(modifiers: modifiers, type: type)
func renderable<Data>(type: Data.Type, data: WidgetData) -> Bool where Data: ViewRenderData {
let result = getModified(data: data, type: type)
return result as? Data.WidgetType != nil
|| result as? SimpleView != nil
|| result as? View != nil
Expand Down
8 changes: 4 additions & 4 deletions Sources/Model/User Interface/View/Widget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ public protocol Widget: AnyView {

/// The view storage.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - type: The type of the app storage.
/// - Returns: The view storage.
func container<Data>(
modifiers: [(AnyView) -> AnyView],
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData

/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - data: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The type of the app storage.
func update<Data>(
_ storage: ViewStorage,
modifiers: [(AnyView) -> AnyView],
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData
Expand Down
43 changes: 43 additions & 0 deletions Sources/Model/User Interface/View/WidgetData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// WidgetData.swift
// Meta
//
// Created by david-swift on 15.08.24.
//

/// Data passed to widgets when initializing or updating the container.
public struct WidgetData {

/// The view modifiers.
public var modifiers: [(AnyView) -> AnyView] = []
/// The scene storage of the parent scene element.
public var sceneStorage: SceneStorage
/// The app storage of the parent app.
public var appStorage: any AppStorage
/// Fields for custom data.
public var fields: [String: Any] = [:]

/// Modify the data so that there are no modifiers.
public var noModifiers: Self {
modify { $0.modifiers = [] }
}

/// Initialize widget data.
/// - Parameters:
/// - sceneStorage: The storage of the parent scene element.
/// - appStorage: The storage of the parent app.
public init(sceneStorage: SceneStorage, appStorage: any AppStorage) {
self.sceneStorage = sceneStorage
self.appStorage = appStorage
}

/// Modify the widget data.
/// - Parameter action: The modification action.
/// - Returns: The data.
public func modify(action: (inout Self) -> Void) -> Self {
var newSelf = self
action(&newSelf)
return newSelf
}

}
12 changes: 6 additions & 6 deletions Sources/View/AppearObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,31 @@ struct AppearObserver: ConvenienceWidget {

/// The view storage.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - type: The type of the app storage.
/// - Returns: The view storage.
func container<Data>(
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let storage = content.storage(modifiers: modifiers, type: type)
let storage = content.storage(data: data, type: type)
modify(storage)
return storage
}

/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - data: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The type of the app storage.
func update<Data>(
_ storage: ViewStorage,
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}

}
Expand Down
17 changes: 11 additions & 6 deletions Sources/View/ContentModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,35 @@ struct ContentModifier<Content>: ConvenienceWidget where Content: AnyView {

/// The view storage.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - type: The type of the app storage.
/// - Returns: The view storage.
func container<Data>(
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
content.storage(modifiers: modifiers + [modifyView], type: type)
content.storage(data: data.modify { $0.modifiers += [modifyView] }, type: type)
}

/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - data: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The type of the app storage.
func update<Data>(
_ storage: ViewStorage,
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
content
.updateStorage(storage, modifiers: modifiers + [modifyView], updateProperties: updateProperties, type: type)
.updateStorage(
storage,
data: data.modify { $0.modifiers += [modifyView] },
updateProperties: updateProperties,
type: type
)
}

/// Apply the modifier to a view.
Expand Down
12 changes: 6 additions & 6 deletions Sources/View/DummyEitherView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,32 @@ struct DummyEitherView: Widget {

/// The view storage.
/// - Parameters:
/// - modifiers: Modify views before being updated.
/// - data: Modify views before being updated.
/// - type: The type of the app storage.
/// - Returns: The view storage.
func container<Data>(
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
type: Data.Type
) -> ViewStorage where Data: ViewRenderData {
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
let storage = content.storage(modifiers: modifiers, type: type)
let storage = content.storage(data: data, type: type)
return storage
}

/// Update the stored content.
/// - Parameters:
/// - storage: The storage to update.
/// - modifiers: Modify views before being updated
/// - data: Modify views before being updated
/// - updateProperties: Whether to update the view's properties.
/// - type: The type of the app storage.
func update<Data>(
_ storage: ViewStorage,
modifiers: [(any AnyView) -> any AnyView],
data: WidgetData,
updateProperties: Bool,
type: Data.Type
) where Data: ViewRenderData {
let content = type.EitherViewType(condition) { view1 ?? [] } else: { view2 ?? [] }
content.updateStorage(storage, modifiers: modifiers, updateProperties: updateProperties, type: type)
content.updateStorage(storage, data: data, updateProperties: updateProperties, type: type)
}

}
Loading

0 comments on commit 4e20539

Please sign in to comment.