Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:

- uses: actions/setup-dotnet@v6
with:
# global.json pins the .NET 11 SDK; the AtomUI build tasks and the test projects run on .NET 10.
# global.json pins the .NET 11 SDK; the test projects run on .NET 10.
global-json-file: global.json
dotnet-version: 10.0.x

Expand Down
14 changes: 9 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# TKCode.Arc agent guide

TKCode.Arc is an Arc-style (uiarc.dev) component library for Avalonia built on AtomUI 6.2.8.
TKCode.Arc is an Arc-style (uiarc.dev) component library for Avalonia 12.1.4. Every package depends only on Avalonia and the in-house MIT `TKCode.Arc.Foundation` (token runtime, theme manager, package registration, localization; design/architecture/foundation.md).
Design source of truth: `design/` (start at `design/overview.md`).

## Layout

- `src/TKCode.Arc.Foundation` (+ `TKCode.Arc.Foundation.Generators`, shipped as its analyzer): token and theme runtime (`ArcThemeManager`, `ArcTheme`, presets), `AppBuilder.UseArc(...)` / `ArcBuilder`, language runtime (`TKCode.Arc.Localization`), generators for tokens, theme files and `Localization/*.xlf`.
- `src/TKCode.Arc.Core`: ArcDesign tokens, palette, motion engine (`TKCode.Arc.Motion`), shared primitives (`TKCode.Arc.Primitives`), Lucide glyphs.
- `src/TKCode.Arc`: controls, organized as `<Category>/<Component>/` (category = Arc sidebar group).
- `tests/`: `TKCode.Arc.Core.Tests` (pure), `TKCode.Arc.Tests` (headless Avalonia).
- `tests/`: `TKCode.Arc.Core.Tests` (pure), `TKCode.Arc.Tests` (headless Avalonia), `TKCode.Arc.Foundation.Tests` (runtime + generator snapshots), `TKCode.Arc.BaseTheme.Tests` (base theme in an app with no other theme), `TKCode.Arc.Mobile.Tests`; `tests/Baselines/` holds the token and preset snapshots.
- `gallery/`: gallery shell (home page, routes, top bar) and pages; `TKCode.Arc.Gallery.Shots` holds the docs screenshots and `TKCode.Arc.Gallery.Content` the pages' Markdown as resources (the Browser host serves both as files and fetches a page's text when it is shown, through `GalleryTexts`; section 6.11). A component's page is its docs page `Docs/<Category>/<Component>/<lang>.md` with live examples embedded by `<Demo name="Name" />`, each written as `Demos/<Category>/<Component>/<Name>.axaml` (plus `.axaml.cs` only when it needs logic); the demo's Code tab shows those files (sections 4 and 6.7). `TKCode.Arc.Gallery.Generators` turns `<!-- api: Type -->` into API tables from the TKCode.Arc XML docs, collects the demos and checks translations (design/engineering/gallery-app.md section 6). Shell text comes from `Localization/*.xlf` via `GalleryText` (hosts call `UseArcGallery()`; section 5.1). The sidebar's Docs section (Introduction, Installation, Theming, Motion, AI and MCP, Changelog) is `Guides/<Page>/<lang>.md`, read at run time by `GalleryGuides` (section 6.9). `TKCode.Arc.Gallery.Llms` writes the web site's files for AI assistants from the same pages (`llms.txt`, `llms-full.txt`, every page as Markdown, the agent skill; hand-written text in its `Content/`; section 6.10).
- `tools/lucide/generate_lucide.py`: regenerates Lucide glyph geometry.
- `tools/docshots`: checks a docs page without a build (`check`), captures its screenshots (`shoot`: each `![caption](images/Name.png)` line plus the `xml` block under it, light and dark, as lossless WebP files `images/Name.webp`; install `cwebp` for small files) and renders a gallery page (`page`); see design/engineering/gallery-app.md section 6.8.
Expand All @@ -18,11 +19,13 @@ Read `design/engineering/implementation-guide.md` before writing a component: fi

## Non-negotiable rules

- Controls derive from Avalonia base types; only public AtomUI APIs are used (design/architecture/atomui-integration.md).
- Controls derive from Avalonia base types; only public Avalonia APIs are used. No AtomUI code or packages, and none of its source is consulted (clean room; design/decisions/0031-replace-atomui.md).
- Class names have no prefix; flat namespace `TKCode.Arc`; xmlns `https://github.com/AIDotNet/TKCode.Arc`.
- Every public control has a `[ControlDesignToken]` `<Control>Token` in the same namespace with at least one property (an empty token crashes at runtime) and no name from `design/engineering/reserved-token-names.md`; Semantic Part paths start with `arc.`.
- Every public control has a `[ControlDesignToken]` `<Control>Token` in the same namespace with at least one property (ARCF003). Its identity (class name minus `Token`, or `[ControlDesignToken(Identity = "...")]`) is unique across all Arc packages, Mobile included (Mobile uses `MobileButton`, `MobileSwitch`, ...; design/engineering/reserved-token-names.md). Token property types are limited to those in foundation.md section 3.4 (ARCF001).
- Colors, radii, shadows and fonts come from `{arc:ArcDesignTokenResource ...}`; control tokens hold geometry and accent-independent derived colors only.
- Themes live in `<Component>/Themes/<Component>Theme.axaml`; never create `*Themes.axaml` aggregates.
- Themes live in `<Component>/Themes/<Component>Theme.axaml` with `x:Class="<RootNamespace>.<FileName>" x:ClassModifier="internal"` (flat, e.g. `TKCode.Arc.ButtonTheme`, `TKCode.Arc.Mobile.PageTheme`; the generator supplies the class and loads the file on first use; ARCF010–014). A `ControlTheme` key is defined by one file only (ARCF013). Never create `*Themes.axaml` aggregates.
- Avalonia built-ins inside templates (`ScrollViewer`, `ScrollBar`, `ItemsControl`, `PathIcon`, `TextSelectionHandle`, popup hosts) get their theme from Core's base theme (`src/TKCode.Arc.Core/Theme/Base/`), which steps aside when the app uses Fluent or Simple. Other built-ins (e.g. `TextBox`) have no template: use the Arc control. Scroll bars auto-hide; set `ScrollViewer.AllowAutoHide="False"` to keep them.
- Theme changes go through `ArcTheme.Apply(t => ...)` / `ArcThemeManager.Apply`; never block on `ArcThemeManager.ApplyAsync` from the UI thread (its publish step needs the UI thread, so `.Wait()` / `.Result` deadlocks; await it or call `Apply`).
- Transitions are declared in ControlTheme setters under `^:motion-full` / `^:motion-reduced` (set `arc:ArcMotion.Tracks="True"`); code-driven motion uses `MotionValue<T>` and must say why ControlTheme cannot express it.
- C# control-flow bodies always use braces. No reflection; AOT and trimming safe.
- Every Arc component needs gallery docs pages `gallery/TKCode.Arc.Gallery/Docs/<Category>/<Component>/{en-US,zh-CN,zh-TW}.md` (checked by `GalleryDocsTests`; design/engineering/gallery-app.md section 6.6), and its public properties and events need XML `<summary>` text (they feed the API tables).
Expand All @@ -34,5 +37,6 @@ Read `design/engineering/implementation-guide.md` before writing a component: fi
```bash
dotnet build TKCode.Arc.slnx
dotnet test tests/TKCode.Arc.Core.Tests
dotnet test tests/TKCode.Arc.Foundation.Tests
dotnet test tests/TKCode.Arc.Tests
```
140 changes: 138 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,147 @@
All notable changes to TKCode.Arc are listed here. Versions follow [Semantic Versioning](https://semver.org/);
while the version carries a `-preview` suffix the API may still change between releases.

NuGet packages published with each release: `TKCode.Arc`, `TKCode.Arc.Core`, `TKCode.Arc.Fonts`,
`TKCode.Arc.Icons.Lucide`, `TKCode.Arc.Blocks`, `TKCode.Arc.AdvancedCharts`, `TKCode.Arc.Special`,
NuGet packages published with each release: `TKCode.Arc`, `TKCode.Arc.Core`, `TKCode.Arc.Foundation` (from 0.4),
`TKCode.Arc.Fonts`, `TKCode.Arc.Icons.Lucide`, `TKCode.Arc.Blocks`, `TKCode.Arc.AdvancedCharts`, `TKCode.Arc.Special`,
`TKCode.Arc.Workflow`, `TKCode.Arc.Themes.Pixel`, `TKCode.Arc.Themes.Corners`, `TKCode.Arc.Mobile`, `TKCode.Arc.Terminal`, `TKCode.Arc.Audio`,
`TKCode.Arc.Audio.Native` and `TKCode.Arc.Audio.Browser`.

## 0.4.0-preview.1 - Unreleased

Arc no longer depends on AtomUI. Every package now depends only on Avalonia and on the new MIT package
`TKCode.Arc.Foundation`, so the whole dependency chain is under permissive licenses and closed-source applications can
publish Arc as a single file or with NativeAOT (issue #25). The application entry point and the theme API change; the
controls, their properties and the XAML (`{arc:ArcDesignTokenResource ...}`, `{arc:XxxTokenResource ...}`) do not. See
*Migrating from 0.3 to 0.4* below.

### Added

- **Foundation** (new package `TKCode.Arc.Foundation`): the theme manager (`ArcThemeManager`, `ArcTheme`,
`ArcThemeState`, `ArcThemeBuilder`), theme presets (`ArcThemePreset`), local themes (`ArcThemeScope`), package
registration (`AppBuilder.UseArc(...)`, `ArcBuilder`), languages (`ArcLanguage`, `ArcLanguageManager`) and a source
generator for tokens, theme files and language catalogs. No reflection; trimming and NativeAOT safe. A control's
theme loads the first time a control of that type is used.
- **ArcImage** and `ImageSource` (Core): background image loading (http, `avares://`, files, `data:`, bitmaps) with a
shared cache, on desktop and in the browser.
- **ArcOverlayHost** (Core): a panel with its own overlay layer, so dialogs, drawers and sheets opened inside it stay
within it.
- **Base theme** (Core): Arc's own themes for the Avalonia controls its templates use (`ScrollViewer`, `ScrollBar`,
`ItemsControl`, `PathIcon`, `TextSelectionHandle` and the popup hosts). It steps aside when the application uses
`FluentTheme` or `SimpleTheme`.
- **Scroll bars** look like `ScrollArea`: thin, shown while the pointer is over the view or while it scrolls, hidden
at rest. `ScrollViewer.AllowAutoHide="False"` keeps them visible.
- **Corners theme**: `UseArcCorners()` and `t.Corners(style)`; ready-made `ArcCornerPresets.Square` and
`ArcCornerPresets.Half`.
- **Menus**: long menus scroll inside `MaxDropDownHeight`.
- **Gallery**: the desktop window draws its own title bar (logo, title, pin on top; window buttons on Windows and
Linux).

### Changed

- **Breaking: entry point.** `AppBuilder.UseAtomUI(b => b.UseDesktopControls().UseArc())` is now
`AppBuilder.UseArc(arc => arc.UseArcControls())`. The `Application` class no longer needs to be `partial`.
- **Breaking: theme API.** `ThemeConfigBuilder`, `ThemeAlgorithm`, `IThemeManager.ApplyThemeAsync` and `WithArc(...)`
are replaced by `ArcTheme.Apply(t => ...)`, `ArcThemeManager.Apply` / `ApplyAsync` and `WithTheme(...)` in `UseArc`.
- **Breaking: Pixel and Corners** are theme presets: `t.Pixel()` and `t.Corners(style)`. `ArcPixelTheme`,
`ArcPixelScope`, `WithArcCorners` and `ArcCornerScope` are removed; a preset skips the packages the application has
not registered.
- **Breaking: languages** move to `TKCode.Arc.Localization` (`ArcLanguage`, `ArcLanguageManager`); `UseLanguages` is
on `ArcBuilder`.
- **Breaking: images and popups.** `AsyncImage` is replaced by `ArcImage` (`IsLoaded` is now `IsImageLoaded`).
`ArcPopup` derives from Avalonia's `Popup`; `OpenMotion` and `CloseMotion` are `ArcPopupMotion`,
`AdjustPlacementAnchor` lost its `ref flipX, ref flipY` parameters, and `IsOpen` stays true until the exit motion
finishes. `ArcPopupFrame` replaces `BaseMotionActor`, and `ArcOverlayHost` replaces `ScopeAwareOverlayLayerPanel`.
- **Breaking: mobile token identities** are `MobileButton`, `MobileCheckbox`, `MobileDialog`, `MobileSwitch` and
`MobileToast`, so they no longer share names with the desktop controls.
- Arc no longer shares its theme with AtomUI controls; applications that mix the two stay on 0.3.x.

### Fixed

- **ToastStack**: a toast dismissed while the stack resumes its timers no longer breaks the resume.
- **DataGrid**: the search box of the filter list is drawn again (it had no template).
- **PasswordField**: long strength labels no longer overlap the strength bar (#23).
- **Gallery**: the text box of the env variables event log is drawn again (it had no template).

### Migrating from 0.3 to 0.4

1. Remove every `AtomUI.*` package reference and `using AtomUI...;` line; Arc brings in what it needs.
2. Replace `UseAtomUI(...)` with `UseArc(...)` and rename `UseArc()` (the controls) to `UseArcControls()`. The other
package calls keep their names and move into the `UseArc` callback.
3. Replace theme requests with `ArcTheme.Apply`. A change names only what changes; the rest of the state stays.
4. The `App` class may drop its `partial` keyword; nothing generates code into it any more.
5. With the .NET 11 SDK and `PublishAot`, set `<UseOptimizedPublish>false</UseOptimizedPublish>` in the application
project.

```csharp
// 0.3
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseAtomUI(builder =>
{
builder.UseDesktopControls();
builder.UseArc();
builder.UseArcBlocks();
builder.UseLanguages(LanguageTags.EnUS, [LanguageTags.EnUS, LanguageTags.ZhCN]);
});

// 0.4
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseArc(arc => arc
.UseArcControls()
.UseArcBlocks()
.UseLanguages(LanguageTags.EnUS, [LanguageTags.EnUS, LanguageTags.ZhCN])
.WithTheme(t => t.Appearance(ArcAppearance.System)));
```

```csharp
// 0.3
var builder = new ThemeConfigBuilder()
.WithAlgorithms(ThemeAlgorithm.Default, ThemeAlgorithm.Dark)
.WithArc(arc => arc.Accent(ArcAccentPreset.Blue).MotionMode(ArcMotionLevel.Reduced));
await manager.ApplyThemeAsync(new ThemeRequest(IThemeManager.DEFAULT_THEME_ID, builder.Build(),
ThemeTransitionReason.UserRequest));

// 0.4
ArcTheme.Apply(t => t
.Appearance(ArcAppearance.Dark)
.Accent(ArcAccentPreset.Blue)
.MotionLevel(ArcMotionLevel.Reduced));
```

| 0.3 (AtomUI) | 0.4 |
|---|---|
| `AppBuilder.UseAtomUI(b => ...)`, `UseDesktopControls()` | `AppBuilder.UseArc(arc => ...)` |
| `UseArc()` (the controls) | `UseArcControls()` |
| `UseArcCore`, `UseArcBlocks`, `UseArcAdvancedCharts`, `UseArcSpecial`, `UseArcWorkflow`, `UseArcMobile`, `UseArcPixelTheme`, `UseArcGallery` on `IAtomUIBuilder` | The same names on `ArcBuilder`; new `UseArcCorners()` |
| `builder.UseLanguages(...)` | `arc.UseLanguages(...)`, same arguments |
| `partial class App`, `ApplicationStartup.Attach` | Not needed |
| `ThemeConfigBuilder`, `WithAlgorithms(ThemeAlgorithm.Dark)` | `t.Appearance(ArcAppearance.Dark)` (or `Light`, `System`) |
| `IThemeManager.ApplyThemeAsync(new ThemeRequest(...))` | `ArcTheme.Apply(t => ...)`, `ArcThemeManager.Current.Apply` / `ApplyAsync` (await it; never block on it on the UI thread) |
| `WithArc(a => a.Accent(preset or color))` | `t.Accent(preset or color)` |
| `WithArc(a => a.Token(name, value))` | `t.Token(name, value)` |
| `WithArc(a => a.ControlToken(catalog, control, name, value))` | `t.Token(identity, name, value)` |
| `WithArc(a => a.MotionMode(level))` | `t.MotionLevel(level)` |
| `WithArcCorners(style, scope)`, `arc.Corners(style, scope)`, `ArcCornerScope` | `t.Corners(style)`, `ArcCornerPresets` |
| `ArcPixelTheme.CreateRequest(...)`, `WithArcPixel(...)`, `ArcPixelScope` | `t.Pixel()`, `ArcPixelPresets`; back to Arc with `t.RemovePreset(ArcPixelPresets.Slot)` |
| `using AtomUI.Localization;` | `using TKCode.Arc.Localization;` |
| `Application.Current.GetLanguageManager()`, `ILanguageManager` | `ArcLanguageManager.Current` |
| `GetLocalizer().Get(kind)`, `Format(kind, args)` | `XxxLangResources.Get(kind)`, `XxxLangResources.Format(kind, args)` |
| `LanguageChanged`, `ChangeLanguage(tag)` | `Changed`, `Set(tag)` (or `ArcLanguage.Changed`, `ArcLanguage.Set`) |
| `FormattingCulture`, `CurrentLanguage` | `ArcLanguage.Culture`, `ArcLanguage.Current` |
| Mobile token overrides by catalog | Identities `MobileButton`, `MobileCheckbox`, `MobileDialog`, `MobileSwitch`, `MobileToast` |
| `AtomUI.Controls.ImageSource`, `AsyncImage`, `IsLoaded` | `TKCode.Arc.ImageSource`, `ArcImage`, `IsImageLoaded` |
| `ArcPopup` on AtomUI's `Popup`, `BaseMotionActor` | `ArcPopup` on Avalonia's `Popup`, `ArcPopupFrame`; `OpenMotion` / `CloseMotion` are `ArcPopupMotion` |
| `AdjustPlacementAnchor(placement, ref flipX, ref flipY)` | `AdjustPlacementAnchor(placement)` |
| `ScopeAwareOverlayLayerPanel` | `ArcOverlayHost` |
| AtomUI's themes for `ScrollViewer`, `ScrollBar`, `ItemsControl` and the popup hosts | Arc's base theme; scroll bars hide at rest (`ScrollViewer.AllowAutoHide="False"` keeps them) |

- **Windows and overlays.** Without AtomUI's window theme, a plain Avalonia `Window` has no overlay layer unless its
template has a `VisualLayerManager` with `EnableOverlayLayer="True"`. `ThemeSwitcher` needs one for its transition;
without it, it switches directly.
- **AtomUI controls.** An application that also uses AtomUI controls no longer gets one shared theme for both. Stay on
0.3.x until those screens are moved to Arc.

## 0.3.1-preview.6 - 2026-10-28

### Added
Expand Down
8 changes: 3 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@
<StreamMarkVersion>0.1.1</StreamMarkVersion>
</PropertyGroup>
<ItemGroup>
<!-- AtomUI and Avalonia are pinned exactly: Arc is rebuilt for every AtomUI release (ADR 0001). -->
<PackageVersion Include="AtomUI.Core" Version="[$(AtomUIVersion)]" />
<PackageVersion Include="AtomUI.Controls" Version="[$(AtomUIVersion)]" />
<PackageVersion Include="AtomUI.Desktop.Controls" Version="[$(AtomUIVersion)]" />
<PackageVersion Include="AtomUI.Icons.Shared" Version="[$(AtomUIVersion)]" />
<PackageVersion Include="Avalonia" Version="[$(AvaloniaVersion)]" />
<PackageVersion Include="Avalonia.Browser" Version="[$(AvaloniaVersion)]" />
<PackageVersion Include="Avalonia.Desktop" Version="[$(AvaloniaVersion)]" />
Expand All @@ -31,5 +26,8 @@
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<!-- Base theme tests: ArcBaseTheme must yield to these themes. -->
<PackageVersion Include="Avalonia.Themes.Fluent" Version="[$(AvaloniaVersion)]" />
<PackageVersion Include="Avalonia.Themes.Simple" Version="[$(AvaloniaVersion)]" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 brotli gzip \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
# The SDK is the one global.json pins (11.0.100-rc.2). The .NET 10 runtime is needed too: AtomUI's build worker is net10.
# The SDK is the one global.json pins (11.0.100-rc.2). The .NET 10 runtime is needed too: gallery/TKCode.Arc.Gallery.Llms (run below) targets net10.0.
COPY global.json .
RUN curl -sSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh \
&& bash /tmp/dotnet-install.sh --jsonfile global.json --install-dir /opt/dotnet \
Expand Down
Loading
Loading