Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add _ShapeView and background modifiers support to Fiber renderers #491

Merged
merged 68 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
84018db
Initial Reconciler using visitor pattern
carson-katri Feb 6, 2022
aa8c4dd
Preliminary static HTML renderer using the new reconciler
carson-katri Feb 6, 2022
67b3509
Add environment
carson-katri Feb 7, 2022
7f96bc1
Initial DOM renderer
carson-katri Feb 7, 2022
4ea2247
Nearly-working and simplified reconciler
carson-katri Feb 11, 2022
026f00b
Working reconciler for HTML/DOM renderers
carson-katri Feb 15, 2022
e340ed5
Rename files, and split code across files
carson-katri Feb 15, 2022
885ba06
Add some documentation and refinements
carson-katri Feb 16, 2022
dd8a2eb
Remove GraphRendererTests
carson-katri Feb 16, 2022
88f689f
Initial layout engine (only implemented for the TestRenderer)
carson-katri Feb 19, 2022
a9af988
Layout engine for the DOM renderer
carson-katri Feb 21, 2022
26ce229
Refined layout pass
carson-katri Mar 3, 2022
2891e6b
Revise positioning and restoration of position styles on .update
carson-katri Mar 3, 2022
055c826
Re-add Optional.body for StackReconciler-based renderers
carson-katri Mar 3, 2022
7b98447
Merge branch 'fiber/core' of https://github.com/TokamakUI/Tokamak int…
carson-katri Mar 7, 2022
9902ace
Add text measurement
carson-katri Mar 7, 2022
d576798
Add spacing to StackLayout
carson-katri Mar 7, 2022
c457dce
Add benchmarks to compare the stack/fiber reconcilers
carson-katri Apr 5, 2022
be6aa89
Fix some issues created for the StackReconciler, and add update bench…
carson-katri Apr 5, 2022
ac5c110
Add BenchmarkState.measure to only calculate the time to update
carson-katri Apr 6, 2022
0d82cec
Fix hang in update shallow benchmark
carson-katri Apr 6, 2022
42c7c01
Merge branch 'main' into fiber/core
MaxDesiatov Apr 10, 2022
7ef9bed
Fix build errors
MaxDesiatov Apr 10, 2022
34cd9d7
Address build issues
MaxDesiatov Apr 10, 2022
6a846f4
Merge branch 'main' into fiber/core
MaxDesiatov May 12, 2022
7d705eb
Merge branch 'main' of https://github.com/TokamakUI/Tokamak into fibe…
carson-katri May 19, 2022
a70a938
Remove File.swift headers
carson-katri May 19, 2022
485da76
Rename Element -> FiberElement and Element.Data -> FiberElement.Content
carson-katri May 20, 2022
81deb5a
Add doc comment explaining unowned usage
carson-katri May 20, 2022
74ee853
Add doc comments explaining implicitly unwrapped optionals
carson-katri May 21, 2022
8a8c075
Attempt to use Swift instead of JS for applying mutations
carson-katri May 22, 2022
b7cc779
Fix issue with not applying updates to DOMFiberElement
carson-katri May 22, 2022
78d0bf5
Add comment explaining manual implementation of Hashable for Property…
carson-katri May 22, 2022
4becea3
Fix linter issues
carson-katri May 22, 2022
8c9141b
Remove dynamicMember label from subscript
carson-katri May 23, 2022
33f0e67
Re-enable carton test
carson-katri May 23, 2022
316468c
Merge fiber/core
carson-katri May 23, 2022
4d86f89
Attempt GTK fix
carson-katri May 23, 2022
3189e77
Add option to disable layout in the FiberReconciler
carson-katri May 23, 2022
104f14f
Re-enable TokamakDemo with StackReconciler
carson-katri May 23, 2022
0d0a9c7
Merge fiber/core
carson-katri May 24, 2022
836ac36
Merge main (take ours)
carson-katri May 24, 2022
389b3e8
Restore CI config
carson-katri May 24, 2022
a20d94b
Restore CI config
carson-katri May 24, 2022
05d5a24
Add file headers and cleanup structure
carson-katri May 24, 2022
f628cba
Merge branch 'main' of github.com:tokamakui/tokamak into fiber/layout
carson-katri May 26, 2022
d507848
Add 'px' to font-size in test outputs
carson-katri May 26, 2022
65e78e8
Remove extra newlines
carson-katri May 26, 2022
f94d733
Keep track of 'elementChildren' so children are positioned in the cor…
carson-katri May 27, 2022
001cd4b
Use a ViewVisitor to pass the correct View type to the proposeSize fu…
carson-katri May 27, 2022
f89d24d
Add support for view modifiers
carson-katri May 28, 2022
10368b1
Add frame modifier to demonstrate modifiers
carson-katri May 28, 2022
f926fbe
Fix TestRenderer
carson-katri May 28, 2022
4d96217
Remove unused property
carson-katri May 28, 2022
aeb5983
Fix doc comment
carson-katri May 28, 2022
3265b74
Fix linter issues and refactor slightly
carson-katri May 28, 2022
b6c5d8e
Fix benchmark builds
carson-katri May 28, 2022
8ca135c
Attempt to fix benchmarks
carson-katri May 28, 2022
8779df6
Fix sibling layout issues
carson-katri May 28, 2022
90e1c5d
Restore original demo
carson-katri May 28, 2022
2538a68
Support overriding visit function in renderer and _ShapeView drawing
carson-katri May 29, 2022
caee340
Support background modifier
carson-katri May 29, 2022
fbd915e
Merge main
carson-katri Jun 6, 2022
c5b9f0a
Resolve reconciler issues due to Optionals and elementIndex being set…
carson-katri Jun 6, 2022
63a2526
Remove Brewfile.lock.json
carson-katri Jun 6, 2022
52eba18
Attempt to fix rendering tests
carson-katri Jun 15, 2022
0f4ae41
Formatting nits
carson-katri Jun 15, 2022
671d4d1
Fix Gradient rendering
carson-katri Jun 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/TokamakCore/Fiber/Fiber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ public extension FiberReconciler {
}
property.set(value: value, on: &content)
}
if var environmentReader = content as? EnvironmentReader {
environmentReader.setContent(from: environment)
// swiftlint:disable:next force_cast
content = environmentReader as! T
}
return state
}

Expand Down
25 changes: 7 additions & 18 deletions Sources/TokamakCore/Fiber/FiberReconciler+TreeReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ extension FiberReconciler {
update: { fiber, scene, _ in
fiber.update(with: &scene)
},
visitChildren: { $0._visitChildren }
visitChildren: { $1._visitChildren }
)
}

Expand All @@ -95,7 +95,9 @@ extension FiberReconciler {
update: { fiber, view, elementIndex in
fiber.update(with: &view, elementIndex: elementIndex)
},
visitChildren: { $0._visitChildren }
visitChildren: { reconciler, view in
reconciler?.renderer.viewVisitor(for: view) ?? view._visitChildren
}
)
}

Expand All @@ -105,7 +107,7 @@ extension FiberReconciler {
createFiber: (inout T, Renderer.ElementType?, Fiber?, Fiber?, Int?, FiberReconciler?)
-> Fiber,
update: (Fiber, inout T, Int?) -> Renderer.ElementType.Content?,
visitChildren: (T) -> (TreeReducer.SceneVisitor) -> ()
visitChildren: (FiberReconciler?, T) -> (TreeReducer.SceneVisitor) -> ()
) {
// Create the node and its element.
var nextValue = nextValue
Expand All @@ -125,7 +127,7 @@ extension FiberReconciler {
)
resultChild = Result(
fiber: existing,
visitChildren: visitChildren(nextValue),
visitChildren: visitChildren(partialResult.fiber?.reconciler, nextValue),
parent: partialResult,
child: existing.child,
alternateChild: existing.alternate?.child,
Expand All @@ -134,13 +136,6 @@ extension FiberReconciler {
layoutContexts: partialResult.layoutContexts
)
partialResult.nextExisting = existing.sibling

// If this fiber has an element, increment the elementIndex for its parent.
if let key = key,
existing.element != nil
{
partialResult.elementIndices[key] = partialResult.elementIndices[key, default: 0] + 1
}
} else {
let elementParent = partialResult.fiber?.element != nil
? partialResult.fiber
Expand All @@ -165,15 +160,9 @@ extension FiberReconciler {
fiber.alternate = alternate
partialResult.nextExistingAlternate = alternate.sibling
}
// If this fiber has an element, increment the elementIndex for its parent.
if let key = key,
fiber.element != nil
{
partialResult.elementIndices[key] = partialResult.elementIndices[key, default: 0] + 1
}
resultChild = Result(
fiber: fiber,
visitChildren: visitChildren(nextValue),
visitChildren: visitChildren(partialResult.fiber?.reconciler, nextValue),
parent: partialResult,
child: nil,
alternateChild: fiber.alternate?.child,
Expand Down
17 changes: 13 additions & 4 deletions Sources/TokamakCore/Fiber/FiberReconciler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public final class FiberReconciler<Renderer: FiberRenderer> {
}

func visit<V>(_ view: V) where V: View {
visitAny(view, visitChildren: view._visitChildren)
visitAny(view, visitChildren: reconciler.renderer.viewVisitor(for: view))
}

func visit<S>(_ scene: S) where S: Scene {
Expand Down Expand Up @@ -224,8 +224,7 @@ public final class FiberReconciler<Renderer: FiberRenderer> {
let previous = node.fiber?.alternate?.element
{
// This is a completely different type of view.
mutations
.append(.replace(parent: parent, previous: previous, replacement: element))
mutations.append(.replace(parent: parent, previous: previous, replacement: element))
} else if let newContent = node.newContent,
newContent != element.content
{
Expand Down Expand Up @@ -327,15 +326,25 @@ public final class FiberReconciler<Renderer: FiberRenderer> {
/// The main reconciler loop.
func mainLoop() {
while true {
// If this fiber has an element, set its `elementIndex`
// and increment the `elementIndices` value for its `elementParent`.
if node.fiber?.element != nil,
let elementParent = node.fiber?.elementParent
{
let key = ObjectIdentifier(elementParent)
node.fiber?.elementIndex = elementIndices[key, default: 0]
elementIndices[key] = elementIndices[key, default: 0] + 1
}

// Perform work on the node.
reconcile(node)

// Ensure the TreeReducer can access the `elementIndices`.
node.elementIndices = elementIndices

// Compute the children of the node.
let reducer = TreeReducer.SceneVisitor(initialResult: node)
node.visitChildren(reducer)
elementIndices = node.elementIndices

// As we walk down the tree, propose a size for each View.
if reconciler.renderer.useDynamicLayout,
Expand Down
17 changes: 17 additions & 0 deletions Sources/TokamakCore/Fiber/FiberRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public protocol FiberRenderer {
associatedtype ElementType: FiberElement
/// Check whether a `View` is a primitive for this renderer.
static func isPrimitive<V>(_ view: V) -> Bool where V: View
func visitPrimitiveChildren<Primitive, Visitor>(
_ view: Primitive
) -> ViewVisitorF<Visitor>? where Primitive: View, Visitor: ViewVisitor
/// Apply the mutations to the elements.
func commit(_ mutations: [Mutation<Self>])
/// The root element all top level views should be mounted on.
Expand All @@ -40,6 +43,20 @@ public protocol FiberRenderer {
public extension FiberRenderer {
var defaultEnvironment: EnvironmentValues { .init() }

func visitPrimitiveChildren<Primitive, Visitor>(
_ view: Primitive
) -> ViewVisitorF<Visitor>? where Primitive: View, Visitor: ViewVisitor {
nil
}

func viewVisitor<V: View, Visitor: ViewVisitor>(for view: V) -> ViewVisitorF<Visitor> {
if Self.isPrimitive(view) {
return visitPrimitiveChildren(view) ?? view._visitChildren
} else {
return view._visitChildren
}
}

@discardableResult
func render<V: View>(_ view: V) -> FiberReconciler<Self> {
.init(self, view)
Expand Down
85 changes: 85 additions & 0 deletions Sources/TokamakCore/Fiber/Layout/BackgroundLayoutComputer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 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 Carson Katri on 5/24/22.
//

import Foundation

/// A `LayoutComputer` that constrains a background to a foreground.
final class BackgroundLayoutComputer: LayoutComputer {
let proposedSize: CGSize
let alignment: Alignment

init(proposedSize: CGSize, alignment: Alignment) {
self.proposedSize = proposedSize
self.alignment = alignment
}

func proposeSize<V>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
where V: View
{
if index == 0 {
// The foreground can pick their size.
return proposedSize
} else {
// The background is constrained to the foreground.
return context.children.first?.dimensions.size ?? .zero
}
}

func position(_ child: LayoutContext.Child, in context: LayoutContext) -> CGPoint {
let foregroundSize = ViewDimensions(
size: .init(
width: context.children.first?.dimensions.width ?? 0,
height: context.children.first?.dimensions.height ?? 0
),
alignmentGuides: [:]
)
return .init(
x: foregroundSize[alignment.horizontal] - child.dimensions[alignment.horizontal],
y: foregroundSize[alignment.vertical] - child.dimensions[alignment.vertical]
)
}

func requestSize(in context: LayoutContext) -> CGSize {
let childSize = context.children.reduce(CGSize.zero) {
.init(
width: max($0.width, $1.dimensions.width),
height: max($0.height, $1.dimensions.height)
)
}
return .init(width: childSize.width, height: childSize.height)
}
}

public extension _BackgroundLayout {
static func _makeView(_ inputs: ViewInputs<Self>) -> ViewOutputs {
.init(
inputs: inputs,
layoutComputer: {
BackgroundLayoutComputer(proposedSize: $0, alignment: inputs.content.alignment)
}
)
}
}

public extension _BackgroundStyleModifier {
static func _makeView(_ inputs: ViewInputs<Self>) -> ViewOutputs {
.init(
inputs: inputs,
layoutComputer: { BackgroundLayoutComputer(proposedSize: $0, alignment: .center) }
)
}
}
11 changes: 6 additions & 5 deletions Sources/TokamakCore/Fiber/Layout/FlexLayoutComputer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,25 @@
import Foundation

/// A `LayoutComputer` that fills its parent.
struct FlexLayoutComputer: LayoutComputer {
@_spi(TokamakCore)
public struct FlexLayoutComputer: LayoutComputer {
let proposedSize: CGSize

init(proposedSize: CGSize) {
public init(proposedSize: CGSize) {
self.proposedSize = proposedSize
}

func proposeSize<V>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
public func proposeSize<V>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
where V: View
{
proposedSize
}

func position(_ child: LayoutContext.Child, in context: LayoutContext) -> CGPoint {
public func position(_ child: LayoutContext.Child, in context: LayoutContext) -> CGPoint {
.zero
}

func requestSize(in context: LayoutContext) -> CGSize {
public func requestSize(in context: LayoutContext) -> CGSize {
proposedSize
}
}
11 changes: 6 additions & 5 deletions Sources/TokamakCore/Fiber/Layout/FrameLayoutComputer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,27 @@
import Foundation

/// A `LayoutComputer` that uses a specified size in one or more axes.
struct FrameLayoutComputer: LayoutComputer {
@_spi(TokamakCore)
public struct FrameLayoutComputer: LayoutComputer {
let proposedSize: CGSize
let width: CGFloat?
let height: CGFloat?
let alignment: Alignment

init(proposedSize: CGSize, width: CGFloat?, height: CGFloat?, alignment: Alignment) {
public init(proposedSize: CGSize, width: CGFloat?, height: CGFloat?, alignment: Alignment) {
self.proposedSize = proposedSize
self.width = width
self.height = height
self.alignment = alignment
}

func proposeSize<V>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
public func proposeSize<V>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
where V: View
{
.init(width: width ?? proposedSize.width, height: height ?? proposedSize.height)
}

func position(_ child: LayoutContext.Child, in context: LayoutContext) -> CGPoint {
public func position(_ child: LayoutContext.Child, in context: LayoutContext) -> CGPoint {
let size = ViewDimensions(
size: .init(
width: width ?? child.dimensions.width,
Expand All @@ -51,7 +52,7 @@ struct FrameLayoutComputer: LayoutComputer {
)
}

func requestSize(in context: LayoutContext) -> CGSize {
public func requestSize(in context: LayoutContext) -> CGSize {
let childSize = context.children.reduce(CGSize.zero) {
.init(
width: max($0.width, $1.dimensions.width),
Expand Down
11 changes: 6 additions & 5 deletions Sources/TokamakCore/Fiber/Layout/ShrinkWrapLayoutComputer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,25 @@
import Foundation

/// A `LayoutComputer` that shrinks to the size of its children.
struct ShrinkWrapLayoutComputer: LayoutComputer {
@_spi(TokamakCore)
public struct ShrinkWrapLayoutComputer: LayoutComputer {
let proposedSize: CGSize

init(proposedSize: CGSize) {
public init(proposedSize: CGSize) {
self.proposedSize = proposedSize
}

func proposeSize<V>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
public func proposeSize<V>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
where V: View
{
proposedSize
}

func position(_ child: LayoutContext.Child, in context: LayoutContext) -> CGPoint {
public func position(_ child: LayoutContext.Child, in context: LayoutContext) -> CGPoint {
.zero
}

func requestSize(in context: LayoutContext) -> CGSize {
public func requestSize(in context: LayoutContext) -> CGSize {
context.children.reduce(CGSize.zero) {
.init(
width: max($0.width, $1.dimensions.width),
Expand Down
12 changes: 6 additions & 6 deletions Sources/TokamakCore/Fiber/LayoutComputer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
import Foundation

/// The currently computed children.
struct LayoutContext {
var children: [Child]
public struct LayoutContext {
public var children: [Child]

struct Child {
let index: Int
let dimensions: ViewDimensions
public struct Child {
public let index: Int
public let dimensions: ViewDimensions
}
}

Expand All @@ -40,7 +40,7 @@ struct LayoutContext {
/// The same `LayoutComputer` instance will be used for any given view during a single layout pass.
///
/// Sizes from `proposeSize` will be clamped, so it is safe to return negative numbers.
protocol LayoutComputer {
public protocol LayoutComputer {
/// Will be called every time a child is evaluated.
/// The calls will always be in order, and no more than one call will be made per child.
func proposeSize<V: View>(for child: V, at index: Int, in context: LayoutContext) -> CGSize
Expand Down
Loading