Browse Source

Merge branch 'master' into refactor/grokys-internalize

Max Katz 2 years ago
parent
commit
7ec8ad38af

+ 1 - 1
packages/Avalonia/Avalonia.csproj

@@ -5,7 +5,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-      <PackageReference Include="Avalonia.BuildServices" Version="0.0.15" />
+      <PackageReference Include="Avalonia.BuildServices" Version="0.0.16" />
       <ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
       <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
         <PrivateAssets>all</PrivateAssets>

+ 7 - 5
src/Avalonia.Controls/VirtualizingStackPanel.cs

@@ -662,9 +662,12 @@ namespace Avalonia.Controls
             _scrollViewer?.UnregisterAnchorCandidate(element);
 
             var recycleKey = element.GetValue(RecycleKeyProperty);
-            Debug.Assert(recycleKey is not null);
 
-            if (recycleKey == s_itemIsItsOwnContainer)
+            if (recycleKey is null)
+            {
+                RemoveInternalChild(element);
+            }
+            else if (recycleKey == s_itemIsItsOwnContainer)
             {
                 element.IsVisible = false;
             }
@@ -687,9 +690,8 @@ namespace Avalonia.Controls
             Debug.Assert(ItemContainerGenerator is not null);
 
             var recycleKey = element.GetValue(RecycleKeyProperty);
-            Debug.Assert(recycleKey is not null);
-
-            if (recycleKey == s_itemIsItsOwnContainer)
+            
+            if (recycleKey is null || recycleKey == s_itemIsItsOwnContainer)
             {
                 RemoveInternalChild(element);
             }

+ 25 - 8
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -7,7 +7,6 @@ using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Rendering.Utilities;
 using Avalonia.Utilities;
-using Avalonia.Media.Imaging;
 using SkiaSharp;
 using ISceneBrush = Avalonia.Media.ISceneBrush;
 
@@ -26,7 +25,7 @@ namespace Avalonia.Skia
         private readonly Stack<double> _opacityStack = new();
         private readonly Matrix? _postTransform;
         private double _currentOpacity = 1.0f;
-        private readonly bool _canTextUseLcdRendering;
+        private readonly bool _disableSubpixelTextRendering;
         private Matrix _currentTransform;
         private bool _disposed;
         private GRContext? _grContext;
@@ -59,11 +58,11 @@ namespace Avalonia.Skia
             /// Dpi of drawings.
             /// </summary>
             public Vector Dpi;
-            
+
             /// <summary>
-            /// Render text without Lcd rendering.
+            /// Render text without subpixel antialiasing.
             /// </summary>
-            public bool DisableTextLcdRendering;
+            public bool DisableSubpixelTextRendering;
 
             /// <summary>
             /// GPU-accelerated context (optional)
@@ -135,7 +134,7 @@ namespace Avalonia.Skia
 
             _dpi = createInfo.Dpi;
             _disposables = disposables;
-            _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
+            _disableSubpixelTextRendering = createInfo.DisableSubpixelTextRendering;
             _grContext = createInfo.GrContext;
             _gpu = createInfo.Gpu;
             if (_grContext != null)
@@ -519,7 +518,23 @@ namespace Avalonia.Skia
             {
                 var glyphRunImpl = (GlyphRunImpl)glyphRun;
 
-                var textBlob = glyphRunImpl.GetTextBlob(RenderOptions);
+                var textRenderOptions = RenderOptions;
+
+                if (_disableSubpixelTextRendering)
+                {
+                    switch (textRenderOptions.TextRenderingMode)
+                    {
+                        case TextRenderingMode.Unspecified
+                            when textRenderOptions.EdgeMode == EdgeMode.Antialias || textRenderOptions.EdgeMode == EdgeMode.Unspecified:
+                        case TextRenderingMode.SubpixelAntialias:
+                            {
+                                textRenderOptions = textRenderOptions with { TextRenderingMode = TextRenderingMode.Antialias };
+                                break;
+                            }
+                    }
+                }
+
+                var textBlob = glyphRunImpl.GetTextBlob(textRenderOptions);
 
                 Canvas.DrawText(textBlob, (float)glyphRun.BaselineOrigin.X,
                     (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint);
@@ -969,6 +984,7 @@ namespace Avalonia.Skia
 
                 using (var ctx = intermediate.CreateDrawingContext())
                 {
+                    ctx.RenderOptions = RenderOptions;
                     ctx.Clear(Colors.Transparent);
                     content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
                 }
@@ -997,6 +1013,7 @@ namespace Avalonia.Skia
             using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi);
             using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
             {
+                ctx.RenderOptions = RenderOptions;
                 ctx.PushClip(calc.IntermediateClip);
                 content.Render(ctx, transform);
                 ctx.PopClip();
@@ -1283,7 +1300,7 @@ namespace Avalonia.Skia
                 Height = pixelSize.Height,
                 Dpi = _dpi,
                 Format = format,
-                DisableTextLcdRendering = !_canTextUseLcdRendering,
+                DisableTextLcdRendering = isLayer ? _disableSubpixelTextRendering : true,
                 GrContext = _grContext,
                 Gpu = _gpu,
                 Session = _session,

+ 1 - 3
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
 using Avalonia.Reactive;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Platform;
-using Avalonia.Rendering;
 using SkiaSharp;
 
 namespace Avalonia.Skia
@@ -54,8 +53,7 @@ namespace Avalonia.Skia
             var createInfo = new DrawingContextImpl.CreateInfo
             {
                 Surface = _framebufferSurface,
-                Dpi = framebuffer.Dpi,
-                DisableTextLcdRendering = true
+                Dpi = framebuffer.Dpi
             };
 
             return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);

+ 0 - 1
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@@ -30,7 +30,6 @@ namespace Avalonia.Skia
                 GrContext = session.GrContext,
                 Surface = session.SkSurface,
                 Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
-                DisableTextLcdRendering = true,
                 Gpu = _skiaGpu,
                 CurrentSession =  session
             };

+ 1 - 1
src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs

@@ -20,7 +20,7 @@ namespace Avalonia.Skia.Helpers
             {
                 Canvas = canvas,
                 Dpi = dpi,
-                DisableTextLcdRendering = true,
+                DisableSubpixelTextRendering = true,
             };
 
             return new DrawingContextImpl(createInfo);

+ 2 - 2
src/Skia/Avalonia.Skia/PictureRenderTarget.cs

@@ -39,7 +39,7 @@ internal class PictureRenderTarget : IDisposable
         {
             Canvas = canvas,
             Dpi = _dpi,
-            DisableTextLcdRendering = true,
+            DisableSubpixelTextRendering = true,
             GrContext = _grContext,
             Gpu = _gpu,
         };
@@ -52,4 +52,4 @@ internal class PictureRenderTarget : IDisposable
     }
 
     public void Dispose() => _picture?.Dispose();
-}
+}

+ 1 - 1
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@@ -106,7 +106,7 @@ namespace Avalonia.Skia
             {
                 Surface = _surface.Surface,
                 Dpi = Dpi,
-                DisableTextLcdRendering = _disableLcdRendering,
+                DisableSubpixelTextRendering = _disableLcdRendering,
                 GrContext = _grContext,
                 Gpu = _gpu,
             };

+ 55 - 5
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@@ -646,7 +646,7 @@ namespace Avalonia.Controls.UnitTests
         {
             // Issue #11272
             using var app = App();
-            var (_, _, itemsControl) = CreateUnrootedTarget();
+            var (_, _, itemsControl) = CreateUnrootedTarget<ItemsControl>();
             var container = new Decorator { Margin = new Thickness(100) };
             var root = new TestRoot(true, container);
             
@@ -657,11 +657,49 @@ namespace Avalonia.Controls.UnitTests
             root.LayoutManager.ExecuteLayoutPass();
         }
 
+        [Fact]
+        public void Supports_Null_Recycle_Key_When_Scrolling()
+        {
+            using var app = App();
+            var (_, scroll, itemsControl) = CreateUnrootedTarget<NonRecyclingItemsControl>();
+            var root = CreateRoot(itemsControl);
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+
+            var firstItem = itemsControl.ContainerFromIndex(0)!;
+            scroll.Offset = new(0, 20);
+
+            Layout(itemsControl);
+
+            Assert.Null(firstItem.Parent);
+            Assert.Null(firstItem.VisualParent);
+            Assert.DoesNotContain(firstItem, itemsControl.ItemsPanelRoot!.Children);
+        }
+
+        [Fact]
+        public void Supports_Null_Recycle_Key_When_Clearing_Items()
+        {
+            using var app = App();
+            var (_, _, itemsControl) = CreateUnrootedTarget<NonRecyclingItemsControl>();
+            var root = CreateRoot(itemsControl);
+
+            root.LayoutManager.ExecuteInitialLayoutPass();
+
+            var firstItem = itemsControl.ContainerFromIndex(0)!;
+            itemsControl.ItemsSource = null;
+
+            Layout(itemsControl);
+
+            Assert.Null(firstItem.Parent);
+            Assert.Null(firstItem.VisualParent);
+            Assert.Empty(itemsControl.ItemsPanelRoot!.Children);            
+        }
+
         [Fact]
         public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements()
         {
             var items = new[] { "foo", "bar", "baz" };
-            var (target, _, itemsControl) = CreateUnrootedTarget(items: items);
+            var (target, _, itemsControl) = CreateUnrootedTarget<ItemsControl>(items: items);
             var container = new Decorator { Margin = new Thickness(100), Child = itemsControl };
             var root = new TestRoot(true, container);
 
@@ -738,7 +776,7 @@ namespace Avalonia.Controls.UnitTests
             Optional<IDataTemplate?> itemTemplate = default,
             IEnumerable<Style>? styles = null)
         {
-            var (target, scroll, itemsControl) = CreateUnrootedTarget(items, itemTemplate);
+            var (target, scroll, itemsControl) = CreateUnrootedTarget<ItemsControl>(items, itemTemplate);
             var root = CreateRoot(itemsControl, styles);
 
             root.LayoutManager.ExecuteInitialLayoutPass();
@@ -746,9 +784,10 @@ namespace Avalonia.Controls.UnitTests
             return (target, scroll, itemsControl);
         }
 
-        private static (VirtualizingStackPanel, ScrollViewer, ItemsControl) CreateUnrootedTarget(
+        private static (VirtualizingStackPanel, ScrollViewer, T) CreateUnrootedTarget<T>(
             IEnumerable<object>? items = null,
             Optional<IDataTemplate?> itemTemplate = default)
+                where T : ItemsControl, new()
         {
             var target = new VirtualizingStackPanel();
 
@@ -766,7 +805,7 @@ namespace Avalonia.Controls.UnitTests
                 Template = ScrollViewerTemplate(),
             };
 
-            var itemsControl = new ItemsControl
+            var itemsControl = new T
             {
                 ItemsSource = items,
                 Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)),
@@ -840,5 +879,16 @@ namespace Avalonia.Controls.UnitTests
 
             public event NotifyCollectionChangedEventHandler? CollectionChanged;
         }
+
+        private class NonRecyclingItemsControl : ItemsControl
+        {
+            protected override Type StyleKeyOverride => typeof(ItemsControl);
+
+            protected internal override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
+            {
+                recycleKey = null;
+                return true;
+            }
+        }
     }
 }