From 6b81b5a93543bb8dcf679b6fca4e2e6c6b2a4261 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 25 Feb 2023 00:51:42 -0500 Subject: [PATCH] Make headless easier to use with HeadlessWindowExtensions --- .../HeadlessWindowExtensions.cs | 50 +++++++++++++++++++ .../Avalonia.Headless/HeadlessWindowImpl.cs | 28 ++++++++--- .../Avalonia.Headless/IHeadlessWindow.cs | 8 +-- .../Avalonia.Headless.UnitTests/InputTests.cs | 9 ++-- .../RenderingTests.cs | 10 ++-- .../TestApplication.cs | 6 +-- .../ThreadingTests.cs | 2 +- 7 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs new file mode 100644 index 0000000000..ad5b7e680c --- /dev/null +++ b/src/Headless/Avalonia.Headless/HeadlessWindowExtensions.cs @@ -0,0 +1,50 @@ +using System; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Media.Imaging; +using Avalonia.Threading; + +namespace Avalonia.Headless; + +public static class HeadlessWindowExtensions +{ + public static Bitmap? CaptureRenderedFrame(this TopLevel topLevel) + { + var impl = GetImpl(topLevel); + AvaloniaHeadlessPlatform.ForceRenderTimerTick(); + return impl.GetLastRenderedFrame(); + } + + public static Bitmap? GetLastRenderedFrame(this TopLevel topLevel) => + GetImpl(topLevel).GetLastRenderedFrame(); + + public static void KeyPress(this TopLevel topLevel, Key key, RawInputModifiers modifiers) => + GetImpl(topLevel).KeyPress(key, modifiers); + + public static void KeyRelease(this TopLevel topLevel, Key key, RawInputModifiers modifiers) => + GetImpl(topLevel).KeyRelease(key, modifiers); + + public static void MouseDown(this TopLevel topLevel, Point point, MouseButton button, + RawInputModifiers modifiers = RawInputModifiers.None) => GetImpl(topLevel).MouseDown(point, button, modifiers); + + public static void MouseMove(this TopLevel topLevel, Point point, + RawInputModifiers modifiers = RawInputModifiers.None) => GetImpl(topLevel).MouseMove(point, modifiers); + + public static void MouseUp(this TopLevel topLevel, Point point, MouseButton button, + RawInputModifiers modifiers = RawInputModifiers.None) => GetImpl(topLevel).MouseUp(point, button, modifiers); + + public static void MouseWheel(this TopLevel topLevel, Point point, Vector delta, + RawInputModifiers modifiers = RawInputModifiers.None) => GetImpl(topLevel).MouseWheel(point, delta, modifiers); + + public static void DragDrop(this TopLevel topLevel, Point point, RawDragEventType type, IDataObject data, + DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None) => + GetImpl(topLevel).DragDrop(point, type, data, effects, modifiers); + + private static IHeadlessWindow GetImpl(this TopLevel topLevel) + { + Dispatcher.UIThread.RunJobs(); + return topLevel.PlatformImpl as IHeadlessWindow ?? + throw new InvalidOperationException("TopLevel must be a headless window."); + } +} diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index e1c09107c8..35f2774965 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -271,12 +271,18 @@ namespace Avalonia.Headless Input?.Invoke(new RawKeyEventArgs(_keyboard, Timestamp, InputRoot!, RawKeyEventType.KeyUp, key, modifiers)); } - void IHeadlessWindow.MouseDown(Point point, int button, RawInputModifiers modifiers) + void IHeadlessWindow.MouseDown(Point point, MouseButton button, RawInputModifiers modifiers) { Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!, - button == 0 ? RawPointerEventType.LeftButtonDown : - button == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.RightButtonDown, - point, modifiers)); + button switch + { + MouseButton.Left => RawPointerEventType.LeftButtonDown, + MouseButton.Right => RawPointerEventType.RightButtonDown, + MouseButton.Middle => RawPointerEventType.MiddleButtonDown, + MouseButton.XButton1 => RawPointerEventType.XButton1Down, + MouseButton.XButton2 => RawPointerEventType.XButton2Down, + _ => RawPointerEventType.Move, + }, point, modifiers)); } void IHeadlessWindow.MouseMove(Point point, RawInputModifiers modifiers) @@ -285,12 +291,18 @@ namespace Avalonia.Headless RawPointerEventType.Move, point, modifiers)); } - void IHeadlessWindow.MouseUp(Point point, int button, RawInputModifiers modifiers) + void IHeadlessWindow.MouseUp(Point point, MouseButton button, RawInputModifiers modifiers) { Input?.Invoke(new RawPointerEventArgs(MouseDevice, Timestamp, InputRoot!, - button == 0 ? RawPointerEventType.LeftButtonUp : - button == 1 ? RawPointerEventType.MiddleButtonUp : RawPointerEventType.RightButtonUp, - point, modifiers)); + button switch + { + MouseButton.Left => RawPointerEventType.LeftButtonUp, + MouseButton.Right => RawPointerEventType.RightButtonUp, + MouseButton.Middle => RawPointerEventType.MiddleButtonUp, + MouseButton.XButton1 => RawPointerEventType.XButton1Up, + MouseButton.XButton2 => RawPointerEventType.XButton2Up, + _ => RawPointerEventType.Move, + }, point, modifiers)); } void IHeadlessWindow.MouseWheel(Point point, Vector delta, RawInputModifiers modifiers) diff --git a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs index 3391a103d1..f3da2335bc 100644 --- a/src/Headless/Avalonia.Headless/IHeadlessWindow.cs +++ b/src/Headless/Avalonia.Headless/IHeadlessWindow.cs @@ -6,15 +6,15 @@ using Avalonia.Utilities; namespace Avalonia.Headless { - public interface IHeadlessWindow + internal interface IHeadlessWindow { Bitmap? GetLastRenderedFrame(); void KeyPress(Key key, RawInputModifiers modifiers); void KeyRelease(Key key, RawInputModifiers modifiers); - void MouseDown(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None); + void MouseDown(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None); void MouseMove(Point point, RawInputModifiers modifiers = RawInputModifiers.None); - void MouseUp(Point point, int button, RawInputModifiers modifiers = RawInputModifiers.None); + void MouseUp(Point point, MouseButton button, RawInputModifiers modifiers = RawInputModifiers.None); void MouseWheel(Point point, Vector delta, RawInputModifiers modifiers = RawInputModifiers.None); - void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers); + void DragDrop(Point point, RawDragEventType type, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers = RawInputModifiers.None); } } diff --git a/tests/Avalonia.Headless.UnitTests/InputTests.cs b/tests/Avalonia.Headless.UnitTests/InputTests.cs index c4d9f1a517..3c0ecbfdb7 100644 --- a/tests/Avalonia.Headless.UnitTests/InputTests.cs +++ b/tests/Avalonia.Headless.UnitTests/InputTests.cs @@ -1,9 +1,10 @@ using Avalonia.Controls; +using Avalonia.Input; using Avalonia.Layout; using Avalonia.Threading; using Xunit; -namespace Avalonia.Headless.XUnit.Tests; +namespace Avalonia.Headless.UnitTests; public class InputTests { @@ -27,10 +28,8 @@ public class InputTests }; window.Show(); - Dispatcher.UIThread.RunJobs(); - - ((IHeadlessWindow)window.PlatformImpl!).MouseDown(new Point(50, 50), 0); - ((IHeadlessWindow)window.PlatformImpl!).MouseUp(new Point(50, 50), 0); + window.MouseDown(new Point(50, 50), MouseButton.Left); + window.MouseUp(new Point(50, 50), MouseButton.Left); Assert.True(buttonClicked); } diff --git a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs index 67b99541d6..33f15bce1a 100644 --- a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs +++ b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs @@ -1,13 +1,10 @@ -using System.IO; -using System.Linq; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; -using Avalonia.Media.Imaging; using Avalonia.Threading; using Xunit; -namespace Avalonia.Headless.XUnit.Tests; +namespace Avalonia.Headless.UnitTests; public class RenderingTests { @@ -32,8 +29,7 @@ public class RenderingTests Dispatcher.UIThread.RunJobs(); AvaloniaHeadlessPlatform.ForceRenderTimerTick(); - - var frame = ((IHeadlessWindow)window.PlatformImpl!).GetLastRenderedFrame(); + var frame = window.CaptureRenderedFrame(); Assert.NotNull(frame); } } diff --git a/tests/Avalonia.Headless.UnitTests/TestApplication.cs b/tests/Avalonia.Headless.UnitTests/TestApplication.cs index 0b2927fa29..7bfa0144f3 100644 --- a/tests/Avalonia.Headless.UnitTests/TestApplication.cs +++ b/tests/Avalonia.Headless.UnitTests/TestApplication.cs @@ -1,12 +1,12 @@ -using Avalonia.Headless.XUnit; -using Avalonia.Headless.XUnit.Tests; +using Avalonia.Headless.UnitTests; +using Avalonia.Headless.XUnit; using Avalonia.Themes.Simple; using Xunit; [assembly: AvaloniaTestFramework(typeof(TestApplication))] [assembly: CollectionBehavior(DisableTestParallelization = true)] -namespace Avalonia.Headless.XUnit.Tests; +namespace Avalonia.Headless.UnitTests; public class TestApplication : Application { diff --git a/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs b/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs index efcd2d9081..419ee5519e 100644 --- a/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs +++ b/tests/Avalonia.Headless.UnitTests/ThreadingTests.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Avalonia.Threading; using Xunit; -namespace Avalonia.Headless.XUnit.Tests; +namespace Avalonia.Headless.UnitTests; public class ThreadingTests {