182 lines
6.4 KiB
Swift
182 lines
6.4 KiB
Swift
// Copyright 2018-2021 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 Max Desiatov on 03/12/2018.
|
|
//
|
|
|
|
/* A representation of a `View`, which has a `body` of type `Never`, stored in the tree of mounted
|
|
views by `StackReconciler`.
|
|
*/
|
|
public final class MountedHostView<R: Renderer>: MountedElement<R> {
|
|
/** Target of a closest ancestor host view. As a parent of this view
|
|
might not be a host view, but a composite view, we need to pass
|
|
around the target of a host view to its closests descendant host
|
|
views. Thus, a parent target is not always the same as a target of
|
|
a parent `View`. */
|
|
private let parentTarget: R.TargetType
|
|
|
|
/// Target of this host view supplied by a renderer after mounting has completed.
|
|
private(set) var target: R.TargetType?
|
|
|
|
init(
|
|
_ view: AnyView,
|
|
_ parentTarget: R.TargetType,
|
|
_ environmentValues: EnvironmentValues,
|
|
_ parent: MountedElement<R>?
|
|
) {
|
|
self.parentTarget = parentTarget
|
|
|
|
super.init(view, environmentValues, parent)
|
|
}
|
|
|
|
override func mount(
|
|
before sibling: R.TargetType? = nil,
|
|
on parent: MountedElement<R>? = nil,
|
|
with reconciler: StackReconciler<R>
|
|
) {
|
|
guard let target = reconciler.renderer?.mountTarget(
|
|
before: sibling,
|
|
to: parentTarget,
|
|
with: self
|
|
)
|
|
else { return }
|
|
|
|
self.target = target
|
|
|
|
guard !view.children.isEmpty else { return }
|
|
|
|
mountedChildren = view.children.map {
|
|
$0.makeMountedView(target, environmentValues, self)
|
|
}
|
|
|
|
/* Remember that `GroupView`s are always "flattened", their `target` instances are targets of
|
|
their parent elements. We need the insertion "cursor" `sibling` to be preserved when children
|
|
are mounted in that case. Thus pass the `sibling` target to the children if `view` is a
|
|
`GroupView`.
|
|
*/
|
|
let isGroupView = view.type is GroupView.Type
|
|
mountedChildren.forEach {
|
|
$0.mount(before: isGroupView ? sibling : nil, on: self, with: reconciler)
|
|
}
|
|
}
|
|
|
|
override func unmount(with reconciler: StackReconciler<R>) {
|
|
guard let target = target else { return }
|
|
|
|
reconciler.renderer?.unmount(
|
|
target: target,
|
|
from: parentTarget,
|
|
with: self
|
|
) {
|
|
self.mountedChildren.forEach { $0.unmount(with: reconciler) }
|
|
}
|
|
}
|
|
|
|
private var __mountedChild = 0
|
|
|
|
override func update(with reconciler: StackReconciler<R>) {
|
|
guard let target = target else { return }
|
|
|
|
let myStack = __stack
|
|
__stack += 1
|
|
|
|
print("Host.upEnv")
|
|
updateEnvironment()
|
|
target.view = view
|
|
print(
|
|
"Host.renderer?.update \(reconciler.renderer == nil ? "nil" : "\(Swift.type(of: reconciler.renderer!))")"
|
|
)
|
|
reconciler.renderer?.update(target: target, with: self)
|
|
|
|
var childrenViews = view.children
|
|
|
|
print("Host(\(mountedChildren.isEmpty),\(childrenViews.isEmpty)")
|
|
switch (mountedChildren.isEmpty, childrenViews.isEmpty) {
|
|
// if existing children present and new children array is empty
|
|
// then unmount all existing children
|
|
case (false, true):
|
|
mountedChildren.forEach { $0.unmount(with: reconciler) }
|
|
mountedChildren = []
|
|
|
|
// if no existing children then mount all new children
|
|
case (true, false):
|
|
mountedChildren = childrenViews.map { $0.makeMountedView(target, environmentValues, self) }
|
|
mountedChildren.forEach { $0.mount(on: self, with: reconciler) }
|
|
|
|
// if both arrays have items then reconcile by types and keys
|
|
case (false, false):
|
|
var newChildren = [MountedElement<R>]()
|
|
|
|
// iterate through every `mountedChildren` element and compare with
|
|
// a corresponding `childrenViews` element, remount if type differs, otherwise
|
|
// run simple update
|
|
while let mountedChild = mountedChildren.first, let childView = childrenViews.first {
|
|
let newChild: MountedElement<R>
|
|
print(
|
|
"BEGIN todo: \(myStack).\(mountedChildren.count) \(childView.typeConstructorName == mountedChildren[0].view.typeConstructorName ? "a - \(childView.typeConstructorName)" : "b")"
|
|
)
|
|
__mountedChild += 1
|
|
if childView.typeConstructorName == mountedChildren[0].view.typeConstructorName {
|
|
mountedChild.environmentValues = environmentValues
|
|
mountedChild.view = childView
|
|
mountedChild.updateEnvironment()
|
|
print(
|
|
"update -> \(Swift.type(of: mountedChild)), recon -> \(Swift.type(of: reconciler)))"
|
|
)
|
|
mountedChild.update(with: reconciler)
|
|
newChild = mountedChild
|
|
} else {
|
|
/* note the order of operations here: we mount the new child first, use the mounted child
|
|
as a "cursor" sibling when mounting. Only then we can dispose of the old mounted child
|
|
by unmounting it.
|
|
*/
|
|
newChild = childView.makeMountedView(target, environmentValues, self)
|
|
newChild.mount(before: mountedChild.firstDescendantTarget, on: self, with: reconciler)
|
|
mountedChild.unmount(with: reconciler)
|
|
}
|
|
newChildren.append(newChild)
|
|
mountedChildren.removeFirst()
|
|
childrenViews.removeFirst()
|
|
|
|
print("END mountedChild todo: \(myStack).\(mountedChildren.count)")
|
|
}
|
|
|
|
// more mounted views left than views were to be rendered:
|
|
// unmount remaining `mountedChildren`
|
|
if !mountedChildren.isEmpty {
|
|
for child in mountedChildren {
|
|
child.unmount(with: reconciler)
|
|
}
|
|
} else {
|
|
// more views left than children were mounted,
|
|
// mount remaining views
|
|
for firstChild in childrenViews {
|
|
let newChild: MountedElement<R> =
|
|
firstChild.makeMountedView(target, environmentValues, self)
|
|
newChild.mount(on: self, with: reconciler)
|
|
newChildren.append(newChild)
|
|
}
|
|
}
|
|
|
|
__stack -= 1
|
|
mountedChildren = newChildren
|
|
|
|
// both arrays are empty, nothing to reconcile
|
|
case (true, true): ()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var __stack = 0
|