* Initial pass at Layout protocol implementation
* Move layout into a separate pass
* Split reconciler into separate FiberReconcilerPass-es
* Cleanup reconcile pass
* Simplify cache implementation
* Optimize array capacity and persist cache between runs
* Revert viewChildrenCount
* Improve accuracy of stack layout
* Try caching sizeThatFits
* Revise caching of sizeThatFits
* Cleanup layout files and split up
* Further cleanup
* Add ContainedZLayout
* Add frame layouts
* Add snapshots tests that compare against native SwiftUI
* Perform updates from the top of the view hierarchy for dynamic layout
* Add Package.swift
* Fix reconciler bug
* Add test case for reconciler insert bug
* Respect spacing preferences
* Revise cache and spacing logic to match SwiftUI default implementations
* Allow spacing changes based on adjacent View type
* Support view traits in FiberReconciler (and LayoutValueKey by extension)
* Propagate cache invalidation
* Cleanup attributes
* Simplify LayoutPass and improve accuracy
* Cleanup logs
* Add layoutPriority tests
* Revise conflict with main
* Dictionary performance catch
* Remove unneccesary capacity preservation
* Update TokamakCoreBenchmark to handle LayoutView addition at hierarchy root
* Implement AnyLayout and replace LayoutActions
* Allow VStack/HStack to be created without children
* Fix padding implementation
* Formatting fixes
* Space out ViewArguments.swift properties
* Add backing storage to LayoutSubview to move out of ReconcilePass and slightly optimize stack usage
* Initial Reconciler using visitor pattern
* Preliminary static HTML renderer using the new reconciler
* Add environment
* Initial DOM renderer
* Nearly-working and simplified reconciler
* Working reconciler for HTML/DOM renderers
* Rename files, and split code across files
* Add some documentation and refinements
* Remove GraphRendererTests
* Re-add Optional.body for StackReconciler-based renderers
* Add benchmarks to compare the stack/fiber reconcilers
* Fix some issues created for the StackReconciler, and add update benchmarks
* Add BenchmarkState.measure to only calculate the time to update
* Fix hang in update shallow benchmark
* Fix build errors
* Address build issues
* Remove File.swift headers
* Rename Element -> FiberElement and Element.Data -> FiberElement.Content
* Add doc comment explaining unowned usage
* Add doc comments explaining implicitly unwrapped optionals
* Attempt to use Swift instead of JS for applying mutations
* Fix issue with not applying updates to DOMFiberElement
* Add comment explaining manual implementation of Hashable for PropertyInfo
* Fix linter issues
* Remove dynamicMember label from subscript
* Re-enable carton test
* Re-enable TokamakDemo with StackReconciler
Co-authored-by: Max Desiatov <max@desiatov.com>
Had to drop support for Swift 5.4/5.5 and macOS 5.6 jobs, see https://github.com/TokamakUI/Tokamak/pull/475#issuecomment-1092662828 for more details.
Linux builds and `codecov` job were updated to use nightly Swift, which have crashes reproducible in 5.6.0 release fixed.
Also applied a few formatting changes with the latest SwiftFormat.
* Bump JavaScriptKit dependency to 0.12.0.
* Update CHANGELOG.md for 0.9.1 release.
* Fix access to `hash` property
(now that JSObject conforms to Hashable it has a Swift hash property which overrides callAsFunction)
* Update CHANGELOG.md
I've also moved sources in `TokamakDemo` directory into their respective subdirectories for easier navigation.
* Update for JSKit 0.11.0, add async `task` modifier
* Add back new file locations to `NativeDemo`
* Add compiler `#if` check to `TaskDemo`
* Update to JavaScriptKit 0.11.1
* Restrict `TaskDemo` with `compiler(>=5.5)` check
* Replace `compiler` with `swift` in some places
* Revert "Replace `compiler` with `swift` in some places"
This reverts commit 534784ca7b.
* Use Xcode 13.2 on GitHub Actions hosts
* Find `TokamakPackageTests` in the build directory
* Fix macOS tests bundle path
* Make `task` modifier available only on macOS Monterey
* Revert "Use Xcode 13.2 on GitHub Actions hosts"
This reverts commit 63d044f2d5.
* Revert "Fix macOS tests bundle path"
This reverts commit 3ccbc98a2d.
* Revert "Find `TokamakPackageTests` in the build directory"
This reverts commit 68c845bc19.
* Use `canImport(Concurrency)` as an ultimate check
* Use `compiler(>=5.5) && canImport(Concurrency)`
* Clarify new browser version requirements in `README.md`
* Account for `_Concurrency` naming
* Update `README.md`
* Update README.md
Co-authored-by: ezraberch <49635435+ezraberch@users.noreply.github.com>
This updates the project to use Swift 5.4 across all platforms. Swift 5.4 is now also the required version, which allows us to use `@resultBuilder` instead of the deprecated version of this attribute from Swift 5.3.
Use `carton` 0.11.0 or later from now on to build with SwiftWasm 5.4.0.
This adds a dependency on the [SnapshotTesting](https://github.com/pointfreeco/swift-snapshot-testing) library, which allows testing our SVG layout algorithm end-to-end. We use the `--screenshot` flag of Chromium-based browsers (MS Edge in this case) to produce a PNG snapshot of a view rendered with `StaticHTMLRenderer`.
This test works only on macOS for now due to its dependency on `NSImage`, but that should be fine as we'd expect the same SVG output to be rendered in the same way on all platforms.
* Implement snapshot tests with headless MS Edge
* Increase snapshot tests timeout
* Force 1.0 resolution scale for headless Edge
* Avoid complex layouts in the snapshot test
* Exclude dir from target sources, upload failures
* Add a test to verify that fusion works
* Enable fusion of modifiers nested three times
* Filter out empty attributes
* Run snapshot tests only on macOS for now
* Fully exclude snapshot testing on WASI
* Fix `testOptional` snapshot
* Clean up code formatting
* Copy failed snapshots to a readable directory
* Make the copy script more resilient
* Use `--force-color-profile=srgb` Chromium flag
* Re-enable spooky hanger test
* Clean up testSpookyHanger
* Fix linter warnings
* Fix file_length linter warning
* Silence linter warning for `Text.attributes` func
* Split `PathLayout.swift` to appease the linter
This allows fusing nested `.padding` modifiers into a single `div` that sums up padding values from all these modifiers.
Before:
```swift
Text("text").padding(10).padding(20)
```
rendered to this (text styling omitted for brevity):
```html
<div style="padding-top: 20.0px; padding-left: 20.0px; padding-bottom: 20.0px; padding-right: 20.0px;">
<div style="padding-top: 10.0px; padding-left: 10.0px; padding-bottom: 10.0px; padding-right: 10.0px;">
<span>text</span>
</div>
</div>
```
Now it renders as
```html
<div style="padding-top: 30.0px; padding-left: 30.0px; padding-bottom: 30.0px; padding-right: 30.0px;">
<span>text</span>
</div>
```
I hope this approach could be applied to other modifier combinations where it makes sense (in separate PRs).
* Attempt `padding` modifier fusion
* Fix linter warning
* Add a test to verify that fusion works
* Enable fusion of modifiers nested three times
* Filter out empty attributes
* Run snapshot tests only on macOS for now
* Fully exclude snapshot testing on WASI
* Fix `testOptional` snapshot
* Clean up code formatting
Most of the changes are related to the use of OpenCombineShim (available in upstream OpenCombine now) instead of CombineShim. But there is also a new test added during the investigation of #367, where an app is rendered end-to end, which is a good way to expand our test suite I think.
* Use immediate scheduler in TestRenderer
This allows running our test suite on WASI too, which doesn't have Dispatch and also can't wait on XCTest expectations. Previously none of our tests (especially runtime reflection tests) ran on WASI.
* Run `carton test` and `carton bundle` in separate jobs
* Bump year in the `LICENSE` file
* Add reconciler stress tests for elaborate testing
* Move default App implementation to TestRenderer
* Use OpenCombineShim instead of CombineShim
This allows writing tests for `TokamakStaticHTML`, `TokamakDOM`, and `TokamakGTK` targets.
The issue was caused by conflicting `ViewDeferredToRenderer` conformances declared in different modules, including the `TokamakTestRenderer` module.
This works around a general limitation in Swift, which was [discussed at length on Swift Forums previously](https://forums.swift.org/t/an-implementation-model-for-rational-protocol-conformance-behavior/37171). When multiple conflicting conformances to the same protocol (`ViewDeferredToRenderer` in our case) exist in different modules, only one of them is available in a given binary (even a test binary). Also, only of them can be loaded and used. Which one exactly is loaded can't be known at compile-time, which is hard to debug and leads to breaking tests that cover code in different renderers. We had to disable `TokamakStaticHTMLTests` for this reason.
The workaround is to declare two new functions in the `Renderer` protocol:
```swift
public protocol Renderer: AnyObject {
// ...
// Functions unrelated to the issue at hand skipped for brevity.
/** Returns a body of a given pritimive view, or `nil` if `view` is not a primitive view for
this renderer.
*/
func body(for view: Any) -> AnyView?
/** Returns `true` if a given view type is a primitive view that should be deferred to this
renderer.
*/
func isPrimitiveView(_ type: Any.Type) -> Bool
}
```
Now each renderer can declare their own protocols for their primitive views, i.e. `HTMLPrimitive`, `DOMPrimitive`, `GTKPrimitive` etc, delegating to them from the implementations of `body(for view:)` and `isPrimitiveView(_:)`. Conformances to these protocols can't conflict across different modules. Also, these protocols can have `internal` visibility, as opposed to `ViewDeferredToRenderer`, which had to be declared as `public` in `TokamakCore` to be visible in renderer modules.
This makes attributes order deterministic and allows testing against HTML renderer output, while currently attributes order is random.
Benchmarks results:
```
name time std iterations
---------------------------------------------------------------------
render Text 9667.000 ns ± 4.35 % 145213
render App unsorted attributes 51917.000 ns ± 4.23 % 26835
render App sorted attributes 52375.000 ns ± 1.62 % 26612
render List unsorted attributes 34546833.500 ns ± 0.79 % 40
render List sorted attributes 34620000.500 ns ± 0.69 % 40
```
Looks like on average there's ~0.2% difference in performance. I was leaning towards enabling sorting by default, but we're benchmarking here only with short attribute dictionaries, I wonder if the difference could become prominent for elements with more attributes. I kept sorting disabled by default after all, but still configurable.
`var html: String` on `StaticHTMLRenderer` was changed to `func render(shouldSortAttributes: Bool = false) -> String` to allow configuring this directly.
* Sort attributes in HTML nodes when rendering
* Make sorting configurable, add benchmarks
* Disable sorting by default, clean up product name
* Fix build errors
Our OpenCombine fork no longer depends on Runtime, and we don't need much from it other than struct metadata. I removed the unused bits and bobs and kept only a minimal subset of it that we really need. This should make it easier for us to test and debug, as #367 has shown that some weird stuff may still lurk in that area.
* Add a test for environment injection
We had some issues in this code area previously and I'm thinking of refactoring it in attempt to fix#367. Would be great to increase the test coverage here before further refactoring.
* Update copyright years in `MountedElement.swift`
* Update copyright years in the rest of the files
* Vend the Runtime library directly
* Remove unused class, enum, tuple, func reflection
* Remove unused models and protocol metadata
* Remove unused MetadataType and NominalMetadataType
* Remove unused protocols, rename RelativePointer
* Remove more unused protocols
* Use immutable pointers for reflection
* Update copyright headers
This should allow us to remove the Runtime dependency eventually, which seems to be unstable, especially across different platforms and Swift versions.
Seems to resolve in one instance https://github.com/TokamakUI/Tokamak/issues/367. There are a few other places where `typeInfo` is still used, I'll clean that up in a follow-up PR.
* Replace uses of the Runtime library with stdlib
* Remove irrelevant Runtime library imports
* Add TokamakCoreBenchmark target
* Add a benchmark target and a script to run it
Benchmarks need to be built in the release mode, that's why I created a separate `benchmark.sh` script to build it and run it.
I've also cleaned up a compiler warning in `TextEditor.swift` and bumped macOS agents to use Xcode 12.3 instead of 12.2.
* Benchmark `App` and `WindowGroup` rendering
* Add a `benchmark` task to `tasks.json`
* Exit `NativeDemo` directory before benchmarking
* Build the GTK renderer on Ubuntu on CI
* Add `--enable-test-discovery` flag to `Makefile`
* Use OpenCombine branch w/ no Runtime dependency
* Run `sudo apt-get update` on Ubuntu hosts
* Update OpenCombine dependency
* Pin OpenCombineJS dependency
* Update label.yml
* GTK shape support
* Support for ellipsis
* Allow drawing 'outside' of current frame. Experimental.
* Correctly support Capsules (rounded rects with nil cornerSize)
* Use boundingRect.size for Path size
* Refactored GdkRGBA from AnyColorBox.ResolvedValue to be reusable
* Added 'resolveToCairo(in environment:)' extension on Color
* Create slightly lighter View type hierarchies.
Based on the work discussed in #306.
* TokamakGTK implementation
* Fix macOS GTK Renderer impl
* Always release text in Picker. Use 'destroy_data' parameter to release closure boxes in GSignal.swift
* Revert commenting out this code
* Specify the product explicitly in Makefile
* Add GTK renderer build for macOS on CI
* Prevent xcodebuild from seeing GTK code
Co-authored-by: Carson Katri <carson.katri@gmail.com>
Co-authored-by: Morten Bek Ditlevsen <morten@ka-ching.dk>
* Add Image view
* Add image to demo
* Update progress.md
* Alt text
* Use Bundle type to load images, remove systemName
* Add `logo-header.png` resource to `TokamakDemo`
* Reduce image size in the demo
* Allow bundles passed to `Image` to be optional
* Pass `nil` as a default `bundle` to `Image`
Co-authored-by: Max Desiatov <max@desiatov.com>
* Ignore xcodeproj generated by SwiftPM
* Update to use official OpenCombine to avoid Xcode build error
* Use forked version with ObservableObject implementation
* Fix ambigious error
* Ignore SwiftPM edit mode package
* Update toolchain version
Requires #276 to be merged, as earlier versions of JavaScriptKit can't be depended on in macOS builds due to unsafe flags.
* Add Link View
* Add Publish support
* Remove #if checks
* Upgrade swift snapshot
* Try swiftwasm-action@main
* Remove Publish support from this repo
* Remove TokamakPublish target
* Allow tests to be run on macOS
* Update `ci.yml` to build and run the test product
* Trigger CI on all PRs without branch restrictions
* Rename linux_build to swiftwasm_build in ci.yml
Co-authored-by: Carson Katri <carson.katri@gmail.com>
* Add Link View
* Add Publish support
* Remove #if checks
* Upgrade swift snapshot
* Try swiftwasm-action@main
* Remove Publish support from this repo
* Remove TokamakPublish target
This PR requires `carton` 0.6.0 that you can install from Homebrew as usual.
To cleanly manage scheduler closures, new `JSScheduler` class is introduced that conforms to OpenCombine's `Scheduler` protocol. I think it will be moved to OpenCombineJS in the future.
* Fix compatibility with JavaScriptKit 0.7
* Formatting update
* Specify `carton` 0.6.0 as a requirement
* Optimize immediate schedule function
* Update formatting
This allows specifying 0.3 (to be released after this PR is merged) dependency on Tokamak in `carton` templates, otherwise branch/commit dependencies in our `Package.swift` can't be correctly resolved.
`swift test` is temporarily disabled on macOS as the upstream Swift toolchain doesn't support unsafe flags in the JavaScriptKit dependency together with a `from` constraint. We could run it on Linux, but my OpenCombine fork doesn't support Linux builds yet (logged as #263).
* Use the latest 5.3 snapshot in `.swift-version`
These SwiftWasm snapshots should be more stable in general and also have a workaround for https://github.com/swiftwasm/JavaScriptKit/issues/6 included. They still use the old metadata layout, so Runtime and OpenCombine dependencies had to be updated in `Package.swift` for `@ObservableObject` to work with these snapshots.
* Fix linter warning
We can't run our basic reconciler tests in a WASI environment yet because `XCTestExpectation` is not available on WASI as it relies on the presence of `Dispatch`. We can run these tests on macOS though, and even on Linux in the future when Swift 5.3 is available for Linux on GitHub Actions.
My current OpenCombine fork doesn't build on macOS and it was much easier to add a new `CombineShim` module that uses native Combine there.
This change is required to make Tokamak compatible with `carton` 0.4, which bundles the latest JavaScriptKit runtime. Currently the new runtime and old Swift parts of JavaScriptKit are incompatible with each other, which leads to a white screen when building and running Tokamak from `main` with `carton` 0.4.
It's already a part of a couple of other PRs, but opening this one separately to expedite it as `carton` 0.4 is already released. I haven't announced it widely yet, so I hope we can merge this in quickly to avoid any confusion among our early adopters who try to use Tokamak with the latest version of `carton` 🙂
Adding this module as a dependency, Tokamak users would only need to add a single import regardless of the platform they're targeting. Thus, instead of
```swift
#if canImport(SwiftUI)
import SwiftUI
#else
import TokamakDOM
#endif
```
a single `import TokamakShim` is enough. `TokamakShim` re-exports correct modules based on a target platform.
I've also renamed the `TokamakDemo Native` directory to `NativeDemo` for brevity.
`xcodebuild` output in the `macos_demo_build` job is now passed to `xcpretty` for more readable build logs.
This pulls a fork of OpenCombine that can be compiled with the same SwiftWasm snapshot we use in `main`.
The only caveat is that this doesn't work for `ObservableObject`s that are subclasses of other classes with `@Published` properties. This issue needs to be fixed in [the Runtime library fork](https://github.com/MaxDesiatov/Runtime). Since this is a rare case, and fixing it wouldn't change this `@ObservedObject` implementation, I think it's ready for review as is.