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
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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 {
|
enum Content {
|
||||||
case keyPath(KeyPath<EnvironmentValues, Value>)
|
case keyPath(KeyPath<EnvironmentValues, Value>)
|
||||||
case value(Value)
|
case value(Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var content: Environment<Value>.Content
|
var content: Environment<Value>.Content
|
||||||
|
let keyPath: KeyPath<EnvironmentValues, Value>
|
||||||
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
|
public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
|
||||||
content = .keyPath(keyPath)
|
content = .keyPath(keyPath)
|
||||||
|
self.keyPath = keyPath
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func setContent(from values: EnvironmentValues) {
|
||||||
|
content = .value(values[keyPath: keyPath])
|
||||||
}
|
}
|
||||||
|
|
||||||
public var wrappedValue: Value {
|
public var wrappedValue: Value {
|
||||||
|
|
|
@ -14,12 +14,14 @@
|
||||||
|
|
||||||
public protocol EnvironmentKey {
|
public protocol EnvironmentKey {
|
||||||
associatedtype Value
|
associatedtype Value
|
||||||
static var defaultValue: Self.Value { get }
|
static var defaultValue: Value { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier {
|
protocol EnvironmentModifier {
|
||||||
public typealias Body = Never
|
func modifyEnvironment(_ values: inout EnvironmentValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier, EnvironmentModifier {
|
||||||
public let keyPath: WritableKeyPath<EnvironmentValues, Value>
|
public let keyPath: WritableKeyPath<EnvironmentValues, Value>
|
||||||
public let value: Value
|
public let value: Value
|
||||||
|
|
||||||
|
@ -27,6 +29,14 @@ public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier {
|
||||||
self.keyPath = keyPath
|
self.keyPath = keyPath
|
||||||
self.value = value
|
self.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
func modifyEnvironment(_ values: inout EnvironmentValues) {
|
||||||
|
values[keyPath: keyPath] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
|
|
|
@ -31,9 +31,12 @@ final class MountedCompositeView<R: Renderer>: MountedView<R>, Hashable {
|
||||||
private let parentTarget: R.TargetType
|
private let parentTarget: R.TargetType
|
||||||
|
|
||||||
var state = [Any]()
|
var state = [Any]()
|
||||||
|
var environmentValues: EnvironmentValues
|
||||||
|
|
||||||
init(_ view: AnyView, _ parentTarget: R.TargetType) {
|
init(_ view: AnyView, _ parentTarget: R.TargetType,
|
||||||
|
_ environmentValues: EnvironmentValues) {
|
||||||
self.parentTarget = parentTarget
|
self.parentTarget = parentTarget
|
||||||
|
self.environmentValues = environmentValues
|
||||||
|
|
||||||
super.init(view)
|
super.init(view)
|
||||||
}
|
}
|
||||||
|
@ -41,7 +44,8 @@ final class MountedCompositeView<R: Renderer>: MountedView<R>, Hashable {
|
||||||
override func mount(with reconciler: StackReconciler<R>) {
|
override func mount(with reconciler: StackReconciler<R>) {
|
||||||
let childBody = reconciler.render(compositeView: self)
|
let childBody = reconciler.render(compositeView: self)
|
||||||
|
|
||||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget)
|
let child: MountedView<R> = childBody.makeMountedView(parentTarget,
|
||||||
|
environmentValues)
|
||||||
mountedChildren = [child]
|
mountedChildren = [child]
|
||||||
child.mount(with: reconciler)
|
child.mount(with: reconciler)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +62,8 @@ final class MountedCompositeView<R: Renderer>: MountedView<R>, Hashable {
|
||||||
switch (mountedChildren.last, reconciler.render(compositeView: self)) {
|
switch (mountedChildren.last, reconciler.render(compositeView: self)) {
|
||||||
// no mounted children, but children available now
|
// no mounted children, but children available now
|
||||||
case let (nil, childBody):
|
case let (nil, childBody):
|
||||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget)
|
let child: MountedView<R> = childBody.makeMountedView(parentTarget,
|
||||||
|
environmentValues)
|
||||||
mountedChildren = [child]
|
mountedChildren = [child]
|
||||||
child.mount(with: reconciler)
|
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, then mount a new one with the new `childBody`
|
||||||
wrapper.unmount(with: reconciler)
|
wrapper.unmount(with: reconciler)
|
||||||
|
|
||||||
let child: MountedView<R> = childBody.makeMountedView(parentTarget)
|
let child: MountedView<R> = childBody.makeMountedView(parentTarget,
|
||||||
|
environmentValues)
|
||||||
mountedChildren = [child]
|
mountedChildren = [child]
|
||||||
child.mount(with: reconciler)
|
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.
|
/// Target of this host view supplied by a renderer after mounting has completed.
|
||||||
private var target: R.TargetType?
|
private var target: R.TargetType?
|
||||||
|
|
||||||
|
private let environmentValues: EnvironmentValues
|
||||||
|
|
||||||
init(_ view: AnyView,
|
init(_ view: AnyView,
|
||||||
_ parentTarget: R.TargetType) {
|
_ parentTarget: R.TargetType,
|
||||||
|
_ environmentValues: EnvironmentValues) {
|
||||||
self.parentTarget = parentTarget
|
self.parentTarget = parentTarget
|
||||||
|
self.environmentValues = environmentValues
|
||||||
|
|
||||||
super.init(view)
|
super.init(view)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +52,9 @@ public final class MountedHostView<R: Renderer>: MountedView<R> {
|
||||||
|
|
||||||
guard !view.children.isEmpty else { return }
|
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) }
|
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
|
// if no existing children then mount all new children
|
||||||
case (true, false):
|
case (true, false):
|
||||||
mountedChildren = childrenViews.map { $0.makeMountedView(target) }
|
mountedChildren = childrenViews.map {
|
||||||
|
$0.makeMountedView(target, environmentValues)
|
||||||
|
}
|
||||||
mountedChildren.forEach { $0.mount(with: reconciler) }
|
mountedChildren.forEach { $0.mount(with: reconciler) }
|
||||||
|
|
||||||
// if both arrays have items then reconcile by types and keys
|
// 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
|
newChild = child
|
||||||
} else {
|
} else {
|
||||||
child.unmount(with: reconciler)
|
child.unmount(with: reconciler)
|
||||||
newChild = firstChild.makeMountedView(target)
|
newChild = firstChild.makeMountedView(target, environmentValues)
|
||||||
newChild.mount(with: reconciler)
|
newChild.mount(with: reconciler)
|
||||||
}
|
}
|
||||||
newChildren.append(newChild)
|
newChildren.append(newChild)
|
||||||
|
@ -118,7 +126,7 @@ public final class MountedHostView<R: Renderer>: MountedView<R> {
|
||||||
// mount remaining views
|
// mount remaining views
|
||||||
for firstChild in childrenViews {
|
for firstChild in childrenViews {
|
||||||
let newChild: MountedView<R> =
|
let newChild: MountedView<R> =
|
||||||
firstChild.makeMountedView(target)
|
firstChild.makeMountedView(target, environmentValues)
|
||||||
newChild.mount(with: reconciler)
|
newChild.mount(with: reconciler)
|
||||||
newChildren.append(newChild)
|
newChildren.append(newChild)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,15 +36,16 @@ public class MountedView<R: Renderer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
func makeMountedView<R: Renderer>(_ parentTarget: R.TargetType)
|
func makeMountedView<R: Renderer>(_ parentTarget: R.TargetType,
|
||||||
|
_ environmentValues: EnvironmentValues)
|
||||||
-> MountedView<R> {
|
-> MountedView<R> {
|
||||||
let anyView = self as? AnyView ?? AnyView(self)
|
let anyView = self as? AnyView ?? AnyView(self)
|
||||||
if anyView.type == EmptyView.self {
|
if anyView.type == EmptyView.self {
|
||||||
return MountedNull(anyView)
|
return MountedNull(anyView)
|
||||||
} else if anyView.bodyType == Never.self && !(anyView.type is ViewDeferredToRenderer.Type) {
|
} else if anyView.bodyType == Never.self && !(anyView.type is ViewDeferredToRenderer.Type) {
|
||||||
return MountedHostView(anyView, parentTarget)
|
return MountedHostView(anyView, parentTarget, environmentValues)
|
||||||
} else {
|
} else {
|
||||||
return MountedCompositeView(anyView, parentTarget)
|
return MountedCompositeView(anyView, parentTarget, environmentValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ public final class StackReconciler<R: Renderer> {
|
||||||
self.scheduler = scheduler
|
self.scheduler = scheduler
|
||||||
rootTarget = target
|
rootTarget = target
|
||||||
|
|
||||||
rootView = view.makeMountedView(target)
|
rootView = view.makeMountedView(target, EnvironmentValues())
|
||||||
|
|
||||||
rootView.mount(with: self)
|
rootView.mount(with: self)
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,28 @@ public final class StackReconciler<R: Renderer> {
|
||||||
}
|
}
|
||||||
try! stateProperty.set(value: state, on: &compositeView.view.view)
|
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
|
// swiftlint:enable force_try
|
||||||
|
|
||||||
let result = compositeView.view.bodyClosure(compositeView.view.view)
|
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()
|
SpacerDemo()
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("Forced to bottom.")
|
Text("Forced to bottom.")
|
||||||
|
EnvironmentDemo()
|
||||||
|
.font(.system(size: 21))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
div
|
div
|
||||||
|
|
Loading…
Reference in New Issue