DemoViewer.NET.Extensions.Sdk.Ui 2.1.2

dotnet add package DemoViewer.NET.Extensions.Sdk.Ui --version 2.1.2
                    
NuGet\Install-Package DemoViewer.NET.Extensions.Sdk.Ui -Version 2.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DemoViewer.NET.Extensions.Sdk.Ui" Version="2.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DemoViewer.NET.Extensions.Sdk.Ui" Version="2.1.2" />
                    
Directory.Packages.props
<PackageReference Include="DemoViewer.NET.Extensions.Sdk.Ui" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DemoViewer.NET.Extensions.Sdk.Ui --version 2.1.2
                    
#r "nuget: DemoViewer.NET.Extensions.Sdk.Ui, 2.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DemoViewer.NET.Extensions.Sdk.Ui@2.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DemoViewer.NET.Extensions.Sdk.Ui&version=2.1.2
                    
Install as a Cake Addin
#tool nuget:?package=DemoViewer.NET.Extensions.Sdk.Ui&version=2.1.2
                    
Install as a Cake Tool

DemoViewer.NET Extensions SDK: UI kit

The controls, theme names and view-model base an extension's views use so they look like the app they run in. It builds on DemoViewer.NET.Extensions.Sdk and is versioned with it.

<ItemGroup>
  <PackageReference Include="DemoViewer.NET.Extensions.Sdk" Version="2.1.*" />
  <PackageReference Include="DemoViewer.NET.Extensions.Sdk.Ui" Version="2.1.*" />
  <PackageReference Include="Avalonia" Version="12.1.4" />
  <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
</ItemGroup>

Reference Avalonia and CommunityToolkit.Mvvm directly: the XAML compiler and the MVVM source generators run only in a project that references their package itself. The app ships this assembly, so a copy in your extension's folder is never loaded.

View models and views

Derive view models from ExtensionViewModel, or implement IExtensionViewModel from the SDK on your own ObservableObject. The host then builds the view for one by naming wherever it shows it: a pane, a panel, a status chip flyout or any ContentControl in your own views. MyExtension.ViewModels.StatsViewModel resolves to MyExtension.Views.StatsView, looked up in the view model's own assembly; the view needs a public parameterless constructor. A view model without the marker renders as its type name.

Controls

Namespace DemoViewer.NET.Extensions.Sdk.Ui.Controls, in XAML xmlns:ui="using:DemoViewer.NET.Extensions.Sdk.Ui.Controls".

Control What it is
StatusChip, StatusChipViewModel A dot and a label that opens a flyout. Set DotState (Off, Working, Good, Degraded, Error), IsPulsing, IsHollow, Label, Tooltip and FlyoutContent; the colour follows the theme.
KeyValueTable, KvpRow A two-column key/value list; delta rows show the previous value struck through.
MarkdownBlock Headings, paragraphs, lists, bold and inline code from a Markdown string.
GameIcon A CS2 icon by key (equipment/ak47, modifier/headshot), sized by IconHeight and tinted by Foreground. The artwork is the game's own and ships inside the app, not in this package, so a key draws the control's Fallback text anywhere but in the app.
GifView An animated GIF from a file path.
ParseLinkChip, OpenExternal A monospace link that opens a file in the user's editor or a URL in the browser.
MapView A CS2 map with pan and zoom, your layers and your pointer tool. See below.
SceneView, ISceneSource The 2D scene over frames of your own: markers, trails, utility, ink with the drawing tools, your layers and tools. See below.
SceneTimeline, TimelineView A scrub bar over a frame clock of your own, with band and marker rows from your tracks. See below.

MapView

MapView shows a map with no demo behind it: set MapName (de_mirage) and the app loads the map's radar art and floors; HasMap is false where it has none, so say so beside the view. You add to it by composition:

MapView map = new() { MapName = "de_nuke" };
ILayerHandle heat = map.AddLayer("heat", () => new HeatLayer(document));
PickTool pick = new(document);
IDisposable tool = map.AddTool(pick);
map.SetPrimaryTool(pick);

A layer is an ISceneLayer (DemoViewer.NET.Playback2D.Compositing) and a tool an IMapTool (DemoViewer.NET.Playback2D.Tools), both in the DemoViewer.NET.Playback2D.Scene package. The primary tool gets every press the view does not pan; a left press it refuses pans the map, and Space, Control, the middle button and the wheel always pan and zoom. EscapePressed fires for Escape with no gesture to cancel. PaneAt(point), Panes and Space give the panes and floors for a hit test of your own, and Invalidate repaints after something a layer draws changed.

The host files a layer under your extension as ext.<your extension id>.<id>, so the id is yours to choose from letters, digits, ., - and _. It is yours alone: the host files it under your extension, and the filed form cannot collide with the view's own layers or with another extension's, so even the radar's own id is yours to take locally. ILayerHandle.LayerId reports the filed id, the built layer's own Id is not read, and an id a layer on the view already holds is refused rather than replaced. The radar's and the floor label's ids are the view's own. Layers and tools survive the view leaving and re-entering the tree; the factories run again when it rebuilds.

SceneView and SceneTimeline

SceneView draws what moves: set Source to an ISceneSource, usually your view model, and raise its FrameUpdated on the UI thread whenever Frame or anything else it exposes changes. Publish a new Scene2DFrame each time rather than changing one in place, since the render thread replays the frame it was handed. MapAsset gives the floors and radar art (from MapAssets.TryLoad, or null for the grid), and Ink an AnnotationSession the drawing tools edit; select a tool with SetActiveTool and drive hold-to-pan and cancel from your keys with SetHoldPan and CancelGesture. A press goes to ISceneSource.OnPress first and to the active tool only if you refuse it. The text tool raises TextEditRequested; show an editor there and hand the result to CompleteTextEdit. AddLayer works as on MapView, and AddTool files a pointer tool the same way: IToolHandle.ToolId is the id SetActiveTool and ActiveTool speak.

AddTool takes an IPointerTool, while MapView and IPlaybackSurface.AddTool take an IMapTool. To put a map tool on a scene, wrap it: view.AddTool(new MapToolAdapter(tool)). One per view, because IMapTool carries no id of its own and the adapter answers with the first-party map tool's, so a second is refused as a duplicate; implement IPointerTool directly if you need more than one.

SceneTimeline is the model of a scrub bar, owned by the view model: register ITimelineTracks, Rebuild with the clock's ITimelineData, move the playhead with UpdatePlayhead and seek when SeekRequested fires. TimelineView shows it; bind its Timeline to yours. Both controls need the app, and outside it they draw nothing and a timeline holds nothing.

To resolve keys on a surface of your own, ask IExtensionContext.Keymap.ActionFor(scope, key, modifiers), the tool scope first while a tool is active, and name gestures in your hints with GestureText. Refresh the hints on Changed, and subscribe only while your view is on screen: the keymap lives as long as your extension.

To draw a map without a view, MapAssets.TryLoad("de_mirage") loads the map's floors, radar images and places as an IMapAsset you dispose, and MapAssets.RenderPng draws a Scene2DFrame to a PNG the way the app's export does, for a thumbnail. MapIcons hands a layer the game's weapon and grenade art. Both work from any thread; outside the app nothing loads and a picture throws.

DisplayText.Sanitize strips the invisible Unicode format characters that make Avalonia's line wrapping throw on some player names; run every player name through it before showing it. BulkObservableCollection<T> replaces or extends its contents with one Reset instead of an event per item.

Theme

The app's palette and shared styles are loaded once for the whole process, so your XAML uses them directly: {DynamicResource TextMid} and Classes="ghost". A misspelt or renamed name resolves to nothing without an error, so the names are published as constants in DemoViewer.NET.Extensions.Sdk.Ui.Theming:

  • ThemeTokens lists every palette key with its dark and light value. Every key exists in every theme, built-in or a user's.
  • StyleClasses lists every class the shared styles select on, with the controls it styles: Primary, Ghost, Chip, Mono, Card, SectionLabel, ColLabel, DataList and the rest.
<Border Background="{DynamicResource {x:Static theme:ThemeTokens.PanelBg}}" />

Both are generated from the app's own palette and style files when the SDK is built, so they match the app release the package version ships with. A control that draws itself resolves a token with ThemeColors.Get(ThemeTokens.AccentInteractive, ActualThemeVariant, fallback).

Testing your views

src/Testing/DemoViewer.NET.Extensions.Testing in the DemoViewer.NET repository holds the headless test support the app's own suites use: a shared headless session (HeadlessSession.RunOnUi) and a recording IModuleContext fake. Import its DemoViewer.NET.Extensions.Testing.props from a TUnit test project that references Avalonia.Headless and Avalonia.Skia, and name your app builder with [assembly: AvaloniaTestApplication(typeof(TestAppBuilder))].

Compatibility

This kit ships at the SDK's version and binds the same way: the assembly version is Major.0.0.0, so a view built against any 2.x kit loads on every 2.x host. The SDK package's own README carries the full list of what 2.0 changed. The breaks in this kit are SceneView.ActiveTool, SceneView.SetActiveTool and SceneView.AddTool, which work in tool ids rather than ToolKind; SceneView.AddLayer and MapView.AddLayer, which return an ILayerHandle and refuse a duplicate id; MapAssets.RenderPng, which takes layer factories in place of an overlay document; and MapAssets.TryLoad, which is now MapAssets.Acquire and returns a lease you dispose.

2.1 adds SceneView.PanesArranged and breaks nothing. The SDK package's README lists the rest of 2.1.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.2 54 11/2/2026
2.1.1 70 10/27/2026
2.0.2 55 10/27/2026