How To: Avalonia UI Overlay
Any Avalonia control placed in the same layout container as MonoGameControl is composited on top of the game surface. Avalonia handles the compositing automatically — no extra render pass, no blitting.
Reference files:
Overlay/OverlayWindow.axaml ·
Overlay/OverlayViewModel.cs ·
Advanced/MainWindow.axaml
Basic pattern: Panel as root
Use Panel as the root container. MonoGameControl fills the panel first; any
additional controls are layered on top in z-order:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mg="clr-namespace:Thunder.MonoGame.Avalonia.Controls;assembly=MonoGame.Framework"
x:Class="MyApp.MainWindow">
<Panel>
<!-- Game surface — fills the panel -->
<mg:MonoGameControl x:Name="GameHost" />
<!-- Avalonia overlay — composited on top -->
<Button HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="16"
Content="Pause" />
</Panel>
</Window>
The Overlay sample (Overlay/OverlayWindow.axaml) uses exactly this structure with a
data-bound pause button.
MVVM pattern
The ViewModel owns the game instance and exposes commands. XAML binds both Game and
the overlay controls declaratively — no code-behind logic is required:
<Panel>
<mg:MonoGameControl Game="{Binding Game}" />
<Button HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="16"
Command="{Binding TogglePauseCommand}"
Content="{Binding PauseButtonText}" />
</Panel>
// ViewModel
public partial class MainViewModel : ObservableObject
{
public MyGame Game { get; } = new MyGame();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(PauseButtonText))]
private bool _isPaused;
public string PauseButtonText => _isPaused ? "Resume" : "Pause";
[RelayCommand]
private void TogglePause() => IsPaused = !IsPaused;
partial void OnIsPausedChanged(bool value) => Game.IsPaused = value;
}
See Overlay/OverlayViewModel.cs
for the complete working example.
Advanced pattern: draggable overlay
The Advanced sample uses a Canvas over the MonoGameControl. Controls on a Canvas
are positioned via Canvas.Left / Canvas.Top, which the code-behind updates during
pointer drag events:
<Panel>
<mg:MonoGameControl x:Name="Host" Game="{Binding Game}" />
<Canvas x:Name="OverlayCanvas"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<StackPanel x:Name="OverlayPanel">
<Border x:Name="DragHandle" Cursor="SizeAll" ...>
<TextBlock Text="≡ Drag me" />
</Border>
<Button Command="{Binding TogglePauseCommand}" ... />
</StackPanel>
</Canvas>
</Panel>
See Advanced/MainWindow.axaml
for the full draggable overlay with snap-to-corner buttons, bouncer controls, and
multi-screen toggles.
Compositing note
Avalonia composites the overlay and the game surface on the same frame. There is no separate overlay pass or additional latency — the overlay controls appear at the same frame rate as the game.