Implement Environment (#135)
* Environment impl * Fix line lengths * Danger and review fixes * rename _modifyEnvironment to modifyEnvironment
This commit is contained in:
parent
b1b5693b66
commit
1d71fe0c7d
|
@ -12,15 +12,25 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
@propertyWrapper public struct Environment<Value> {
|
||||
protocol EnvironmentReader {
|
||||
mutating func setContent(from values: EnvironmentValues)
|
||||
}
|
||||
|
||||
@propertyWrapper public struct Environment<Value>: EnvironmentReader {
|
||||
enum Content {
|
||||
case keyPath(KeyPath<EnvironmentValues, Value>)
|
||||
case value(Value)
|
||||
}
|
||||
|
||||
var content: Environment<Value>.Content
|
||||
let keyPath: KeyPath<EnvironmentValues, Value>
|
||||
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
|
||||
content = .keyPath(keyPath)
|
||||
self.keyPath = keyPath
|
||||
}
|
||||
|
||||
mutating func setContent(from values: EnvironmentValues) {
|
||||
content = .value(values[keyPath: keyPath])
|
||||
}
|
||||
|
||||
public var wrappedValue: Value {
|
||||
|
|
|
@ -14,12 +14,14 @@
|
|||
|
||||
public protocol EnvironmentKey {
|
||||
associatedtype Value
|
||||
static var defaultValue: Self.Value { get }
|
||||
static var defaultValue: Value { get }
|
||||
}
|
||||
|
||||
public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier {
|
||||
public typealias Body = Never
|
||||
protocol EnvironmentModifier {
|
||||
func modifyEnvironment(_ values: inout EnvironmentValues)
|
||||
}
|
||||
|
||||
public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier, EnvironmentModifier {
|
||||
public let keyPath: WritableKeyPath<EnvironmentValues, Value>
|
||||
public let value: Value
|
||||
|
||||
|
@ -27,6 +29,14 @@ public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier {
|
|||
self.keyPath = keyPath
|
||||
self.value = value
|
||||
}
|
||||
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
}
|
||||
|
||||
func modifyEnvironment(_ values: inout EnvironmentValues) {
|
||||
values[keyPath: keyPath] = value
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
|
|
@ -31,9 +31,12 @@ final class MountedCompositeView<R: Renderer>: MountedView<R>, Hashable {
|
|||
private let parentTarget: R.TargetType
|
||||
|
||||
var state = [Any]()
|
||||
var environmentValues: EnvironmentValues
|
||||
|
||||
init(_ view: AnyView, _ parentTarget: R.TargetType) {
|
||||
init(_ view: AnyView, _ parentTarget: R.TargetType,
|
||||
_ environmentValues: EnvironmentValues) {
|
||||
self.parentTarget = parentTarget
|
||||
self.environmentValues = environmentValues
|
||||
|
||||
super.init(view)
|
||||
}
|
||||
|
@ -41,7 +44,8 @@ final class MountedCompositeView<R: Renderer>: MountedView<R>, Hashable {
|
|||
override func mount(with reconciler: StackReconciler<R>) {
|
||||
let childBody = reconciler.render(compositeView: self)
|
||||
|
||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget)
|
||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget,
|
||||
environmentValues)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
}
|
||||
|
@ -58,7 +62,8 @@ final class MountedCompositeView<R: Renderer>: MountedView<R>, Hashable {
|
|||
switch (mountedChildren.last, reconciler.render(compositeView: self)) {
|
||||
// no mounted children, but children available now
|
||||
case let (nil, childBody):
|
||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget)
|
||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget,
|
||||
environmentValues)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
|
||||
|
@ -81,7 +86,8 @@ final class MountedCompositeView<R: Renderer>: MountedView<R>, Hashable {
|
|||
// wrapper, then mount a new one with the new `childBody`
|
||||
wrapper.unmount(with: reconciler)
|
||||
|
||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget)
|
||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget,
|
||||
environmentValues)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
}
|
||||
|
|
|
@ -31,9 +31,13 @@ public final class MountedHostView<R: Renderer>: MountedView<R> {
|
|||
/// Target of this host view supplied by a renderer after mounting has completed.
|
||||
private var target: R.TargetType?
|
||||
|
||||
private let environmentValues: EnvironmentValues
|
||||
|
||||
init(_ view: AnyView,
|
||||
_ parentTarget: R.TargetType) {
|
||||
_ parentTarget: R.TargetType,
|
||||
_ environmentValues: EnvironmentValues) {
|
||||
self.parentTarget = parentTarget
|
||||
self.environmentValues = environmentValues
|
||||
|
||||
super.init(view)
|
||||
}
|
||||
|
@ -48,7 +52,9 @@ public final class MountedHostView<R: Renderer>: MountedView<R> {
|
|||
|
||||
guard !view.children.isEmpty else { return }
|
||||
|
||||
mountedChildren = view.children.map { $0.makeMountedView(target) }
|
||||
mountedChildren = view.children.map {
|
||||
$0.makeMountedView(target, environmentValues)
|
||||
}
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
}
|
||||
|
||||
|
@ -81,7 +87,9 @@ public final class MountedHostView<R: Renderer>: MountedView<R> {
|
|||
|
||||
// if no existing children then mount all new children
|
||||
case (true, false):
|
||||
mountedChildren = childrenViews.map { $0.makeMountedView(target) }
|
||||
mountedChildren = childrenViews.map {
|
||||
$0.makeMountedView(target, environmentValues)
|
||||
}
|
||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||
|
||||
// if both arrays have items then reconcile by types and keys
|
||||
|
@ -99,7 +107,7 @@ public final class MountedHostView<R: Renderer>: MountedView<R> {
|
|||
newChild = child
|
||||
} else {
|
||||
child.unmount(with: reconciler)
|
||||
newChild = firstChild.makeMountedView(target)
|
||||
newChild = firstChild.makeMountedView(target, environmentValues)
|
||||
newChild.mount(with: reconciler)
|
||||
}
|
||||
newChildren.append(newChild)
|
||||
|
@ -118,7 +126,7 @@ public final class MountedHostView<R: Renderer>: MountedView<R> {
|
|||
// mount remaining views
|
||||
for firstChild in childrenViews {
|
||||
let newChild: MountedView<R> =
|
||||
firstChild.makeMountedView(target)
|
||||
firstChild.makeMountedView(target, environmentValues)
|
||||
newChild.mount(with: reconciler)
|
||||
newChildren.append(newChild)
|
||||
}
|
||||
|
|
|
@ -36,15 +36,16 @@ public class MountedView<R: Renderer> {
|
|||
}
|
||||
|
||||
extension View {
|
||||
func makeMountedView<R: Renderer>(_ parentTarget: R.TargetType)
|
||||
func makeMountedView<R: Renderer>(_ parentTarget: R.TargetType,
|
||||
_ environmentValues: EnvironmentValues)
|
||||
-> MountedView<R> {
|
||||
let anyView = self as? AnyView ?? AnyView(self)
|
||||
if anyView.type == EmptyView.self {
|
||||
return MountedNull(anyView)
|
||||
} else if anyView.bodyType == Never.self && !(anyView.type is ViewDeferredToRenderer.Type) {
|
||||
return MountedHostView(anyView, parentTarget)
|
||||
return MountedHostView(anyView, parentTarget, environmentValues)
|
||||
} else {
|
||||
return MountedCompositeView(anyView, parentTarget)
|
||||
return MountedCompositeView(anyView, parentTarget, environmentValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public final class StackReconciler<R: Renderer> {
|
|||
self.scheduler = scheduler
|
||||
rootTarget = target
|
||||
|
||||
rootView = view.makeMountedView(target)
|
||||
rootView = view.makeMountedView(target, EnvironmentValues())
|
||||
|
||||
rootView.mount(with: self)
|
||||
}
|
||||
|
@ -88,6 +88,28 @@ public final class StackReconciler<R: Renderer> {
|
|||
}
|
||||
try! stateProperty.set(value: state, on: &compositeView.view.view)
|
||||
}
|
||||
|
||||
let viewInfo = try! typeInfo(of: compositeView.view.type)
|
||||
if viewInfo
|
||||
.genericTypes
|
||||
.filter({ $0 is EnvironmentModifier.Type }).count > 0 {
|
||||
// Apply Environment changes:
|
||||
if let modifier = try? viewInfo
|
||||
.property(named: "modifier")
|
||||
.get(from: compositeView.view.view) as? EnvironmentModifier {
|
||||
modifier.modifyEnvironment(&compositeView.environmentValues)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject @Environment values
|
||||
// In the future we can also inject @EnvironmentObject values
|
||||
for prop in viewInfo.properties.filter({ $0.type is EnvironmentReader.Type }) {
|
||||
// swiftlint:disable force_cast
|
||||
var wrapper = try! prop.get(from: compositeView.view.view) as! EnvironmentReader
|
||||
wrapper.setContent(from: compositeView.environmentValues)
|
||||
try! prop.set(value: wrapper, on: &compositeView.view.view)
|
||||
// swiftlint:enable force_cast
|
||||
}
|
||||
// swiftlint:enable force_try
|
||||
|
||||
let result = compositeView.view.bodyClosure(compositeView.view.view)
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2020 Tokamak contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Created by Carson Katri on 6/30/20.
|
||||
//
|
||||
|
||||
import TokamakCore
|
||||
|
||||
public typealias Environment = TokamakCore.Environment
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2019-2020 Tokamak contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Created by Carson Katri on 6/30/20.
|
||||
//
|
||||
|
||||
import TokamakDOM
|
||||
|
||||
struct EnvironmentDemo: View {
|
||||
@Environment(\.font) var font: Font?
|
||||
|
||||
var body: some View {
|
||||
if let font = font {
|
||||
return Text("\(font)")
|
||||
} else {
|
||||
return Text("`font` environment not set.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,8 @@ let renderer = DOMRenderer(
|
|||
SpacerDemo()
|
||||
Spacer()
|
||||
Text("Forced to bottom.")
|
||||
EnvironmentDemo()
|
||||
.font(.system(size: 21))
|
||||
}
|
||||
},
|
||||
div
|
||||
|
|
Loading…
Reference in New Issue