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
<PackageReference Include="DemoViewer.NET.Extensions.Sdk.Ui" Version="2.1.2" />
<PackageVersion Include="DemoViewer.NET.Extensions.Sdk.Ui" Version="2.1.2" />
<PackageReference Include="DemoViewer.NET.Extensions.Sdk.Ui" />
paket add DemoViewer.NET.Extensions.Sdk.Ui --version 2.1.2
#r "nuget: DemoViewer.NET.Extensions.Sdk.Ui, 2.1.2"
#:package DemoViewer.NET.Extensions.Sdk.Ui@2.1.2
#addin nuget:?package=DemoViewer.NET.Extensions.Sdk.Ui&version=2.1.2
#tool nuget:?package=DemoViewer.NET.Extensions.Sdk.Ui&version=2.1.2
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:
ThemeTokenslists every palette key with its dark and light value. Every key exists in every theme, built-in or a user's.StyleClasseslists every class the shared styles select on, with the controls it styles:Primary,Ghost,Chip,Mono,Card,SectionLabel,ColLabel,DataListand 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 | Versions 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. |
-
net10.0
- Avalonia (>= 12.1.4)
- Avalonia.Skia (>= 12.1.4)
- CommunityToolkit.Mvvm (>= 8.4.2)
- DemoViewer.NET.Extensions.Sdk (= 2.1.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.