CompositorTestServices.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Runtime.InteropServices;
  5. using System.Threading.Tasks;
  6. using Avalonia.Controls;
  7. using Avalonia.Controls.Embedding;
  8. using Avalonia.Controls.Platform.Surfaces;
  9. using Avalonia.Controls.Presenters;
  10. using Avalonia.Controls.Templates;
  11. using Avalonia.Data;
  12. using Avalonia.Input;
  13. using Avalonia.Input.Raw;
  14. using Avalonia.Platform;
  15. using Avalonia.Rendering;
  16. using Avalonia.Rendering.Composition;
  17. using Avalonia.Threading;
  18. using Xunit;
  19. namespace Avalonia.UnitTests;
  20. public class CompositorTestServices : IDisposable
  21. {
  22. private readonly IDisposable _app;
  23. public Compositor Compositor { get; }
  24. internal CompositingRenderer Renderer { get; }
  25. public ManualRenderTimer Timer { get; } = new();
  26. public EmbeddableControlRoot TopLevel { get; }
  27. public DebugEvents Events { get; } = new();
  28. public void Dispose()
  29. {
  30. TopLevel.StopRendering();
  31. TopLevel.Dispose();
  32. _app.Dispose();
  33. }
  34. public CompositorTestServices(Size? size = null, IPlatformRenderInterface renderInterface = null)
  35. {
  36. var services = TestServices.MockPlatformRenderInterface;
  37. if (renderInterface != null)
  38. services = services.With(renderInterface: renderInterface);
  39. _app = UnitTestApplication.Start(services);
  40. try
  41. {
  42. AvaloniaLocator.CurrentMutable.Bind<IRenderTimer>().ToConstant(Timer);
  43. Compositor = new Compositor(new RenderLoop(Timer), null,
  44. true, new DispatcherCompositorScheduler(), true, Dispatcher.UIThread);
  45. var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000));
  46. TopLevel = new EmbeddableControlRoot(impl)
  47. {
  48. Template = new FuncControlTemplate((parent, scope) =>
  49. {
  50. var presenter = new ContentPresenter
  51. {
  52. [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
  53. };
  54. scope.Register("PART_ContentPresenter", presenter);
  55. return presenter;
  56. })
  57. };
  58. TopLevel.Prepare();
  59. TopLevel.StartRendering();
  60. RunJobs();
  61. Renderer = ((CompositingRenderer)TopLevel.Renderer);
  62. Renderer.CompositionTarget.Server.DebugEvents = Events;
  63. }
  64. catch
  65. {
  66. _app.Dispose();
  67. throw;
  68. }
  69. }
  70. public void RunJobs()
  71. {
  72. Dispatcher.UIThread.RunJobs();
  73. Timer.TriggerTick();
  74. Dispatcher.UIThread.RunJobs();
  75. }
  76. public void AssertRects(params Rect[] rects)
  77. {
  78. RunJobs();
  79. var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
  80. var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
  81. Assert.Equal(toAssert, invalidated);
  82. Events.Rects.Clear();
  83. }
  84. public void AssertRenderedVisuals(int renderVisuals)
  85. {
  86. RunJobs();
  87. Assert.Equal(Events.RenderedVisuals, renderVisuals);
  88. Events.Rects.Clear();
  89. }
  90. public void AssertHitTest(double x, double y, Func<Visual, bool> filter, params object[] expected)
  91. => AssertHitTest(new Point(x, y), filter, expected);
  92. public void AssertHitTest(Point pt, Func<Visual, bool> filter, params object[] expected)
  93. {
  94. RunJobs();
  95. var tested = Renderer.HitTest(pt, TopLevel, filter);
  96. Assert.Equal(expected, tested);
  97. }
  98. public void AssertHitTestFirst(Point pt, Func<Visual, bool> filter, object expected)
  99. {
  100. RunJobs();
  101. var tested = Renderer.HitTest(pt, TopLevel, filter).First();
  102. Assert.Equal(expected, tested);
  103. }
  104. public class DebugEvents : ICompositionTargetDebugEvents
  105. {
  106. public List<Rect> Rects = new();
  107. public int RenderedVisuals { get; private set; }
  108. public void IncrementRenderedVisuals()
  109. {
  110. RenderedVisuals++;
  111. }
  112. public void RectInvalidated(Rect rc)
  113. {
  114. Rects.Add(rc);
  115. }
  116. public void Reset()
  117. {
  118. Rects.Clear();
  119. RenderedVisuals = 0;
  120. }
  121. }
  122. public class ManualRenderTimer : IRenderTimer
  123. {
  124. public event Action<TimeSpan> Tick;
  125. public bool RunsInBackground => false;
  126. public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero);
  127. }
  128. class TopLevelImpl : ITopLevelImpl
  129. {
  130. private readonly Compositor _compositor;
  131. public TopLevelImpl(Compositor compositor, Size clientSize)
  132. {
  133. ClientSize = clientSize;
  134. _compositor = compositor;
  135. }
  136. public void Dispose()
  137. {
  138. }
  139. public double DesktopScaling => 1;
  140. public IPlatformHandle Handle { get; }
  141. public Size ClientSize { get; }
  142. public Size? FrameSize { get; }
  143. public double RenderScaling => 1;
  144. public IEnumerable<object> Surfaces { get; } = new[] { new DummyFramebufferSurface() };
  145. public Action<RawInputEventArgs> Input { get; set; }
  146. public Action<Rect> Paint { get; set; }
  147. public Action<Size, WindowResizeReason> Resized { get; set; }
  148. public Action<double> ScalingChanged { get; set; }
  149. public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
  150. class DummyFramebufferSurface : IFramebufferPlatformSurface
  151. {
  152. public ILockedFramebuffer Lock()
  153. {
  154. var ptr = Marshal.AllocHGlobal(128);
  155. return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96),
  156. PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr));
  157. }
  158. public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
  159. }
  160. public Compositor Compositor => _compositor;
  161. public void Invalidate(Rect rect)
  162. {
  163. }
  164. public void SetInputRoot(IInputRoot inputRoot)
  165. {
  166. }
  167. public Point PointToClient(PixelPoint point) => default;
  168. public PixelPoint PointToScreen(Point point) => new();
  169. public void SetCursor(ICursorImpl cursor)
  170. {
  171. }
  172. public Action Closed { get; set; }
  173. public Action LostFocus { get; set; }
  174. public IMouseDevice MouseDevice { get; } = new MouseDevice();
  175. public IPopupImpl CreatePopup() => throw new NotImplementedException();
  176. public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel)
  177. {
  178. }
  179. public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
  180. public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
  181. {
  182. }
  183. public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
  184. public object TryGetFeature(Type featureType) => null;
  185. }
  186. }
  187. public class NullCompositorScheduler : ICompositorScheduler
  188. {
  189. public void CommitRequested(Compositor compositor)
  190. {
  191. }
  192. }
  193. public class DispatcherCompositorScheduler : ICompositorScheduler
  194. {
  195. public void CommitRequested(Compositor compositor)
  196. {
  197. Dispatcher.UIThread.Post(() => compositor.Commit(), DispatcherPriority.UiThreadRender);
  198. }
  199. }