From a503a3e1f0315a7b12ee4b595140f13ef2b0c3fa Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 24 Feb 2023 18:51:54 +0100 Subject: [PATCH 01/11] fix(AvaloniaPropertyAnalyzer): Avoid AVP1031 when Avalonia Field Accessibily in not Public Considering following case ```bash Warning AVP1031 Missing accessor: Avalonia.Controls.DefinitionBase.PrivateSharedSizeScopeProperty is writeable, but this CLR property lacks a set accessor Avalonia.Controls (net6.0) C:\GitHub\Avalonia\src\Avalonia.Controls\DefinitionBase.cs 418 Active Warning AVP1031 Missing accessor: Avalonia.Controls.TabItem.TabStripPlacementProperty is writeable, but this CLR property lacks a set accessor Avalonia.Controls (net6.0) C:\GitHub\Avalonia\src\Avalonia.Controls\TabItem.cs 45 Active ``` --- .../AvaloniaPropertyAnalyzer.CompileAnalyzer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs index 0a27602604..af9b367368 100644 --- a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs +++ b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs @@ -396,7 +396,7 @@ public partial class AvaloniaPropertyAnalyzer hostTypeRef = new(_avaloniaObjectType, Location.None); // assume that an attached property applies everywhere until we find its registration } - var result = new AvaloniaPropertyDescription(inferredName, propertyType, valueType) { HostType = hostTypeRef }; + var result = new AvaloniaPropertyDescription(inferredName, propertyType, valueType) { HostType = hostTypeRef }; // assume that the property is owned by its containing type at the point of assignment, until we find its registration result.SetAssignment(s, new(s.ContainingType, Location.None)); @@ -570,7 +570,7 @@ public partial class AvaloniaPropertyAnalyzer if (_allGetSetMethods.Contains(originalMethod)) { - if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } && + if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } && GetReferencedProperty(invocation.Arguments[0]) is { } refProp && refProp.description.AssignedTo.TryGetValue(refProp.storageSymbol, out var ownerType) && !DerivesFrom(context.ContainingSymbol.ContainingType, ownerType.Type) && @@ -694,11 +694,11 @@ public partial class AvaloniaPropertyAnalyzer void VerifyAccessor(IMethodSymbol? method, string verb, string methodName) { - if (method == null) + if (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Public && method is null) { context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName)); } - else if (method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility) + else if (method is not null && method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility) { context.ReportDiagnostic(Diagnostic.Create(InconsistentAccessibility, method.Locations[0], "property accessor", avaloniaPropertyStorage)); } From db72ae64fc6040e4fe09d0ee6fab74038d99a0f1 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 27 Feb 2023 08:37:11 +0100 Subject: [PATCH 02/11] Prevent AssetLoader.GetAssets from crashing --- src/Avalonia.Base/Platform/AssetLoader.cs | 155 ++++++++++++------ .../AssetLoaderTests.cs | 13 +- 2 files changed, 116 insertions(+), 52 deletions(-) diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs index 659cfb75df..7df446e854 100644 --- a/src/Avalonia.Base/Platform/AssetLoader.cs +++ b/src/Avalonia.Base/Platform/AssetLoader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -62,7 +63,7 @@ namespace Avalonia.Platform /// True if the asset could be found; otherwise false. public bool Exists(Uri uri, Uri? baseUri = null) { - return GetAsset(uri, baseUri) != null; + return TryGetAsset(uri, baseUri, out _); } /// @@ -94,21 +95,27 @@ namespace Avalonia.Platform /// public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) { - var asset = GetAsset(uri, baseUri); - - if (asset == null) + if (TryGetAsset(uri, baseUri, out var assetDescriptor)) { - throw new FileNotFoundException($"The resource {uri} could not be found."); + return (assetDescriptor.GetStream(), assetDescriptor.Assembly); } - return (asset.GetStream(), asset.Assembly); + throw new FileNotFoundException($"The resource {uri} could not be found."); } public Assembly? GetAssembly(Uri uri, Uri? baseUri) { if (!uri.IsAbsoluteUri && baseUri != null) + { uri = new Uri(baseUri, uri); - return GetAssembly(uri)?.Assembly; + } + + if (TryGetAssembly(uri, out var assemblyDescriptor)) + { + return assemblyDescriptor.Assembly; + } + + return null; } /// @@ -121,99 +128,145 @@ namespace Avalonia.Platform { if (uri.IsAbsoluteResm()) { - var assembly = GetAssembly(uri); + if (!TryGetAssembly(uri, out var assembly)) + { + assembly = _defaultResmAssembly; + } return assembly?.Resources? - .Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0) - .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? - Enumerable.Empty(); + .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) + .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? + Enumerable.Empty(); } uri = uri.EnsureAbsolute(baseUri); + if (uri.IsAvares()) { - var (asm, path) = GetResAsmAndPath(uri); - if (asm == null) + if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) { - throw new ArgumentException( - "No default assembly, entry assembly or explicit assembly specified; " + - "don't know where to look up for the resource, try specifying assembly explicitly."); + return Enumerable.Empty(); } - if (asm.AvaloniaResources == null) + if (assembly?.AvaloniaResources == null) + { return Enumerable.Empty(); + } - if (path[path.Length - 1] != '/') + if (path.Length > 0 && path[path.Length - 1] != '/') + { path += '/'; + } - return asm.AvaloniaResources + return assembly.AvaloniaResources .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) - .Select(x => new Uri($"avares://{asm.Name}{x.Key}")); + .Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); } return Enumerable.Empty(); } - - private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri) - { + + private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) + { + assetDescriptor = null; + if (uri.IsAbsoluteResm()) { - var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly; - - if (asm == null) + if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) { - throw new ArgumentException( - "No default assembly, entry assembly or explicit assembly specified; " + - "don't know where to look up for the resource, try specifying assembly explicitly."); + assembly = _defaultResmAssembly; } - var resourceKey = uri.AbsolutePath; - IAssetDescriptor? rv = null; - asm.Resources?.TryGetValue(resourceKey, out rv); - return rv; + if (assembly?.Resources != null) + { + var resourceKey = uri.AbsolutePath; + + if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) + { + return true; + } + } } uri = uri.EnsureAbsolute(baseUri); if (uri.IsAvares()) { - var (asm, path) = GetResAsmAndPath(uri); - if (asm.AvaloniaResources == null) - return null; - asm.AvaloniaResources.TryGetValue(path, out var desc); - return desc; + if (TryGetResAsmAndPath(uri, out var assembly, out var path)) + { + if (assembly.AvaloniaResources == null) + { + return false; + } + + if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) + { + return true; + } + } } - throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri)); + return false; } - private static (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri) + private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) { - var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority); - return (asm, uri.GetUnescapeAbsolutePath()); + path = uri.GetUnescapeAbsolutePath(); + + if (TryLoadAssembly(uri.Authority, out assembly)) + { + return true; + } + + return false; } - - private static IAssemblyDescriptor? GetAssembly(Uri? uri) + + private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) { + assembly = null; + if (uri != null) { if (!uri.IsAbsoluteUri) - return null; - if (uri.IsAvares()) - return GetResAsmAndPath(uri).asm; + { + return false; + } + + if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) + { + return true; + } if (uri.IsResm()) { var assemblyName = uri.GetAssemblyNameFromQuery(); - if (assemblyName.Length > 0) - return s_assemblyDescriptorResolver.GetAssembly(assemblyName); + + if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) + { + return true; + } } } - return null; + return false; + } + + private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) + { + assembly = null; + + try + { + assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName); + + return true; + } + catch (Exception) { } + + return false; } #endif - + public static void RegisterResUriParsers() { if (!UriParser.IsKnownScheme("avares")) diff --git a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs index 28fb19e119..894b6578e3 100644 --- a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs @@ -9,7 +9,7 @@ namespace Avalonia.Base.UnitTests; public class AssetLoaderTests : IDisposable { - public class MockAssembly : Assembly {} + public class MockAssembly : Assembly { } private const string AssemblyNameWithWhitespace = "Awesome Library"; @@ -50,6 +50,17 @@ public class AssetLoaderTests : IDisposable Assert.Equal(AssemblyNameWithNonAscii, assemblyActual?.FullName); } + [Fact] + public void Invalid_AssemblyName_Should_Yield_Empty_Enumerable() + { + var uri = new Uri($"avares://InvalidAssembly"); + var loader = new AssetLoader(); + + var assemblyActual = loader.GetAssets(uri, null); + + Assert.Empty(assemblyActual); + } + private static IAssemblyDescriptor CreateAssemblyDescriptor(string assemblyName) { var assembly = Mock.Of(); From cfd35d17d51a0d8c4136f0233285a856773f491e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Feb 2023 11:18:38 +0100 Subject: [PATCH 03/11] Added failing `SetCurrentValue` test. `SetCurrent_Value_Persists_When_Toggling_Style_1` passes but `SetCurrent_Value_Persists_When_Toggling_Style_2` fails as it uses a different code-path. --- .../AvaloniaObjectTests_SetCurrentValue.cs | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs index 8ad36a583e..c850fbdb08 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs @@ -1,6 +1,9 @@ using System; +using Avalonia.Controls; using Avalonia.Data; using Avalonia.Diagnostics; +using Avalonia.Styling; +using Avalonia.UnitTests; using Xunit; using Observable = Avalonia.Reactive.Observable; @@ -275,6 +278,79 @@ namespace Avalonia.Base.UnitTests Assert.Equal("style", target.Foo); } + [Fact] + public void SetCurrent_Value_Persists_When_Toggling_Style_1() + { + var target = new Class1(); + var root = new TestRoot(target) + { + Styles = + { + new Style(x => x.OfType().Class("foo")) + { + Setters = { new Setter(Class1.BarProperty, "bar") }, + } + } + }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + target.SetCurrentValue(Class1.FooProperty, "current"); + + Assert.Equal("current", target.Foo); + Assert.Equal("bardefault", target.Bar); + + target.Classes.Add("foo"); + + Assert.Equal("current", target.Foo); + Assert.Equal("bar", target.Bar); + + target.Classes.Remove("foo"); + + Assert.Equal("current", target.Foo); + Assert.Equal("bardefault", target.Bar); + } + + [Fact] + public void SetCurrent_Value_Persists_When_Toggling_Style_2() + { + var target = new Class1(); + var root = new TestRoot(target) + { + Styles = + { + new Style(x => x.OfType().Class("foo")) + { + Setters = + { + new Setter(Class1.BarProperty, "bar"), + new Setter(Class1.InheritedProperty, "inherited"), + }, + } + } + }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + + target.SetCurrentValue(Class1.FooProperty, "current"); + + Assert.Equal("current", target.Foo); + Assert.Equal("bardefault", target.Bar); + Assert.Equal("inheriteddefault", target.Inherited); + + target.Classes.Add("foo"); + + Assert.Equal("current", target.Foo); + Assert.Equal("bar", target.Bar); + Assert.Equal("inherited", target.Inherited); + + target.Classes.Remove("foo"); + + Assert.Equal("current", target.Foo); + Assert.Equal("bardefault", target.Bar); + Assert.Equal("inheriteddefault", target.Inherited); + } + private BindingPriority GetPriority(AvaloniaObject target, AvaloniaProperty property) { return target.GetDiagnostic(property).Priority; @@ -285,16 +361,19 @@ namespace Avalonia.Base.UnitTests return target.GetDiagnostic(property).IsOverriddenCurrentValue; } - private class Class1 : AvaloniaObject + private class Class1 : Control { public static readonly StyledProperty FooProperty = AvaloniaProperty.Register(nameof(Foo), "foodefault"); + public static readonly StyledProperty BarProperty = + AvaloniaProperty.Register(nameof(Bar), "bardefault"); public static readonly StyledProperty InheritedProperty = AvaloniaProperty.Register(nameof(Inherited), "inheriteddefault", inherits: true); public static readonly StyledProperty CoercedProperty = AvaloniaProperty.Register(nameof(Coerced), coerce: Coerce); public string Foo => GetValue(FooProperty); + public string Bar => GetValue(BarProperty); public string Inherited => GetValue(InheritedProperty); public double Coerced => GetValue(CoercedProperty); public double CoerceMax { get; set; } = 100; From d84f8225bee13c50060b79304273a1d2fd4ab548 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 27 Feb 2023 11:21:01 +0100 Subject: [PATCH 04/11] Fix value re-evaluation with SetCurrentValue. `SetCurrentValue`'s value was being discarded if a style with more than one setter was was re-evaluated as `ValueStore.ReevaluationEffectiveValues` removed the effective value. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index 0887f11ec9..53cd3ff307 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -924,7 +924,7 @@ namespace Avalonia.PropertyStore { _effectiveValues.GetKeyValue(i, out var key, out var e); - if (e.Priority == BindingPriority.Unset) + if (e.Priority == BindingPriority.Unset && !e.IsOverridenCurrentValue) { RemoveEffectiveValue(key, i); e.DisposeAndRaiseUnset(this, key); From 49c57a610c891d87315d4e247196ef948cd816cd Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Mon, 27 Feb 2023 20:18:44 +0100 Subject: [PATCH 05/11] Make `Avalonia.Win32.Input.KeyInterop` public --- src/Windows/Avalonia.Win32/Input/KeyInterop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs b/src/Windows/Avalonia.Win32/Input/KeyInterop.cs index f5b2d462ab..1ab4c0d2dc 100644 --- a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs +++ b/src/Windows/Avalonia.Win32/Input/KeyInterop.cs @@ -4,7 +4,7 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32.Input { - static class KeyInterop + public static class KeyInterop { private static readonly Dictionary s_virtualKeyFromKey = new Dictionary { From 3791fa8a51ed75a8b170d776d459b061e9352909 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Thu, 23 Feb 2023 23:44:33 +0100 Subject: [PATCH 06/11] Don't add ItemsControl items to its panel logical children --- src/Avalonia.Controls/Panel.cs | 50 +++++++++++++++---- .../Presenters/ItemsPresenter.cs | 1 + .../ItemsControlTests.cs | 15 +++++- .../Avalonia.Controls.UnitTests/PanelTests.cs | 15 ++++++ .../TabControlTests.cs | 2 +- .../TreeViewTests.cs | 2 +- 6 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index fa18ee468c..035e775af7 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -1,13 +1,12 @@ using System; -using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; +using Avalonia.Controls.Presenters; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; using Avalonia.Reactive; -using Avalonia.Styling; namespace Avalonia.Controls { @@ -26,6 +25,16 @@ namespace Avalonia.Controls public static readonly StyledProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly DirectProperty IsItemsHostProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsItemsHost), + o => o.IsItemsHost, + (o, v) => o.IsItemsHost = v, + unsetValue: false); + /// /// Initializes static members of the class. /// @@ -34,6 +43,7 @@ namespace Avalonia.Controls AffectsRender(BackgroundProperty); } + private bool _isItemsHost; private EventHandler? _childIndexChanged; /// @@ -59,6 +69,15 @@ namespace Avalonia.Controls set { SetValue(BackgroundProperty, value); } } + /// + /// Gets whether the hosts the items created by an . + /// + public bool IsItemsHost + { + get => _isItemsHost; + set => SetAndRaise(IsItemsHostProperty, ref _isItemsHost, value); + } + event EventHandler? IChildIndexProvider.ChildIndexChanged { add @@ -129,24 +148,29 @@ namespace Avalonia.Controls /// The event args. protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e) { - List controls; - switch (e.Action) { case NotifyCollectionChangedAction.Add: - controls = e.NewItems!.OfType().ToList(); - LogicalChildren.InsertRange(e.NewStartingIndex, controls); + if (!IsItemsHost) + { + LogicalChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType().ToList()); + } VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType()); break; case NotifyCollectionChangedAction.Move: - LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex); - VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex); + if (!IsItemsHost) + { + LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex); + } + VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex); break; case NotifyCollectionChangedAction.Remove: - controls = e.OldItems!.OfType().ToList(); - LogicalChildren.RemoveAll(controls); + if (!IsItemsHost) + { + LogicalChildren.RemoveAll(e.OldItems!.OfType().ToList()); + } VisualChildren.RemoveAll(e.OldItems!.OfType()); break; @@ -155,7 +179,10 @@ namespace Avalonia.Controls { var index = i + e.OldStartingIndex; var child = (Control)e.NewItems![i]!; - LogicalChildren[index] = child; + if (!IsItemsHost) + { + LogicalChildren[index] = child; + } VisualChildren[index] = child; } break; @@ -200,6 +227,7 @@ namespace Avalonia.Controls return child is Control control ? Children.IndexOf(control) : -1; } + /// public bool TryGetTotalCount(out int count) { count = Children.Count; diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index e8eaac7d17..0a762c438d 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -167,6 +167,7 @@ namespace Avalonia.Controls.Presenters Panel = ItemsPanel.Build(); Panel.SetValue(TemplatedParentProperty, TemplatedParent); + Panel.IsItemsHost = true; _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo; LogicalChildren.Add(Panel); VisualChildren.Add(Panel); diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index f074972cef..3aaf62f0bf 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -73,6 +73,19 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(target, target.Presenter.Panel.TemplatedParent); } + [Fact] + public void Panel_Should_Have_ItemsHost_Set_To_True() + { + var target = new ItemsControl(); + + target.Template = GetTemplate(); + target.Items = new[] { "Foo" }; + target.ApplyTemplate(); + target.Presenter!.ApplyTemplate(); + + Assert.True(target.Presenter.Panel!.IsItemsHost); + } + [Fact] public void Container_Should_Have_TemplatedParent_Set_To_Null() { @@ -634,7 +647,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Presenter.ApplyTemplate(); - var item = target.Presenter.Panel.LogicalChildren[0]; + var item = target.LogicalChildren[0]; Assert.Null(NameScope.GetNameScope((TextBlock)item)); } diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs index a31f0dd4c2..82f133d533 100644 --- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs @@ -141,5 +141,20 @@ namespace Avalonia.Controls.UnitTests var panel = new Panel(); Assert.Throws(() => panel.Children.Add(null!)); } + + [Fact] + public void Adding_Control_To_Items_Host_Panel_Should_Not_Affect_Logical_Children() + { + var child = new Control(); + var realParent = new ContentControl { Content = child }; + var panel = new Panel { IsItemsHost = true }; + + panel.Children.Add(child); + + Assert.Empty(panel.LogicalChildren); + Assert.Same(child.Parent, realParent); + Assert.Same(child.GetLogicalParent(), realParent); + Assert.Same(child.GetVisualParent(), panel); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 4c5397db60..0f72b2101a 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -327,7 +327,7 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); - var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren(); + var logicalChildren = target.GetLogicalChildren(); var result = logicalChildren .OfType() diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 1a9fb33b7e..2ca716fa8f 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1178,7 +1178,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Presenter.ApplyTemplate(); - var item = target.Presenter.Panel.LogicalChildren[0]; + var item = target.LogicalChildren[0]; Assert.Null(NameScope.GetNameScope((TreeViewItem)item)); } From b6701f63fd13f09165cbe0c599ddacfb55b15730 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 28 Feb 2023 17:07:16 +0100 Subject: [PATCH 07/11] Made Panel.IsItemsHost setter internal --- src/Avalonia.Controls/Panel.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 035e775af7..eff6603727 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -25,16 +25,6 @@ namespace Avalonia.Controls public static readonly StyledProperty BackgroundProperty = Border.BackgroundProperty.AddOwner(); - /// - /// Defines the property. - /// - public static readonly DirectProperty IsItemsHostProperty = - AvaloniaProperty.RegisterDirect( - nameof(IsItemsHost), - o => o.IsItemsHost, - (o, v) => o.IsItemsHost = v, - unsetValue: false); - /// /// Initializes static members of the class. /// @@ -43,7 +33,6 @@ namespace Avalonia.Controls AffectsRender(BackgroundProperty); } - private bool _isItemsHost; private EventHandler? _childIndexChanged; /// @@ -72,11 +61,7 @@ namespace Avalonia.Controls /// /// Gets whether the hosts the items created by an . /// - public bool IsItemsHost - { - get => _isItemsHost; - set => SetAndRaise(IsItemsHostProperty, ref _isItemsHost, value); - } + public bool IsItemsHost { get; internal set; } event EventHandler? IChildIndexProvider.ChildIndexChanged { From 12d418db336efad25859fbf8566768f9a1e86152 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Mar 2023 12:30:20 +0100 Subject: [PATCH 08/11] fix: Address Review --- .../AvaloniaPropertyAnalyzer.CompileAnalyzer.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs index af9b367368..ff70edde8e 100644 --- a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs +++ b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs @@ -694,11 +694,14 @@ public partial class AvaloniaPropertyAnalyzer void VerifyAccessor(IMethodSymbol? method, string verb, string methodName) { - if (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Public && method is null) + if (method is null) { - context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName)); + if (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Public) + { + context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName)); + } } - else if (method is not null && method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility) + else if (method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility) { context.ReportDiagnostic(Diagnostic.Create(InconsistentAccessibility, method.Locations[0], "property accessor", avaloniaPropertyStorage)); } From 9201f237541cca0eae59972f7552b36b7cef1b1c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Mar 2023 12:46:10 +0100 Subject: [PATCH 09/11] fix: Signal warnig if protected --- .../AvaloniaPropertyAnalyzer.CompileAnalyzer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs index ff70edde8e..0c0b487dba 100644 --- a/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs +++ b/src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs @@ -696,7 +696,9 @@ public partial class AvaloniaPropertyAnalyzer { if (method is null) { - if (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Public) + if (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Public || + (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Protected + && avaloniaPropertyStorage.ContainingSymbol.DeclaredAccessibility == Accessibility.Public)) { context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName)); } From a4b3d30affffcc32d4692aebdb84eb97cd7b52d2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 1 Mar 2023 20:42:27 +0600 Subject: [PATCH 10/11] Use proper baseline origin --- src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 4d8759f545..4b09bc9280 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -21,7 +21,7 @@ namespace Avalonia.Rendering.SceneGraph Matrix transform, IImmutableBrush foreground, IRef glyphRun) - : base(new Rect(glyphRun.Item.Size), transform, foreground) + : base(new Rect(glyphRun.Item.BaselineOrigin, glyphRun.Item.Size), transform, foreground) { GlyphRun = glyphRun.Clone(); } From c41193bccb97938daf55d9c64ca5b30a942fdc34 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Mar 2023 16:18:54 +0100 Subject: [PATCH 11/11] feat: AvaloniaVS defered loading Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package Part of AvaloniaUI/AvaloniaVS#311 --- packages/Avalonia/Avalonia.props | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/Avalonia/Avalonia.props b/packages/Avalonia/Avalonia.props index 6f21971d3d..26908b1081 100644 --- a/packages/Avalonia/Avalonia.props +++ b/packages/Avalonia/Avalonia.props @@ -6,4 +6,9 @@ false + + + + +