diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 8ac2fb28ed..8f1b39ae12 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 0580761631..2f275431ff 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/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); } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 18cb87dfd1..f74ba179c1 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/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 _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. /// public Vector Dpi; - + /// - /// Render text without Lcd rendering. + /// Render text without subpixel antialiasing. /// - public bool DisableTextLcdRendering; + public bool DisableSubpixelTextRendering; /// /// 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, diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index a22b67e09e..64afaa6817 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/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); diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index ce292eb826..6db6083ba7 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/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 }; diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs index b774ddf411..6a726dc9dc 100644 --- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs +++ b/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); diff --git a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs b/src/Skia/Avalonia.Skia/PictureRenderTarget.cs index 280b7c27cd..02cc1c0676 100644 --- a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs +++ b/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(); -} \ No newline at end of file +} diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 92210c30e2..c695e8ba41 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/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, }; diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index 1fddaab910..c5a6ecc376 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/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(); 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(); + 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(); + 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(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 itemTemplate = default, IEnumerable