From cf3bdf8507f86a8e5d6d4c6f9818c42feef3d82c Mon Sep 17 00:00:00 2001 From: Rayyan Tahir Date: Tue, 3 Jul 2018 17:31:44 +0500 Subject: [PATCH 01/30] When TextBox IsReadOnly property is true, no need to show caret. --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 1 + src/Avalonia.Controls/TextBox.cs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index fb0d8fb7b1..96c1665afe 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -9,6 +9,7 @@ Gap="16"> + Date: Tue, 3 Jul 2018 23:15:51 +0200 Subject: [PATCH 02/30] Don't create virtualizer before panel. Otherwise, an `ItemVirtualizerNone` is created which can materialize all items, even if `ItemVirtualizationMode.Simple` is set. Hopefully fixes #1707. --- .../Presenters/ItemVirtualizer.cs | 5 +++ .../Presenters/ItemsPresenter.cs | 36 +++++++++++-------- .../ItemsPresenterTests_Virtualization.cs | 9 +++++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index fee326dacc..e293cff211 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs @@ -161,6 +161,11 @@ namespace Avalonia.Controls.Presenters /// An . public static ItemVirtualizer Create(ItemsPresenter owner) { + if (owner.Panel == null) + { + return null; + } + var virtualizingPanel = owner.Panel as IVirtualizingPanel; var scrollable = (ILogicalScrollable)owner; ItemVirtualizer result = null; diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 590bfa25ac..f8d62a1cbf 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -22,7 +22,6 @@ namespace Avalonia.Controls.Presenters nameof(VirtualizationMode), defaultValue: ItemVirtualizationMode.None); - private ItemVirtualizer _virtualizer; private bool _canHorizontallyScroll; private bool _canVerticallyScroll; @@ -76,21 +75,27 @@ namespace Avalonia.Controls.Presenters /// bool ILogicalScrollable.IsLogicalScrollEnabled { - get { return _virtualizer?.IsLogicalScrollEnabled ?? false; } + get { return Virtualizer?.IsLogicalScrollEnabled ?? false; } } /// - Size IScrollable.Extent => _virtualizer.Extent; + Size IScrollable.Extent => Virtualizer?.Extent ?? Size.Empty; /// Vector IScrollable.Offset { - get { return _virtualizer.Offset; } - set { _virtualizer.Offset = CoerceOffset(value); } + get { return Virtualizer?.Offset ?? new Vector(); } + set + { + if (Virtualizer != null) + { + Virtualizer.Offset = CoerceOffset(value); + } + } } /// - Size IScrollable.Viewport => _virtualizer.Viewport; + Size IScrollable.Viewport => Virtualizer?.Viewport ?? Bounds.Size; /// Action ILogicalScrollable.InvalidateScroll { get; set; } @@ -101,6 +106,8 @@ namespace Avalonia.Controls.Presenters /// Size ILogicalScrollable.PageScrollSize => new Size(0, 1); + internal ItemVirtualizer Virtualizer { get; private set; } + /// bool ILogicalScrollable.BringIntoView(IControl target, Rect targetRect) { @@ -110,29 +117,30 @@ namespace Avalonia.Controls.Presenters /// IControl ILogicalScrollable.GetControlInDirection(NavigationDirection direction, IControl from) { - return _virtualizer?.GetControlInDirection(direction, from); + return Virtualizer?.GetControlInDirection(direction, from); } public override void ScrollIntoView(object item) { - _virtualizer?.ScrollIntoView(item); + Virtualizer?.ScrollIntoView(item); } /// protected override Size MeasureOverride(Size availableSize) { - return _virtualizer?.MeasureOverride(availableSize) ?? Size.Empty; + return Virtualizer?.MeasureOverride(availableSize) ?? Size.Empty; } protected override Size ArrangeOverride(Size finalSize) { - return _virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty; + return Virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty; } /// protected override void PanelCreated(IPanel panel) { - _virtualizer = ItemVirtualizer.Create(this); + Virtualizer?.Dispose(); + Virtualizer = ItemVirtualizer.Create(this); ((ILogicalScrollable)this).InvalidateScroll?.Invoke(); if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty)) @@ -149,7 +157,7 @@ namespace Avalonia.Controls.Presenters protected override void ItemsChanged(NotifyCollectionChangedEventArgs e) { - _virtualizer?.ItemsChanged(Items, e); + Virtualizer?.ItemsChanged(Items, e); } private Vector CoerceOffset(Vector value) @@ -162,8 +170,8 @@ namespace Avalonia.Controls.Presenters private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e) { - _virtualizer?.Dispose(); - _virtualizer = ItemVirtualizer.Create(this); + Virtualizer?.Dispose(); + Virtualizer = ItemVirtualizer.Create(this); ((ILogicalScrollable)this).InvalidateScroll?.Invoke(); } } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 7eb44c5354..5aa6d50425 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -194,6 +194,15 @@ namespace Avalonia.Controls.UnitTests.Presenters } } + [Fact] + public void Should_Not_Create_Virtualizer_Before_Panel() + { + var target = CreateTarget(); + + Assert.Null(target.Panel); + Assert.Null(target.Virtualizer); + } + [Fact] public void Changing_VirtualizationMode_None_To_Simple_Should_Update_Control() { From 06dbc915839a3161ebeb7e4e4da93b48f3f59a53 Mon Sep 17 00:00:00 2001 From: "rayyantahir2010@hotmail.com" Date: Wed, 4 Jul 2018 12:11:57 +0500 Subject: [PATCH 03/30] Moved caret visibility decision to helper method and called that method in OnTemplateApplied also --- src/Avalonia.Controls/TextBox.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index a4c6a6cdf7..ec32326cd9 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -262,7 +262,7 @@ namespace Avalonia.Controls if (IsFocused) { - _presenter.ShowCaret(); + DecideCaretVisibility(); } } @@ -282,15 +282,20 @@ namespace Avalonia.Controls } else { - if (!IsReadOnly) - _presenter?.ShowCaret(); - else - _presenter?.HideCaret(); + DecideCaretVisibility(); } e.Handled = true; } + private void DecideCaretVisibility() + { + if (!IsReadOnly || IsReadOnlyCaretVisible) + _presenter?.ShowCaret(); + else + _presenter?.HideCaret(); + } + protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); From 387de6a3f70e3eb87256b29d0ccc4d94555d126d Mon Sep 17 00:00:00 2001 From: "rayyantahir2010@hotmail.com" Date: Wed, 4 Jul 2018 12:29:08 +0500 Subject: [PATCH 04/30] Fix build failure --- src/Avalonia.Controls/TextBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index ec32326cd9..f5900f99e4 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -124,7 +124,7 @@ namespace Avalonia.Controls ScrollViewer.HorizontalScrollBarVisibilityProperty, horizontalScrollBarVisibility, BindingPriority.Style); - _undoRedoHelper = new UndoRedoHelper(this); + _undoRedoHelper = new UndoRedoHelper(this); } public bool AcceptsReturn @@ -290,7 +290,7 @@ namespace Avalonia.Controls private void DecideCaretVisibility() { - if (!IsReadOnly || IsReadOnlyCaretVisible) + if (!IsReadOnly) _presenter?.ShowCaret(); else _presenter?.HideCaret(); From 33172127cc04025fb4e6e567d357706a68b367d2 Mon Sep 17 00:00:00 2001 From: "rayyantahir2010@hotmail.com" <=> Date: Wed, 4 Jul 2018 12:54:54 +0500 Subject: [PATCH 05/30] Added the ability to copy from a readonly textbox --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 2 +- src/Avalonia.Controls/Presenters/TextPresenter.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 96c1665afe..a717518655 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -9,7 +9,7 @@ Gap="16"> - + Date: Thu, 5 Jul 2018 08:53:12 +0200 Subject: [PATCH 06/30] Call AddOwner on Slider.Orientation instead of declaring a new property --- src/Avalonia.Controls/Slider.cs | 5 +-- .../SliderTests.cs | 39 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/SliderTests.cs diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 2ef0af2852..31113812d1 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); + ScrollBar.OrientationProperty.AddOwner(); /// /// Defines the property. @@ -41,8 +41,7 @@ namespace Avalonia.Controls /// static Slider() { - PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical"); - PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal"); + OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); Thumb.DragStartedEvent.AddClassHandler(x => x.OnThumbDragStarted, RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler(x => x.OnThumbDragDelta, RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble); diff --git a/tests/Avalonia.Controls.UnitTests/SliderTests.cs b/tests/Avalonia.Controls.UnitTests/SliderTests.cs new file mode 100644 index 0000000000..dc47d9eb89 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/SliderTests.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class SliderTests + { + [Fact] + public void Default_Orientation_Should_Be_Horizontal() + { + var slider = new Slider(); + Assert.Equal(Orientation.Horizontal, slider.Orientation); + } + + [Fact] + public void Should_Set_Horizontal_Class() + { + var slider = new Slider + { + Orientation = Orientation.Horizontal + }; + + Assert.Contains(slider.Classes, ":horizontal".Equals); + } + + [Fact] + public void Should_Set_Vertical_Class() + { + var slider = new Slider + { + Orientation = Orientation.Vertical + }; + + Assert.Contains(slider.Classes, ":vertical".Equals); + } + } +} From 3154137c2a3eb859a2bed48ee8b07625925dd999 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 16 Jul 2018 20:00:55 +0200 Subject: [PATCH 07/30] Initial --- .../Media/Imaging/BitmapScalingMode.cs | 12 ++++++ .../Platform/IDrawingContextImpl.cs | 5 ++- .../SceneGraph/DeferredDrawingContextImpl.cs | 8 ++-- .../Rendering/SceneGraph/ImageNode.cs | 24 +++++++++-- .../Media/DrawingContextImpl.cs | 43 ++++++++++++++++--- .../Media/Imaging/BitmapImpl.cs | 10 +++-- 6 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs new file mode 100644 index 0000000000..36d239c9d9 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs @@ -0,0 +1,12 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +namespace Avalonia.Visuals.Media.Imaging +{ + public enum BitmapScalingMode + { + LowQuality, + MediumQuality, + HighQuality + } +} diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 04bbe713f2..f511daf9b2 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -7,6 +7,8 @@ using Avalonia.Utilities; namespace Avalonia.Platform { + using Avalonia.Visuals.Media.Imaging; + /// /// Defines the interface through which drawing occurs. /// @@ -30,7 +32,8 @@ namespace Avalonia.Platform /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect); + /// Controls + void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = BitmapScalingMode.LowQuality); /// /// Draws a bitmap image. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index f3dbfbd8fb..7172696e81 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -10,6 +10,8 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { + using Avalonia.Visuals.Media.Imaging; + /// /// A drawing context which builds a scene graph. /// @@ -114,13 +116,13 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect) + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = BitmapScalingMode.LowQuality) { var next = NextDrawAs(); - if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect)) + if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, scalingMode)) { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect)); + Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, scalingMode)); } else { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 06fdb3f86c..96a8731715 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -7,6 +7,8 @@ using Avalonia.Utilities; namespace Avalonia.Rendering.SceneGraph { + using Avalonia.Visuals.Media.Imaging; + /// /// A node in the scene graph which represents an image draw. /// @@ -20,7 +22,8 @@ namespace Avalonia.Rendering.SceneGraph /// The draw opacity. /// The source rect. /// The destination rect. - public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect) + /// + public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) : base(destRect, transform, null) { Transform = transform; @@ -28,8 +31,11 @@ namespace Avalonia.Rendering.SceneGraph Opacity = opacity; SourceRect = sourceRect; DestRect = destRect; + ScalingMode = scalingMode; } + + /// /// Gets the transform with which the node will be drawn. /// @@ -55,6 +61,14 @@ namespace Avalonia.Rendering.SceneGraph /// public Rect DestRect { get; } + /// + /// Gets or sets the scaling mode. + /// + /// + /// The scaling mode. + /// + public BitmapScalingMode ScalingMode { get; } + /// /// Determines if this draw operation equals another. /// @@ -63,18 +77,20 @@ namespace Avalonia.Rendering.SceneGraph /// The opacity of the other draw operation. /// The source rect of the other draw operation. /// The dest rect of the other draw operation. + /// /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect) + public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) { return transform == Transform && Equals(source.Item, Source.Item) && opacity == Opacity && sourceRect == SourceRect && - destRect == DestRect; + destRect == DestRect && + scalingMode == ScalingMode; } /// @@ -83,7 +99,7 @@ namespace Avalonia.Rendering.SceneGraph // TODO: Probably need to introduce some kind of locking mechanism in the case of // WriteableBitmap. context.Transform = Transform; - context.DrawImage(Source, Opacity, SourceRect, DestRect); + context.DrawImage(Source, Opacity, SourceRect, DestRect, ScalingMode); } /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index bdbbdab2b9..27115e164a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -13,6 +13,8 @@ using SharpDX.Mathematics.Interop; namespace Avalonia.Direct2D1.Media { + using Avalonia.Visuals.Media.Imaging; + /// /// Draws using Direct2D1. /// @@ -100,16 +102,43 @@ namespace Avalonia.Direct2D1.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect) + /// + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) { using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) { - _renderTarget.DrawBitmap( - d2d.Value, - destRect.ToSharpDX(), - (float)opacity, - BitmapInterpolationMode.Linear, - sourceRect.ToSharpDX()); + if (_renderTarget is DeviceContext deviceContext) + { + deviceContext.DrawBitmap(d2d.Value, destRect.ToDirect2D(), (float)opacity, GetInterpolationMode(scalingMode), sourceRect.ToDirect2D(), null); + } + else + { + var interpolationMode = scalingMode == BitmapScalingMode.LowQuality + ? BitmapInterpolationMode.NearestNeighbor + : BitmapInterpolationMode.Linear; + + _renderTarget.DrawBitmap( + d2d.Value, + destRect.ToSharpDX(), + (float)opacity, + interpolationMode, + sourceRect.ToSharpDX()); + } + } + } + + private static InterpolationMode GetInterpolationMode(BitmapScalingMode scalingMode) + { + switch (scalingMode) + { + case BitmapScalingMode.LowQuality: + return InterpolationMode.NearestNeighbor; + case BitmapScalingMode.MediumQuality: + return InterpolationMode.Linear; + case BitmapScalingMode.HighQuality: + return InterpolationMode.HighQualityCubic; + default: + throw new ArgumentOutOfRangeException(nameof(scalingMode), scalingMode, null); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index d58f023391..c792334ec1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,11 +1,15 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using System.IO; using Avalonia.Platform; using SharpDX.WIC; -using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { + using SharpDX.Direct2D1; + public abstract class BitmapImpl : IBitmapImpl, IDisposable { public BitmapImpl(ImagingFactory imagingFactory) @@ -17,7 +21,7 @@ namespace Avalonia.Direct2D1.Media public abstract int PixelWidth { get; } public abstract int PixelHeight { get; } - public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); + public abstract OptionalDispose GetDirect2DBitmap(RenderTarget target); public void Save(string fileName) { From b108ffcab4e2123a56cb39dd3eb6a6da1849c879 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 18 Jul 2018 19:48:31 +0200 Subject: [PATCH 08/30] Initial implementation of BitmapScaleMode --- .gitignore | 1 + Avalonia.sln | 42 +++++++++++++++++++ src/Avalonia.Controls/Image.cs | 19 ++++++++- src/Avalonia.Visuals/Media/DrawingContext.cs | 9 ++-- .../Platform/IDrawingContextImpl.cs | 2 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 32 +++++++++++--- 6 files changed, 94 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 583a2b8a2b..f0cbd79cbc 100644 --- a/.gitignore +++ b/.gitignore @@ -182,3 +182,4 @@ project.lock.json ## BenchmarkDotNet ################## BenchmarkDotNet.Artifacts/ +/src/ImageInterpRepro diff --git a/Avalonia.sln b/Avalonia.sln index d1c5026e58..7938b95ac5 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -187,6 +187,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp.N EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageInterpRepro", "src\ImageInterpRepro\ImageInterpRepro.csproj", "{54A1062E-126E-4813-98A7-8DB9D949CD93}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2514,6 +2516,46 @@ Global {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|NetCoreOnly.Build.0 = Release|Any CPU {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.ActiveCfg = Release|Any CPU {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.Build.0 = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|NetCoreOnly.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|NetCoreOnly.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|x86.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhone.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|NetCoreOnly.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|NetCoreOnly.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|x86.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|x86.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhone.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|x86.ActiveCfg = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|x86.Build.0 = Debug|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|Any CPU.Build.0 = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhone.ActiveCfg = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhone.Build.0 = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|NetCoreOnly.Build.0 = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|x86.ActiveCfg = Release|Any CPU + {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index f6f11aa9ad..a9f24f5ce4 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -7,6 +7,8 @@ using Avalonia.Media.Imaging; namespace Avalonia.Controls { + using Avalonia.Visuals.Media.Imaging; + /// /// Displays a image. /// @@ -24,10 +26,17 @@ namespace Avalonia.Controls public static readonly StyledProperty StretchProperty = AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); + /// + /// Defines the property. + /// + public static readonly StyledProperty ScalingModeProperty = + AvaloniaProperty.Register(nameof(BitmapScalingMode)); + static Image() { AffectsRender(SourceProperty); AffectsRender(StretchProperty); + AffectsRender(ScalingModeProperty); } /// @@ -48,6 +57,12 @@ namespace Avalonia.Controls set { SetValue(StretchProperty, value); } } + public BitmapScalingMode ScalingMode + { + get { return (BitmapScalingMode)GetValue(ScalingModeProperty); } + set { SetValue(ScalingModeProperty, value); } + } + /// /// Renders the control. /// @@ -68,7 +83,7 @@ namespace Avalonia.Controls Rect sourceRect = new Rect(sourceSize) .CenterRect(new Rect(destRect.Size / scale)); - context.DrawImage(source, 1, sourceRect, destRect); + context.DrawImage(source, 1, sourceRect, destRect, ScalingMode); } } @@ -100,4 +115,4 @@ namespace Avalonia.Controls } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 962f2c1ba8..9cb8712f5b 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -5,6 +5,8 @@ using Avalonia.Platform; namespace Avalonia.Media { + using Avalonia.Visuals.Media.Imaging; + public sealed class DrawingContext : IDisposable { private int _currentLevel; @@ -68,11 +70,12 @@ namespace Avalonia.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) + /// + public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = default) { Contract.Requires(source != null); - PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect); + PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect, scalingMode); } /// @@ -309,4 +312,4 @@ namespace Avalonia.Media return pen?.Brush != null && pen.Thickness > 0; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index f511daf9b2..fbd742f6c3 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -33,7 +33,7 @@ namespace Avalonia.Platform /// The rect in the image to draw. /// The rect in the output to draw to. /// Controls - void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = BitmapScalingMode.LowQuality); + void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = default); /// /// Draws a bitmap image. diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index b7ce6eedc4..5878038a81 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -14,6 +14,8 @@ using SkiaSharp; namespace Avalonia.Skia { + using Avalonia.Visuals.Media.Imaging; + /// /// Skia based drawing context. /// @@ -95,24 +97,44 @@ namespace Avalonia.Skia } /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect) + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) { - var drawableImage = (IDrawableBitmapImpl) source.Item; + var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); using (var paint = - new SKPaint {Color = new SKColor(255, 255, 255, (byte) (255 * opacity * _currentOpacity))}) + new SKPaint + { + Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) + }) { + paint.FilterQuality = GetInterpolationMode(scalingMode); + drawableImage.Draw(this, s, d, paint); } } + private static SKFilterQuality GetInterpolationMode(BitmapScalingMode scalingMode) + { + switch (scalingMode) + { + case BitmapScalingMode.LowQuality: + return SKFilterQuality.Low; + case BitmapScalingMode.MediumQuality: + return SKFilterQuality.Medium; + case BitmapScalingMode.HighQuality: + return SKFilterQuality.High; + default: + throw new ArgumentOutOfRangeException(nameof(scalingMode), scalingMode, null); + } + } + /// public void DrawImage(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { PushOpacityMask(opacityMask, opacityMaskRect); - DrawImage(source, 1, new Rect(0, 0, source.Item.PixelWidth, source.Item.PixelHeight), destRect); + DrawImage(source, 1, new Rect(0, 0, source.Item.PixelWidth, source.Item.PixelHeight), destRect, default(BitmapScalingMode)); PopOpacityMask(); } @@ -686,4 +708,4 @@ namespace Avalonia.Skia } } } -} \ No newline at end of file +} From 16da122924d6447187b76482fbcba769abb98b5f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 18 Jul 2018 22:21:25 +0200 Subject: [PATCH 09/30] Use WeakSubscribe in ItemsPresenters. This not only prevents memory leaks, but also accidental double-subscribes as we have an `IDisposable` to represent the subscription state. --- .../Presenters/ItemsPresenterBase.cs | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 5a56e52029..e9dc75a236 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Specialized; +using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Templates; using Avalonia.Styling; @@ -40,6 +41,7 @@ namespace Avalonia.Controls.Presenters ItemsControl.MemberSelectorProperty.AddOwner(); private IEnumerable _items; + private IDisposable _itemsSubscription; private bool _createdPanel; private IItemContainerGenerator _generator; @@ -63,24 +65,12 @@ namespace Avalonia.Controls.Presenters set { - if (_createdPanel) - { - INotifyCollectionChanged incc = _items as INotifyCollectionChanged; - - if (incc != null) - { - incc.CollectionChanged -= ItemsCollectionChanged; - } - } + _itemsSubscription?.Dispose(); + _itemsSubscription = null; - if (_createdPanel && value != null) + if (_createdPanel && value is INotifyCollectionChanged incc) { - INotifyCollectionChanged incc = value as INotifyCollectionChanged; - - if (incc != null) - { - incc.CollectionChanged += ItemsCollectionChanged; - } + _itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged); } SetAndRaise(ItemsProperty, ref _items, value); @@ -233,11 +223,9 @@ namespace Avalonia.Controls.Presenters _createdPanel = true; - INotifyCollectionChanged incc = Items as INotifyCollectionChanged; - - if (incc != null) + if (_itemsSubscription == null && Items is INotifyCollectionChanged incc) { - incc.CollectionChanged += ItemsCollectionChanged; + _itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged); } PanelCreated(Panel); @@ -263,4 +251,4 @@ namespace Avalonia.Controls.Presenters (e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this); } } -} \ No newline at end of file +} From 881069fd5d932a10b02197be18e5ae9a4a92f623 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 19 Jul 2018 07:05:21 +0200 Subject: [PATCH 10/30] Introduce RenderOptions --- .gitignore | 3 +- Avalonia.sln | 42 -------------------------- src/Avalonia.Controls/Image.cs | 17 ++--------- src/Avalonia.Controls/RenderOptions.cs | 36 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 58 deletions(-) create mode 100644 src/Avalonia.Controls/RenderOptions.cs diff --git a/.gitignore b/.gitignore index f0cbd79cbc..dd814ba26f 100644 --- a/.gitignore +++ b/.gitignore @@ -181,5 +181,4 @@ project.lock.json ################## ## BenchmarkDotNet ################## -BenchmarkDotNet.Artifacts/ -/src/ImageInterpRepro +BenchmarkDotNet.Artifacts/ \ No newline at end of file diff --git a/Avalonia.sln b/Avalonia.sln index 7938b95ac5..d1c5026e58 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -187,8 +187,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp.N EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageInterpRepro", "src\ImageInterpRepro\ImageInterpRepro.csproj", "{54A1062E-126E-4813-98A7-8DB9D949CD93}" -EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -2516,46 +2514,6 @@ Global {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|NetCoreOnly.Build.0 = Release|Any CPU {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.ActiveCfg = Release|Any CPU {E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.Build.0 = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|NetCoreOnly.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|NetCoreOnly.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhone.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|NetCoreOnly.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|NetCoreOnly.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|x86.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.AppStore|x86.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhone.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|x86.ActiveCfg = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Debug|x86.Build.0 = Debug|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|Any CPU.Build.0 = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhone.ActiveCfg = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhone.Build.0 = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|NetCoreOnly.Build.0 = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|x86.ActiveCfg = Release|Any CPU - {54A1062E-126E-4813-98A7-8DB9D949CD93}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index a9f24f5ce4..beddc75917 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -26,17 +26,10 @@ namespace Avalonia.Controls public static readonly StyledProperty StretchProperty = AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform); - /// - /// Defines the property. - /// - public static readonly StyledProperty ScalingModeProperty = - AvaloniaProperty.Register(nameof(BitmapScalingMode)); - static Image() { AffectsRender(SourceProperty); AffectsRender(StretchProperty); - AffectsRender(ScalingModeProperty); } /// @@ -57,12 +50,6 @@ namespace Avalonia.Controls set { SetValue(StretchProperty, value); } } - public BitmapScalingMode ScalingMode - { - get { return (BitmapScalingMode)GetValue(ScalingModeProperty); } - set { SetValue(ScalingModeProperty, value); } - } - /// /// Renders the control. /// @@ -83,7 +70,9 @@ namespace Avalonia.Controls Rect sourceRect = new Rect(sourceSize) .CenterRect(new Rect(destRect.Size / scale)); - context.DrawImage(source, 1, sourceRect, destRect, ScalingMode); + var scalingMode = RenderOptions.GetBitmapScalingMode(this); + + context.DrawImage(source, 1, sourceRect, destRect, scalingMode); } } diff --git a/src/Avalonia.Controls/RenderOptions.cs b/src/Avalonia.Controls/RenderOptions.cs new file mode 100644 index 0000000000..dff1d940d0 --- /dev/null +++ b/src/Avalonia.Controls/RenderOptions.cs @@ -0,0 +1,36 @@ +namespace Avalonia.Controls +{ + using Avalonia.Visuals.Media.Imaging; + + public class RenderOptions + { + /// + /// Defines the property. + /// + public static readonly StyledProperty BitmapScalingModeProperty = + AvaloniaProperty.RegisterAttached( + "BitmapScalingMode", + BitmapScalingMode.LowQuality, + inherits: true); + + /// + /// Gets the value of the BitmapScalingMode attached property for a control. + /// + /// The control. + /// The control's left coordinate. + public static BitmapScalingMode GetBitmapScalingMode(AvaloniaObject element) + { + return element.GetValue(BitmapScalingModeProperty); + } + + /// + /// Sets the value of the BitmapScalingMode attached property for a control. + /// + /// The control. + /// The left value. + public static void SetBitmapScalingMode(AvaloniaObject element, double value) + { + element.SetValue(BitmapScalingModeProperty, value); + } + } +} From 4d525aac6cded30a6b97a28e36584f858a8defc1 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 19 Jul 2018 09:50:14 +0200 Subject: [PATCH 11/30] Code style --- src/Avalonia.Controls/Remote/RemoteWidget.cs | 4 +++- src/Avalonia.Controls/RenderOptions.cs | 10 +++++---- src/Avalonia.Visuals/Media/DrawingContext.cs | 5 ++--- .../Media/Imaging/BitmapScalingMode.cs | 18 +++++++++++++-- .../Platform/IDrawingContextImpl.cs | 4 ++-- .../SceneGraph/DeferredDrawingContextImpl.cs | 6 ++--- .../Rendering/SceneGraph/ImageNode.cs | 22 +++++++++---------- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- .../Media/DrawingContextImpl.cs | 8 +++---- .../Rendering/DeferredRendererTests.cs | 4 +++- .../SceneGraph/DrawOperationTests.cs | 10 ++++++++- 11 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index ea8c3ebe52..594208b51c 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -10,6 +10,8 @@ using PixelFormat = Avalonia.Platform.PixelFormat; namespace Avalonia.Controls.Remote { + using Avalonia.Visuals.Media.Imaging; + public class RemoteWidget : Control { private readonly IAvaloniaRemoteTransportConnection _connection; @@ -76,4 +78,4 @@ namespace Avalonia.Controls.Remote base.Render(context); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/RenderOptions.cs b/src/Avalonia.Controls/RenderOptions.cs index dff1d940d0..26f7ed260b 100644 --- a/src/Avalonia.Controls/RenderOptions.cs +++ b/src/Avalonia.Controls/RenderOptions.cs @@ -1,7 +1,10 @@ -namespace Avalonia.Controls -{ - using Avalonia.Visuals.Media.Imaging; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Controls +{ public class RenderOptions { /// @@ -10,7 +13,6 @@ public static readonly StyledProperty BitmapScalingModeProperty = AvaloniaProperty.RegisterAttached( "BitmapScalingMode", - BitmapScalingMode.LowQuality, inherits: true); /// diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 9cb8712f5b..85e165be36 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; using Avalonia.Media.Imaging; using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media -{ - using Avalonia.Visuals.Media.Imaging; - +{ public sealed class DrawingContext : IDisposable { private int _currentLevel; diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs index 36d239c9d9..32b98c92a3 100644 --- a/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs @@ -3,10 +3,24 @@ namespace Avalonia.Visuals.Media.Imaging { + /// + /// Controls the performance and quality of bitmap scaling. + /// public enum BitmapScalingMode { - LowQuality, + /// + /// Highest quality but worst performance. + /// + HighQuality, + + /// + /// Good performance an decent image quality. + /// MediumQuality, - HighQuality + + /// + /// The best performance but worst image quality. + /// + LowQuality } } diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index fbd742f6c3..7f0b2a9904 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -32,8 +32,8 @@ namespace Avalonia.Platform /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// Controls - void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = default); + /// Controls + void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode = default); /// /// Draws a bitmap image. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 7172696e81..ac69687e60 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -116,13 +116,13 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = BitmapScalingMode.LowQuality) + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) { var next = NextDrawAs(); - if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, scalingMode)) + if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapScalingMode)) { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, scalingMode)); + Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapScalingMode)); } else { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 96a8731715..e896d9a015 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -22,8 +22,8 @@ namespace Avalonia.Rendering.SceneGraph /// The draw opacity. /// The source rect. /// The destination rect. - /// - public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) + /// The bitmap scaling mode. + public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) : base(destRect, transform, null) { Transform = transform; @@ -31,10 +31,8 @@ namespace Avalonia.Rendering.SceneGraph Opacity = opacity; SourceRect = sourceRect; DestRect = destRect; - ScalingMode = scalingMode; - } - - + BitmapScalingMode = bitmapScalingMode; + } /// /// Gets the transform with which the node will be drawn. @@ -62,12 +60,12 @@ namespace Avalonia.Rendering.SceneGraph public Rect DestRect { get; } /// - /// Gets or sets the scaling mode. + /// Gets the bitmap scaling mode. /// /// /// The scaling mode. /// - public BitmapScalingMode ScalingMode { get; } + public BitmapScalingMode BitmapScalingMode { get; } /// /// Determines if this draw operation equals another. @@ -77,20 +75,20 @@ namespace Avalonia.Rendering.SceneGraph /// The opacity of the other draw operation. /// The source rect of the other draw operation. /// The dest rect of the other draw operation. - /// + /// /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) + public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) { return transform == Transform && Equals(source.Item, Source.Item) && opacity == Opacity && sourceRect == SourceRect && destRect == DestRect && - scalingMode == ScalingMode; + bitmapScalingMode == BitmapScalingMode; } /// @@ -99,7 +97,7 @@ namespace Avalonia.Rendering.SceneGraph // TODO: Probably need to introduce some kind of locking mechanism in the case of // WriteableBitmap. context.Transform = Transform; - context.DrawImage(Source, Opacity, SourceRect, DestRect, ScalingMode); + context.DrawImage(Source, Opacity, SourceRect, DestRect, BitmapScalingMode); } /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 5878038a81..d119998f10 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -97,7 +97,7 @@ namespace Avalonia.Skia } /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) { var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); @@ -109,7 +109,7 @@ namespace Avalonia.Skia Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) }) { - paint.FilterQuality = GetInterpolationMode(scalingMode); + paint.FilterQuality = GetInterpolationMode(bitmapScalingMode); drawableImage.Draw(this, s, d, paint); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 27115e164a..d49ef2cd89 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -102,18 +102,18 @@ namespace Avalonia.Direct2D1.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode) + /// + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) { using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) { if (_renderTarget is DeviceContext deviceContext) { - deviceContext.DrawBitmap(d2d.Value, destRect.ToDirect2D(), (float)opacity, GetInterpolationMode(scalingMode), sourceRect.ToDirect2D(), null); + deviceContext.DrawBitmap(d2d.Value, destRect.ToDirect2D(), (float)opacity, GetInterpolationMode(bitmapScalingMode), sourceRect.ToDirect2D(), null); } else { - var interpolationMode = scalingMode == BitmapScalingMode.LowQuality + var interpolationMode = bitmapScalingMode == BitmapScalingMode.LowQuality ? BitmapInterpolationMode.NearestNeighbor : BitmapInterpolationMode.Linear; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 7821f60a51..655888cf31 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -16,6 +16,8 @@ using Xunit; namespace Avalonia.Visuals.UnitTests.Rendering { + using Avalonia.Visuals.Media.Imaging; + public class DeferredRendererTests { [Fact] @@ -336,7 +338,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null)); var borderLayer = target.Layers[border].Bitmap; - context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny(), It.IsAny())); + context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny(), It.IsAny(), default(BitmapScalingMode))); } private DeferredRenderer CreateTargetAndRunFrame( diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 8c905dab2f..996cb289c8 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -8,6 +8,8 @@ using Xunit; namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { + using Avalonia.Visuals.Media.Imaging; + public class DrawOperationTests { [Fact] @@ -46,7 +48,13 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph public void Image_Node_Releases_Reference_To_Bitmap_On_Dispose() { var bitmap = RefCountable.Create(Mock.Of()); - var imageNode = new ImageNode(Matrix.Identity, bitmap, 1, new Rect(1,1,1,1), new Rect(1,1,1,1)); + var imageNode = new ImageNode( + Matrix.Identity, + bitmap, + 1, + new Rect(1, 1, 1, 1), + new Rect(1, 1, 1, 1), + BitmapScalingMode.LowQuality); Assert.Equal(2, bitmap.RefCount); From f6c9aa88b93c2b00e2b84ab951bba846e583b375 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 19 Jul 2018 10:04:28 +0200 Subject: [PATCH 12/30] Naming fixes --- src/Avalonia.Controls/Remote/RemoteWidget.cs | 4 +--- src/Avalonia.Visuals/Media/DrawingContext.cs | 6 +++--- src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs | 2 +- .../Rendering/SceneGraph/DeferredDrawingContextImpl.cs | 3 +-- src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs | 5 ++--- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 5 ++--- src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 3 +-- .../Rendering/DeferredRendererTests.cs | 6 ++++-- .../Rendering/SceneGraph/DrawOperationTests.cs | 5 ++--- 9 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index 594208b51c..618a0c3f62 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -9,9 +9,7 @@ using Avalonia.Threading; using PixelFormat = Avalonia.Platform.PixelFormat; namespace Avalonia.Controls.Remote -{ - using Avalonia.Visuals.Media.Imaging; - +{ public class RemoteWidget : Control { private readonly IAvaloniaRemoteTransportConnection _connection; diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 85e165be36..0d7d41e46e 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -69,12 +69,12 @@ namespace Avalonia.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// - public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode scalingMode = default) + /// + public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode = default) { Contract.Requires(source != null); - PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect, scalingMode); + PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect, bitmapScalingMode); } /// diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs index 32b98c92a3..5c91dd993d 100644 --- a/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs @@ -14,7 +14,7 @@ namespace Avalonia.Visuals.Media.Imaging HighQuality, /// - /// Good performance an decent image quality. + /// Good performance and decent image quality. /// MediumQuality, diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index ac69687e60..fbd258fdbe 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -7,11 +7,10 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Utilities; using Avalonia.VisualTree; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Rendering.SceneGraph { - using Avalonia.Visuals.Media.Imaging; - /// /// A drawing context which builds a scene graph. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index e896d9a015..db5c672b97 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -4,11 +4,10 @@ using System; using Avalonia.Platform; using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Rendering.SceneGraph { - using Avalonia.Visuals.Media.Imaging; - /// /// A node in the scene graph which represents an image draw. /// @@ -75,7 +74,7 @@ namespace Avalonia.Rendering.SceneGraph /// The opacity of the other draw operation. /// The source rect of the other draw operation. /// The dest rect of the other draw operation. - /// + /// The bitmap scaling mode. /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 15ebf31754..f0e56a80cf 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -10,12 +10,11 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia -{ - using Avalonia.Visuals.Media.Imaging; - +{ /// /// Skia based drawing context. /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 801f72b881..0bad70991d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -7,14 +7,13 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; namespace Avalonia.Direct2D1.Media { - using Avalonia.Visuals.Media.Imaging; - /// /// Draws using Direct2D1. /// diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 655888cf31..12ca2777ec 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Subjects; + using Avalonia.Controls; using Avalonia.Data; using Avalonia.Media; @@ -10,14 +11,15 @@ using Avalonia.Rendering; using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; using Avalonia.UnitTests; +using Avalonia.Visuals.Media.Imaging; using Avalonia.VisualTree; + using Moq; + using Xunit; namespace Avalonia.Visuals.UnitTests.Rendering { - using Avalonia.Visuals.Media.Imaging; - public class DeferredRendererTests { [Fact] diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 996cb289c8..664d240966 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -3,13 +3,12 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; using Moq; using Xunit; namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph -{ - using Avalonia.Visuals.Media.Imaging; - +{ public class DrawOperationTests { [Fact] From c0dd75bcce6ae22d67e15da6a290c218fa9ec20e Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Fri, 20 Jul 2018 09:40:37 +0200 Subject: [PATCH 13/30] Refactoring Add interpolation to TileBrush --- src/Avalonia.Controls/Image.cs | 6 +-- src/Avalonia.Controls/RenderOptions.cs | 38 -------------- src/Avalonia.Visuals/Media/DrawingContext.cs | 6 +-- src/Avalonia.Visuals/Media/ITileBrush.cs | 12 ++++- ...lingMode.cs => BitmapInterpolationMode.cs} | 19 ++++--- .../Media/Immutable/ImmutableImageBrush.cs | 11 +++-- .../Media/Immutable/ImmutableTileBrush.cs | 16 ++++-- .../Media/Immutable/ImmutableVisualBrush.cs | 12 +++-- src/Avalonia.Visuals/Media/RenderOptions.cs | 38 ++++++++++++++ src/Avalonia.Visuals/Media/TileBrush.cs | 15 ++++++ .../Platform/IDrawingContextImpl.cs | 4 +- .../SceneGraph/DeferredDrawingContextImpl.cs | 6 +-- .../Rendering/SceneGraph/ImageNode.cs | 16 +++--- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 33 +++++++------ .../Media/DrawingContextImpl.cs | 49 ++++++++----------- .../Media/ImageBrushImpl.cs | 9 ++-- .../Rendering/DeferredRendererTests.cs | 2 +- .../SceneGraph/DrawOperationTests.cs | 5 +- 18 files changed, 172 insertions(+), 125 deletions(-) delete mode 100644 src/Avalonia.Controls/RenderOptions.cs rename src/Avalonia.Visuals/Media/Imaging/{BitmapScalingMode.cs => BitmapInterpolationMode.cs} (71%) create mode 100644 src/Avalonia.Visuals/Media/RenderOptions.cs diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index beddc75917..ffccfdf081 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -1,14 +1,12 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Visuals.Media; namespace Avalonia.Controls -{ - using Avalonia.Visuals.Media.Imaging; - +{ /// /// Displays a image. /// diff --git a/src/Avalonia.Controls/RenderOptions.cs b/src/Avalonia.Controls/RenderOptions.cs deleted file mode 100644 index 26f7ed260b..0000000000 --- a/src/Avalonia.Controls/RenderOptions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using Avalonia.Visuals.Media.Imaging; - -namespace Avalonia.Controls -{ - public class RenderOptions - { - /// - /// Defines the property. - /// - public static readonly StyledProperty BitmapScalingModeProperty = - AvaloniaProperty.RegisterAttached( - "BitmapScalingMode", - inherits: true); - - /// - /// Gets the value of the BitmapScalingMode attached property for a control. - /// - /// The control. - /// The control's left coordinate. - public static BitmapScalingMode GetBitmapScalingMode(AvaloniaObject element) - { - return element.GetValue(BitmapScalingModeProperty); - } - - /// - /// Sets the value of the BitmapScalingMode attached property for a control. - /// - /// The control. - /// The left value. - public static void SetBitmapScalingMode(AvaloniaObject element, double value) - { - element.SetValue(BitmapScalingModeProperty, value); - } - } -} diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 0d7d41e46e..173801720d 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -69,12 +69,12 @@ namespace Avalonia.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// - public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode = default) + /// + public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default) { Contract.Requires(source != null); - PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect, bitmapScalingMode); + PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect, bitmapInterpolationMode); } /// diff --git a/src/Avalonia.Visuals/Media/ITileBrush.cs b/src/Avalonia.Visuals/Media/ITileBrush.cs index 8e2349f506..6c29b159de 100644 --- a/src/Avalonia.Visuals/Media/ITileBrush.cs +++ b/src/Avalonia.Visuals/Media/ITileBrush.cs @@ -1,5 +1,7 @@ namespace Avalonia.Media { + using Avalonia.Visuals.Media.Imaging; + /// /// A brush which displays a repeating image. /// @@ -35,5 +37,13 @@ /// Gets the brush's tile mode. /// TileMode TileMode { get; } + + /// + /// Gets the bitmap interpolation mode. + /// + /// + /// The bitmap interpolation mode. + /// + BitmapInterpolationMode BitmapInterpolationMode { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapInterpolationMode.cs similarity index 71% rename from src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs rename to src/Avalonia.Visuals/Media/Imaging/BitmapInterpolationMode.cs index 5c91dd993d..7e6d330618 100644 --- a/src/Avalonia.Visuals/Media/Imaging/BitmapScalingMode.cs +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapInterpolationMode.cs @@ -6,21 +6,26 @@ namespace Avalonia.Visuals.Media.Imaging /// /// Controls the performance and quality of bitmap scaling. /// - public enum BitmapScalingMode + public enum BitmapInterpolationMode { /// - /// Highest quality but worst performance. + /// Uses the default behavior of the underling render backend. + /// + Default, + + /// + /// The best performance but worst image quality. /// - HighQuality, - + LowQuality, + /// /// Good performance and decent image quality. /// - MediumQuality, + MediumQuality, /// - /// The best performance but worst image quality. + /// Highest quality but worst performance. /// - LowQuality + HighQuality } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs index 678f99c20d..15da8f8b43 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs @@ -1,5 +1,5 @@ -using System; -using Avalonia.Media.Imaging; +using Avalonia.Media.Imaging; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media.Immutable { @@ -21,6 +21,7 @@ namespace Avalonia.Media.Immutable /// How the source rectangle will be stretched to fill the destination rect. /// /// The tile mode. + /// The bitmap interpolation mode. public ImmutableImageBrush( IBitmap source, AlignmentX alignmentX = AlignmentX.Center, @@ -29,7 +30,8 @@ namespace Avalonia.Media.Immutable double opacity = 1, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, - TileMode tileMode = TileMode.None) + TileMode tileMode = TileMode.None, + BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) : base( alignmentX, alignmentY, @@ -37,7 +39,8 @@ namespace Avalonia.Media.Immutable opacity, sourceRect ?? RelativeRect.Fill, stretch, - tileMode) + tileMode, + bitmapInterpolationMode) { Source = source; } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs index 37c391040f..c7a6a168d7 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Media.Immutable { @@ -19,6 +22,7 @@ namespace Avalonia.Media.Immutable /// How the source rectangle will be stretched to fill the destination rect. /// /// The tile mode. + /// Controls the quality of interpolation. protected ImmutableTileBrush( AlignmentX alignmentX, AlignmentY alignmentY, @@ -26,7 +30,8 @@ namespace Avalonia.Media.Immutable double opacity, RelativeRect sourceRect, Stretch stretch, - TileMode tileMode) + TileMode tileMode, + BitmapInterpolationMode bitmapInterpolationMode) { AlignmentX = alignmentX; AlignmentY = alignmentY; @@ -35,6 +40,7 @@ namespace Avalonia.Media.Immutable SourceRect = sourceRect; Stretch = stretch; TileMode = tileMode; + BitmapInterpolationMode = bitmapInterpolationMode; } /// @@ -49,7 +55,8 @@ namespace Avalonia.Media.Immutable source.Opacity, source.SourceRect, source.Stretch, - source.TileMode) + source.TileMode, + source.BitmapInterpolationMode) { } @@ -73,5 +80,8 @@ namespace Avalonia.Media.Immutable /// public TileMode TileMode { get; } + + /// + public BitmapInterpolationMode BitmapInterpolationMode { get; } } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs index b07d98867c..f1f61a6e65 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Visuals.Media.Imaging; using Avalonia.VisualTree; namespace Avalonia.Media.Immutable @@ -21,6 +24,7 @@ namespace Avalonia.Media.Immutable /// How the source rectangle will be stretched to fill the destination rect. /// /// The tile mode. + /// Controls the quality of interpolation. public ImmutableVisualBrush( IVisual visual, AlignmentX alignmentX = AlignmentX.Center, @@ -29,7 +33,8 @@ namespace Avalonia.Media.Immutable double opacity = 1, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, - TileMode tileMode = TileMode.None) + TileMode tileMode = TileMode.None, + BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) : base( alignmentX, alignmentY, @@ -37,7 +42,8 @@ namespace Avalonia.Media.Immutable opacity, sourceRect ?? RelativeRect.Fill, stretch, - tileMode) + tileMode, + bitmapInterpolationMode) { Visual = visual; } diff --git a/src/Avalonia.Visuals/Media/RenderOptions.cs b/src/Avalonia.Visuals/Media/RenderOptions.cs new file mode 100644 index 0000000000..7efe1c7349 --- /dev/null +++ b/src/Avalonia.Visuals/Media/RenderOptions.cs @@ -0,0 +1,38 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Visuals.Media +{ + public class RenderOptions + { + /// + /// Defines the property. + /// + public static readonly StyledProperty BitmapInterpolationMode = + AvaloniaProperty.RegisterAttached( + "BitmapInterpolationMode", + inherits: true); + + /// + /// Gets the value of the BitmapInterpolationMode attached property for a control. + /// + /// The control. + /// The control's left coordinate. + public static BitmapInterpolationMode GetBitmapScalingMode(AvaloniaObject element) + { + return element.GetValue(BitmapInterpolationMode); + } + + /// + /// Sets the value of the BitmapInterpolationMode attached property for a control. + /// + /// The control. + /// The left value. + public static void SetBitmapScalingMode(AvaloniaObject element, BitmapInterpolationMode value) + { + element.SetValue(BitmapInterpolationMode, value); + } + } +} diff --git a/src/Avalonia.Visuals/Media/TileBrush.cs b/src/Avalonia.Visuals/Media/TileBrush.cs index 3a7f9d9920..5a6da8f2e1 100644 --- a/src/Avalonia.Visuals/Media/TileBrush.cs +++ b/src/Avalonia.Visuals/Media/TileBrush.cs @@ -3,6 +3,9 @@ namespace Avalonia.Media { + using Avalonia.Visuals.Media; + using Avalonia.Visuals.Media.Imaging; + /// /// Describes how a is tiled. /// @@ -129,5 +132,17 @@ namespace Avalonia.Media get { return (TileMode)GetValue(TileModeProperty); } set { SetValue(TileModeProperty, value); } } + + /// + /// Gets or sets the bitmap interpolation mode. + /// + /// + /// The bitmap interpolation mode. + /// + public BitmapInterpolationMode BitmapInterpolationMode + { + get { return RenderOptions.GetBitmapScalingMode(this); } + set { RenderOptions.SetBitmapScalingMode(this, value); } + } } } diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 7f0b2a9904..4ddbbf4dd5 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -32,8 +32,8 @@ namespace Avalonia.Platform /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// Controls - void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode = default); + /// Controls + void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default); /// /// Draws a bitmap image. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index fbd258fdbe..42ecde6a6d 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -115,13 +115,13 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { var next = NextDrawAs(); - if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapScalingMode)) + if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)) { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapScalingMode)); + Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)); } else { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index db5c672b97..35fad25276 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -21,8 +21,8 @@ namespace Avalonia.Rendering.SceneGraph /// The draw opacity. /// The source rect. /// The destination rect. - /// The bitmap scaling mode. - public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) + /// The bitmap scaling mode. + public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) : base(destRect, transform, null) { Transform = transform; @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph Opacity = opacity; SourceRect = sourceRect; DestRect = destRect; - BitmapScalingMode = bitmapScalingMode; + BitmapInterpolationMode = bitmapInterpolationMode; } /// @@ -64,7 +64,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The scaling mode. /// - public BitmapScalingMode BitmapScalingMode { get; } + public BitmapInterpolationMode BitmapInterpolationMode { get; } /// /// Determines if this draw operation equals another. @@ -74,20 +74,20 @@ namespace Avalonia.Rendering.SceneGraph /// The opacity of the other draw operation. /// The source rect of the other draw operation. /// The dest rect of the other draw operation. - /// The bitmap scaling mode. + /// The bitmap scaling mode. /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) + public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { return transform == Transform && Equals(source.Item, Source.Item) && opacity == Opacity && sourceRect == SourceRect && destRect == DestRect && - bitmapScalingMode == BitmapScalingMode; + bitmapInterpolationMode == BitmapInterpolationMode; } /// @@ -96,7 +96,7 @@ namespace Avalonia.Rendering.SceneGraph // TODO: Probably need to introduce some kind of locking mechanism in the case of // WriteableBitmap. context.Transform = Transform; - context.DrawImage(Source, Opacity, SourceRect, DestRect, BitmapScalingMode); + context.DrawImage(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode); } /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index f0e56a80cf..7903e6cf6e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -14,7 +14,9 @@ using Avalonia.Visuals.Media.Imaging; using SkiaSharp; namespace Avalonia.Skia -{ +{ + using Avalonia.Controls; + /// /// Skia based drawing context. /// @@ -96,7 +98,7 @@ namespace Avalonia.Skia } /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); @@ -108,24 +110,26 @@ namespace Avalonia.Skia Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) }) { - paint.FilterQuality = GetInterpolationMode(bitmapScalingMode); + paint.FilterQuality = GetInterpolationMode(bitmapInterpolationMode); drawableImage.Draw(this, s, d, paint); } } - private static SKFilterQuality GetInterpolationMode(BitmapScalingMode scalingMode) + private static SKFilterQuality GetInterpolationMode(BitmapInterpolationMode interpolationMode) { - switch (scalingMode) + switch (interpolationMode) { - case BitmapScalingMode.LowQuality: + case BitmapInterpolationMode.LowQuality: return SKFilterQuality.Low; - case BitmapScalingMode.MediumQuality: + case BitmapInterpolationMode.MediumQuality: return SKFilterQuality.Medium; - case BitmapScalingMode.HighQuality: + case BitmapInterpolationMode.HighQuality: return SKFilterQuality.High; + case BitmapInterpolationMode.Default: + return SKFilterQuality.None; default: - throw new ArgumentOutOfRangeException(nameof(scalingMode), scalingMode, null); + throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); } } @@ -133,7 +137,7 @@ namespace Avalonia.Skia public void DrawImage(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { PushOpacityMask(opacityMask, opacityMaskRect); - DrawImage(source, 1, new Rect(0, 0, source.Item.PixelWidth, source.Item.PixelHeight), destRect, default(BitmapScalingMode)); + DrawImage(source, 1, new Rect(0, 0, source.Item.PixelWidth, source.Item.PixelHeight), destRect, BitmapInterpolationMode.Default); PopOpacityMask(); } @@ -378,7 +382,8 @@ namespace Avalonia.Skia /// Target size. /// Tile brush to use. /// Tile brush image. - private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage) + /// The bitmap interpolation mode. + private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage, BitmapInterpolationMode interpolationMode) { var calc = new TileBrushCalculator(tileBrush, new Size(tileBrushImage.PixelWidth, tileBrushImage.PixelHeight), targetSize); @@ -396,7 +401,7 @@ namespace Avalonia.Skia context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); context.Transform = calc.IntermediateTransform; - context.DrawImage(RefCountable.CreateUnownedNotClonable(tileBrushImage), 1, rect, rect); + context.DrawImage(RefCountable.CreateUnownedNotClonable(tileBrushImage), 1, rect, rect, interpolationMode); context.PopClip(); } @@ -505,12 +510,12 @@ namespace Avalonia.Skia } else { - tileBrushImage = (IDrawableBitmapImpl) (tileBrush as IImageBrush)?.Source?.PlatformImpl.Item; + tileBrushImage = (IDrawableBitmapImpl)(tileBrush as IImageBrush)?.Source?.PlatformImpl.Item; } if (tileBrush != null && tileBrushImage != null) { - ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage); + ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage, tileBrush.BitmapInterpolationMode); } else { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 0bad70991d..bf379bac5c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -14,6 +14,8 @@ using SharpDX.Mathematics.Interop; namespace Avalonia.Direct2D1.Media { + using BitmapInterpolationMode = Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode; + /// /// Draws using Direct2D1. /// @@ -101,43 +103,34 @@ namespace Avalonia.Direct2D1.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// - public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapScalingMode bitmapScalingMode) + /// + public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) { - if (_renderTarget is DeviceContext deviceContext) - { - deviceContext.DrawBitmap(d2d.Value, destRect.ToDirect2D(), (float)opacity, GetInterpolationMode(bitmapScalingMode), sourceRect.ToDirect2D(), null); - } - else - { - var interpolationMode = bitmapScalingMode == BitmapScalingMode.LowQuality - ? BitmapInterpolationMode.NearestNeighbor - : BitmapInterpolationMode.Linear; - - _renderTarget.DrawBitmap( - d2d.Value, - destRect.ToSharpDX(), - (float)opacity, - interpolationMode, - sourceRect.ToSharpDX()); - } + var interpolationMode = GetInterpolationMode(bitmapInterpolationMode); + + _renderTarget.DrawBitmap( + d2d.Value, + destRect.ToSharpDX(), + (float)opacity, + interpolationMode, + sourceRect.ToSharpDX()); } } - private static InterpolationMode GetInterpolationMode(BitmapScalingMode scalingMode) + private static SharpDX.Direct2D1.BitmapInterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode) { - switch (scalingMode) + switch (interpolationMode) { - case BitmapScalingMode.LowQuality: - return InterpolationMode.NearestNeighbor; - case BitmapScalingMode.MediumQuality: - return InterpolationMode.Linear; - case BitmapScalingMode.HighQuality: - return InterpolationMode.HighQualityCubic; + case BitmapInterpolationMode.LowQuality: + return SharpDX.Direct2D1.BitmapInterpolationMode.NearestNeighbor; + case BitmapInterpolationMode.MediumQuality: + case BitmapInterpolationMode.HighQuality: + case BitmapInterpolationMode.Default: + return SharpDX.Direct2D1.BitmapInterpolationMode.Linear; default: - throw new ArgumentOutOfRangeException(nameof(scalingMode), scalingMode, null); + throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index 75b397edd6..3fc8760a9e 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using Avalonia.Media; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; @@ -11,7 +10,9 @@ namespace Avalonia.Direct2D1.Media { public sealed class ImageBrushImpl : BrushImpl { - OptionalDispose _bitmap; + private readonly OptionalDispose _bitmap; + + private readonly Visuals.Media.Imaging.BitmapInterpolationMode _bitmapInterpolationMode; public ImageBrushImpl( ITileBrush brush, @@ -41,6 +42,8 @@ namespace Avalonia.Direct2D1.Media GetBrushProperties(brush, calc.DestinationRect)); } } + + _bitmapInterpolationMode = brush.BitmapInterpolationMode; } public override void Dispose() @@ -102,7 +105,7 @@ namespace Avalonia.Direct2D1.Media context.PushClip(calc.IntermediateClip); context.Transform = calc.IntermediateTransform; - context.DrawImage(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect); + context.DrawImage(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect, _bitmapInterpolationMode); context.PopClip(); } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 12ca2777ec..2350a31d5c 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -340,7 +340,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null)); var borderLayer = target.Layers[border].Bitmap; - context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny(), It.IsAny(), default(BitmapScalingMode))); + context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny(), It.IsAny(), BitmapInterpolationMode.Default)); } private DeferredRenderer CreateTargetAndRunFrame( diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 664d240966..2060cc7170 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -1,5 +1,4 @@ -using System; -using Avalonia.Media; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; @@ -53,7 +52,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph 1, new Rect(1, 1, 1, 1), new Rect(1, 1, 1, 1), - BitmapScalingMode.LowQuality); + BitmapInterpolationMode.Default); Assert.Equal(2, bitmap.RefCount); From 5b5971aff37497df9a19fec944126dc2632bedc2 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sun, 22 Jul 2018 19:15:12 +0200 Subject: [PATCH 14/30] Comment fixes --- .gitignore | 2 +- src/Avalonia.Visuals/Media/DrawingContext.cs | 2 +- src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs | 2 +- src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs | 2 +- src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs | 6 +++--- src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index dd814ba26f..583a2b8a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -181,4 +181,4 @@ project.lock.json ################## ## BenchmarkDotNet ################## -BenchmarkDotNet.Artifacts/ \ No newline at end of file +BenchmarkDotNet.Artifacts/ diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 173801720d..60a7a2e518 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -69,7 +69,7 @@ namespace Avalonia.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// + /// The bitmap interpolation mode. public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default) { Contract.Requires(source != null); diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs index c7a6a168d7..c3dd159d04 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs @@ -22,7 +22,7 @@ namespace Avalonia.Media.Immutable /// How the source rectangle will be stretched to fill the destination rect. /// /// The tile mode. - /// Controls the quality of interpolation. + /// The bitmap interpolation mode. protected ImmutableTileBrush( AlignmentX alignmentX, AlignmentY alignmentY, diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 4ddbbf4dd5..3ffcde7165 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -32,7 +32,7 @@ namespace Avalonia.Platform /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// Controls + /// The bitmap interpolation mode. void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default); /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 35fad25276..9e8fca5f84 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -21,7 +21,7 @@ namespace Avalonia.Rendering.SceneGraph /// The draw opacity. /// The source rect. /// The destination rect. - /// The bitmap scaling mode. + /// The bitmap interpolation mode. public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) : base(destRect, transform, null) { @@ -59,7 +59,7 @@ namespace Avalonia.Rendering.SceneGraph public Rect DestRect { get; } /// - /// Gets the bitmap scaling mode. + /// Gets the bitmap interpolation mode. /// /// /// The scaling mode. @@ -74,7 +74,7 @@ namespace Avalonia.Rendering.SceneGraph /// The opacity of the other draw operation. /// The source rect of the other draw operation. /// The dest rect of the other draw operation. - /// The bitmap scaling mode. + /// The bitmap interpolation mode. /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index bf379bac5c..659c89bb58 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -103,7 +103,7 @@ namespace Avalonia.Direct2D1.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// + /// The bitmap interpolation mode. public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) From 81008bd5f7189c07b5edb6b2d7de3881f73f15c7 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Sun, 22 Jul 2018 19:42:24 +0200 Subject: [PATCH 15/30] Naming fixes --- src/Avalonia.Controls/Image.cs | 2 +- src/Avalonia.Visuals/Media/RenderOptions.cs | 8 ++++---- src/Avalonia.Visuals/Media/TileBrush.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index ffccfdf081..50e4f1515e 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -68,7 +68,7 @@ namespace Avalonia.Controls Rect sourceRect = new Rect(sourceSize) .CenterRect(new Rect(destRect.Size / scale)); - var scalingMode = RenderOptions.GetBitmapScalingMode(this); + var scalingMode = RenderOptions.GetBitmapInterpolationMode(this); context.DrawImage(source, 1, sourceRect, destRect, scalingMode); } diff --git a/src/Avalonia.Visuals/Media/RenderOptions.cs b/src/Avalonia.Visuals/Media/RenderOptions.cs index 7efe1c7349..b38ba3f9c9 100644 --- a/src/Avalonia.Visuals/Media/RenderOptions.cs +++ b/src/Avalonia.Visuals/Media/RenderOptions.cs @@ -3,12 +3,12 @@ using Avalonia.Visuals.Media.Imaging; -namespace Avalonia.Visuals.Media +namespace Avalonia.Media { public class RenderOptions { /// - /// Defines the property. + /// Defines the property. /// public static readonly StyledProperty BitmapInterpolationMode = AvaloniaProperty.RegisterAttached( @@ -20,7 +20,7 @@ namespace Avalonia.Visuals.Media /// /// The control. /// The control's left coordinate. - public static BitmapInterpolationMode GetBitmapScalingMode(AvaloniaObject element) + public static BitmapInterpolationMode GetBitmapInterpolationMode(AvaloniaObject element) { return element.GetValue(BitmapInterpolationMode); } @@ -30,7 +30,7 @@ namespace Avalonia.Visuals.Media /// /// The control. /// The left value. - public static void SetBitmapScalingMode(AvaloniaObject element, BitmapInterpolationMode value) + public static void SetBitmapInterpolationMode(AvaloniaObject element, BitmapInterpolationMode value) { element.SetValue(BitmapInterpolationMode, value); } diff --git a/src/Avalonia.Visuals/Media/TileBrush.cs b/src/Avalonia.Visuals/Media/TileBrush.cs index 5a6da8f2e1..506b406856 100644 --- a/src/Avalonia.Visuals/Media/TileBrush.cs +++ b/src/Avalonia.Visuals/Media/TileBrush.cs @@ -141,8 +141,8 @@ namespace Avalonia.Media /// public BitmapInterpolationMode BitmapInterpolationMode { - get { return RenderOptions.GetBitmapScalingMode(this); } - set { RenderOptions.SetBitmapScalingMode(this, value); } + get { return RenderOptions.GetBitmapInterpolationMode(this); } + set { RenderOptions.SetBitmapInterpolationMode(this, value); } } } } From 4c63eb37d2459886b9fbfaba19577e38aa448d7f Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 23 Jul 2018 09:17:06 +0200 Subject: [PATCH 16/30] Namespace fixes --- src/Avalonia.Controls/Image.cs | 1 - src/Avalonia.Controls/Remote/RemoteWidget.cs | 4 ++-- src/Avalonia.Visuals/Media/ITileBrush.cs | 9 ++++++--- src/Avalonia.Visuals/Media/TileBrush.cs | 5 ++--- src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs | 3 +-- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 2 -- .../Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 6 ++---- .../Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs | 10 +++------- 8 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 50e4f1515e..fd90f9f2e8 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -3,7 +3,6 @@ using Avalonia.Media; using Avalonia.Media.Imaging; -using Avalonia.Visuals.Media; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs index 618a0c3f62..ea8c3ebe52 100644 --- a/src/Avalonia.Controls/Remote/RemoteWidget.cs +++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs @@ -9,7 +9,7 @@ using Avalonia.Threading; using PixelFormat = Avalonia.Platform.PixelFormat; namespace Avalonia.Controls.Remote -{ +{ public class RemoteWidget : Control { private readonly IAvaloniaRemoteTransportConnection _connection; @@ -76,4 +76,4 @@ namespace Avalonia.Controls.Remote base.Render(context); } } -} +} \ No newline at end of file diff --git a/src/Avalonia.Visuals/Media/ITileBrush.cs b/src/Avalonia.Visuals/Media/ITileBrush.cs index 6c29b159de..5cffe02193 100644 --- a/src/Avalonia.Visuals/Media/ITileBrush.cs +++ b/src/Avalonia.Visuals/Media/ITileBrush.cs @@ -1,7 +1,10 @@ -namespace Avalonia.Media -{ - using Avalonia.Visuals.Media.Imaging; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Media +{ /// /// A brush which displays a repeating image. /// diff --git a/src/Avalonia.Visuals/Media/TileBrush.cs b/src/Avalonia.Visuals/Media/TileBrush.cs index 506b406856..094208d021 100644 --- a/src/Avalonia.Visuals/Media/TileBrush.cs +++ b/src/Avalonia.Visuals/Media/TileBrush.cs @@ -1,11 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using Avalonia.Visuals.Media.Imaging; + namespace Avalonia.Media { - using Avalonia.Visuals.Media; - using Avalonia.Visuals.Media.Imaging; - /// /// Describes how a is tiled. /// diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 3ffcde7165..debc7103c0 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -4,11 +4,10 @@ using System; using Avalonia.Media; using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; namespace Avalonia.Platform { - using Avalonia.Visuals.Media.Imaging; - /// /// Defines the interface through which drawing occurs. /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 7903e6cf6e..28ac311605 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -15,8 +15,6 @@ using SkiaSharp; namespace Avalonia.Skia { - using Avalonia.Controls; - /// /// Skia based drawing context. /// diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 659c89bb58..ae5dd3ae13 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -7,19 +7,17 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Utilities; -using Avalonia.Visuals.Media.Imaging; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; +using BitmapInterpolationMode = Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode; namespace Avalonia.Direct2D1.Media { - using BitmapInterpolationMode = Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode; - /// /// Draws using Direct2D1. /// - public class DrawingContextImpl : IDrawingContextImpl, IDisposable + public class DrawingContextImpl : IDrawingContextImpl { private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly ILayerFactory _layerFactory; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index c792334ec1..d58f023391 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,15 +1,11 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; +using System; using System.IO; using Avalonia.Platform; using SharpDX.WIC; +using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { - using SharpDX.Direct2D1; - public abstract class BitmapImpl : IBitmapImpl, IDisposable { public BitmapImpl(ImagingFactory imagingFactory) @@ -21,7 +17,7 @@ namespace Avalonia.Direct2D1.Media public abstract int PixelWidth { get; } public abstract int PixelHeight { get; } - public abstract OptionalDispose GetDirect2DBitmap(RenderTarget target); + public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); public void Save(string fileName) { From f4b0e22963eb51a6dd4b0ea111a1bbf64d374fd0 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Mon, 23 Jul 2018 19:06:22 +0200 Subject: [PATCH 17/30] Set default to high quality --- src/Avalonia.Visuals/Media/RenderOptions.cs | 11 ++++++----- src/Avalonia.Visuals/Media/TileBrush.cs | 5 +++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Media/RenderOptions.cs b/src/Avalonia.Visuals/Media/RenderOptions.cs index b38ba3f9c9..5e16f5f9ce 100644 --- a/src/Avalonia.Visuals/Media/RenderOptions.cs +++ b/src/Avalonia.Visuals/Media/RenderOptions.cs @@ -8,11 +8,12 @@ namespace Avalonia.Media public class RenderOptions { /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty BitmapInterpolationMode = + public static readonly StyledProperty BitmapInterpolationModeProperty = AvaloniaProperty.RegisterAttached( - "BitmapInterpolationMode", + "BitmapInterpolationMode", + BitmapInterpolationMode.HighQuality, inherits: true); /// @@ -22,7 +23,7 @@ namespace Avalonia.Media /// The control's left coordinate. public static BitmapInterpolationMode GetBitmapInterpolationMode(AvaloniaObject element) { - return element.GetValue(BitmapInterpolationMode); + return element.GetValue(BitmapInterpolationModeProperty); } /// @@ -32,7 +33,7 @@ namespace Avalonia.Media /// The left value. public static void SetBitmapInterpolationMode(AvaloniaObject element, BitmapInterpolationMode value) { - element.SetValue(BitmapInterpolationMode, value); + element.SetValue(BitmapInterpolationModeProperty, value); } } } diff --git a/src/Avalonia.Visuals/Media/TileBrush.cs b/src/Avalonia.Visuals/Media/TileBrush.cs index 094208d021..2033754137 100644 --- a/src/Avalonia.Visuals/Media/TileBrush.cs +++ b/src/Avalonia.Visuals/Media/TileBrush.cs @@ -77,6 +77,11 @@ namespace Avalonia.Media public static readonly StyledProperty TileModeProperty = AvaloniaProperty.Register(nameof(TileMode)); + static TileBrush() + { + RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue(BitmapInterpolationMode.Default); + } + /// /// Gets or sets the horizontal alignment of a tile in the destination. /// From 70f3ba800ef49951750e14d9931c9a20e62c2810 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Mon, 23 Jul 2018 15:00:54 -0400 Subject: [PATCH 18/30] Added more linux keys & added VS code config to .gitignore --- .gitignore | 5 +++++ src/Gtk/Avalonia.Gtk3/KeyTransform.cs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/.gitignore b/.gitignore index 583a2b8a2b..32acee4c90 100644 --- a/.gitignore +++ b/.gitignore @@ -165,6 +165,11 @@ $RECYCLE.BIN/ ################# .idea +################# +## VS Code +################# +.vscode/ + ################# ## Cake ################# diff --git a/src/Gtk/Avalonia.Gtk3/KeyTransform.cs b/src/Gtk/Avalonia.Gtk3/KeyTransform.cs index 5a34db2e04..69c5a1bf5f 100644 --- a/src/Gtk/Avalonia.Gtk3/KeyTransform.cs +++ b/src/Gtk/Avalonia.Gtk3/KeyTransform.cs @@ -33,17 +33,24 @@ namespace Avalonia.Gtk.Common { GdkKey.Prior, Key.Prior }, //{ GdkKey.?, Key.PageDown } { GdkKey.End, Key.End }, + { GdkKey.KP_End, Key.End }, { GdkKey.Home, Key.Home }, + { GdkKey.KP_Home, Key.Home }, { GdkKey.Left, Key.Left }, + { GdkKey.KP_Left, Key.Left }, { GdkKey.Up, Key.Up }, + { GdkKey.KP_Up, Key.Up }, { GdkKey.Right, Key.Right }, + { GdkKey.KP_Right, Key.Right }, { GdkKey.Down, Key.Down }, + { GdkKey.KP_Down, Key.Down }, { GdkKey.Select, Key.Select }, { GdkKey.Print, Key.Print }, { GdkKey.Execute, Key.Execute }, //{ GdkKey.?, Key.Snapshot } { GdkKey.Insert, Key.Insert }, { GdkKey.Delete, Key.Delete }, + { GdkKey.KP_Delete, Key.Delete }, { GdkKey.Help, Key.Help }, //{ GdkKey.?, Key.D0 } //{ GdkKey.?, Key.D1 } From cdf8d1f8b4810c86a2eaa56ade3a66dedd8efff5 Mon Sep 17 00:00:00 2001 From: wojciech krysiak Date: Mon, 23 Jul 2018 21:09:40 +0200 Subject: [PATCH 19/30] Fix For incorrect handling of NotifyCollectionChangedAction.Reset from IReactiveDerivedList<> --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index eb3fbde8f2..c8425a0f80 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -376,7 +376,7 @@ namespace Avalonia.Controls.Primitives break; case NotifyCollectionChangedAction.Reset: - SelectedIndex = IndexOf(e.NewItems, SelectedItem); + SelectedIndex = IndexOf(Items, SelectedItem); break; } } From 03e859445113e8f2b3ed34383a893cb19ecf517e Mon Sep 17 00:00:00 2001 From: wojciech krysiak Date: Mon, 23 Jul 2018 22:32:13 +0200 Subject: [PATCH 20/30] Bug reproduction unit test --- .../Primitives/SelectingItemsControlTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index c7a3465ac4..14e1b15ebc 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Presenters; @@ -13,6 +14,7 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Markup.Data; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Primitives @@ -686,6 +688,26 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Null(KeyboardNavigation.GetTabOnceActiveElement((InputElement)panel)); } + [Fact] + public void Resetting_Items_Collection_Should_Retain_Selection() + { + var itemsMock = new Mock>(); + var itemsMockAsINCC = itemsMock.As(); + + itemsMock.Object.AddRange(new[] { "Foo", "Bar", "Baz" }); + var target = new SelectingItemsControl + { + Items = itemsMock.Object + }; + + target.SelectedIndex = 1; + + itemsMockAsINCC.Raise(e => e.CollectionChanged += null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + Assert.True(target.SelectedIndex == 1); + } + + private FuncControlTemplate Template() { return new FuncControlTemplate(control => From 7e59520aaff11a8ab24817d446f65fc23a94f2c4 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 25 Jul 2018 13:44:01 +0200 Subject: [PATCH 21/30] Set default render quality to medium --- src/Avalonia.Visuals/Media/RenderOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/RenderOptions.cs b/src/Avalonia.Visuals/Media/RenderOptions.cs index 5e16f5f9ce..180863f9e8 100644 --- a/src/Avalonia.Visuals/Media/RenderOptions.cs +++ b/src/Avalonia.Visuals/Media/RenderOptions.cs @@ -13,7 +13,7 @@ namespace Avalonia.Media public static readonly StyledProperty BitmapInterpolationModeProperty = AvaloniaProperty.RegisterAttached( "BitmapInterpolationMode", - BitmapInterpolationMode.HighQuality, + BitmapInterpolationMode.MediumQuality, inherits: true); /// From 23fdfe51df8ddb4f8d46203db7be7207edb431d2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jul 2018 21:35:44 +0100 Subject: [PATCH 22/30] fix bug that was preventing user from cancelling window close. --- src/Avalonia.Controls/Window.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index d1a023c42c..7e1d8f18f0 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -302,17 +302,23 @@ namespace Avalonia.Controls internal void Close(bool ignoreCancel) { + bool close = true; + try { if (!ignoreCancel && HandleClosing()) { + close = false; return; } } finally { - PlatformImpl?.Dispose(); - HandleClosed(); + if (close) + { + PlatformImpl?.Dispose(); + HandleClosed(); + } } } From fb30bcd9bd1cd24e045e688e22c09036bdad5bd3 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 26 Jul 2018 08:58:54 +0200 Subject: [PATCH 23/30] Some minor issues fixed --- src/Avalonia.Controls/Image.cs | 4 ++-- src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs | 2 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index fd90f9f2e8..f146e3571c 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -67,9 +67,9 @@ namespace Avalonia.Controls Rect sourceRect = new Rect(sourceSize) .CenterRect(new Rect(destRect.Size / scale)); - var scalingMode = RenderOptions.GetBitmapInterpolationMode(this); + var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this); - context.DrawImage(source, 1, sourceRect, destRect, scalingMode); + context.DrawImage(source, 1, sourceRect, destRect, interpolationMode); } } diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index debc7103c0..b11d9f52ab 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -32,7 +32,7 @@ namespace Avalonia.Platform /// The rect in the image to draw. /// The rect in the output to draw to. /// The bitmap interpolation mode. - void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default); + void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default); /// /// Draws a bitmap image. diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 28ac311605..685987ae80 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -381,7 +381,7 @@ namespace Avalonia.Skia /// Tile brush to use. /// Tile brush image. /// The bitmap interpolation mode. - private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage, BitmapInterpolationMode interpolationMode) + private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage) { var calc = new TileBrushCalculator(tileBrush, new Size(tileBrushImage.PixelWidth, tileBrushImage.PixelHeight), targetSize); @@ -399,7 +399,7 @@ namespace Avalonia.Skia context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); context.Transform = calc.IntermediateTransform; - context.DrawImage(RefCountable.CreateUnownedNotClonable(tileBrushImage), 1, rect, rect, interpolationMode); + context.DrawImage(RefCountable.CreateUnownedNotClonable(tileBrushImage), 1, rect, rect, tileBrush.BitmapInterpolationMode); context.PopClip(); } @@ -513,7 +513,7 @@ namespace Avalonia.Skia if (tileBrush != null && tileBrushImage != null) { - ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage, tileBrush.BitmapInterpolationMode); + ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage); } else { From 5062f823b3135a82c530b01b83548135ed1e6450 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Jul 2018 16:01:18 +0100 Subject: [PATCH 24/30] rename stackpanel Gap property to Spacing property. --- samples/BindingDemo/MainWindow.xaml | 26 ++++++------- .../Pages/AutoCompleteBoxPage.xaml | 6 +-- samples/ControlCatalog/Pages/BorderPage.xaml | 6 +-- samples/ControlCatalog/Pages/ButtonPage.xaml | 10 ++--- .../Pages/ButtonSpinnerPage.xaml | 6 +-- .../ControlCatalog/Pages/CalendarPage.xaml | 6 +-- samples/ControlCatalog/Pages/CanvasPage.xaml | 4 +- .../ControlCatalog/Pages/CarouselPage.xaml | 10 ++--- .../ControlCatalog/Pages/CheckBoxPage.xaml | 10 ++--- .../ControlCatalog/Pages/ContextMenuPage.xaml | 6 +-- .../ControlCatalog/Pages/DatePickerPage.xaml | 6 +-- samples/ControlCatalog/Pages/DialogsPage.xaml | 4 +- .../ControlCatalog/Pages/DragAndDropPage.xaml | 6 +-- .../ControlCatalog/Pages/DropDownPage.xaml | 6 +-- .../ControlCatalog/Pages/ExpanderPage.xaml | 6 +-- samples/ControlCatalog/Pages/ImagePage.xaml | 4 +- samples/ControlCatalog/Pages/MenuPage.xaml | 4 +- .../Pages/NumericUpDownPage.xaml | 6 +-- .../ControlCatalog/Pages/ProgressBarPage.xaml | 6 +-- .../ControlCatalog/Pages/RadioButtonPage.xaml | 8 ++-- samples/ControlCatalog/Pages/SliderPage.xaml | 4 +- samples/ControlCatalog/Pages/TextBoxPage.xaml | 10 ++--- samples/ControlCatalog/Pages/ToolTipPage.xaml | 2 +- .../ControlCatalog/Pages/TreeViewPage.xaml | 4 +- samples/VirtualizationDemo/MainWindow.xaml | 2 +- src/Avalonia.Controls/StackPanel.cs | 38 +++++++++---------- .../VirtualizingStackPanel.cs | 12 +++--- src/Avalonia.Diagnostics/DevTools.xaml | 4 +- .../Views/TreePageView.xaml | 4 +- .../StackPanelTests.cs | 14 +++---- 30 files changed, 120 insertions(+), 120 deletions(-) diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml index a69fb75742..95713dc22f 100644 --- a/samples/BindingDemo/MainWindow.xaml +++ b/samples/BindingDemo/MainWindow.xaml @@ -18,18 +18,18 @@ - + - + - + !BooleanString @@ -37,13 +37,13 @@ - + - + @@ -52,7 +52,7 @@ - + @@ -68,11 +68,11 @@ - + - + @@ -87,16 +87,16 @@ - + - + - + @@ -104,7 +104,7 @@ - + @@ -25,7 +25,7 @@ - + @@ -33,4 +33,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml index 1797fb48bc..fba15f6e77 100644 --- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml @@ -1,11 +1,11 @@  - + ButtonSpinner The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element. - + AllowSpin ShowButtonSpinner - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/CalendarPage.xaml b/samples/ControlCatalog/Pages/CalendarPage.xaml index a433fd1add..c47fd766fb 100644 --- a/samples/ControlCatalog/Pages/CalendarPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarPage.xaml @@ -1,13 +1,13 @@ - + Calendar A calendar control for selecting dates + Spacing="16"> - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml b/samples/ControlCatalog/Pages/CanvasPage.xaml index f934f57c22..10a38895a2 100644 --- a/samples/ControlCatalog/Pages/CanvasPage.xaml +++ b/samples/ControlCatalog/Pages/CanvasPage.xaml @@ -1,5 +1,5 @@ - + Canvas A panel which lays out its children by explicit coordinates @@ -31,4 +31,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml b/samples/ControlCatalog/Pages/CarouselPage.xaml index 3468b71fd8..cf9b13c00c 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml @@ -1,9 +1,9 @@ - + Carousel An items control that displays its items as pages that fill the control. - + @@ -20,7 +20,7 @@ - + Transition None @@ -29,7 +29,7 @@ - + Orientation Horizontal @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/CheckBoxPage.xaml b/samples/ControlCatalog/Pages/CheckBoxPage.xaml index a00b3a7bef..154a6254a4 100644 --- a/samples/ControlCatalog/Pages/CheckBoxPage.xaml +++ b/samples/ControlCatalog/Pages/CheckBoxPage.xaml @@ -1,15 +1,15 @@ - + CheckBox A check box control + Spacing="16"> + Spacing="16"> Unchecked Checked Indeterminate @@ -17,7 +17,7 @@ + Spacing="16"> Three State: Unchecked Three State: Checked Three State: Indeterminate @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml b/samples/ControlCatalog/Pages/ContextMenuPage.xaml index 3af823befc..37eeaeb2ac 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml @@ -1,12 +1,12 @@ - + Context Menu A right click menu that can be applied to any control. + Spacing="16"> @@ -33,4 +33,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/DatePickerPage.xaml b/samples/ControlCatalog/Pages/DatePickerPage.xaml index 92cfa7e178..2c34460fce 100644 --- a/samples/ControlCatalog/Pages/DatePickerPage.xaml +++ b/samples/ControlCatalog/Pages/DatePickerPage.xaml @@ -1,13 +1,13 @@ - + DatePicker A control for selecting dates with a calendar drop-down + Spacing="16"> @@ -43,4 +43,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index c3e9435630..710d791f3a 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -1,5 +1,5 @@ - + @@ -9,4 +9,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml index af679d2f9a..1f3cd3ff71 100644 --- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml @@ -1,12 +1,12 @@ - + Drag+Drop Example of Drag+Drop capabilities + Spacing="16"> Drag Me @@ -16,4 +16,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/DropDownPage.xaml b/samples/ControlCatalog/Pages/DropDownPage.xaml index 0a7a88e331..5e2a3102e7 100644 --- a/samples/ControlCatalog/Pages/DropDownPage.xaml +++ b/samples/ControlCatalog/Pages/DropDownPage.xaml @@ -1,9 +1,9 @@ - + DropDown A drop-down list. - + Inline Items Inline Item 2 @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml index e32fa1caf1..91440929f5 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml @@ -1,12 +1,12 @@ - + Expander Expands to show nested content + Spacing="16"> Expanded content @@ -29,4 +29,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml index dc93808f27..78fbf90192 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml +++ b/samples/ControlCatalog/Pages/ImagePage.xaml @@ -1,12 +1,12 @@ - + Image Displays an image + Spacing="16"> No Stretch - + Menu A window menu + Spacing="16"> diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml index a5c911f47d..305bcd177c 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml @@ -1,6 +1,6 @@  - + Numeric up-down control Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel. @@ -26,7 +26,7 @@ VerticalAlignment="Center" Margin="2"> - + @@ -69,7 +69,7 @@ - + Usage of NumericUpDown: - + ProgressBar A progress bar control @@ -7,8 +7,8 @@ - + Spacing="16"> + diff --git a/samples/ControlCatalog/Pages/RadioButtonPage.xaml b/samples/ControlCatalog/Pages/RadioButtonPage.xaml index d382b94f2c..0882817a9a 100644 --- a/samples/ControlCatalog/Pages/RadioButtonPage.xaml +++ b/samples/ControlCatalog/Pages/RadioButtonPage.xaml @@ -1,22 +1,22 @@ - + RadioButton Allows the selection of a single option of many + Spacing="16"> + Spacing="16"> Option 1 Option 2 Option 3 Disabled + Spacing="16"> Three States: Option 1 Three States: Option 2 Three States: Option 3 diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml index e43968cb8e..6db71b5fcc 100644 --- a/samples/ControlCatalog/Pages/SliderPage.xaml +++ b/samples/ControlCatalog/Pages/SliderPage.xaml @@ -1,9 +1,9 @@ - + Slider A control that lets the user select from a range of values by moving a Thumb control along a Track. - + - + TextBox A control into which the user can input text - + Spacing="16"> + @@ -26,13 +26,13 @@ - + - + diff --git a/samples/ControlCatalog/Pages/ToolTipPage.xaml b/samples/ControlCatalog/Pages/ToolTipPage.xaml index aa7d60bd11..ad832b9b82 100644 --- a/samples/ControlCatalog/Pages/ToolTipPage.xaml +++ b/samples/ControlCatalog/Pages/ToolTipPage.xaml @@ -1,6 +1,6 @@ + Spacing="4"> ToolTip A control which pops up a hint when a control is hovered diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml index 5806e58c27..1ab49dbb30 100644 --- a/samples/ControlCatalog/Pages/TreeViewPage.xaml +++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml @@ -1,12 +1,12 @@ - + TreeView Displays a hierachical tree of data. + Spacing="16"> diff --git a/samples/VirtualizationDemo/MainWindow.xaml b/samples/VirtualizationDemo/MainWindow.xaml index eb94253d27..730b61ed54 100644 --- a/samples/VirtualizationDemo/MainWindow.xaml +++ b/samples/VirtualizationDemo/MainWindow.xaml @@ -6,7 +6,7 @@ + Spacing="4"> - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty GapProperty = - AvaloniaProperty.Register(nameof(Gap)); + public static readonly StyledProperty SpacingProperty = + AvaloniaProperty.Register(nameof(Spacing)); /// /// Defines the property. @@ -29,17 +29,17 @@ namespace Avalonia.Controls /// static StackPanel() { - AffectsMeasure(GapProperty); + AffectsMeasure(SpacingProperty); AffectsMeasure(OrientationProperty); } /// - /// Gets or sets the size of the gap to place between child controls. + /// Gets or sets the size of the spacing to place between child controls. /// - public double Gap + public double Spacing { - get { return GetValue(GapProperty); } - set { SetValue(GapProperty, value); } + get { return GetValue(SpacingProperty); } + set { SetValue(SpacingProperty, value); } } /// @@ -152,7 +152,7 @@ namespace Avalonia.Controls double measuredWidth = 0; double measuredHeight = 0; - double gap = Gap; + double spacing = Spacing; bool hasVisibleChild = Children.Any(c => c.IsVisible); foreach (Control child in Children) @@ -162,23 +162,23 @@ namespace Avalonia.Controls if (Orientation == Orientation.Vertical) { - measuredHeight += size.Height + (child.IsVisible ? gap : 0); + measuredHeight += size.Height + (child.IsVisible ? spacing : 0); measuredWidth = Math.Max(measuredWidth, size.Width); } else { - measuredWidth += size.Width + (child.IsVisible ? gap : 0); + measuredWidth += size.Width + (child.IsVisible ? spacing : 0); measuredHeight = Math.Max(measuredHeight, size.Height); } } if (Orientation == Orientation.Vertical) { - measuredHeight -= (hasVisibleChild ? gap : 0); + measuredHeight -= (hasVisibleChild ? spacing : 0); } else { - measuredWidth -= (hasVisibleChild ? gap : 0); + measuredWidth -= (hasVisibleChild ? spacing : 0); } return new Size(measuredWidth, measuredHeight); @@ -194,7 +194,7 @@ namespace Avalonia.Controls var orientation = Orientation; double arrangedWidth = finalSize.Width; double arrangedHeight = finalSize.Height; - double gap = Gap; + double spacing = Spacing; bool hasVisibleChild = Children.Any(c => c.IsVisible); if (Orientation == Orientation.Vertical) @@ -217,25 +217,25 @@ namespace Avalonia.Controls Rect childFinal = new Rect(0, arrangedHeight, width, childHeight); ArrangeChild(child, childFinal, finalSize, orientation); arrangedWidth = Math.Max(arrangedWidth, childWidth); - arrangedHeight += childHeight + (child.IsVisible ? gap : 0); + arrangedHeight += childHeight + (child.IsVisible ? spacing : 0); } else { double height = Math.Max(childHeight, arrangedHeight); Rect childFinal = new Rect(arrangedWidth, 0, childWidth, height); ArrangeChild(child, childFinal, finalSize, orientation); - arrangedWidth += childWidth + (child.IsVisible ? gap : 0); + arrangedWidth += childWidth + (child.IsVisible ? spacing : 0); arrangedHeight = Math.Max(arrangedHeight, childHeight); } } if (orientation == Orientation.Vertical) { - arrangedHeight = Math.Max(arrangedHeight - (hasVisibleChild ? gap : 0), finalSize.Height); + arrangedHeight = Math.Max(arrangedHeight - (hasVisibleChild ? spacing : 0), finalSize.Height); } else { - arrangedWidth = Math.Max(arrangedWidth - (hasVisibleChild ? gap : 0), finalSize.Width); + arrangedWidth = Math.Max(arrangedWidth - (hasVisibleChild ? spacing : 0), finalSize.Width); } return new Size(arrangedWidth, arrangedHeight); @@ -250,4 +250,4 @@ namespace Avalonia.Controls child.Arrange(rect); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index dee537029c..5f7b63c57a 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -200,7 +200,7 @@ namespace Avalonia.Controls private void UpdateAdd(IControl child) { var bounds = Bounds; - var gap = Gap; + var spacing = Spacing; child.Measure(_availableSpace); ++_averageCount; @@ -208,13 +208,13 @@ namespace Avalonia.Controls if (Orientation == Orientation.Vertical) { var height = child.DesiredSize.Height; - _takenSpace += height + gap; + _takenSpace += height + spacing; AddToAverageItemSize(height); } else { var width = child.DesiredSize.Width; - _takenSpace += width + gap; + _takenSpace += width + spacing; AddToAverageItemSize(width); } } @@ -222,18 +222,18 @@ namespace Avalonia.Controls private void UpdateRemove(IControl child) { var bounds = Bounds; - var gap = Gap; + var spacing = Spacing; if (Orientation == Orientation.Vertical) { var height = child.DesiredSize.Height; - _takenSpace -= height + gap; + _takenSpace -= height + spacing; RemoveFromAverageItemSize(height); } else { var width = child.DesiredSize.Width; - _takenSpace -= width + gap; + _takenSpace -= width + spacing; RemoveFromAverageItemSize(width); } diff --git a/src/Avalonia.Diagnostics/DevTools.xaml b/src/Avalonia.Diagnostics/DevTools.xaml index bb1b2e841e..844670e794 100644 --- a/src/Avalonia.Diagnostics/DevTools.xaml +++ b/src/Avalonia.Diagnostics/DevTools.xaml @@ -7,7 +7,7 @@ - + Hold Ctrl+Shift over a control to inspect. Focused: @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Views/TreePageView.xaml index a715ca6fc5..57398851ad 100644 --- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml +++ b/src/Avalonia.Diagnostics/Views/TreePageView.xaml @@ -5,7 +5,7 @@ - + @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs index dca2c5df35..ba2716775a 100644 --- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs @@ -54,11 +54,11 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Lays_Out_Children_Vertically_With_Gap() + public void Lays_Out_Children_Vertically_With_Spacing() { var target = new StackPanel { - Gap = 10, + Spacing = 10, Children = { new Border { Height = 20, Width = 120 }, @@ -77,11 +77,11 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Lays_Out_Children_Horizontally_With_Gap() + public void Lays_Out_Children_Horizontally_With_Spacing() { var target = new StackPanel { - Gap = 10, + Spacing = 10, Orientation = Orientation.Horizontal, Children = { @@ -150,11 +150,11 @@ namespace Avalonia.Controls.UnitTests [Theory] [InlineData(Orientation.Horizontal)] [InlineData(Orientation.Vertical)] - public void Gap_Not_Added_For_Invisible_Children(Orientation orientation) + public void Spacing_Not_Added_For_Invisible_Children(Orientation orientation) { var targetThreeChildrenOneInvisble = new StackPanel { - Gap = 40, + Spacing = 40, Orientation = orientation, Children = { @@ -165,7 +165,7 @@ namespace Avalonia.Controls.UnitTests }; var targetTwoChildrenNoneInvisible = new StackPanel { - Gap = 40, + Spacing = 40, Orientation = orientation, Children = { From ca7204ad9a13f8e415ab0d075c8dc61a4ea58279 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Jul 2018 16:55:53 +0100 Subject: [PATCH 25/30] freeze designer support assembly version. --- .../Avalonia.DesignerSupport.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 9f54137e47..5ccb98b64d 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -1,7 +1,12 @@  netstandard2.0 - false + + 0.7.0 true @@ -37,11 +42,6 @@ - - - Properties\SharedAssemblyInfo.cs - - \ No newline at end of file From 14ed0191aff36768e57173a16b3d83307d413704 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Jul 2018 17:41:58 +0100 Subject: [PATCH 26/30] Dont freeze if someone implements window close cancellation. --- src/Avalonia.Controls/WindowCollection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/WindowCollection.cs b/src/Avalonia.Controls/WindowCollection.cs index c21a12f05b..df79c3e3c8 100644 --- a/src/Avalonia.Controls/WindowCollection.cs +++ b/src/Avalonia.Controls/WindowCollection.cs @@ -96,7 +96,7 @@ namespace Avalonia { while (_windows.Count > 0) { - _windows[0].Close(); + _windows[0].Close(true); } } @@ -131,4 +131,4 @@ namespace Avalonia } } } -} \ No newline at end of file +} From 079302a1233d64ee4f738ea6aac464ffa1ef7a7b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Jul 2018 16:39:35 -0500 Subject: [PATCH 27/30] Fix bug in AnimatorKeyFrame.ValueProperty registration. --- src/Avalonia.Animation/AnimatorKeyFrame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs index bd9c7a0184..0276c6fa92 100644 --- a/src/Avalonia.Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs @@ -16,7 +16,7 @@ namespace Avalonia.Animation public class AnimatorKeyFrame : AvaloniaObject { public static readonly DirectProperty ValueProperty = - AvaloniaProperty.RegisterDirect(nameof(Value), k => k._value, (k, v) => k._value = v); + AvaloniaProperty.RegisterDirect(nameof(Value), k => k.Value, (k, v) => k.Value = v); public AnimatorKeyFrame() { From 2345818aab00359a51375157bbad978e66e1b079 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Jul 2018 16:40:45 -0500 Subject: [PATCH 28/30] Change completion to be notified via a callback. Only notify that an animation is complete if all animators used in the animation are complete. --- src/Avalonia.Animation/Animation.cs | 57 ++++++++----------- .../AnimatorStateMachine`1.cs | 6 +- src/Avalonia.Animation/Animator`1.cs | 9 ++- src/Avalonia.Animation/IAnimation.cs | 4 +- src/Avalonia.Animation/IAnimator.cs | 2 +- .../Animation/TransformAnimator.cs | 6 +- 6 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 98fb652c4a..c228a49ec7 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -43,7 +43,6 @@ namespace Avalonia.Animation return null; } - private bool _isChildrenChanged = false; private List _subscription = new List(); public AvaloniaList _animators { get; set; } = new AvaloniaList(); @@ -77,16 +76,6 @@ namespace Avalonia.Animation /// public Easing Easing { get; set; } = new LinearEasing(); - /// - /// Triggers when the animation is completed. - /// - public event EventHandler Done; - - public Animation() - { - this.CollectionChanged += delegate { _isChildrenChanged = true; }; - } - private IList InterpretKeyframes(Animatable control) { var handlerList = new List<(Type type, AvaloniaProperty property)>(); @@ -152,11 +141,32 @@ namespace Avalonia.Animation } /// - public IDisposable Apply(Animatable control, IObservable matchObs) + public IDisposable Apply(Animatable control, IObservable match, Action onComplete) { - foreach (IAnimator animator in InterpretKeyframes(control)) + var animators = InterpretKeyframes(control); + if (animators.Count == 1) { - _subscription.Add(animator.Apply(this, control, matchObs)); + _subscription.Add(animators[0].Apply(this, control, match, onComplete)); + } + else + { + var completionTasks = onComplete != null ? new List() : null; + foreach (IAnimator animator in InterpretKeyframes(control)) + { + Action animatorOnComplete = null; + if (onComplete != null) + { + var tcs = new TaskCompletionSource(); + animatorOnComplete = () => tcs.SetResult(null); + completionTasks.Add(tcs.Task); + } + _subscription.Add(animator.Apply(this, control, match, animatorOnComplete)); + } + + if (onComplete != null) + { + Task.WhenAll(completionTasks).ContinueWith(_ => onComplete()); + } } return this; } @@ -169,26 +179,9 @@ namespace Avalonia.Animation if (this.RepeatCount == RepeatCount.Loop) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); - EventHandler doneCallback = null; - doneCallback = (sender, args) => - { - if (sender == control) - { - run.SetResult(null); - this.Done -= doneCallback; - } - }; - - this.Done += doneCallback; - - this.Apply(control, Observable.Return(true)); + this.Apply(control, Observable.Return(true), () => run.SetResult(null)); return run.Task; } - - internal void SetDone(Animatable control) - { - Done?.Invoke(control, null); - } } } diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 4bffd5c145..87e189c997 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -35,6 +35,7 @@ namespace Avalonia.Animation private T _neutralValue; internal bool _unsubscribe = false; private IObserver _targetObserver; + private readonly Action _onComplete; [Flags] private enum KeyFramesStates @@ -51,7 +52,7 @@ namespace Avalonia.Animation Disposed } - public void Initialize(Animation animation, Animatable control, Animator animator) + public AnimatorStateMachine(Animation animation, Animatable control, Animator animator, Action onComplete) { _parent = animator; _targetAnimation = animation; @@ -82,6 +83,7 @@ namespace Avalonia.Animation _currentState = KeyFramesStates.DoDelay; else _currentState = KeyFramesStates.DoRun; + _onComplete = onComplete; } public void Step(PlayState _playState, Func Interpolator) @@ -245,7 +247,7 @@ namespace Avalonia.Animation } _targetObserver.OnCompleted(); - _targetAnimation.SetDone(_targetControl); + _onComplete?.Invoke(); Dispose(); handled = true; break; diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index a1eef87e1e..eb8b40647d 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -35,7 +35,7 @@ namespace Avalonia.Animation } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch) + public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete) { if (!_isVerfifiedAndConverted) VerifyConvertKeyFrames(); @@ -45,7 +45,7 @@ namespace Avalonia.Animation .Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause) .Subscribe(_ => { - var timerObs = RunKeyFrames(animation, control); + var timerObs = RunKeyFrames(animation, control, onComplete); }); } @@ -97,10 +97,9 @@ namespace Avalonia.Animation /// /// Runs the KeyFrames Animation. /// - private IDisposable RunKeyFrames(Animation animation, Animatable control) + private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) { - var stateMachine = new AnimatorStateMachine(); - stateMachine.Initialize(animation, control, this); + var stateMachine = new AnimatorStateMachine(animation, control, this, onComplete); Timing.AnimationStateTimer .TakeWhile(_ => !stateMachine._unsubscribe) diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index 734eb3e479..905d90fa52 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -13,11 +13,11 @@ namespace Avalonia.Animation /// /// Apply the animation to the specified control /// - IDisposable Apply(Animatable control, IObservable match); + IDisposable Apply(Animatable control, IObservable match, Action onComplete = null); /// /// Run the animation to the specified control /// Task RunAsync(Animatable control); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index 6acca4d697..8b763db603 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -17,6 +17,6 @@ namespace Avalonia.Animation /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch); + IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete); } } diff --git a/src/Avalonia.Visuals/Animation/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/TransformAnimator.cs index 46cefbd061..61cac695b1 100644 --- a/src/Avalonia.Visuals/Animation/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/TransformAnimator.cs @@ -19,7 +19,7 @@ namespace Avalonia.Animation DoubleAnimator childKeyFrames; /// - public override IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch) + public override IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete) { var ctrl = (Visual)control; @@ -51,7 +51,7 @@ namespace Avalonia.Animation // It's a transform object so let's target that. if (renderTransformType == Property.OwnerType) { - return childKeyFrames.Apply(animation, ctrl.RenderTransform, obsMatch); + return childKeyFrames.Apply(animation, ctrl.RenderTransform, obsMatch, onComplete); } // It's a TransformGroup and try finding the target there. else if (renderTransformType == typeof(TransformGroup)) @@ -60,7 +60,7 @@ namespace Avalonia.Animation { if (transform.GetType() == Property.OwnerType) { - return childKeyFrames.Apply(animation, transform, obsMatch); + return childKeyFrames.Apply(animation, transform, obsMatch, onComplete); } } } From 7d1b7593a5572bbc2b74ac3e55c42065a9ba4852 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 2 Aug 2018 15:31:34 -0500 Subject: [PATCH 29/30] Dispose subscriptions on completed code-behind run of animation. --- src/Avalonia.Animation/Animation.cs | 39 +++++++++++++---------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index c228a49ec7..56bc667f8a 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -12,13 +12,14 @@ using System.Reflection; using System.Linq; using System.Threading.Tasks; using System.Reactive.Linq; +using System.Reactive.Disposables; namespace Avalonia.Animation { /// /// Tracks the progress of an animation. /// - public class Animation : AvaloniaList, IDisposable, IAnimation + public class Animation : AvaloniaList, IAnimation { private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { @@ -43,7 +44,6 @@ namespace Avalonia.Animation return null; } - private List _subscription = new List(); public AvaloniaList _animators { get; set; } = new AvaloniaList(); /// @@ -76,10 +76,11 @@ namespace Avalonia.Animation /// public Easing Easing { get; set; } = new LinearEasing(); - private IList InterpretKeyframes(Animatable control) + private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control) { var handlerList = new List<(Type type, AvaloniaProperty property)>(); var animatorKeyFrames = new List(); + var subscriptions = new List(); foreach (var keyframe in this) { @@ -104,7 +105,7 @@ namespace Avalonia.Animation var newKF = new AnimatorKeyFrame(handler, cue); - _subscription.Add(newKF.BindSetter(setter, control)); + subscriptions.Add(newKF.BindSetter(setter, control)); animatorKeyFrames.Add(newKF); } @@ -126,32 +127,21 @@ namespace Avalonia.Animation animator.Add(keyframe); } - return newAnimatorInstances; - } - - /// - /// Cancels the animation. - /// - public void Dispose() - { - foreach (var sub in _subscription) - { - sub.Dispose(); - } + return (newAnimatorInstances, subscriptions); } /// public IDisposable Apply(Animatable control, IObservable match, Action onComplete) { - var animators = InterpretKeyframes(control); + var (animators, subscriptions) = InterpretKeyframes(control); if (animators.Count == 1) { - _subscription.Add(animators[0].Apply(this, control, match, onComplete)); + subscriptions.Add(animators[0].Apply(this, control, match, onComplete)); } else { var completionTasks = onComplete != null ? new List() : null; - foreach (IAnimator animator in InterpretKeyframes(control)) + foreach (IAnimator animator in animators) { Action animatorOnComplete = null; if (onComplete != null) @@ -160,7 +150,7 @@ namespace Avalonia.Animation animatorOnComplete = () => tcs.SetResult(null); completionTasks.Add(tcs.Task); } - _subscription.Add(animator.Apply(this, control, match, animatorOnComplete)); + subscriptions.Add(animator.Apply(this, control, match, animatorOnComplete)); } if (onComplete != null) @@ -168,7 +158,7 @@ namespace Avalonia.Animation Task.WhenAll(completionTasks).ContinueWith(_ => onComplete()); } } - return this; + return new CompositeDisposable(subscriptions); } /// @@ -179,7 +169,12 @@ namespace Avalonia.Animation if (this.RepeatCount == RepeatCount.Loop) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); - this.Apply(control, Observable.Return(true), () => run.SetResult(null)); + IDisposable subscriptions = null; + subscriptions = this.Apply(control, Observable.Return(true), () => + { + run.SetResult(null); + subscriptions.Dispose(); + }); return run.Task; } From 5e4555e36637e8e9b031d1ea63bed3b14f9f0e36 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 2 Aug 2018 15:45:27 -0500 Subject: [PATCH 30/30] Add conditional access to satisfy our inspector. --- src/Avalonia.Animation/Animation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 56bc667f8a..3dc5b5c71a 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -173,7 +173,7 @@ namespace Avalonia.Animation subscriptions = this.Apply(control, Observable.Return(true), () => { run.SetResult(null); - subscriptions.Dispose(); + subscriptions?.Dispose(); }); return run.Task;