stackotter / swift-cross-ui

A cross-platform declarative UI framework, inspired by SwiftUI.
https://stackotter.github.io/swift-cross-ui/documentation/swiftcrossui/
MIT License
652 stars 36 forks source link

[MERGED] Implement experimental WinUIBackend #75

Closed LorenzoFerri closed 7 months ago

LorenzoFerri commented 9 months ago

This pull request aims to implement a backend that relies on the native Windows Runtime. It uses the bindings provided by thebrowsercompany in order to use modern WinUI components.

As explained in this example a requirement to use this library on Windows is to use the Swift SDK provided at: thebrowsercompany/swift-build

The WinUIBackend manages to run most of the examples, but some things are still not working:

I also have doubt about the way I implemented createLayoutTransparentStack but I think it's working?

Apart from these problems all the controls element seems to be working properly so here some screenshots from my computer (dark theme, purple accent):

Counter Example Random Number Generator Example Greetings Generator Example Windowing Example Controls Example

Let me know if you have any suggestions.

stackotter commented 9 months ago

This looks amazing! 🎉 It's very exciting how native it looks!

I haven't reviewed the code changes yet but just the screenshots alone look great.

  • I have no idea on how to implement the getInheritedOrientation as I didn't find anything related on Windows API.

This also isn't part of the Gtk API, it's added by us (kind of a janky way to fix inheriting orientation from parent containers).

As explained in this example a requirement to use this library on Windows is to use the Swift SDK provided at: thebrowsercompany/swift-build

Interesting, any idea why the regular SwiftPM build system doesn't work? Had a quick poke around but couldn't find an explanation

  • The updatePicker method is half implemented. The picker works only if it does not change the options at runtime as I failed to understand how to get the current items of the picker.

Ah yes, that was incredibly annoying in Gtk too lol. Which may indicate that I need to rethink that part of the API to possibly pass the previous options as well as the current options, or something along those lines? Now that I think about it, similar changes could probably be made to avoid the need for getInheritedOrientation (by passing the expected orientation to the backend instead of getting the backend to inspect the current state of the UI).

LorenzoFerri commented 9 months ago

Interesting, any idea why the regular SwiftPM build system doesn't work? Had a quick poke around but couldn't find an explanation

No idea, the only exaplanation that I found is this:

This project may require Swift toolchain builds which are more recent than the latest released version. For best results, find the release tag used by the GitHub Actions build workflow and download the corresponding toolchain build from swift-build releases.

I know that they are actively contributing to Swift so maybe they need some experimental feature to make the bindings work?

Ah yes, that was incredibly annoying in Gtk too lol. Which may indicate that I need to rethink that part of the API to possibly pass the previous options as well as the current options, or something along those lines? Now that I think about it, similar changes could probably be made to avoid the need for getInheritedOrientation (by passing the expected orientation to the backend instead of getting the backend to inspect the current state of the UI).

Yes I think passing the previous options would make it easier, great idea.

LorenzoFerri commented 9 months ago

Please wait a bit more to merge as I just found out how to make Grid work, so I'm gonna fix some of the problem that I mentioned earlier

LorenzoFerri commented 9 months ago

image image Ok also those are working now, if now I can figure out how to fix the Spacer and the foregroundColor I would say it would be in a usable state. About the animation during navigation I found out I'm not the only one with this problem: https://github.com/thebrowsercompany/windows-samples/issues/14

stackotter commented 9 months ago

Interesting, any idea why the regular SwiftPM build system doesn't work? Had a quick poke around but couldn't find an explanation

No idea, the only exaplanation that I found is this:

This project may require Swift toolchain builds which are more recent than the latest released version. For best results, find the release tag used by the GitHub Actions build workflow and download the corresponding toolchain build from swift-build releases.

I know that they are actively contributing to Swift so maybe they need some experimental feature to make the bindings work?

Ah yes, that was incredibly annoying in Gtk too lol. Which may indicate that I need to rethink that part of the API to possibly pass the previous options as well as the current options, or something along those lines? Now that I think about it, similar changes could probably be made to avoid the need for getInheritedOrientation (by passing the expected orientation to the backend instead of getting the backend to inspect the current state of the UI).

Yes I think passing the previous options would make it easier, great idea.

Ah ok, sounds like it's just cause they'll often use features that haven't landed in main yet, makes sense. Probably mostly works with regular toolchains except for when they start using a new feature or something along those lines, I'll have to look into that.

Ok also those are working now, if now I can figure out how to fix the Spacer and the foregroundColor I would say it would be in a usable state. About the animation during navigation I found out I'm not the only one with this problem: thebrowsercompany/windows-samples#14

Sounds good, just let me know when you're ready to merge.

LorenzoFerri commented 9 months ago

Ok so I gave another try implementig the foregroundColor but I still didn't find a solution. In Windows a Style object is a group of setters of some properties:

let style = Style(.init(name: "TextBlock", kind: .primitive))
style.setters.append(Setter(TextBlock.foregroundProperty, "Red"))

I can now apply this to a text block with:

widget.style = style

Great now the text is red! The problem is that I can apply this style only to TextBlock, so if I try to apply it directly to the styleContainer I get a runtime error.

In order to handle shared style Windows uses the concept of Resources. You can attach resources to a widget, but even if a style resource is added, those setters are not automatically executed. Because of this there is no form of cascading style like in the web.

To apply the foregroundColor I would need to know the desired style in updateTextView and apply it from there. I tried a solution where I would keep track of each widget parent and inside updateTextView I would traverse up to the first styleContainer with a TextBlock resource style set. This worked for the first render, but when I tried in the RandomNumberGeneratorExample to change color, because the execution of the widgets start from the inside out, the result was one render behind.

Do you have any suggestion?

stackotter commented 9 months ago

To apply the foregroundColor I would need to know the desired style in updateTextView and apply it from there. I tried a solution where I would keep track of each widget parent and inside updateTextView I would traverse up to the first styleContainer with a TextBlock resource style set. This worked for the first render, but when I tried in the RandomNumberGeneratorExample to change color, because the execution of the widgets start from the inside out, the result was one render behind.

Ah very interesting, I've had a few similar issues with frameworks having opposite architectures (e.g. LVGL requires making parents before children, so I ended up making a weird lazy creation system with closures and stuff, it was kinda cool, but a bit dodgy probably). It wouldn't be great, but is it possible to traverse back down from the style container to update all of its children?

LorenzoFerri commented 8 months ago

Hi, sorry but I didn't find time last week to work on this, but I just pushed a few fixes. As you suggested traversing down from the style container to all the childrens works. It's not really efficent but at least it works, I just needed to make a small fix for nested foreground properties like this:

VStack {
    Text("I should be yellow")
        .foregroundColor(.yellow)
    Text("I should be red")
}
.foregroundColor(.red)

Since by drilling down the update .red would overwrite .yellow

I also fixed the Spacer, now it expands both horizontaly and vertically depending if it's inside a VStack or HStack, however I still ignore the values expandHorizontally and expandVertically passed to updateSpacer, I just assumed it always expand.

stackotter commented 8 months ago

I'm trying to test this on my Windows laptop and I can't get swift-cwinrt to build successfully. My error is expected ';' at end of declaration list on line 45 of Sources/CWinRT/include/RestrictedErrorInfo.h. Is this something that you've run into in the past? I tried playing around with -Xcc -std=... and -Xcxx -std=... cause that helped me yesterday when I ran into a similar issue building cpp on Linux, but it didn't seem to change anything this time. I'll open an issue on the swift-cwinrt repo if it's not just an issue caused by me not knowing the ins and outs of development on Windows.

Also, for the Windows build action we can probably do something similar to what swift-winrt's GitHub action does here: https://github.com/thebrowsercompany/swift-winrt/blob/main/.github/actions/windows-build/action.yml

stackotter commented 8 months ago

Don't worry about those issues that I was running into, managed to get things sorted out eventually.

This is so cool! Can't wait to flesh out the AppKit backend too and essentially have write-once native-everywhere. The windowing seems a little broken but it's broken in many ways on existing platforms anyway so don't worry about that. Happy to merge once Windows CI is passing (and I can help out with sorting out the CI if needed) 👍

stackotter commented 7 months ago

I had to do some weird merge conflict resolution stuff so I merged through the command line and it didn't merge the PR 😭 it mustn't have recognised the commits cause they were rebased. Sorry about that, the commits are still authored by you though.