From 35bd69f23c24878f7534160b1bd6b6b7d4a12612 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 25 Nov 2019 23:01:27 +0100 Subject: [PATCH] Remove AvaloniaProperty.Initialized. It was only needed for pseudoclasses. Move pseudoclass registration to an engine that doesn't require `AvaloniaProperty.Initialized`, implemented in `PseudoclassEngine`. --- src/Avalonia.Base/AvaloniaObject.cs | 1 - src/Avalonia.Base/AvaloniaProperty.cs | 39 ------ src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 45 ------- src/Avalonia.Base/DirectPropertyBase.cs | 15 --- src/Avalonia.Base/StyledPropertyBase.cs | 15 --- src/Avalonia.Styling/PseudoclassEngine.cs | 120 ++++++++++++++++++ src/Avalonia.Styling/StyledElement.cs | 19 +-- .../AvaloniaObjectTests_Attached.cs | 17 --- .../AvaloniaObjectTests_Direct.cs | 16 --- .../AvaloniaPropertyTests.cs | 23 ---- .../DirectPropertyTests.cs | 22 ---- 11 files changed, 126 insertions(+), 206 deletions(-) create mode 100644 src/Avalonia.Styling/PseudoclassEngine.cs diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 5bddf95616..e427c64945 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -34,7 +34,6 @@ namespace Avalonia public AvaloniaObject() { VerifyAccess(); - AvaloniaPropertyRegistry.Instance.NotifyInitialized(this); } /// diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 8e5716a5bf..cd9b40f94a 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -22,7 +22,6 @@ namespace Avalonia public static readonly object UnsetValue = new UnsetValueType(); private static int s_nextId; - private readonly Subject _initialized; private readonly Subject _changed; private readonly PropertyMetadata _defaultMetadata; private readonly Dictionary _metadata; @@ -55,7 +54,6 @@ namespace Avalonia throw new ArgumentException("'name' may not contain periods."); } - _initialized = new Subject(); _changed = new Subject(); _metadata = new Dictionary(); @@ -83,7 +81,6 @@ namespace Avalonia Contract.Requires(source != null); Contract.Requires(ownerType != null); - _initialized = source._initialized; _changed = source._changed; _metadata = new Dictionary(); @@ -138,22 +135,6 @@ namespace Avalonia /// public virtual bool IsReadOnly => false; - /// - /// Gets an observable that is fired when this property is initialized on a - /// new instance. - /// - /// - /// This observable is fired each time a new is constructed - /// for all properties registered on the object's type. The default value of the property - /// for the object is passed in the args' NewValue (OldValue will always be - /// . - /// - /// - /// An observable that is fired when this property is initialized on a new - /// instance. - /// - public IObservable Initialized => _initialized; - /// /// Gets an observable that is fired when this property changes on any /// instance. @@ -482,26 +463,6 @@ namespace Avalonia return Name; } - /// - /// True if has any observers. - /// - internal bool HasNotifyInitializedObservers => _initialized.HasObservers; - - /// - /// Notifies the observable. - /// - /// The object being initialized. - internal abstract void NotifyInitialized(IAvaloniaObject o); - - /// - /// Notifies the observable. - /// - /// The observable arguments. - internal void NotifyInitialized(AvaloniaPropertyChangedEventArgs e) - { - _initialized.OnNext(e); - } - /// /// Notifies the observable. /// diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 0734b64721..50ffb51550 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -407,51 +407,6 @@ namespace Avalonia _inheritedCache.Clear(); } - internal void NotifyInitialized(AvaloniaObject o) - { - Contract.Requires(o != null); - - var type = o.GetType(); - - if (!_initializedCache.TryGetValue(type, out var initializationData)) - { - var visited = new HashSet(); - - initializationData = new List(); - - foreach (AvaloniaProperty property in GetRegistered(type)) - { - if (property.IsDirect) - { - initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property)); - } - else - { - initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type)); - } - - visited.Add(property); - } - - foreach (AvaloniaProperty property in GetRegisteredAttached(type)) - { - if (!visited.Contains(property)) - { - initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type)); - - visited.Add(property); - } - } - - _initializedCache.Add(type, initializationData); - } - - foreach (PropertyInitializationData data in initializationData) - { - data.Property.NotifyInitialized(o); - } - } - private readonly struct PropertyInitializationData { public AvaloniaProperty Property { get; } diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 7a0be065eb..39ed3b084f 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -101,21 +101,6 @@ namespace Avalonia return (DirectPropertyMetadata)base.GetMetadata(type); } - /// - internal override void NotifyInitialized(IAvaloniaObject o) - { - if (HasNotifyInitializedObservers) - { - var e = new AvaloniaPropertyChangedEventArgs( - o, - this, - default, - InvokeGetter(o), - BindingPriority.Unset); - NotifyInitialized(e); - } - } - /// internal override void RouteClearValue(IAvaloniaObject o) { diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index 8c4d683ae0..d1f961a567 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -181,21 +181,6 @@ namespace Avalonia /// object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); - /// - internal override void NotifyInitialized(IAvaloniaObject o) - { - if (HasNotifyInitializedObservers) - { - var e = new AvaloniaPropertyChangedEventArgs( - o, - this, - default, - o.GetValue(this), - BindingPriority.Unset); - NotifyInitialized(e); - } - } - /// internal override void RouteClearValue(IAvaloniaObject o) { diff --git a/src/Avalonia.Styling/PseudoclassEngine.cs b/src/Avalonia.Styling/PseudoclassEngine.cs new file mode 100644 index 0000000000..6c8f8a832a --- /dev/null +++ b/src/Avalonia.Styling/PseudoclassEngine.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls; + +namespace Avalonia +{ + internal static class PseudoclassEngine + { + private static Dictionary> _entries = new Dictionary>(); + private static Dictionary> _properties = new Dictionary>(); + private static Dictionary> _cache = new Dictionary>(); + + public static void Created(StyledElement element) + { + var t = element.GetType(); + + if (!_cache.TryGetValue(t, out var list)) + { + list = new List(); + + foreach (var i in _entries) + { + if (i.Key.IsAssignableFrom(t)) + { + list.AddRange(i.Value); + } + } + + _cache.Add(t, list); + } + + foreach (var i in list) + { + i.Update(element); + } + } + + public static void Register( + Type type, + AvaloniaProperty property, + Func selector, + string className) + { + if (!_entries.TryGetValue(type, out var list)) + { + list = new List(); + _entries.Add(type, list); + } + + var entry = new Entry(type, property, selector, className); + list.Add(entry); + + if (!_properties.TryGetValue(property, out list)) + { + list = new List(); + _properties.Add(property, list); + property.Changed.Subscribe(ValueChanged); + } + + list.Add(entry); + } + + private static void ValueChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.Sender is StyledElement element && + _properties.TryGetValue(e.Property, out var list)) + { + var t = e.Sender.GetType(); + + foreach (var i in list) + { + if (i.Type.IsAssignableFrom(t)) + { + i.Update(element); + } + } + } + } + + private interface IEntry + { + Type Type { get; } + void Update(StyledElement element); + } + + private class Entry : IEntry + { + public Entry( + Type type, + AvaloniaProperty property, + Func selector, + string className) + { + Type = type; + Property = property; + Selector = selector; + ClassName = className; + } + + public Type Type { get; } + public AvaloniaProperty Property { get; } + public Func Selector { get; } + public string ClassName { get; } + + public void Update(StyledElement element) + { + var value = element.GetValue(Property); + + if (Selector(value)) + { + ((IPseudoClasses)element.Classes).Add(ClassName); + } + else + { + ((IPseudoClasses)element.Classes).Remove(ClassName); + } + } + } + } +} diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index cc7da2b436..90e1259ce6 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -82,6 +82,7 @@ namespace Avalonia public StyledElement() { _isAttachedToLogicalTree = this is IStyleRoot; + PseudoclassEngine.Created(this); } /// @@ -557,19 +558,11 @@ namespace Avalonia throw new ArgumentException("Cannot supply an empty className."); } - property.Changed.Merge(property.Initialized) - .Where(e => e.Sender is TOwner) - .Subscribe(e => - { - if (selector((TProperty)e.NewValue)) - { - ((StyledElement)e.Sender).PseudoClasses.Add(className); - } - else - { - ((StyledElement)e.Sender).PseudoClasses.Remove(className); - } - }); + PseudoclassEngine.Register( + typeof(TOwner), + property, + selector, + className); } protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs index 44e2976e03..474ef9abec 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs @@ -16,19 +16,6 @@ namespace Avalonia.Base.UnitTests Assert.Equal("foodefault", target.GetValue(Class2.FooProperty)); } - [Fact] - public void AvaloniaProperty_Initialized_Is_Called_For_Attached_Property() - { - bool raised = false; - - using (Class1.FooProperty.Initialized.Subscribe(x => raised = true)) - { - new Class3(); - } - - Assert.True(raised); - } - private class Base : AvaloniaObject { } @@ -46,9 +33,5 @@ namespace Avalonia.Base.UnitTests public static readonly AttachedProperty FooProperty = Class1.FooProperty.AddOwner(); } - - private class Class3 : Base - { - } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 4110c3771f..b1a5b5ae92 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -424,22 +424,6 @@ namespace Avalonia.Base.UnitTests Assert.Equal("second", target.Foo); } - [Fact] - public void Property_Notifies_Initialized() - { - bool raised = false; - - Class1.FooProperty.Initialized.Subscribe(e => - raised = e.Property == Class1.FooProperty && - e.OldValue == AvaloniaProperty.UnsetValue && - (string)e.NewValue == "initial" && - e.Priority == BindingPriority.Unset); - - var target = new Class1(); - - Assert.True(raised); - } - [Fact] public void Binding_Error_Reverts_To_Default_Value() { diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index 90b8bcff63..6bb8dfe1f5 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -78,24 +78,6 @@ namespace Avalonia.Base.UnitTests Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode); } - [Fact] - public void Initialized_Observable_Fired() - { - bool invoked = false; - - Class1.FooProperty.Initialized.Subscribe(x => - { - Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue); - Assert.Equal("default", x.NewValue); - Assert.Equal(BindingPriority.Unset, x.Priority); - invoked = true; - }); - - var target = new Class1(); - - Assert.True(invoked); - } - [Fact] public void Changed_Observable_Fired() { @@ -141,11 +123,6 @@ namespace Avalonia.Base.UnitTests OverrideMetadata(typeof(T), metadata); } - internal override void NotifyInitialized(IAvaloniaObject o) - { - throw new NotImplementedException(); - } - internal override IDisposable RouteBind( IAvaloniaObject o, IObservable> source, diff --git a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs index c2a8b03f15..8f62dc9136 100644 --- a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs @@ -1,33 +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 System.Reactive.Subjects; -using Avalonia.Data; using Xunit; namespace Avalonia.Base.UnitTests { public class DirectPropertyTests { - [Fact] - public void Initialized_Observable_Fired() - { - bool invoked = false; - - Class1.FooProperty.Initialized.Subscribe(x => - { - Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue); - Assert.Equal("foo", x.NewValue); - Assert.Equal(BindingPriority.Unset, x.Priority); - invoked = true; - }); - - var target = new Class1(); - - Assert.True(invoked); - } - [Fact] public void IsDirect_Property_Returns_True() { @@ -69,7 +48,6 @@ namespace Avalonia.Base.UnitTests var p2 = p1.AddOwner(o => null, (o, v) => { }); Assert.Same(p1.Changed, p2.Changed); - Assert.Same(p1.Initialized, p2.Initialized); } private class Class1 : AvaloniaObject