Commit Graph

19 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 005996262a
Add Canvas and TimelineView to DOM renderer (#449)
* 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>
2021-09-28 10:27:35 -04: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 d35e37c4f5
Add a snapshot test for `Path` SVG layout (#412)
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
2021-06-21 16:45:21 +01: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 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
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 3451d9ea12
Pass sibling to `Renderer.mount`, fix update order (#301)
Resolves, but adds no tests cases to the test suite for #294. See the issue for the detailed description of the problem.

I will add end-to-end tests for this in future PRs.

I've tested these cases manually so far:

```swift
struct Choice: View {
  @State private var choice = false

  var body: some View {
    HStack {
      Button("Trigger") {
        choice.toggle()
      }
      if choice {
        Group {
          Text("true")
          Text("true")
        }
      } else {
        VStack {
          Text("false")
        }
      }
      Text("end")
    }
  }
}
```

Note the `Group` view with multiple children in this one, it uncovered required checks for `GroupView` conformance.

Also tested these more simple cases:

```swift
struct Choice: View {
  @State private var choice = false

  var body: some View {
    HStack {
      Button("Trigger") {
        choice.toggle()
      }
      if choice {
        Group {
          // single child
          Text("true")
        }
      } else {
        VStack {
          Text("false")
        }
      }
      Text("end")
    }
  }
}
```

and

```swift
struct Choice: View {
  @State private var choice = false

  var body: some View {
    HStack {
      Button("Trigger") {
        choice.toggle()
      }
      if choice {
        // single child, no nesting
        Text("true")
      } else {
        VStack {
          Text("false")
        }
      }
      Text("end")
    }
  }
}
```
2020-11-11 19:34:45 +00:00
Max Desiatov b7434a2e54
Add `GeometryReader` implementation (#239)
This is just an empty API at the moment. I hope it can be implemented purely in the `deferredBody` of `GeometryReader` with [the ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) without requiring any tweaks in the `Renderer` protocol or the reconciler.

Seems like I need the `domRef` modifier that writes `JSObjectRef` to a given binding working first, as discussed in #231.
2020-08-11 16:47:12 +01:00
Max Desiatov b93be40a19
Add `_targetRef` and `_domRef` modifiers (#240)
Resolves partially #231. `_targetRef` is a modifier that can be used by any renderer, while `_domRef` is an adaptation of that for `DOMRenderer`. Both are underscored as they are not available in SwiftUI, and also their stability is currently not so well known to us, we may consider changing this API in the future.

Example use:

```swift
struct DOMRefDemo: View {
  @State var button: JSObjectRef?

  var body: some View {
    Button("Click me") {
      button?.innerHTML = "This text was set directly through a DOM reference"
    }._domRef($button)
  }
}
```

I've also fixed all known line length warnings in this PR.
2020-08-02 22:01:38 +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 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 2b93f37d64
Add SwiftUI App Lifecycle (#195) 2020-07-22 16:57:33 -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
Max Desiatov 38ca0093a7
Add AppearanceActionModifier, onAppear/onDisappear (#145)
This is required to unblock #136 as I think it would make sense for the `DOMEnvironment` view there to add/remove its color scheme listener in `onAppear`/`onDisappear` closures.

I've also restored full SwiftUI compatibility in the signature of `func modifier<Modifier>(_ modifier: Modifier)`. Consequently `ViewDeferredToRenderer` had to be implemented on `ModifiedContent` then instead of `_ViewModifier_Content` to make it work.
2020-07-02 16:12:08 +01:00
Max Desiatov 1632db77b9
Rename MountedView to MountedViews for consistency 2020-07-01 23:22:06 +01:00