* Add initial implementations of Canvas and TimelineView
* Add CanvasDemo
* Add the demo to the native project
* Use Xcode 13.0 for macOS builds
* Disable macOS builds until Monterey is available
* Mark CanvasDemo as iOS 15/macOS 12 only, fix LinkButtonStyle reference on iOS
* Add _VariadicView and symbol rendering
* Fix linter warnings
* Add image support
* Revise AnimationTimelineSchedule and requestAnimationFrame cancellation
* Fix pausing of animated TimelineView in TokamakDOM
Co-authored-by: Max Desiatov <max@desiatov.com>
This is a small thing I noticed while browsing the code. If you save the snapshots with the `html` extension then you can make it easy to see preview the actual html in a browser locally.
* Save HTML snapshots with .html extension.
* Apply suggestions from code review
Co-authored-by: Max Desiatov <max@desiatov.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.
`Toolbar` is new in SwiftUI. It is coupled fairly closely with `NavigationView`, so this should be integrated with that somehow (#130). It was made similar to macOS which allows more than a leading/trailing `ToolbarItem`.
Resolves#316.
No functional change is introduced, only core APIs are added but aren't implemented anywhere yet.
This is a step towards reducing the size of #169.
* Add `ToolbarItem` and its builder functions
* Silence file_length linter warning
This adds `ProgressView` using the `<progress>` tag on the web.
* Add ProgressView implementation
* Fix native demo
* Enable Foundation.Progress in non-WASI environments
* Fix wasm build
* Update progress coc
* Improve snapshot copy error handling
* Use RenderingTests as directory name
* Fix snapshots CI script
* Make test failures fail the CI job
* Snapshot script debugging
* Copy failed snapshots in a different way
* Call `exit 1` when tests fail
* Use correct directory in the upload step
* Update test image
* Update .github/workflows/ci.yml
Co-authored-by: ezraberch <49635435+ezraberch@users.noreply.github.com>
Co-authored-by: Max Desiatov <max@desiatov.com>
It's much easier to implement stack spacing when stacks are rendered as single-row or single-column grids, and grid gaps already work in all browsers. For this we need to slightly bump browser version requirements, most notably from Safari 11 to Safari 12.
Resolves#272.
* Remove unused properties in `StackDemo`
* Implement stack spacing with grid gaps
* Fix GTK build
* Bump browser requirements in README.md
* Remove outdated FIXME
* Generalize snapshot timeouts
* Prevent excessive CSS style leaks of properties
This allows TokamakDemo to be built with [latest SwiftWasm 5.4 toolchain](https://github.com/swiftwasm/swift/releases/tag/swift-wasm-5.4-SNAPSHOT-2021-06-17-a) in **debug mode** (compiler still crashes when building for release).
BTW this import could be anywhere in the target, couldn't find a file that felt natural to include it in.
* Move import statement to CGStubs.swift
The PR fixes multiple bugs which prevent stroked shapes from rendering correctly.
1. Allow environment injection into `_StrokedShape`. This causes stroked shapes to no longer crash (#322).
2. Change `Path.strokedPath` and `Path.trimmedPath` to allow the `sizing` of the base shape to be inherited. Without this, stroked shapes can appear as 0x0 in size, making them invisible.
3. Change `_ShapeView` in the StaticHTML renderer to merge attributes in the `svg` rather than placing the `svg` in a `div`. This allows proper rendering when multiple shapes are in a stack.
Finally, the `Path` demo has been modified to add a stroked circle.
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
Resolves#404.
This also allows us to write more tests that are source-compatible with SwiftUI.
* Use `CGFloat`, `CGPoint`, `CGRect` from Foundation
* Fix GTK build
* Fix macOS build
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
`DOMRenderer.mount` contains code which can be necessary to properly render spacers. However, `update` can overwrite what this code does, leading to the problem described in #395.
This PR modifies `update` to fix this issue.
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.
Currently, `DOMRenderer` can only handle `Button`s where is the label is `Text`. If it is any other `View`, the `Button` is not rendered. This is the cause of #403.
This PR removes this restriction. Additionally, it expands the `ButtonStyle` demo to include `Button`s with complex labels.
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
When _domRef is used to directly modify the DOM, this causes the state of the DOM to no longer match the View from which it was rendered. When the renderer later tries to unmount the modified element, this can cause a crash.
This PR fixes the crash by catching (and ignoring) this failure in DOMRenderer.unmount. This fixes#326 and #369, which are the same issue.
Note that directly modifying the DOM with `_domRef` can still cause problems, as the state mismatch remains. For example, an update to the `View` can cause the renderer to overwrite those DOM changes.
This fixes#320 by adding a SwiftUI-compatible `DatePicker`. However, `DatePickerStyle` is not supported.
This uses the HTML inputs `date`, `time`, or `datetime-local`, depending on the given `displayedComponents`. This means that not all browsers show the picker, as Mac Safari currently does not support them. Safari on Mac will just show an ISO-format text field. If the date is in an invalid format, the binding will not receive updates until it becomes parseable by JSDate.
On supported browsers, the binding gets updated in real time, as you would expect, with a Foundation.Date, just like SwiftUI.
* Add DatePicker to TokamakCore and TokamakDOM
* Fix crash on invalid date
* Update progress.md and add credit
* Fix time zone related issues with the DatePicker
* Add DatePickerDemo to the TokamakDemo
* Fix overview for DatePicker
* Fix NativeDemo build
Resolves#218.
`String(describing:)` initializer applied to metatypes does not include a module name, which can cause problems if two different types with same name come from different modules.
OTOH `String(reflecting:)` does include module name, which makes these reflection strings slightly longer, but should prevent obscure issues with name collisions from happening.