Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/ci-uno.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: CI (Uno)

# Build gate for the Uno Platform port (src/Reactor.Uno + samples/Uno).
#
# Kept separate from ci.yml on purpose: the main CI drives the Windows / Windows
# App SDK build against Reactor.slnx and must never restore Uno.Sdk. This leg
# restores Uno.Sdk (from nuget.org, incl. the current 6.7.0-dev.* build the port
# pins) and builds Reactor.Uno.slnx for desktop and WebAssembly, plus Android and
# iOS in a second job. It is path-gated so it only runs when the Uno port or the
# shared source it source-shares (src/Reactor, src/Reactor.Advanced) changes.
#
# Both samples are Uno single-projects targeting all four heads, so the whole
# solution builds per-TFM for desktop / wasm / android / ios. Android and iOS run
# in a second job because they need extra workloads. See issue #2033.

on:
pull_request:
paths:
- 'src/Reactor.Uno/**'
- 'samples/Uno/**'
- 'src/Reactor/**'
# The port also source-shares Charting/, Markdown/ and the DataGrid from
# Reactor.Advanced (spec 062 Track B moved them out of core), so changes
# there can break the Uno build exactly like changes to src/Reactor.
- 'src/Reactor.Advanced/**'
# The Uno gallery source-shares ~10k lines from the Windows gallery.
- 'samples/ReactorGallery/**'
- 'Reactor.Uno.slnx'
- 'global.json'
- '.github/workflows/ci-uno.yml'
push:
branches: [main]
paths:
- 'src/Reactor.Uno/**'
- 'samples/Uno/**'
- 'src/Reactor/**'
# The port also source-shares Charting/, Markdown/ and the DataGrid from
# Reactor.Advanced (spec 062 Track B moved them out of core), so changes
# there can break the Uno build exactly like changes to src/Reactor.
- 'src/Reactor.Advanced/**'
# The Uno gallery source-shares ~10k lines from the Windows gallery.
- 'samples/ReactorGallery/**'
- 'Reactor.Uno.slnx'
- 'global.json'
- '.github/workflows/ci-uno.yml'
workflow_dispatch:

concurrency:
group: ci-uno-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build-uno:
name: Build Reactor.Uno (desktop + wasm)
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Setup .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.0.x

# Required for the net10.0-browserwasm target.
- name: Install wasm-tools workload
run: dotnet workload install wasm-tools

# Per-TFM builds (not a bare slnx build) so the library's net10.0-android
# TFM is skipped — desktop and wasm are the sample-backed, validated targets.
- name: Build desktop
run: dotnet build Reactor.Uno.slnx -f net10.0-desktop -c Debug

- name: Build WebAssembly
run: dotnet build Reactor.Uno.slnx -f net10.0-browserwasm -c Debug

build-mobile:
name: Build Reactor.Uno (android + ios)
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Setup .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
with:
dotnet-version: 10.0.x

- name: Install mobile workloads
run: dotnet workload install android ios

# Produces a signed APK for both samples, so the Android head cannot rot.
- name: Build Android
run: dotnet build Reactor.Uno.slnx -f net10.0-android -c Debug

# Managed compile only -- a device build/deploy needs a Mac, but this keeps
# the iOS head honest.
- name: Build iOS
run: dotnet build Reactor.Uno.slnx -f net10.0-ios -c Debug
28 changes: 28 additions & 0 deletions Reactor.Uno.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Solution>
<!--
Dedicated solution for the Uno Platform port. Kept SEPARATE from
Reactor.slnx on purpose: the main solution drives the Windows / Windows
App SDK build (and CI restores/builds it), so pulling the Uno.Sdk projects
into it would force Uno.Sdk + Skia/WASM/Android package restore onto every
Windows build. Each project here resolves the Uno.Sdk from its own nearby
global.json (src/Reactor.Uno, samples/Uno) and builds in isolation.

Build everything: dotnet build Reactor.Uno.slnx
The file-based single-file app (samples/Uno/file-based/Counter.cs) is not a
project; run it with `dotnet run Counter.cs` from its folder.

samples/Uno/ReactorUnoDroid (the Android head) is deliberately NOT listed
here either: it targets only net10.0-android, and this solution is built
per-TFM (`-f net10.0-desktop` / `-f net10.0-browserwasm`), which would fail
that project with NETSDK1005. Build it directly instead:
cd samples/Uno/ReactorUnoDroid && dotnet build
-->
<Folder Name="/src/">
<Project Path="src/Reactor.Uno/Reactor.Uno.csproj" />
</Folder>
<Folder Name="/samples/">
<Project Path="samples/Uno/ReactorUnoCounter/ReactorUnoCounter.csproj" />
<Project Path="samples/Uno/ReactorUnoShowcase/ReactorUnoShowcase.csproj" />
<Project Path="samples/Uno/ReactorGalleryUno/ReactorGalleryUno.csproj" />
</Folder>
</Solution>
3 changes: 3 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
"Uno.Sdk": "6.7.0-dev.117"
}
}
16 changes: 16 additions & 0 deletions samples/Uno/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project>
<!--
Isolation boundary for the Uno samples. Like src/Reactor.Uno, these are
Uno.Sdk single-project apps that must NOT inherit the Windows build
infrastructure: the repo-root samples/Directory.Build.props chains up to
the WinAppSDK-tuned root Directory.Build.props (which imports
Reactor.targets). MSBuild stops walking up at the first match, so this
file stops that inheritance — the samples build against the Uno.Sdk pinned
in this folder's global.json, with Central Package Management OFF
(Directory.Packages.props) and the isolated nuget.config alongside.
Intentionally does NOT chain to the parent.
-->
<PropertyGroup>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>
11 changes: 11 additions & 0 deletions samples/Uno/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project>
<!--
Nearest Directory.Packages.props wins, so this shadows the repo-root one and
keeps Central Package Management OFF for the Uno port. Uno.Sdk supplies the
versions of its implicit (UnoFeatures) packages from the SDK version pinned
in global.json; the few extra packages we add carry inline versions.
-->
<PropertyGroup>
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
127 changes: 127 additions & 0 deletions samples/Uno/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Reactor on Uno Platform — samples

Samples that run **Microsoft.UI.Reactor** on Uno Platform Skia targets
(desktop, WebAssembly, Android), backed by the
[`Reactor.Uno`](../../src/Reactor.Uno) port library.

| Sample | What it shows | Targets |
| --- | --- | --- |
| [`ReactorUnoCounter`](ReactorUnoCounter) | Minimal counter — the smallest Reactor-on-Uno app | desktop, wasm, android, ios |
| [`ReactorUnoShowcase`](ReactorUnoShowcase) | ToggleSwitch, Slider, ProgressBar, CheckBox, ComboBox, pickers, multi-window | desktop, wasm, android, ios |
| [`file-based/Counter.cs`](file-based/Counter.cs) | A whole WinUI-style Reactor app in **one `.cs` file** (`dotnet run Counter.cs`) | desktop |

Both app samples are **single Uno projects targeting all four heads** — the
`Component` source is identical everywhere; only the entry point differs.

## Prerequisites

- **.NET 10 SDK** (`10.0.100`+ — see [`global.json`](global.json))
- The **`wasm-tools`** workload for the WebAssembly target: `dotnet workload install wasm-tools`
- The **`android`** / **`ios`** workloads for the mobile heads: `dotnet workload install android ios`
(iOS compiles on Windows; deploying to a device still needs a Mac)

The Uno projects resolve `Uno.Sdk` from the nearby `global.json` and restore from
`nuget.org` only (see [`nuget.config`](nuget.config)); they do **not** inherit the
repo-root Windows/WinAppSDK build infrastructure.

## Run

### ReactorUnoCounter / ReactorUnoShowcase

```bash
# Desktop (X11 / Win32 / macOS / FrameBuffer — picked at runtime)
cd samples/Uno/ReactorUnoCounter
dotnet run -f net10.0-desktop

# WebAssembly (opens a localhost URL in your browser)
dotnet run -f net10.0-browserwasm
```

Swap `ReactorUnoCounter` for `ReactorUnoShowcase` to run the control showcase.

### Android / iOS

The same projects, different heads. Only the entry point is platform-specific:
desktop and **iOS** both use `ReactorApp.Run<T>()` (Uno gives the Apple heads the
same host-builder shape as desktop); **Android** is the one target with no console
entry point, so `Platforms/Android/Main.Android.cs` starts from an `Activity` and
asks Reactor for the `Application`:

```csharp
public class Application : Microsoft.UI.Xaml.NativeApplication
{
public Application(IntPtr javaReference, JniHandleOwnership transfer)
: base(() => ReactorApp.CreateApplication<CounterApp>("Reactor Counter (Uno)"),
javaReference, transfer) { }
}
```

```bash
cd samples/Uno/ReactorUnoCounter

# Deploy + launch on a connected device or emulator (check `adb devices` first)
dotnet build -f net10.0-android -t:Run

# iOS — compiles on Windows; deploying to a device needs a Mac
dotnet build -f net10.0-ios
```

> **Don't sideload the Debug APK.** A Debug Android build uses .NET's *Fast
> Deployment*: the managed assemblies are **not inside the APK** — the deploy
> tooling pushes them separately to `files/.__override__/`. Installing the APK by
> hand therefore produces an app that aborts at startup with
> `No assemblies found in '…/.__override__/arm64-v8a'` (SIGABRT), which the phone
> reports only as a generic failure. Use `-t:Run`, or build a self-contained APK
> with `-p:EmbedAssembliesIntoApk=true` (or `-c Release`) if you really need to
> sideload one.

> Reactor apps contain **no XAML**, so there is no `ApplicationDefinition` for
> Uno's iOS Hot Restart helper generator to read and it fails with `Uno0005`. The
> samples set `<UnoDisableHotRestartHelperGeneration>true</...>`, which disables
> only VS's iOS *Hot Restart* deploy helper — not Hot Reload.

### file-based/Counter.cs

A single-file app — no project, no `.csproj`. Reactor owns the Uno `Application`,
so the file's root component must **not** be named `App` (Uno.Sdk generates its own
`App`).

```bash
cd samples/Uno/file-based
dotnet run Counter.cs
```

## Build the whole Uno set

From the repo root:

```bash
dotnet build Reactor.Uno.slnx
```

The solution builds per-TFM across all four heads:

```bash
dotnet build Reactor.Uno.slnx -f net10.0-desktop
dotnet build Reactor.Uno.slnx -f net10.0-browserwasm
dotnet build Reactor.Uno.slnx -f net10.0-android
dotnet build Reactor.Uno.slnx -f net10.0-ios
```

> The file-based `Counter.cs` is not a project and is not in the solution — run it
> directly with `dotnet run Counter.cs`.

## Hot Reload

`dotnet watch` (desktop) and the Uno Dev Server (wasm / Android / iOS) both
re-render edits to a `Component.Render()` body live, preserving `UseState`.

No opt-in is required: there is **no `HotReload` UnoFeature**, the dev-server
client is referenced automatically in Debug builds, and the Dev Server itself is
started by the IDE. (`.UseStudio()` is not needed either — that enables **Hot
Design**, Uno's runtime *XAML* designer, which doesn't apply to a XAML-free
framework.)

One caveat: from the CLI, `dotnet watch -f <tfm>` only works against a
**single-targeted** head — see
[the Hot Reload section in the port README](../../src/Reactor.Uno/README.md#hot-reload).
11 changes: 11 additions & 0 deletions samples/Uno/ReactorGalleryUno/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Name disambiguation for the source-shared gallery pages.
//
// Every gallery page carries `using Microsoft.UI.Xaml.Controls;` alongside
// `using static Microsoft.UI.Reactor.Factories;`. On Uno that pulls Uno's own
// SelectionMode into scope next to Reactor's, and the shared source refers to
// the bare name — so pin it here rather than forking the pages.
//
// Reactor's Reactor.Uno library does exactly the same thing for the same reason
// (see src/Reactor.Uno/GlobalUsings.cs).

global using SelectionMode = Microsoft.UI.Reactor.Controls.SelectionMode;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:supportsRtl="true"></application>
</manifest>
23 changes: 23 additions & 0 deletions samples/Uno/ReactorGalleryUno/Platforms/Android/Main.Android.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using Android.Runtime;
using Microsoft.UI.Reactor;
using WinUIGalleryReactor;

namespace ReactorGalleryUno.Droid;

[global::Android.App.ApplicationAttribute(
Label = "@string/ApplicationName",
LargeHeap = true,
HardwareAccelerated = true,
Theme = "@style/AppTheme"
)]
public class Application : Microsoft.UI.Xaml.NativeApplication
{
public Application(IntPtr javaReference, JniHandleOwnership transfer)
: base(
() => ReactorApp.CreateApplication<GalleryShell>("Reactor Gallery (Uno)"),
javaReference,
transfer)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Android.App;
using Android.Views;

namespace ReactorGalleryUno.Droid;

[Activity(
MainLauncher = true,
ConfigurationChanges = global::Uno.UI.ActivityHelper.AllConfigChanges,
WindowSoftInputMode = SoftInput.AdjustNothing | SoftInput.StateHidden
)]
public class MainActivity : Microsoft.UI.Xaml.ApplicationActivity
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ApplicationName">Reactor Gallery</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<!-- Deliberately minimal: no Theme.SplashScreen entry, so this head does not
depend on the Uno.Resizetizer-generated splash colour/drawable. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowNoTitle">true</item>
</style>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# See http://developer.xamarin.com/guides/android/advanced_topics/garbage_collection/
MONO_GC_PARAMS=bridge-implementation=new,nursery-size=32m,soft-heap-limit=256m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
Loading
Loading