diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5156bac3b5..f6929f8dee 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,6 +29,7 @@ jobs: inputs: testResultsFormat: 'VSTest' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' + condition: not(canceled()) - job: macOS pool: @@ -77,6 +78,7 @@ jobs: inputs: testResultsFormat: 'VSTest' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' + condition: not(canceled()) - task: PublishBuildArtifacts@1 inputs: @@ -111,6 +113,7 @@ jobs: inputs: testResultsFormat: 'VSTest' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' + condition: not(canceled()) - task: PublishBuildArtifacts@1 inputs: diff --git a/build.cake b/build.cake index c781c0f2e5..f10a12c4e6 100644 --- a/build.cake +++ b/build.cake @@ -50,6 +50,7 @@ Setup(context => Information("IsPullRequest: " + parameters.IsPullRequest); Information("IsMainRepo: " + parameters.IsMainRepo); Information("IsMasterBranch: " + parameters.IsMasterBranch); + Information("IsReleaseBranch: " + parameters.IsReleaseBranch); Information("IsTagged: " + parameters.IsTagged); Information("IsReleasable: " + parameters.IsReleasable); Information("IsMyGetRelease: " + parameters.IsMyGetRelease); diff --git a/build/SharedVersion.props b/build/SharedVersion.props index 6ca65014b1..b46ac16a79 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Avalonia - 0.7.0 + 0.7.1 Copyright 2018 © The AvaloniaUI Project https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md https://github.com/AvaloniaUI/Avalonia/ diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme index c1cc9b4887..1a665d3ea5 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme @@ -38,7 +38,7 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" useCustomWorkingDirectory = "YES" - customWorkingDirectory = "$PROJECT_DIR/../../../../samples/ControlCatalog" + customWorkingDirectory = "$PROJECT_DIR/../../../../samples/ControlCatalog.NetCore" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" @@ -58,7 +58,7 @@ diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index ae209f085e..dee42fb795 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net461;netcoreapp2.0 @@ -27,6 +27,11 @@ false None + <_PackageFiles Include="Avalonia.props"> + build/Avalonia.props + false + None + diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props new file mode 100644 index 0000000000..8234dc4fde --- /dev/null +++ b/packages/Avalonia/Avalonia.props @@ -0,0 +1,6 @@ + + + $(MSBuildThisFileDirectory)\..\tools\netcoreapp2.0\designer\Avalonia.Designer.HostApp.dll + $(MSBuildThisFileDirectory)\..\tools\net461\designer\Avalonia.Designer.HostApp.exe + + diff --git a/parameters.cake b/parameters.cake index 8d813accbb..d13e503bd0 100644 --- a/parameters.cake +++ b/parameters.cake @@ -9,6 +9,7 @@ public class Parameters public string MasterBranch { get; private set; } public string ReleasePlatform { get; private set; } public string ReleaseConfiguration { get; private set; } + public string ReleaseBranchPrefix { get; private set; } public string MSBuildSolution { get; private set; } public bool IsLocalBuild { get; private set; } public bool IsRunningOnUnix { get; private set; } @@ -18,6 +19,7 @@ public class Parameters public bool IsPullRequest { get; private set; } public bool IsMainRepo { get; private set; } public bool IsMasterBranch { get; private set; } + public bool IsReleaseBranch { get; private set; } public bool IsTagged { get; private set; } public bool IsReleasable { get; private set; } public bool IsMyGetRelease { get; private set; } @@ -46,8 +48,9 @@ public class Parameters SkipTests = context.HasArgument("skip-tests"); // CONFIGURATION - MainRepo = "AvaloniaUI/Avalonia"; + MainRepo = "https://github.com/AvaloniaUI/Avalonia"; MasterBranch = "master"; + ReleaseBranchPrefix = "refs/heads/release/"; ReleaseConfiguration = "Release"; MSBuildSolution = "./dirs.proj"; @@ -59,12 +62,14 @@ public class Parameters IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts"; IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest; - IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, buildSystem.AppVeyor.Environment.Repository.Name); - IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, buildSystem.AppVeyor.Environment.Repository.Branch); + IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, context.EnvironmentVariable("BUILD_REPOSITORY_URI")); + IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, context.EnvironmentVariable("BUILD_SOURCEBRANCHNAME")); + IsReleaseBranch = context.EnvironmentVariable("BUILD_SOURCEBRANCH").ToLower().StartsWith(ReleaseBranchPrefix.ToLower()); IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag && !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name); IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); IsMyGetRelease = !IsTagged && IsReleasable; + IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; // VERSION Version = context.Argument("force-nuget-version", GetVersion()); @@ -92,9 +97,13 @@ public class Parameters } else if (IsRunningOnAzure) { + if(!IsNuGetRelease) + { // Use AssemblyVersion with Build as version Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta"; - PublishTestResults = true; + } + + PublishTestResults = true; } // DIRECTORIES diff --git a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml index b428cd1b9f..f7e1c08cac 100644 --- a/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml +++ b/samples/ControlCatalog/Pages/LayoutTransformControlPage.xaml @@ -19,7 +19,7 @@ - Layout Transform + diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs index 1731950222..58f3413780 100644 --- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs +++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs @@ -104,7 +104,12 @@ namespace Avalonia.Collections case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Replace: Remove(e.OldStartingIndex, e.OldItems); - Add(e.NewStartingIndex, e.NewItems); + int newIndex = e.NewStartingIndex; + if(newIndex > e.OldStartingIndex) + { + newIndex -= e.OldItems.Count; + } + Add(newIndex, e.NewItems); break; case NotifyCollectionChangedAction.Remove: diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 17cdbf834c..e1767fca36 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -211,10 +211,10 @@ namespace Avalonia.Controls.Remote.Server } if(obj is KeyEventMessage key) { - Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); - Dispatcher.UIThread.Post(() => { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + Input?.Invoke(new RawKeyEventArgs( KeyboardDevice, 0, @@ -225,10 +225,10 @@ namespace Avalonia.Controls.Remote.Server } if(obj is TextInputEventMessage text) { - Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); - Dispatcher.UIThread.Post(() => { + Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); + Input?.Invoke(new RawTextInputEventArgs( KeyboardDevice, 0, diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 091a4ad285..ff410ddbe3 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -76,6 +76,7 @@ namespace Avalonia.Native .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature())) + .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows)) .Bind().ToConstant(opts); } diff --git a/src/Avalonia.Native/GlPlatformFeature.cs b/src/Avalonia.Native/GlPlatformFeature.cs index dbcdd244cd..97785e4a81 100644 --- a/src/Avalonia.Native/GlPlatformFeature.cs +++ b/src/Avalonia.Native/GlPlatformFeature.cs @@ -100,12 +100,12 @@ namespace Avalonia.Native public IGlDisplay Display { get; } - public System.Drawing.Size PixelSize + public PixelSize Size { get { var s = _session.GetPixelSize(); - return new System.Drawing.Size(s.Width, s.Height); + return new PixelSize(s.Width, s.Height); } } diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs index 19894e4ec5..c99c869c4f 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs @@ -7,8 +7,7 @@ namespace Avalonia.OpenGL public interface IEglWindowGlPlatformSurfaceInfo { IntPtr Handle { get; } - // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged - System.Drawing.Size PixelSize { get; } + PixelSize Size { get; } double Scaling { get; } } @@ -71,7 +70,7 @@ namespace Avalonia.OpenGL } public IGlDisplay Display => _context.Display; - public System.Drawing.Size PixelSize => _info.PixelSize; + public PixelSize Size => _info.Size; public double Scaling => _info.Scaling; } } diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs index 22641d7b53..4b0de05b77 100644 --- a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs +++ b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs @@ -5,8 +5,7 @@ namespace Avalonia.OpenGL public interface IGlPlatformSurfaceRenderingSession : IDisposable { IGlDisplay Display { get; } - // TODO: Change to PixelSize struct once https://github.com/AvaloniaUI/Avalonia/pull/1889 is merged - System.Drawing.Size PixelSize { get; } + PixelSize Size { get; } double Scaling { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index bf778fcc93..4d1c4b1ab0 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -46,7 +46,7 @@ - 2 + 1 0.5 10 diff --git a/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs b/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs index c5b3c048b1..9ebfe290e1 100644 --- a/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs +++ b/src/Avalonia.Visuals/Platform/LockedFramebuffer.cs @@ -11,7 +11,7 @@ namespace Avalonia.Platform { _onDispose = onDispose; Address = address; - Size = Size; + Size = size; RowBytes = rowBytes; Dpi = dpi; Format = format; diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index d373e7ef2a..08f3803e9b 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -125,6 +125,20 @@ namespace Avalonia.Rendering if (m.HasValue) { var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value); + + //use transformedbounds as previous render state of the visual bounds + //so we can invalidate old and new bounds of a control in case it moved/shrinked + if (visual.TransformedBounds.HasValue) + { + var trb = visual.TransformedBounds.Value; + var trBounds = trb.Bounds.TransformToAABB(trb.Transform); + + if (trBounds != bounds) + { + _renderRoot?.Invalidate(trBounds); + } + } + _renderRoot?.Invalidate(bounds); } } @@ -191,7 +205,7 @@ namespace Avalonia.Rendering } } - static IEnumerable HitTest( + private static IEnumerable HitTest( IVisual visual, Point p, Func filter) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index e5fcf1ba1d..f26c21d1b6 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -304,7 +304,7 @@ namespace Avalonia { var thisOffset = GetOffsetFrom(common, this); var thatOffset = GetOffsetFrom(common, visual); - return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); + return -thatOffset * thisOffset; } return null; @@ -454,13 +454,28 @@ namespace Avalonia /// The ancestor visual. /// The visual. /// The visual offset. - private static Vector GetOffsetFrom(IVisual ancestor, IVisual visual) + private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual) { - var result = new Vector(); + var result = Matrix.Identity; while (visual != ancestor) { - result = new Vector(result.X + visual.Bounds.X, result.Y + visual.Bounds.Y); + if (visual.RenderTransform?.Value != null) + { + var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size); + var offset = Matrix.CreateTranslation(origin); + var renderTransform = (-offset) * visual.RenderTransform.Value * (offset); + + result *= renderTransform; + } + + var topLeft = visual.Bounds.TopLeft; + + if (topLeft != default) + { + result *= Matrix.CreateTranslation(topLeft); + } + visual = visual.VisualParent; if (visual == null) diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 6e8cf5cb18..0273f6a7d8 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -510,12 +510,12 @@ namespace Avalonia.Gtk3 : new ImmediateRenderer(root); } - System.Drawing.Size EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.PixelSize + PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size { get { var cs = ClientSize; - return new System.Drawing.Size((int)Math.Max(1, LastKnownScaleFactor * cs.Width), + return new PixelSize((int)Math.Max(1, LastKnownScaleFactor * cs.Width), (int)Math.Max(1, LastKnownScaleFactor * ClientSize.Height)); } } diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index b2c936d6ad..5efbc0861e 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -143,23 +143,26 @@ namespace Avalonia.Skia // Create bitmap using default platform settings _bitmap = new SKBitmap(destinationInfo.Width, destinationInfo.Height); + SKColorType bitmapColorType; if (!_bitmap.CanCopyTo(destinationInfo.ColorType)) { + bitmapColorType = _bitmap.ColorType; _bitmap.Dispose(); throw new Exception( - $"Unable to create pixel format shim for conversion from {_bitmap.ColorType} to {destinationInfo.ColorType}"); + $"Unable to create pixel format shim for conversion from {bitmapColorType} to {destinationInfo.ColorType}"); } Surface = SKSurface.Create(_bitmap.Info, _bitmap.GetPixels(), _bitmap.RowBytes); if (Surface == null) { + bitmapColorType = _bitmap.ColorType; _bitmap.Dispose(); throw new Exception( - $"Unable to create pixel format shim surface for conversion from {_bitmap.ColorType} to {destinationInfo.ColorType}"); + $"Unable to create pixel format shim surface for conversion from {bitmapColorType} to {destinationInfo.ColorType}"); } SurfaceCopyHandler = Disposable.Create(CopySurface); diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs index 0435e1060a..a6269473a6 100644 --- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs @@ -28,7 +28,7 @@ namespace Avalonia.Skia var gl = disp.GlInterface; gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var fb); - var size = session.PixelSize; + var size = session.Size; var scaling = session.Scaling; GRBackendRenderTargetDesc desc = new GRBackendRenderTargetDesc diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs index 9c331f662d..19b90685bf 100644 --- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -61,7 +61,7 @@ namespace Avalonia.Win32 } } - public PixelSize Size => new PixelSize(_bmpInfo.biWidth, _bmpInfo.biHeight); + public PixelSize Size => new PixelSize(_bmpInfo.biWidth, -_bmpInfo.biHeight); public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, int height = -1) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index d78c213299..21986bd2ba 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -933,14 +933,14 @@ namespace Avalonia.Win32 _topmost = value; } - - System.Drawing.Size EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.PixelSize + + PixelSize EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Size { get { RECT rect; GetClientRect(_hwnd, out rect); - return new System.Drawing.Size( + return new PixelSize( Math.Max(1, rect.right - rect.left), Math.Max(1, rect.bottom - rect.top)); } diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs index b996db8d48..7f118a2c1d 100644 --- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs +++ b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListExtenionsTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Avalonia.Collections; using Xunit; @@ -82,13 +81,31 @@ namespace Avalonia.Base.UnitTests.Collections Assert.Equal(source, result); } - [Fact] - public void CreateDerivedList_Handles_MoveRange() + [Theory] + [InlineData(0, 2, 3)] + [InlineData(0, 2, 4)] + [InlineData(0, 2, 5)] + [InlineData(0, 4, 4)] + [InlineData(1, 2, 0)] + [InlineData(1, 2, 4)] + [InlineData(1, 2, 5)] + [InlineData(1, 4, 0)] + [InlineData(2, 2, 0)] + [InlineData(2, 2, 1)] + [InlineData(2, 2, 3)] + [InlineData(2, 2, 4)] + [InlineData(2, 2, 5)] + [InlineData(4, 2, 0)] + [InlineData(4, 2, 1)] + [InlineData(4, 2, 3)] + [InlineData(5, 1, 0)] + [InlineData(5, 1, 3)] + public void CreateDerivedList_Handles_MoveRange(int oldIndex, int count, int newIndex) { - var source = new AvaloniaList(new[] { 0, 1, 2, 3 }); + var source = new AvaloniaList(new[] { 0, 1, 2, 3, 4, 5 }); var target = source.CreateDerivedList(x => new Wrapper(x)); - source.MoveRange(1, 2, 0); + source.MoveRange(oldIndex, count, newIndex); var result = target.Select(x => x.Value).ToList(); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs new file mode 100644 index 0000000000..82294246b1 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using Avalonia.Collections; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.VisualTree; +using Moq; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering +{ + public class ImmediateRendererTests + { + [Fact] + public void AddDirty_Call_RenderRoot_Invalidate() + { + var visual = new Mock(); + var child = new Mock() { CallBase = true }; + var renderRoot = visual.As(); + + visual.As().Setup(v => v.Bounds).Returns(new Rect(0, 0, 400, 400)); + + child.As().Setup(v => v.Bounds).Returns(new Rect(10, 10, 100, 100)); + child.As().Setup(v => v.VisualParent).Returns(visual.Object); + + var target = new ImmediateRenderer(visual.Object); + + target.AddDirty(child.Object); + + renderRoot.Verify(v => v.Invalidate(new Rect(10, 10, 100, 100))); + } + + [Fact] + public void AddDirty_With_RenderTransform_Call_RenderRoot_Invalidate() + { + var visual = new Mock(); + var child = new Mock() { CallBase = true }; + var renderRoot = visual.As(); + + visual.As().Setup(v => v.Bounds).Returns(new Rect(0, 0, 400, 400)); + + child.As().Setup(v => v.Bounds).Returns(new Rect(100, 100, 100, 100)); + child.As().Setup(v => v.VisualParent).Returns(visual.Object); + child.Object.RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 }; + + var target = new ImmediateRenderer(visual.Object); + + target.AddDirty(child.Object); + + renderRoot.Verify(v => v.Invalidate(new Rect(50, 50, 200, 200))); + } + + [Fact] + public void AddDirty_For_Child_Moved_Should_Invalidate_Previous_Bounds() + { + var visual = new Mock() { CallBase = true }; + var child = new Mock() { CallBase = true }; + var renderRoot = visual.As(); + var renderTarget = visual.As(); + + renderRoot.Setup(r => r.CreateRenderTarget()).Returns(renderTarget.Object); + renderTarget.Setup(r => r.CreateDrawingContext(It.IsAny())).Returns(Mock.Of()); + + visual.As().Setup(v => v.Bounds).Returns(new Rect(0, 0, 400, 400)); + visual.As().Setup(v => v.VisualChildren).Returns(new AvaloniaList() { child.As().Object }); + + Rect childBounds = new Rect(0, 0, 100, 100); + child.As().Setup(v => v.Bounds).Returns(() => childBounds); + child.As().Setup(v => v.VisualParent).Returns(visual.Object); + child.As().Setup(v => v.VisualChildren).Returns(new AvaloniaList()); + + var invalidationCalls = new List(); + + renderRoot.Setup(v => v.Invalidate(It.IsAny())).Callback(v => invalidationCalls.Add(v)); + + var target = new ImmediateRenderer(visual.Object); + + target.AddDirty(child.Object); + + Assert.Equal(new Rect(0, 0, 100, 100), invalidationCalls[0]); + + target.Paint(new Rect(0, 0, 100, 100)); + + //move child 100 pixels bottom/right + childBounds = new Rect(100, 100, 100, 100); + + //renderer should invalidate old child bounds with new one + //as on old area there can be artifacts + target.AddDirty(child.Object); + + //invalidate first old position + Assert.Equal(new Rect(0, 0, 100, 100), invalidationCalls[1]); + + //then new position + Assert.Equal(new Rect(100, 100, 100, 100), invalidationCalls[2]); + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs index b62bf5858d..0414ac4c74 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Avalonia.Controls; +using Avalonia.Media; using Avalonia.Rendering; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -192,5 +193,48 @@ namespace Avalonia.Visuals.UnitTests Assert.Throws(() => root2.Child = child); Assert.Empty(root2.GetVisualChildren()); } + + [Fact] + public void TransformToVisual_Should_Work() + { + var child = new Decorator { Width = 100, Height = 100 }; + var root = new TestRoot() { Child = child, Width = 400, Height = 400 }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(new Point(), root.DesiredSize)); + + var tr = child.TransformToVisual(root); + + Assert.NotNull(tr); + + var point = root.Bounds.TopLeft * tr; + + //child is centered (400 - 100)/2 + Assert.Equal(new Point(150, 150), point); + } + + [Fact] + public void TransformToVisual_With_RenderTransform_Should_Work() + { + var child = new Decorator + { + Width = 100, + Height = 100, + RenderTransform = new ScaleTransform() { ScaleX = 2, ScaleY = 2 } + }; + var root = new TestRoot() { Child = child, Width = 400, Height = 400 }; + + root.Measure(Size.Infinity); + root.Arrange(new Rect(new Point(), root.DesiredSize)); + + var tr = child.TransformToVisual(root); + + Assert.NotNull(tr); + + var point = root.Bounds.TopLeft * tr; + + //child is centered (400 - 100*2 scale)/2 + Assert.Equal(new Point(100, 100), point); + } } }