@ -1,64 +1,22 @@
using System ;
using System ;
using System.Diagnostics ;
using Avalonia.Controls.Platform ;
using Avalonia.Reactive ;
using Avalonia.Input ;
using Avalonia.Input ;
using Avalonia.Input.Platform ;
using Avalonia.Input.Platform ;
using Avalonia.Platform ;
using Avalonia.Platform ;
using Avalonia.Rendering ;
using Avalonia.Rendering ;
using Avalonia.Rendering.Composition ;
using Avalonia.Rendering.Composition ;
using Avalonia.Threading ;
using System.Collections.Generic ;
using System.Collections.Generic ;
using Avalonia.Threading ;
namespace Avalonia.Headless
namespace Avalonia.Headless
{
{
public static class AvaloniaHeadlessPlatform
public static class AvaloniaHeadlessPlatform
{
{
internal static Compositor ? Compositor { get ; private set ; }
internal static Compositor ? Compositor { get ; private set ; }
private static RenderTimer ? s_renderTimer ;
private static IRenderTimer ? s_renderTimer ;
private class RenderTimer : DefaultRenderTimer
{
private readonly int _f ramesPerSecond ;
private Action ? _f orceTick ;
protected override IDisposable StartCore ( Action < TimeSpan > tick )
{
var st = Stopwatch . StartNew ( ) ;
_f orceTick = ( ) = > tick ( st . Elapsed ) ;
var timer = new DispatcherTimer ( DispatcherPriority . UiThreadRender )
{
Interval = TimeSpan . FromSeconds ( 1.0 / _f ramesPerSecond ) ,
Tag = "HeadlessRenderTimer"
} ;
timer . Tick + = ( s , e ) = > tick ( st . Elapsed ) ;
timer . Start ( ) ;
return Disposable . Create ( ( ) = >
{
_f orceTick = null ;
timer . Stop ( ) ;
} ) ;
}
public RenderTimer ( int framesPerSecond ) : base ( framesPerSecond )
private class HeadlessWindowingPlatform ( PixelFormat frameBufferFormat ) : IWindowingPlatform
{
_f ramesPerSecond = framesPerSecond ;
}
public override bool RunsInBackground = > false ;
public void ForceTick ( ) = > _f orceTick ? . Invoke ( ) ;
}
private class HeadlessWindowingPlatform : IWindowingPlatform
{
{
readonly PixelFormat _f rameBufferFormat ;
public IWindowImpl CreateWindow ( ) = > new HeadlessWindowImpl ( false , frameBufferFormat ) ;
public HeadlessWindowingPlatform ( PixelFormat frameBufferFormat )
{
_f rameBufferFormat = frameBufferFormat ;
}
public IWindowImpl CreateWindow ( ) = > new HeadlessWindowImpl ( false , _f rameBufferFormat ) ;
public ITopLevelImpl CreateEmbeddableTopLevel ( ) = > CreateEmbeddableWindow ( ) ;
public ITopLevelImpl CreateEmbeddableTopLevel ( ) = > CreateEmbeddableWindow ( ) ;
public IWindowImpl CreateEmbeddableWindow ( ) = > throw new PlatformNotSupportedException ( ) ;
public IWindowImpl CreateEmbeddableWindow ( ) = > throw new PlatformNotSupportedException ( ) ;
@ -73,12 +31,16 @@ namespace Avalonia.Headless
}
}
}
}
}
}
internal static void Initialize ( AvaloniaHeadlessPlatformOptions opts )
internal static void Initialize ( AvaloniaHeadlessPlatformOptions opts )
{
{
var clipboardImpl = new HeadlessClipboardImplStub ( ) ;
var clipboardImpl = new HeadlessClipboardImplStub ( ) ;
var clipboard = new Clipboard ( clipboardImpl ) ;
var clipboard = new Clipboard ( clipboardImpl ) ;
s_renderTimer = opts . ShouldRenderOnUIThread
? new HeadlessRenderTimer ( opts . Fps )
: new SleepLoopRenderTimer ( opts . Fps ) ;
AvaloniaLocator . CurrentMutable
AvaloniaLocator . CurrentMutable
. Bind < IClipboardImpl > ( ) . ToConstant ( clipboardImpl )
. Bind < IClipboardImpl > ( ) . ToConstant ( clipboardImpl )
. Bind < IClipboard > ( ) . ToConstant ( clipboard )
. Bind < IClipboard > ( ) . ToConstant ( clipboard )
@ -86,7 +48,7 @@ namespace Avalonia.Headless
. Bind < IPlatformSettings > ( ) . ToSingleton < DefaultPlatformSettings > ( )
. Bind < IPlatformSettings > ( ) . ToSingleton < DefaultPlatformSettings > ( )
. Bind < IPlatformIconLoader > ( ) . ToSingleton < HeadlessIconLoaderStub > ( )
. Bind < IPlatformIconLoader > ( ) . ToSingleton < HeadlessIconLoaderStub > ( )
. Bind < IKeyboardDevice > ( ) . ToConstant ( new KeyboardDevice ( ) )
. Bind < IKeyboardDevice > ( ) . ToConstant ( new KeyboardDevice ( ) )
. Bind < IRenderLoop > ( ) . ToConstant ( Rendering . RenderLoop . FromTimer ( s_renderTimer = new RenderTimer ( 6 0 ) ) )
. Bind < IRenderLoop > ( ) . ToConstant ( Rendering . RenderLoop . FromTimer ( s_renderTimer ) )
. Bind < IWindowingPlatform > ( ) . ToConstant ( new HeadlessWindowingPlatform ( opts . FrameBufferFormat ) )
. Bind < IWindowingPlatform > ( ) . ToConstant ( new HeadlessWindowingPlatform ( opts . FrameBufferFormat ) )
. Bind < PlatformHotkeyConfiguration > ( ) . ToSingleton < PlatformHotkeyConfiguration > ( )
. Bind < PlatformHotkeyConfiguration > ( ) . ToSingleton < PlatformHotkeyConfiguration > ( )
. Bind < KeyGestureFormatInfo > ( ) . ToConstant ( new KeyGestureFormatInfo ( new Dictionary < Key , string > ( ) { } ) ) ;
. Bind < KeyGestureFormatInfo > ( ) . ToConstant ( new KeyGestureFormatInfo ( new Dictionary < Key , string > ( ) { } ) ) ;
@ -100,15 +62,58 @@ namespace Avalonia.Headless
/// <param name="count">Count of frames to be ticked on the timer.</param>
/// <param name="count">Count of frames to be ticked on the timer.</param>
public static void ForceRenderTimerTick ( int count = 1 )
public static void ForceRenderTimerTick ( int count = 1 )
{
{
for ( var c = 0 ; c < count ; c + + )
if ( s_renderTimer is HeadlessRenderTimer timer )
s_renderTimer ? . ForceTick ( ) ;
{
for ( var c = 0 ; c < count ; c + + )
{
timer . ForceTick ( ) ;
}
}
else
{
if ( Compositor is not { } compositor )
{
throw new InvalidOperationException ( "Compositor is not initialized." ) ;
}
var frame = new DispatcherFrame ( ) ;
var task = compositor . RequestCommitAsync ( ) ;
task . ContinueWith ( static ( _ , s ) = > ( ( DispatcherFrame ) s ! ) . Continue = false , frame ) ;
Dispatcher . CurrentDispatcher . PushFrame ( frame ) ;
task . GetAwaiter ( ) . GetResult ( ) ;
}
}
}
}
}
/// <summary>
/// Options for configuring the Avalonia headless platform.
/// </summary>
public class AvaloniaHeadlessPlatformOptions
public class AvaloniaHeadlessPlatformOptions
{
{
/// <summary>
/// Gets or sets the number of frames per second at which the renderer should run.
/// Default 60.
/// </summary>
public int Fps { get ; set ; } = 6 0 ;
/// <summary>
/// Render directly on the UI thread instead of using a dedicated render thread.
/// This can be usable if your device doesn't have multiple cores to begin with.
/// This setting is false by default.
/// </summary>
public bool ShouldRenderOnUIThread { get ; set ; } = true ;
/// <summary>
/// Gets or sets a value indicating whether to use headless drawing mode, which allows rendering without creating an actual window.
/// </summary>
/// <remarks>
/// Disable this option if you are using Avalonia.Skia or another drawing backend.
/// </remarks>
public bool UseHeadlessDrawing { get ; set ; } = true ;
public bool UseHeadlessDrawing { get ; set ; } = true ;
/// <summary>
/// Gets or sets the pixel format to be used for the headless Window framebuffers.
/// </summary>
public PixelFormat FrameBufferFormat { get ; set ; } = PixelFormat . Rgba8888 ;
public PixelFormat FrameBufferFormat { get ; set ; } = PixelFormat . Rgba8888 ;
}
}