Commit Graph

28 Commits

Author SHA1 Message Date
Andrew Barba 2ba548810c
Support meta tags in StaticHTMLRenderer (#483)
This PR adds the ability to control `<head>` tags using a new `HTMLTitle` view and `HTMLMeta` view. Taking inspiration from Next.js `<NextHead>`, you can use these views anywhere in your view hierarchy and they will be hoisted to the top `<head>` section of the html.

Use as a view:

```swift
var body: some View {
  VStack {
    ...
    HTMLTitle("Hello, Tokamak")
    HTMLMeta(charset: "utf-8")
    ...
  }
}
```

Use as a view modifier:

```swift
var body: some View {
  VStack {
    ...
  }
  .htmlTitle("Hello, Tokamak")
  .htmlMeta(charset: "utf-8")
}
```

And the resulting html (no matter where these are used in your view hierarchy):

```html
<html>
  <head>
    <title>Hello, Tokamak</title>
    <meta charset="utf-8">
  </head>
  <body>
    ...
  </body>
</html>
```
2022-05-23 20:03:28 +00:00
Carson Katri 9a568ab9cf
Add View Traits and transitions (#426) 2021-07-28 09:40:12 -04:00
Carson Katri ab5e564ada
Animation implementation using the Web Animations API (#427) 2021-07-13 08:48:45 -04:00
Max Desiatov ac69bbc3e5
Add reconciler stress tests for elaborate testing (#381)
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
2021-06-15 23:01:45 +01:00
Max Desiatov 5926e9f182
Replace `ViewDeferredToRenderer`, fix renderer tests (#408)
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.
2021-06-07 17:24:02 +01:00
Max Desiatov 0e89ea9529
Clean up metadata reflection code (#372)
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
2021-01-27 18:24:04 +00:00
Max Desiatov 192c43b140
Refactor environment injection, add a test (#371)
* 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
2021-01-25 11:39:09 +00:00
Max Desiatov e04b7934fb
Replace uses of the Runtime library with stdlib (#370)
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
2021-01-24 15:26:51 +00:00
Max Desiatov 67aea3cc3b
Fix crashes in views with optional content (#364)
* Add TokamakStaticHTMLTests target

* Add AnyOptional, clarify conformances issues
2021-01-20 08:07:01 +03:00
David Hunt 6955e56f77
Fixed a small issue with re-renderers being dropped (#356)
The code was clearing the queuedRerenders set after processing all of the updates on each re-renderer.  However, during processing it is possible that new values were enqueued.  By clearing the set afterwards, these newly queued re-renderers were accidentally dropped.

This would cause some Views to not update when @State was changed.
2021-01-18 15:39:03 +03:00
Carson Katri 9d347f49f3
Add Preferences (#307)
This adds the `PreferenceKey` protocol and related modifiers.

* Initial PreferenceKey implementation

* Don't send default value to match SwiftUI behavior

* Add CustomDebugStringConvertible conformance to Color

* PR fixes

* Fix onAppear and preference modification calls

* Attempt macOS build fix

* Fix <background/overlay>PreferenceValue

* Implement/revise transformPreference

* Fix linter warnings, apply SwiftFormat

Co-authored-by: Max Desiatov <max@desiatov.com>
2020-12-04 11:19:14 +00:00
Max Desiatov 2383a17c2d
Implement `StateObject` property wrapper (#260)
Resolves #158.

Fixes a bug where `NavigationView` destination was reset after scene phase changes (or any re-renders caused by environment changes for that matter).

This was caused by `@ObservedObject` destination being recreated, now `@StateObject` persists it across re-renders.

The `setter` property of the `ValueStorage` protocol is now moved to a separate `WritableValueStorage` protocol. The reasoning is that `StateObject` doesn't need its wrapped value to be set directly as it operates on it by reference, not by value, thus `StateObject` doesn't need any wrapped value setters.
2020-08-17 16:01:49 +01:00
Max Desiatov b9f5ef07ab
Fix `NavigationView` broken state after re-render (#259) 2020-08-15 18:07:19 +01:00
Max Desiatov c4c9eb595e
Update to the latest version of SwiftFormat (#250)
* Update to the latest version of SwiftFormat

This fixes inconsistencies in argument and parameter formatting that we previously had.

* Fix function length in `Path.swift`

* Fix linter warnings

* More formatting cleanups

* Add `StrokeStyle.zero` in the `StaticHTML` module
2020-08-05 17:30:19 +01:00
Max Desiatov c7b5e75e1a
Add `ColorScheme` environment (#136)
You can see the dark scheme environment text representation updated in `EnvironmentDemo`. I suggest adding default dark mode styles in a separate PR, I've created #237 as a reminder for that.
2020-08-02 18:55:35 +01:00
Max Desiatov e37d13017c
Add basic `ButtonStyle` implementation (#214)
This based off the `buttonstyles` branch by @Outcue.

Initially it didn't work because mounted host views didn't propagate their environment on updates. This is now fixed by adding `updateEnvironment` function on `MountedElement` base class and calling it in the initializer. Manual environment updates are no longer needed in `makeMounted...` factory functions. `makeMountedApp` is no longer needed at all and `MountedApp` initializer can be used directly then.
2020-08-01 18:46:34 +01:00
Max Desiatov 2c539d9319
Make reconciler tests build and run on macOS (#229)
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.
2020-07-30 16:48:09 +01:00
Max Desiatov 1271281b75
Fix environment changes causing remounted scenes with lost state (#223)
Implements support for custom scenes by adding the new `MountedScene` type and fixes environment propagation from mounted parent to mounted child elements.

This will unblock https://github.com/swiftwasm/Tokamak/pull/136 and potentially https://github.com/swiftwasm/Tokamak/pull/214. In the former scenes are always completely unmounted and remounted from scratch when root environment changes, which causes descendants to lose all `@State` values. I saw some environment propagation issues in the latter, but not sure if those were caused by the lack of correct scene updates, just wanted to tackle the usual suspects first.

I've also improved reconciler-related doc comments to clarify some of the design desicions and naming.

Resolves #222.
2020-07-29 21:37:38 +01:00
Max Desiatov 7c6b1812ae
Add `DefaultApp` type to simplify `DOMRenderer.init` (#217)
This means that the `View` initializer of `DOMRenderer` now can delegate to the `App` initializer by creating a wrapping `DefaultApp`. It would simplify https://github.com/swiftwasm/Tokamak/pull/136 for me, where I no longer need to implement color scheme observation for these two different codepaths separately.
2020-07-29 09:26:08 +01:00
Max Desiatov f5af009db2
Unify code of `MountedApp`/`MountedCompositeView` (#219)
We currently have the reconciler code duplicated in these types. I also have a draft `MountedScene` implementation, which most probably would rely on the same reconcilliation algorithm. In this PR it's made generic and can be shared across these types of mounted elements.
2020-07-28 18:01:29 +01:00
Carson Katri c68c70a7d7
Implement `DynamicProperty` (#213) 2020-07-25 17:32:28 -04:00
Carson Katri 2b93f37d64
Add SwiftUI App Lifecycle (#195) 2020-07-22 16:57:33 -04:00
Jed Fox f0e2b054dc
Add Toggle implementation (#159)
* Fix Button.body

* Add support for renderers customizing default environment values

* Add ParentView conformances

* Add Toggle

* long lines

* Update Path.swift

* Update Path.swift

* Update Sources/TokamakDOM/DOMRenderer.swift

Co-authored-by: Max Desiatov <max@desiatov.com>

* bodyBuild → bodyClosure

* Update progress.md

* Update progress.md, implement Toggle(_ configuration: ToggleStyleConfiguration)

* Fix demo on native

* Hopefully fix issue

* Hopefully fix issue for real

* maybe this will work

* Update ToggleDemo.swift

* AnyToggleStyle → _AnyToggleStyle

* Fix remaining AnyToggleStyle

* Clean up unnecessary files

* Move typealias to Core.swift

* Revert change to ListDemo, remove unused let

Co-authored-by: Max Desiatov <max@desiatov.com>
2020-07-20 18:21:32 -04:00
Jed Fox 314a6f3a5a
Allow non-consecutive state variables (#191)
* Fix error when state variables are non-consecutive

* Update one of the demos to cause it to fail without the fix
2020-07-16 17:10:09 -04:00
Max Desiatov ffa686c7dc
Add @ObservedObject implementation (#171)
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.
2020-07-16 20:39:47 +01:00
Carson Katri 525cefe6bc
Allow @Environment on all MountedViews (#146) 2020-07-02 09:36:16 -04:00
Carson Katri 1d71fe0c7d
Implement Environment (#135)
* Environment impl

* Fix line lengths

* Danger and review fixes

* rename _modifyEnvironment to modifyEnvironment
2020-07-01 08:28:09 -04:00
Max Desiatov 735251fb13
Rename Tokamak module to TokamakCore 2020-06-23 11:47:54 +01:00