committed by
GitHub
32 changed files with 1141 additions and 200 deletions
@ -0,0 +1,6 @@ |
|||
namespace Avalonia.Rendering.Composition; |
|||
|
|||
internal interface ICompositionTargetDebugEvents |
|||
{ |
|||
void RectInvalidated(Rect rc); |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform.Storage; |
|||
|
|||
namespace Avalonia.X11.NativeDialogs; |
|||
|
|||
internal class CompositeStorageProvider : IStorageProvider |
|||
{ |
|||
private readonly IEnumerable<Func<Task<IStorageProvider?>>> _factories; |
|||
public CompositeStorageProvider(IEnumerable<Func<Task<IStorageProvider?>>> factories) |
|||
{ |
|||
_factories = factories; |
|||
} |
|||
|
|||
public bool CanOpen => true; |
|||
public bool CanSave => true; |
|||
public bool CanPickFolder => true; |
|||
|
|||
private async Task<IStorageProvider> EnsureStorageProvider() |
|||
{ |
|||
foreach (var factory in _factories) |
|||
{ |
|||
var provider = await factory(); |
|||
if (provider is not null) |
|||
{ |
|||
return provider; |
|||
} |
|||
} |
|||
|
|||
throw new InvalidOperationException("Neither DBus nor GTK are available on the system"); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options) |
|||
{ |
|||
var provider = await EnsureStorageProvider().ConfigureAwait(false); |
|||
return await provider.OpenFilePickerAsync(options).ConfigureAwait(false); |
|||
} |
|||
|
|||
public async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options) |
|||
{ |
|||
var provider = await EnsureStorageProvider().ConfigureAwait(false); |
|||
return await provider.SaveFilePickerAsync(options).ConfigureAwait(false); |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options) |
|||
{ |
|||
var provider = await EnsureStorageProvider().ConfigureAwait(false); |
|||
return await provider.OpenFolderPickerAsync(options).ConfigureAwait(false); |
|||
} |
|||
|
|||
public async Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark) |
|||
{ |
|||
var provider = await EnsureStorageProvider().ConfigureAwait(false); |
|||
return await provider.OpenFileBookmarkAsync(bookmark).ConfigureAwait(false); |
|||
} |
|||
|
|||
public async Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark) |
|||
{ |
|||
var provider = await EnsureStorageProvider().ConfigureAwait(false); |
|||
return await provider.OpenFolderBookmarkAsync(bookmark).ConfigureAwait(false); |
|||
} |
|||
} |
|||
@ -0,0 +1,417 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Shapes; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Base.UnitTests.Rendering; |
|||
|
|||
public class CompositorHitTestingTests : CompositorTestsBase |
|||
{ |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Find_Controls_At_Point() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
var border = new Border |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
}; |
|||
|
|||
s.TopLevel.Content = border; |
|||
|
|||
s.AssertHitTest(new Point(100, 100), null, border); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Not_Find_Empty_Controls_At_Point() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
var border = new Border |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
}; |
|||
|
|||
s.TopLevel.Content = border; |
|||
|
|||
s.AssertHitTest(new Point(100, 100), null); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Not_Find_Invisible_Controls_At_Point() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
Border visible, border; |
|||
s.TopLevel.Content = border = new Border |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center, |
|||
IsVisible = false, |
|||
Child = visible = new Border |
|||
{ |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Stretch, |
|||
VerticalAlignment = VerticalAlignment.Stretch, |
|||
} |
|||
}; |
|||
|
|||
s.AssertHitTest(new Point(100, 100), null); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Not_Find_Control_Outside_Point() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
var border = new Border |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
|
|||
}; |
|||
s.TopLevel.Content = border; |
|||
|
|||
s.AssertHitTest(new Point(10, 10), null); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Return_Top_Controls_First() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
Panel container = new Panel |
|||
{ |
|||
Width = 200, |
|||
Height = 200, |
|||
Children = |
|||
{ |
|||
new Border |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
}, |
|||
new Border |
|||
{ |
|||
Width = 50, |
|||
Height = 50, |
|||
Background = Brushes.Blue, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
} |
|||
} |
|||
}; |
|||
s.TopLevel.Content = container; |
|||
|
|||
s.AssertHitTest(new Point(100, 100), null, container.Children[1], container.Children[0]); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Return_Top_Controls_First_With_ZIndex() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
Panel container = new Panel |
|||
{ |
|||
Width = 200, |
|||
Height = 200, |
|||
Children = |
|||
{ |
|||
new Border |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
ZIndex = 1, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
}, |
|||
new Border |
|||
{ |
|||
Width = 50, |
|||
Height = 50, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
}, |
|||
new Border |
|||
{ |
|||
Width = 75, |
|||
Height = 75, |
|||
ZIndex = 2, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
} |
|||
} |
|||
|
|||
}; |
|||
s.TopLevel.Content = container; |
|||
|
|||
s.AssertHitTest(new Point(100, 100), null, new[] { container.Children[2], container.Children[0], container.Children[1] }); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
Border target; |
|||
Panel container = new Panel |
|||
{ |
|||
Width = 200, |
|||
Height = 200, |
|||
Background = Brushes.Red, |
|||
ClipToBounds = false, |
|||
Children = |
|||
{ |
|||
new Border |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
ZIndex = 1, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Left, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
Child = target = new Border |
|||
{ |
|||
Width = 50, |
|||
Height = 50, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Left, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
RenderTransform = new TranslateTransform(110, 110), |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
s.TopLevel.Content = container; |
|||
|
|||
s.AssertHitTest(new Point(120, 120), null, target, container); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
Border target; |
|||
Panel container = new Panel |
|||
{ |
|||
Width = 100, |
|||
Height = 200, |
|||
Background = Brushes.Red, |
|||
Children = |
|||
{ |
|||
new Panel() |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
Margin = new Thickness(0, 100, 0, 0), |
|||
ClipToBounds = true, |
|||
Children = |
|||
{ |
|||
(target = new Border() |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
Margin = new Thickness(0, -100, 0, 0) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
|
|||
}; |
|||
s.TopLevel.Content = container; |
|||
|
|||
s.AssertHitTest(new Point(50, 50), null, container); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(100, 200))) |
|||
{ |
|||
Border target; |
|||
Border item1; |
|||
Border item2; |
|||
ScrollContentPresenter scroll; |
|||
Panel container = new Panel |
|||
{ |
|||
Width = 100, |
|||
Height = 200, |
|||
Background = Brushes.Red, |
|||
Children = |
|||
{ |
|||
(target = new Border() |
|||
{ |
|||
Name = "b1", |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
}), |
|||
new Border() |
|||
{ |
|||
Name = "b2", |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
Margin = new Thickness(0, 100, 0, 0), |
|||
Child = scroll = new ScrollContentPresenter() |
|||
{ |
|||
CanHorizontallyScroll = true, |
|||
CanVerticallyScroll = true, |
|||
Content = new StackPanel() |
|||
{ |
|||
Children = |
|||
{ |
|||
(item1 = new Border() |
|||
{ |
|||
Name = "b3", |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
}), |
|||
(item2 = new Border() |
|||
{ |
|||
Name = "b4", |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
}), |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
s.TopLevel.Content = container; |
|||
|
|||
scroll.UpdateChild(); |
|||
|
|||
s.AssertHitTestFirst(new Point(50, 150), null, item1); |
|||
|
|||
s.AssertHitTestFirst(new Point(50,50), null, target); |
|||
|
|||
scroll.Offset = new Vector(0, 100); |
|||
|
|||
s.AssertHitTestFirst(new Point(50, 150), null, item2); |
|||
|
|||
s.AssertHitTestFirst(new Point(50,50), null, target); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Not_Find_Path_When_Outside_Fill() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(200, 200))) |
|||
{ |
|||
Path path = new Path |
|||
{ |
|||
Width = 200, |
|||
Height = 200, |
|||
Fill = Brushes.Red, |
|||
Data = StreamGeometry.Parse("M100,0 L0,100 100,100") |
|||
}; |
|||
s.TopLevel.Content = path; |
|||
|
|||
s.AssertHitTest(new Point(100, 100), null, path); |
|||
s.AssertHitTest(new Point(10, 10), null); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Respect_Geometry_Clip() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(400, 400))) |
|||
{ |
|||
Canvas canvas; |
|||
Border border = new Border |
|||
{ |
|||
Background = Brushes.Red, |
|||
Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = canvas = new Canvas |
|||
{ |
|||
Background = Brushes.Yellow, |
|||
Margin = new Thickness(10), |
|||
} |
|||
|
|||
}; |
|||
s.TopLevel.Content = border; |
|||
|
|||
s.RunJobs(); |
|||
Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds); |
|||
|
|||
s.AssertHitTest(new Point(200,200), null, canvas, border); |
|||
|
|||
s.AssertHitTest(new Point(110, 110), null); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Accommodate_ICustomHitTest() |
|||
{ |
|||
using (var s = new CompositorServices(new Size(300, 200))) |
|||
{ |
|||
Border border = new CustomHitTestBorder |
|||
{ |
|||
ClipToBounds = false, |
|||
Width = 100, |
|||
Height = 100, |
|||
Background = Brushes.Red, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
}; |
|||
|
|||
s.TopLevel.Content = border; |
|||
|
|||
s.AssertHitTest(75, 100, null, border); |
|||
s.AssertHitTest(125, 100, null, border); |
|||
s.AssertHitTest(175, 100, null); |
|||
} |
|||
} |
|||
|
|||
private IDisposable TestApplication() |
|||
{ |
|||
return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Base.UnitTests.Rendering; |
|||
|
|||
public class CompositorInvalidationTests : CompositorTestsBase |
|||
{ |
|||
[Fact] |
|||
public void Control_Should_Invalidate_Own_Rect_When_Added() |
|||
{ |
|||
using (var s = new CompositorCanvas()) |
|||
{ |
|||
var control = new Border() |
|||
{ |
|||
Background = Brushes.Red, Width = 20, Height = 10, |
|||
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50 |
|||
}; |
|||
s.Canvas.Children.Add(control); |
|||
s.AssertRects(new Rect(30, 50, 20, 10)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Control_Should_Invalidate_Own_Rect_When_Removed() |
|||
{ |
|||
using (var s = new CompositorCanvas()) |
|||
{ |
|||
var control = new Border() |
|||
{ |
|||
Background = Brushes.Red, Width = 20, Height = 10, |
|||
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50 |
|||
}; |
|||
s.Canvas.Children.Add(control); |
|||
s.RunJobs(); |
|||
s.Events.Rects.Clear(); |
|||
s.Canvas.Children.Remove(control); |
|||
s.AssertRects(new Rect(30, 50, 20, 10)); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Control_Should_Invalidate_Both_Own_Rects_When_Moved() |
|||
{ |
|||
using (var s = new CompositorCanvas()) |
|||
{ |
|||
var control = new Border() |
|||
{ |
|||
Background = Brushes.Red, Width = 20, Height = 10, |
|||
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50 |
|||
}; |
|||
s.Canvas.Children.Add(control); |
|||
s.RunJobs(); |
|||
s.Events.Rects.Clear(); |
|||
control[Canvas.LeftProperty] = 55; |
|||
s.AssertRects(new Rect(30, 50, 20, 10), |
|||
new Rect(55, 50, 20, 10) |
|||
); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Control_Should_Invalidate_Child_Rects_When_Moved() |
|||
{ |
|||
using (var s = new CompositorCanvas()) |
|||
{ |
|||
var control = new Decorator() |
|||
{ |
|||
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50, |
|||
Padding = new Thickness(10), |
|||
Child = new Border() |
|||
{ |
|||
Width = 20, Height = 10, |
|||
Background = Brushes.Red |
|||
} |
|||
}; |
|||
s.Canvas.Children.Add(control); |
|||
s.RunJobs(); |
|||
s.Events.Rects.Clear(); |
|||
control[Canvas.LeftProperty] = 55; |
|||
s.AssertRects(new Rect(40, 60, 20, 10), |
|||
new Rect(65, 60, 20, 10) |
|||
); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Control_Should_Invalidate_Child_Rects_When_Becomes_Invisible() |
|||
{ |
|||
using (var s = new CompositorCanvas()) |
|||
{ |
|||
var control = new Decorator() |
|||
{ |
|||
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50, |
|||
Padding = new Thickness(10), |
|||
Child = new Border() |
|||
{ |
|||
Width = 20, Height = 10, |
|||
Background = Brushes.Red |
|||
} |
|||
}; |
|||
s.Canvas.Children.Add(control); |
|||
s.RunJobs(); |
|||
s.Events.Rects.Clear(); |
|||
control.IsVisible = false; |
|||
s.AssertRects(new Rect(40, 60, 20, 10)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,208 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Reactive.Disposables; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Embedding; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Data; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Rendering.Composition; |
|||
using Avalonia.Threading; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using JetBrains.Annotations; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Base.UnitTests.Rendering; |
|||
|
|||
public class CompositorTestsBase |
|||
{ |
|||
public class DebugEvents : ICompositionTargetDebugEvents |
|||
{ |
|||
public List<Rect> Rects = new(); |
|||
|
|||
public void RectInvalidated(Rect rc) |
|||
{ |
|||
Rects.Add(rc); |
|||
} |
|||
|
|||
public void Reset() |
|||
{ |
|||
Rects.Clear(); |
|||
} |
|||
} |
|||
|
|||
public class ManualRenderTimer : IRenderTimer |
|||
{ |
|||
public event Action<TimeSpan> Tick; |
|||
public bool RunsInBackground => false; |
|||
public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero); |
|||
public Task TriggerBackgroundTick() => Task.Run(TriggerTick); |
|||
} |
|||
|
|||
class TopLevelImpl : ITopLevelImpl |
|||
{ |
|||
private readonly Compositor _compositor; |
|||
public CompositingRenderer Renderer { get; private set; } |
|||
|
|||
public TopLevelImpl(Compositor compositor, Size clientSize) |
|||
{ |
|||
ClientSize = clientSize; |
|||
_compositor = compositor; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public Size ClientSize { get; } |
|||
public Size? FrameSize { get; } |
|||
public double RenderScaling => 1; |
|||
public IEnumerable<object> Surfaces { get; } = Array.Empty<object>(); |
|||
public Action<RawInputEventArgs> Input { get; set; } |
|||
public Action<Rect> Paint { get; set; } |
|||
public Action<Size, PlatformResizeReason> Resized { get; set; } |
|||
public Action<double> ScalingChanged { get; set; } |
|||
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; } |
|||
|
|||
public IRenderer CreateRenderer(IRenderRoot root) |
|||
{ |
|||
return Renderer = new CompositingRenderer(root, _compositor); |
|||
} |
|||
|
|||
public void Invalidate(Rect rect) |
|||
{ |
|||
} |
|||
|
|||
public void SetInputRoot(IInputRoot inputRoot) |
|||
{ |
|||
} |
|||
|
|||
public Point PointToClient(PixelPoint point) => default; |
|||
|
|||
public PixelPoint PointToScreen(Point point) => new(); |
|||
|
|||
public void SetCursor(ICursorImpl cursor) |
|||
{ |
|||
} |
|||
|
|||
public Action Closed { get; set; } |
|||
public Action LostFocus { get; set; } |
|||
public IMouseDevice MouseDevice { get; } = new MouseDevice(); |
|||
public IPopupImpl CreatePopup() => throw new NotImplementedException(); |
|||
|
|||
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) |
|||
{ |
|||
} |
|||
|
|||
public WindowTransparencyLevel TransparencyLevel { get; } |
|||
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } |
|||
} |
|||
|
|||
protected class CompositorServices : IDisposable |
|||
{ |
|||
private readonly IDisposable _app; |
|||
public Compositor Compositor { get; } |
|||
public ManualRenderTimer Timer { get; } = new(); |
|||
public EmbeddableControlRoot TopLevel { get; } |
|||
public CompositingRenderer Renderer { get; } = null!; |
|||
public DebugEvents Events { get; } = new(); |
|||
|
|||
public void Dispose() |
|||
{ |
|||
TopLevel.Renderer.Stop(); |
|||
TopLevel.Dispose(); |
|||
_app.Dispose(); |
|||
} |
|||
|
|||
public CompositorServices(Size? size = null) |
|||
{ |
|||
_app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); |
|||
try |
|||
{ |
|||
AvaloniaLocator.CurrentMutable.Bind<IRenderTimer>().ToConstant(Timer); |
|||
AvaloniaLocator.CurrentMutable.Bind<IRenderLoop>() |
|||
.ToConstant(new RenderLoop(Timer, Dispatcher.UIThread)); |
|||
|
|||
Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(), null); |
|||
var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000)); |
|||
TopLevel = new EmbeddableControlRoot(impl) |
|||
{ |
|||
Template = new FuncControlTemplate((parent, scope) => |
|||
{ |
|||
var presenter = new ContentPresenter |
|||
{ |
|||
[~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty) |
|||
}; |
|||
scope.Register("PART_ContentPresenter", presenter); |
|||
return presenter; |
|||
}) |
|||
}; |
|||
Renderer = impl.Renderer; |
|||
TopLevel.Prepare(); |
|||
TopLevel.Renderer.Start(); |
|||
RunJobs(); |
|||
Renderer.CompositionTarget.Server.DebugEvents = Events; |
|||
} |
|||
catch |
|||
{ |
|||
_app.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
public void RunJobs() |
|||
{ |
|||
Dispatcher.UIThread.RunJobs(); |
|||
Timer.TriggerTick(); |
|||
Dispatcher.UIThread.RunJobs(); |
|||
} |
|||
|
|||
public void AssertRects(params Rect[] rects) |
|||
{ |
|||
RunJobs(); |
|||
var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); |
|||
var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); |
|||
Assert.Equal(toAssert, invalidated); |
|||
Events.Rects.Clear(); |
|||
} |
|||
|
|||
public void AssertHitTest(double x, double y, Func<IVisual, bool> filter, params object[] expected) |
|||
=> AssertHitTest(new Point(x, y), filter, expected); |
|||
public void AssertHitTest(Point pt, Func<IVisual, bool> filter, params object[] expected) |
|||
{ |
|||
RunJobs(); |
|||
var tested = Renderer.HitTest(pt, TopLevel, filter); |
|||
Assert.Equal(expected, tested); |
|||
} |
|||
|
|||
public void AssertHitTestFirst(Point pt, Func<IVisual, bool> filter, object expected) |
|||
{ |
|||
RunJobs(); |
|||
var tested = Renderer.HitTest(pt, TopLevel, filter).First(); |
|||
Assert.Equal(expected, tested); |
|||
} |
|||
} |
|||
|
|||
|
|||
protected class CompositorCanvas : CompositorServices |
|||
{ |
|||
public Canvas Canvas { get; } = new(); |
|||
|
|||
public CompositorCanvas() |
|||
{ |
|||
TopLevel.Content = Canvas; |
|||
RunJobs(); |
|||
Events.Reset(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue