Browse Source

Refactored styling.

- Don't use Rx in the styling system. Instead introduces `IStyleActivator` which is like an `IObservable<bool>`-lite in order to cut down on allocations.
- #nullable enable on touched files
Steven Kirk 5 năm trước cách đây
mục cha
commit
dc55d65287
56 tập tin đã thay đổi với 1166 bổ sung1486 xóa
  1. 11 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  2. 0 1
      src/Avalonia.Styling/Avalonia.Styling.csproj
  3. 0 6
      src/Avalonia.Styling/Controls/NameScopeLocator.cs
  4. 69 69
      src/Avalonia.Styling/StyledElement.cs
  5. 0 77
      src/Avalonia.Styling/Styling/ActivatedObservable.cs
  6. 0 110
      src/Avalonia.Styling/Styling/ActivatedSubject.cs
  7. 0 133
      src/Avalonia.Styling/Styling/ActivatedValue.cs
  8. 67 0
      src/Avalonia.Styling/Styling/Activators/AndActivator.cs
  9. 33 0
      src/Avalonia.Styling/Styling/Activators/IStyleActivator.cs
  10. 17 0
      src/Avalonia.Styling/Styling/Activators/IStyleActivatorSink.cs
  11. 13 0
      src/Avalonia.Styling/Styling/Activators/NotActivator.cs
  12. 67 0
      src/Avalonia.Styling/Styling/Activators/OrActivator.cs
  13. 35 0
      src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs
  14. 55 0
      src/Avalonia.Styling/Styling/Activators/StyleActivatorBase.cs
  15. 72 0
      src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
  16. 32 16
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  17. 13 6
      src/Avalonia.Styling/Styling/ISetter.cs
  18. 20 0
      src/Avalonia.Styling/Styling/ISetterInstance.cs
  19. 6 10
      src/Avalonia.Styling/Styling/IStyle.cs
  20. 22 0
      src/Avalonia.Styling/Styling/IStyleInstance.cs
  21. 14 6
      src/Avalonia.Styling/Styling/IStyleable.cs
  22. 9 7
      src/Avalonia.Styling/Styling/NotSelector.cs
  23. 39 14
      src/Avalonia.Styling/Styling/OrSelector.cs
  24. 11 14
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  25. 48 0
      src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs
  26. 82 0
      src/Avalonia.Styling/Styling/PropertySetterInstance.cs
  27. 31 13
      src/Avalonia.Styling/Styling/Selector.cs
  28. 18 11
      src/Avalonia.Styling/Styling/SelectorMatch.cs
  29. 53 88
      src/Avalonia.Styling/Styling/Setter.cs
  30. 28 137
      src/Avalonia.Styling/Styling/Style.cs
  31. 0 56
      src/Avalonia.Styling/Styling/StyleActivator.cs
  32. 81 0
      src/Avalonia.Styling/Styling/StyleInstance.cs
  33. 12 13
      src/Avalonia.Styling/Styling/Styler.cs
  34. 37 45
      src/Avalonia.Styling/Styling/Styles.cs
  35. 16 93
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  36. 2 17
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  37. 1 1
      tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
  38. 3 3
      tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs
  39. 4 4
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  40. 1 1
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  41. 1 1
      tests/Avalonia.Controls.UnitTests/UserControlTests.cs
  42. 9 1
      tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs
  43. 1 36
      tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
  44. 0 71
      tests/Avalonia.Styling.UnitTests/ActivatedObservableTests.cs
  45. 0 92
      tests/Avalonia.Styling.UnitTests/ActivatedSubjectTests.cs
  46. 0 75
      tests/Avalonia.Styling.UnitTests/ActivatedValueTests.cs
  47. 8 8
      tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
  48. 1 1
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
  49. 29 0
      tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs
  50. 4 4
      tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs
  51. 1 1
      tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
  52. 27 24
      tests/Avalonia.Styling.UnitTests/SetterTests.cs
  53. 42 0
      tests/Avalonia.Styling.UnitTests/StyleActivatorExtensions.cs
  54. 0 169
      tests/Avalonia.Styling.UnitTests/StyleActivatorTests.cs
  55. 16 42
      tests/Avalonia.Styling.UnitTests/StyleTests.cs
  56. 5 4
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

+ 11 - 6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@@ -6,6 +6,8 @@ using System.Collections.Specialized;
 using System.Reactive;
 using System.Reactive;
 using System.Reactive.Linq;
 using System.Reactive.Linq;
 using Avalonia.Collections;
 using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 
 
@@ -22,22 +24,25 @@ namespace Avalonia.Diagnostics.ViewModels
             Type = visual.GetType().Name;
             Type = visual.GetType().Name;
             Visual = visual;
             Visual = visual;
 
 
-            if (visual is IStyleable styleable)
+            if (visual is IControl control)
             {
             {
+                var removed = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
+                    x => control.DetachedFromLogicalTree += x,
+                    x => control.DetachedFromLogicalTree -= x);
                 var classesChanged = Observable.FromEventPattern<
                 var classesChanged = Observable.FromEventPattern<
                         NotifyCollectionChangedEventHandler,
                         NotifyCollectionChangedEventHandler,
                         NotifyCollectionChangedEventArgs>(
                         NotifyCollectionChangedEventArgs>(
-                    x => styleable.Classes.CollectionChanged += x,
-                    x => styleable.Classes.CollectionChanged -= x)
-                    .TakeUntil(((IStyleable)styleable).StyleDetach);
+                        x => control.Classes.CollectionChanged += x,
+                        x => control.Classes.CollectionChanged -= x)
+                    .TakeUntil(removed);
 
 
                 classesChanged.Select(_ => Unit.Default)
                 classesChanged.Select(_ => Unit.Default)
                     .StartWith(Unit.Default)
                     .StartWith(Unit.Default)
                     .Subscribe(_ =>
                     .Subscribe(_ =>
                     {
                     {
-                        if (styleable.Classes.Count > 0)
+                        if (control.Classes.Count > 0)
                         {
                         {
-                            Classes = "(" + string.Join(" ", styleable.Classes) + ")";
+                            Classes = "(" + string.Join(" ", control.Classes) + ")";
                         }
                         }
                         else
                         else
                         {
                         {

+ 0 - 1
src/Avalonia.Styling/Avalonia.Styling.csproj

@@ -8,5 +8,4 @@
     <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
     <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
   </ItemGroup>
   </ItemGroup>
-  <Import Project="..\..\build\Rx.props" />
 </Project>
 </Project>

+ 0 - 6
src/Avalonia.Styling/Controls/NameScopeLocator.cs

@@ -1,11 +1,5 @@
 using System;
 using System;
-using System.Linq;
 using System.Reactive.Disposables;
 using System.Reactive.Disposables;
-using System.Reactive.Threading.Tasks;
-using System.Reflection;
-using System.Threading.Tasks;
-using Avalonia.LogicalTree;
-using Avalonia.Reactive;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls

+ 69 - 69
src/Avalonia.Styling/StyledElement.cs

@@ -3,8 +3,6 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Linq;
 using System.Linq;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
 using Avalonia.Animation;
 using Avalonia.Animation;
 using Avalonia.Collections;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls;
@@ -14,6 +12,8 @@ using Avalonia.Logging;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
 using Avalonia.Styling;
 using Avalonia.Styling;
 
 
+#nullable enable
+
 namespace Avalonia
 namespace Avalonia
 {
 {
     /// <summary>
     /// <summary>
@@ -29,8 +29,8 @@ namespace Avalonia
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DataContext"/> property.
         /// Defines the <see cref="DataContext"/> property.
         /// </summary>
         /// </summary>
-        public static readonly StyledProperty<object> DataContextProperty =
-            AvaloniaProperty.Register<StyledElement, object>(
+        public static readonly StyledProperty<object?> DataContextProperty =
+            AvaloniaProperty.Register<StyledElement, object?>(
                 nameof(DataContext),
                 nameof(DataContext),
                 inherits: true,
                 inherits: true,
                 notifying: DataContextNotifying);
                 notifying: DataContextNotifying);
@@ -38,34 +38,34 @@ namespace Avalonia
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Name"/> property.
         /// Defines the <see cref="Name"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<StyledElement, string> NameProperty =
-            AvaloniaProperty.RegisterDirect<StyledElement, string>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
+        public static readonly DirectProperty<StyledElement, string?> NameProperty =
+            AvaloniaProperty.RegisterDirect<StyledElement, string?>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
         
         
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Parent"/> property.
         /// Defines the <see cref="Parent"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<StyledElement, IStyledElement> ParentProperty =
-            AvaloniaProperty.RegisterDirect<StyledElement, IStyledElement>(nameof(Parent), o => o.Parent);
+        public static readonly DirectProperty<StyledElement, IStyledElement?> ParentProperty =
+            AvaloniaProperty.RegisterDirect<StyledElement, IStyledElement?>(nameof(Parent), o => o.Parent);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="TemplatedParent"/> property.
         /// Defines the <see cref="TemplatedParent"/> property.
         /// </summary>
         /// </summary>
-        public static readonly DirectProperty<StyledElement, ITemplatedControl> TemplatedParentProperty =
-            AvaloniaProperty.RegisterDirect<StyledElement, ITemplatedControl>(
+        public static readonly DirectProperty<StyledElement, ITemplatedControl?> TemplatedParentProperty =
+            AvaloniaProperty.RegisterDirect<StyledElement, ITemplatedControl?>(
                 nameof(TemplatedParent),
                 nameof(TemplatedParent),
                 o => o.TemplatedParent,
                 o => o.TemplatedParent,
                 (o ,v) => o.TemplatedParent = v);
                 (o ,v) => o.TemplatedParent = v);
 
 
         private int _initCount;
         private int _initCount;
-        private string _name;
+        private string? _name;
         private readonly Classes _classes = new Classes();
         private readonly Classes _classes = new Classes();
-        private ILogicalRoot _logicalRoot;
-        private IAvaloniaList<ILogical> _logicalChildren;
-        private IResourceDictionary _resources;
-        private Styles _styles;
+        private ILogicalRoot? _logicalRoot;
+        private IAvaloniaList<ILogical>? _logicalChildren;
+        private IResourceDictionary? _resources;
+        private Styles? _styles;
         private bool _styled;
         private bool _styled;
-        private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
-        private ITemplatedControl _templatedParent;
+        private List<IStyleInstance>? _appliedStyles;
+        private ITemplatedControl? _templatedParent;
         private bool _dataContextUpdating;
         private bool _dataContextUpdating;
 
 
         /// <summary>
         /// <summary>
@@ -87,12 +87,12 @@ namespace Avalonia
         /// <summary>
         /// <summary>
         /// Raised when the styled element is attached to a rooted logical tree.
         /// Raised when the styled element is attached to a rooted logical tree.
         /// </summary>
         /// </summary>
-        public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
+        public event EventHandler<LogicalTreeAttachmentEventArgs>? AttachedToLogicalTree;
 
 
         /// <summary>
         /// <summary>
         /// Raised when the styled element is detached from a rooted logical tree.
         /// Raised when the styled element is detached from a rooted logical tree.
         /// </summary>
         /// </summary>
-        public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
+        public event EventHandler<LogicalTreeAttachmentEventArgs>? DetachedFromLogicalTree;
 
 
         /// <summary>
         /// <summary>
         /// Occurs when the <see cref="DataContext"/> property changes.
         /// Occurs when the <see cref="DataContext"/> property changes.
@@ -101,7 +101,7 @@ namespace Avalonia
         /// This event will be raised when the <see cref="DataContext"/> property has changed and
         /// This event will be raised when the <see cref="DataContext"/> property has changed and
         /// all subscribers to that change have been notified.
         /// all subscribers to that change have been notified.
         /// </remarks>
         /// </remarks>
-        public event EventHandler DataContextChanged;
+        public event EventHandler? DataContextChanged;
 
 
         /// <summary>
         /// <summary>
         /// Occurs when the styled element has finished initialization.
         /// Occurs when the styled element has finished initialization.
@@ -114,12 +114,12 @@ namespace Avalonia
         /// <see cref="ISupportInitialize"/> is not used, it is called when the styled element is attached
         /// <see cref="ISupportInitialize"/> is not used, it is called when the styled element is attached
         /// to the visual tree.
         /// to the visual tree.
         /// </remarks>
         /// </remarks>
-        public event EventHandler Initialized;
+        public event EventHandler? Initialized;
 
 
         /// <summary>
         /// <summary>
         /// Occurs when a resource in this styled element or a parent styled element has changed.
         /// Occurs when a resource in this styled element or a parent styled element has changed.
         /// </summary>
         /// </summary>
-        public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
+        public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the name of the styled element.
         /// Gets or sets the name of the styled element.
@@ -128,20 +128,12 @@ namespace Avalonia
         /// An element's name is used to uniquely identify an element within the element's name
         /// An element's name is used to uniquely identify an element within the element's name
         /// scope. Once the element is added to a logical tree, its name cannot be changed.
         /// scope. Once the element is added to a logical tree, its name cannot be changed.
         /// </remarks>
         /// </remarks>
-        public string Name
+        public string? Name
         {
         {
-            get
-            {
-                return _name;
-            }
+            get => _name;
 
 
             set
             set
             {
             {
-                if (String.IsNullOrWhiteSpace(value))
-                {
-                    throw new InvalidOperationException("Cannot set Name to null or empty string.");
-                }
-
                 if (_styled)
                 if (_styled)
                 {
                 {
                     throw new InvalidOperationException("Cannot set Name : styled element already styled.");
                     throw new InvalidOperationException("Cannot set Name : styled element already styled.");
@@ -189,7 +181,7 @@ namespace Avalonia
         /// The data context is an inherited property that specifies the default object that will
         /// The data context is an inherited property that specifies the default object that will
         /// be used for data binding.
         /// be used for data binding.
         /// </remarks>
         /// </remarks>
-        public object DataContext
+        public object? DataContext
         {
         {
             get { return GetValue(DataContextProperty); }
             get { return GetValue(DataContextProperty); }
             set { SetValue(DataContextProperty, value); }
             set { SetValue(DataContextProperty, value); }
@@ -214,28 +206,15 @@ namespace Avalonia
         /// </remarks>
         /// </remarks>
         public Styles Styles
         public Styles Styles
         {
         {
-            get { return _styles ?? (Styles = new Styles()); }
-            set
+            get 
             {
             {
-                Contract.Requires<ArgumentNullException>(value != null);
-
-                if (_styles != value)
+                if (_styles is null)
                 {
                 {
-                    if (_styles != null)
-                    {
-                        (_styles as ISetResourceParent)?.SetParent(null);
-                        _styles.ResourcesChanged -= ThisResourcesChanged;
-                    }
-
-                    _styles = value;
-
-                    if (value is ISetResourceParent setParent && setParent.ResourceParent == null)
-                    {
-                        setParent.SetParent(this);
-                    }
-
+                    _styles = new Styles(this);
                     _styles.ResourcesChanged += ThisResourcesChanged;
                     _styles.ResourcesChanged += ThisResourcesChanged;
                 }
                 }
+
+                return _styles;
             }
             }
         }
         }
 
 
@@ -247,7 +226,7 @@ namespace Avalonia
             get => _resources ?? (Resources = new ResourceDictionary());
             get => _resources ?? (Resources = new ResourceDictionary());
             set
             set
             {
             {
-                Contract.Requires<ArgumentNullException>(value != null);
+                value = value ?? throw new ArgumentNullException(nameof(value));
 
 
                 var hadResources = false;
                 var hadResources = false;
 
 
@@ -270,7 +249,7 @@ namespace Avalonia
         /// <summary>
         /// <summary>
         /// Gets the styled element whose lookless template this styled element is part of.
         /// Gets the styled element whose lookless template this styled element is part of.
         /// </summary>
         /// </summary>
-        public ITemplatedControl TemplatedParent
+        public ITemplatedControl? TemplatedParent
         {
         {
             get => _templatedParent;
             get => _templatedParent;
             internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value);
             internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value);
@@ -312,12 +291,12 @@ namespace Avalonia
         /// <summary>
         /// <summary>
         /// Gets the styled element's logical parent.
         /// Gets the styled element's logical parent.
         /// </summary>
         /// </summary>
-        public IStyledElement Parent { get; private set; }
+        public IStyledElement? Parent { get; private set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the styled element's logical parent.
         /// Gets the styled element's logical parent.
         /// </summary>
         /// </summary>
-        ILogical ILogical.LogicalParent => Parent;
+        ILogical? ILogical.LogicalParent => Parent;
 
 
         /// <summary>
         /// <summary>
         /// Gets the styled element's logical children.
         /// Gets the styled element's logical children.
@@ -328,7 +307,7 @@ namespace Avalonia
         bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
         bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
+        IResourceNode? IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
         IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
@@ -344,14 +323,11 @@ namespace Avalonia
         /// </remarks>
         /// </remarks>
         Type IStyleable.StyleKey => GetType();
         Type IStyleable.StyleKey => GetType();
 
 
-        /// <inheritdoc/>
-        IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
-
         /// <inheritdoc/>
         /// <inheritdoc/>
         bool IStyleHost.IsStylesInitialized => _styles != null;
         bool IStyleHost.IsStylesInitialized => _styles != null;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
+        IStyleHost? IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public virtual void BeginInit()
         public virtual void BeginInit()
@@ -397,13 +373,13 @@ namespace Avalonia
         /// <inheritdoc/>
         /// <inheritdoc/>
         void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
         void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
         {
-            this.OnAttachedToLogicalTreeCore(e);
+            OnAttachedToLogicalTreeCore(e);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
         {
-            this.OnDetachedFromLogicalTreeCore(e);
+            OnDetachedFromLogicalTreeCore(e);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -413,7 +389,7 @@ namespace Avalonia
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        bool IResourceProvider.TryGetResource(object key, out object value)
+        bool IResourceProvider.TryGetResource(object key, out object? value)
         {
         {
             value = null;
             value = null;
             return (_resources?.TryGetResource(key, out value) ?? false) ||
             return (_resources?.TryGetResource(key, out value) ?? false) ||
@@ -424,7 +400,7 @@ namespace Avalonia
         /// Sets the styled element's logical parent.
         /// Sets the styled element's logical parent.
         /// </summary>
         /// </summary>
         /// <param name="parent">The parent.</param>
         /// <param name="parent">The parent.</param>
-        void ISetLogicalParent.SetParent(ILogical parent)
+        void ISetLogicalParent.SetParent(ILogical? parent)
         {
         {
             var old = Parent;
             var old = Parent;
 
 
@@ -440,7 +416,7 @@ namespace Avalonia
                     InheritanceParent = parent as AvaloniaObject;
                     InheritanceParent = parent as AvaloniaObject;
                 }
                 }
 
 
-                Parent = (IStyledElement)parent;
+                Parent = (IStyledElement?)parent;
 
 
                 if (_logicalRoot != null)
                 if (_logicalRoot != null)
                 {
                 {
@@ -470,12 +446,13 @@ namespace Avalonia
                     var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent);
                     var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent);
                     OnAttachedToLogicalTreeCore(e);
                     OnAttachedToLogicalTreeCore(e);
                 }
                 }
-
+#nullable disable
                 RaisePropertyChanged(
                 RaisePropertyChanged(
                     ParentProperty,
                     ParentProperty,
                     new Optional<IStyledElement>(old),
                     new Optional<IStyledElement>(old),
                     new BindingValue<IStyledElement>(Parent),
                     new BindingValue<IStyledElement>(Parent),
                     BindingPriority.LocalValue);
                     BindingPriority.LocalValue);
+#nullable enable
             }
             }
         }
         }
 
 
@@ -488,6 +465,16 @@ namespace Avalonia
             InheritanceParent = parent;
             InheritanceParent = parent;
         }
         }
 
 
+        void IStyleable.StyleApplied(IStyleInstance instance)
+        {
+            instance = instance ?? throw new ArgumentNullException(nameof(instance));
+
+            _appliedStyles ??= new List<IStyleInstance>();
+            _appliedStyles.Add(instance);
+        }
+
+        void IStyleable.DetachStyles() => DetachStyles();
+
         protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
         protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
         {
         {
             switch (e.Action)
             switch (e.Action)
@@ -597,7 +584,7 @@ namespace Avalonia
             }
             }
         }
         }
 
 
-        private static ILogicalRoot FindLogicalRoot(IStyleHost e)
+        private static ILogicalRoot? FindLogicalRoot(IStyleHost e)
         {
         {
             while (e != null)
             while (e != null)
             {
             {
@@ -666,7 +653,7 @@ namespace Avalonia
             if (_logicalRoot != null)
             if (_logicalRoot != null)
             {
             {
                 _logicalRoot = null;
                 _logicalRoot = null;
-                _styleDetach.OnNext(this);
+                DetachStyles();
                 OnDetachedFromLogicalTree(e);
                 OnDetachedFromLogicalTree(e);
                 DetachedFromLogicalTree?.Invoke(this, e);
                 DetachedFromLogicalTree?.Invoke(this, e);
 
 
@@ -682,7 +669,7 @@ namespace Avalonia
                 }
                 }
 
 
 #if DEBUG
 #if DEBUG
-                if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
+                if (((INotifyCollectionChangedDebug)Classes).GetCollectionChangedSubscribers()?.Length > 0)
                 {
                 {
                     Logger.TryGet(LogEventLevel.Warning)?.Log(
                     Logger.TryGet(LogEventLevel.Warning)?.Log(
                         LogArea.Control,
                         LogArea.Control,
@@ -710,6 +697,19 @@ namespace Avalonia
             }
             }
         }
         }
 
 
+        private void DetachStyles()
+        {
+            if (_appliedStyles is object)
+            {
+                foreach (var i in _appliedStyles)
+                {
+                    i.Dispose();
+                }
+
+                _appliedStyles.Clear();
+            }
+        }
+
         private void ClearLogicalParent(IEnumerable<ILogical> children)
         private void ClearLogicalParent(IEnumerable<ILogical> children)
         {
         {
             foreach (var i in children)
             foreach (var i in children)

+ 0 - 77
src/Avalonia.Styling/Styling/ActivatedObservable.cs

@@ -1,77 +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 System;
-
-namespace Avalonia.Styling
-{
-    /// <summary>
-    /// An observable which is switched on or off according to an activator observable.
-    /// </summary>
-    /// <remarks>
-    /// An <see cref="ActivatedObservable"/> has two inputs: an activator observable and a 
-    /// <see cref="Source"/> observable which produces the activated value. When the activator 
-    /// produces true, the <see cref="ActivatedObservable"/> will produce the current activated 
-    /// value. When the activator produces false it will produce
-    /// <see cref="AvaloniaProperty.UnsetValue"/>.
-    /// </remarks>
-    internal class ActivatedObservable : ActivatedValue, IDescription
-    {
-        private IDisposable _sourceSubscription;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
-        /// </summary>
-        /// <param name="activator">The activator.</param>
-        /// <param name="source">An observable that produces the activated value.</param>
-        /// <param name="description">The binding description.</param>
-        public ActivatedObservable(
-            IObservable<bool> activator,
-            IObservable<object> source,
-            string description)
-            : base(activator, AvaloniaProperty.UnsetValue, description)
-        {
-            Contract.Requires<ArgumentNullException>(source != null);
-
-            Source = source;
-        }
-
-        /// <summary>
-        /// Gets an observable which produces the <see cref="ActivatedValue"/>.
-        /// </summary>
-        public IObservable<object> Source { get; }
-
-        protected override ActivatorListener CreateListener() => new ValueListener(this);
-
-        protected override void Deinitialize()
-        {
-            base.Deinitialize();
-            _sourceSubscription.Dispose();
-            _sourceSubscription = null;
-        }
-
-        protected override void Initialize()
-        {
-            base.Initialize();
-            _sourceSubscription = Source.Subscribe((ValueListener)Listener);
-        }
-
-        protected virtual void NotifyValue(object value)
-        {
-            Value = value;
-        }
-
-        private class ValueListener : ActivatorListener, IObserver<object>
-        {
-            public ValueListener(ActivatedObservable parent)
-                : base(parent)
-            {
-            }
-            protected new ActivatedObservable Parent => (ActivatedObservable)base.Parent;
-
-            void IObserver<object>.OnCompleted() => Parent.CompletedReceived();
-            void IObserver<object>.OnError(Exception error) => Parent.ErrorReceived(error);
-            void IObserver<object>.OnNext(object value) => Parent.NotifyValue(value);
-        }
-    }
-}

+ 0 - 110
src/Avalonia.Styling/Styling/ActivatedSubject.cs

@@ -1,110 +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 System;
-using System.Reactive.Subjects;
-
-namespace Avalonia.Styling
-{
-    /// <summary>
-    /// A subject which is switched on or off according to an activator observable.
-    /// </summary>
-    /// <remarks>
-    /// An <see cref="ActivatedSubject"/> extends <see cref="ActivatedObservable"/> to
-    /// be an <see cref="ISubject{Object}"/>. When the object is active then values
-    /// received via <see cref="OnNext(object)"/> will be passed to the source subject.
-    /// </remarks>
-    internal class ActivatedSubject : ActivatedObservable, ISubject<object>, IDescription
-    {
-        private bool _completed;
-        private object _pushValue;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ActivatedSubject"/> class.
-        /// </summary>
-        /// <param name="activator">The activator.</param>
-        /// <param name="source">An observable that produces the activated value.</param>
-        /// <param name="description">The binding description.</param>
-        public ActivatedSubject(
-            IObservable<bool> activator,
-            ISubject<object> source,
-            string description)
-            : base(activator, source, description)
-        {
-        }
-
-        /// <summary>
-        /// Gets the underlying subject.
-        /// </summary>
-        public new ISubject<object> Source
-        {
-            get { return (ISubject<object>)base.Source; }
-        }
-
-        public void OnCompleted()
-        {
-            Source.OnCompleted();
-        }
-
-        public void OnError(Exception error)
-        {
-            Source.OnError(error);
-        }
-
-        public void OnNext(object value)
-        {
-            _pushValue = value;
-
-            if (IsActive == true && !_completed)
-            {
-                Source.OnNext(_pushValue);
-            }
-        }
-
-        protected override void ActiveChanged(bool active)
-        {
-            bool first = !IsActive.HasValue;
-
-            base.ActiveChanged(active);
-
-            if (!first)
-            {
-                Source.OnNext(active ? _pushValue : AvaloniaProperty.UnsetValue);
-            }
-        }
-
-        protected override void CompletedReceived()
-        {
-            base.CompletedReceived();
-
-            if (!_completed)
-            {
-                Source.OnCompleted();
-                _completed = true;
-            }
-        }
-
-        protected override void ErrorReceived(Exception error)
-        {
-            base.ErrorReceived(error);
-
-            if (!_completed)
-            {
-                Source.OnError(error);
-                _completed = true;
-            }
-        }
-
-        private void ActivatorCompleted()
-        {
-            _completed = true;
-            Source.OnCompleted();
-        }
-
-        private void ActivatorError(Exception e)
-        {
-            _completed = true;
-            Source.OnError(e);
-        }
-    }
-}

+ 0 - 133
src/Avalonia.Styling/Styling/ActivatedValue.cs

@@ -1,133 +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 System;
-using Avalonia.Reactive;
-
-namespace Avalonia.Styling
-{
-    /// <summary>
-    /// An value which is switched on or off according to an activator observable.
-    /// </summary>
-    /// <remarks>
-    /// An <see cref="ActivatedValue"/> has two inputs: an activator observable and an
-    /// <see cref="Value"/>. When the activator produces true, the 
-    /// <see cref="ActivatedValue"/> will produce the current value. When the activator 
-    /// produces false it will produce <see cref="AvaloniaProperty.UnsetValue"/>.
-    /// </remarks>
-    internal class ActivatedValue : LightweightObservableBase<object>, IDescription
-    {
-        private static readonly object NotSent = new object();
-        private IDisposable _activatorSubscription;
-        private object _value;
-        private object _last = NotSent;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ActivatedObservable"/> class.
-        /// </summary>
-        /// <param name="activator">The activator.</param>
-        /// <param name="value">The activated value.</param>
-        /// <param name="description">The binding description.</param>
-        public ActivatedValue(
-            IObservable<bool> activator,
-            object value,
-            string description)
-        {
-            Contract.Requires<ArgumentNullException>(activator != null);
-
-            Activator = activator;
-            Value = value;
-            Description = description;
-            Listener = CreateListener();
-        }
-
-        /// <summary>
-        /// Gets the activator observable.
-        /// </summary>
-        public IObservable<bool> Activator { get; }
-
-        /// <summary>
-        /// Gets a description of the binding.
-        /// </summary>
-        public string Description { get; }
-
-        /// <summary>
-        /// Gets a value indicating whether the activator is active.
-        /// </summary>
-        public bool? IsActive { get; private set; }
-
-        /// <summary>
-        /// Gets the value that will be produced when <see cref="IsActive"/> is true.
-        /// </summary>
-        public object Value
-        {
-            get => _value;
-            protected set
-            {
-                _value = value;
-                PublishValue();
-            }
-        }
-
-        protected ActivatorListener Listener { get; }
-
-        protected virtual void ActiveChanged(bool active)
-        {
-            IsActive = active;
-            PublishValue();
-        }
-
-        protected virtual void CompletedReceived() => PublishCompleted();
-
-        protected virtual ActivatorListener CreateListener() => new ActivatorListener(this);
-
-        protected override void Deinitialize()
-        {
-            _activatorSubscription.Dispose();
-            _activatorSubscription = null;
-        }
-
-        protected virtual void ErrorReceived(Exception error) => PublishError(error);
-
-        protected override void Initialize()
-        {
-            _activatorSubscription = Activator.Subscribe(Listener);
-        }
-
-        protected override void Subscribed(IObserver<object> observer, bool first)
-        {
-            if (IsActive == true && !first)
-            {
-                observer.OnNext(Value);
-            }
-        }
-
-        private void PublishValue()
-        {
-            if (IsActive.HasValue)
-            {
-                var v = IsActive.Value ? Value : AvaloniaProperty.UnsetValue;
-
-                if (!Equals(v, _last))
-                {
-                    PublishNext(v);
-                    _last = v;
-                }
-            }
-        }
-
-        protected class ActivatorListener : IObserver<bool>
-        {
-            public ActivatorListener(ActivatedValue parent)
-            {
-                Parent = parent;
-            }
-
-            protected ActivatedValue Parent { get; }
-
-            void IObserver<bool>.OnCompleted() => Parent.CompletedReceived();
-            void IObserver<bool>.OnError(Exception error) => Parent.ErrorReceived(error);
-            void IObserver<bool>.OnNext(bool value) => Parent.ActiveChanged(value);
-        }
-    }
-}

+ 67 - 0
src/Avalonia.Styling/Styling/Activators/AndActivator.cs

@@ -0,0 +1,67 @@
+#nullable enable
+
+using System.Collections.Generic;
+
+namespace Avalonia.Styling.Activators
+{
+    internal class AndActivator : StyleActivatorBase, IStyleActivatorSink
+    {
+        private List<IStyleActivator>? _sources;
+        private ulong _flags;
+        private ulong _mask;
+
+        public int Count => _sources?.Count ?? 0;
+
+        public void Add(IStyleActivator activator)
+        {
+            _sources ??= new List<IStyleActivator>();
+            _sources.Add(activator);
+        }
+
+        void IStyleActivatorSink.OnNext(bool value, int tag)
+        {
+            if (value)
+            {
+                _flags |= 1ul << tag;
+            }
+            else
+            {
+                _flags &= ~(1ul << tag);
+            }
+
+            if (_mask != 0)
+            {
+                PublishNext(_flags == _mask);
+            }
+        }
+
+        protected override void Initialize()
+        {
+            if (_sources is object)
+            {
+                var i = 0;
+
+                foreach (var source in _sources)
+                {
+                    source.Subscribe(this, i++);
+                }
+
+                _mask = (1ul << Count) - 1;
+                PublishNext(_flags == _mask);
+            }
+        }
+
+        protected override void Deinitialize()
+        {
+            if (_sources is object)
+            {
+                foreach (var source in _sources)
+                {
+                    source.Unsubscribe(this);
+                }
+            }
+
+            _mask = 0;
+        }
+    }
+}

+ 33 - 0
src/Avalonia.Styling/Styling/Activators/IStyleActivator.cs

@@ -0,0 +1,33 @@
+#nullable enable
+
+using System;
+
+namespace Avalonia.Styling.Activators
+{
+    /// <summary>
+    /// Defines a style activator.
+    /// </summary>
+    /// <remarks>
+    /// A style activator is very similar to an `IObservable{bool}` but is optimized for the
+    /// particular use-case of activating a style according to a selector. It differs from
+    /// an observable in two major ways:
+    /// 
+    /// - Can only have a single subscription
+    /// - The subscription can have a tag associated with it, allowing a subscriber to index
+    ///   into a list of subscriptions without having to allocate additional objects.
+    /// </remarks>
+    public interface IStyleActivator : IDisposable
+    {
+        /// <summary>
+        /// Subscribes to the activator.
+        /// </summary>
+        /// <param name="sink">The listener.</param>
+        /// <param name="tag">An optional tag.</param>
+        void Subscribe(IStyleActivatorSink sink, int tag = 0);
+
+        /// <summary>
+        /// Unsubscribes from the activator.
+        /// </summary>
+        void Unsubscribe(IStyleActivatorSink sink);
+    }
+}

+ 17 - 0
src/Avalonia.Styling/Styling/Activators/IStyleActivatorSink.cs

@@ -0,0 +1,17 @@
+#nullable enable
+
+namespace Avalonia.Styling.Activators
+{
+    /// <summary>
+    /// Receives notifications from an <see cref="IStyleActivator"/>.
+    /// </summary>
+    public interface IStyleActivatorSink
+    {
+        /// <summary>
+        /// Called when the subscribed activator value changes.
+        /// </summary>
+        /// <param name="value">The new value.</param>
+        /// <param name="tag">The subscription tag.</param>
+        void OnNext(bool value, int tag);
+    }
+}

+ 13 - 0
src/Avalonia.Styling/Styling/Activators/NotActivator.cs

@@ -0,0 +1,13 @@
+#nullable enable
+
+namespace Avalonia.Styling.Activators
+{
+    internal class NotActivator : StyleActivatorBase, IStyleActivatorSink
+    {
+        private readonly IStyleActivator _source;
+        public NotActivator(IStyleActivator source) => _source = source;
+        void IStyleActivatorSink.OnNext(bool value, int tag) => PublishNext(!value);
+        protected override void Initialize() => _source.Subscribe(this, 0);
+        protected override void Deinitialize() => _source.Unsubscribe(this);
+    }
+}

+ 67 - 0
src/Avalonia.Styling/Styling/Activators/OrActivator.cs

@@ -0,0 +1,67 @@
+#nullable enable
+
+using System.Collections.Generic;
+
+namespace Avalonia.Styling.Activators
+{
+    internal class OrActivator : StyleActivatorBase, IStyleActivatorSink
+    {
+        private List<IStyleActivator>? _sources;
+        private ulong _flags;
+        private bool _initializing;
+
+        public int Count => _sources?.Count ?? 0;
+
+        public void Add(IStyleActivator activator)
+        {
+            _sources ??= new List<IStyleActivator>();
+            _sources.Add(activator);
+        }
+
+        void IStyleActivatorSink.OnNext(bool value, int tag)
+        {
+            if (value)
+            {
+                _flags |= 1ul << tag;
+            }
+            else
+            {
+                _flags &= ~(1ul << tag);
+            }
+
+            if (!_initializing)
+            {
+                PublishNext(_flags != 0);
+            }
+        }
+
+        protected override void Initialize()
+        {
+            if (_sources is object)
+            {
+                var i = 0;
+
+                _initializing = true;
+
+                foreach (var source in _sources)
+                {
+                    source.Subscribe(this, i++);
+                }
+
+                _initializing = false;
+                PublishNext(_flags != 0);
+            }
+        }
+
+        protected override void Deinitialize()
+        {
+            if (_sources is object)
+            {
+                foreach (var source in _sources)
+                {
+                    source.Unsubscribe(this);
+                }
+            }
+        }
+    }
+}

+ 35 - 0
src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs

@@ -0,0 +1,35 @@
+using System;
+
+#nullable enable
+
+namespace Avalonia.Styling.Activators
+{
+    internal class PropertyEqualsActivator : StyleActivatorBase, IObserver<object>
+    {
+        private readonly IStyleable _control;
+        private readonly AvaloniaProperty _property;
+        private readonly object? _value;
+        private IDisposable? _subscription;
+
+        public PropertyEqualsActivator(
+            IStyleable control,
+            AvaloniaProperty property,
+            object? value)
+        {
+            _control = control ?? throw new ArgumentNullException(nameof(control));
+            _property = property ?? throw new ArgumentNullException(nameof(property));
+            _value = value;
+        }
+
+        protected override void Initialize()
+        {
+            _subscription = _control.GetObservable(_property).Subscribe(this);
+        }
+
+        protected override void Deinitialize() => _subscription?.Dispose();
+
+        void IObserver<object>.OnCompleted() { }
+        void IObserver<object>.OnError(Exception error) { }
+        void IObserver<object>.OnNext(object value) => PublishNext(Equals(value, _value));
+    }
+}

+ 55 - 0
src/Avalonia.Styling/Styling/Activators/StyleActivatorBase.cs

@@ -0,0 +1,55 @@
+#nullable enable
+
+namespace Avalonia.Styling.Activators
+{
+    internal abstract class StyleActivatorBase : IStyleActivator
+    {
+        private IStyleActivatorSink? _sink;
+        private int _tag;
+        private bool? _value;
+
+        public void Subscribe(IStyleActivatorSink sink, int tag = 0)
+        {
+            if (_sink is null)
+            {
+                _sink = sink;
+                _tag = tag;
+                _value = null;
+                Initialize();
+            }
+            else
+            {
+                throw new AvaloniaInternalException("Cannot subscribe to a StyleActivator more than once.");
+            }
+        }
+
+        public void Unsubscribe(IStyleActivatorSink sink)
+        {
+            if (_sink != sink)
+            {
+                throw new AvaloniaInternalException("StyleActivatorSink is not subscribed.");
+            }
+
+            _sink = null;
+            Deinitialize();
+        }
+
+        public void PublishNext(bool value)
+        {
+            if (_value != value)
+            {
+                _value = value;
+                _sink?.OnNext(value, _tag);
+            }
+        }
+
+        public void Dispose()
+        {
+            _sink = null;
+            Deinitialize();
+        }
+
+        protected abstract void Initialize();
+        protected abstract void Deinitialize();
+    }
+}

+ 72 - 0
src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs

@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Avalonia.Collections;
+
+#nullable enable
+
+namespace Avalonia.Styling.Activators
+{
+    internal sealed class StyleClassActivator : StyleActivatorBase
+    {
+        private readonly IList<string> _match;
+        private readonly IAvaloniaReadOnlyList<string> _classes;
+
+        public StyleClassActivator(IAvaloniaReadOnlyList<string> classes, IList<string> match)
+        {
+            _classes = classes;
+            _match = match;
+        }
+
+        public static bool AreClassesMatching(IReadOnlyList<string> classes, IList<string> toMatch)
+        {
+            int remainingMatches = toMatch.Count;
+            int classesCount = classes.Count;
+
+            // Early bail out - we can't match if control does not have enough classes.
+            if (classesCount < remainingMatches)
+            {
+                return false;
+            }
+
+            for (var i = 0; i < classesCount; i++)
+            {
+                var c = classes[i];
+
+                if (toMatch.Contains(c))
+                {
+                    --remainingMatches;
+
+                    // Already matched so we can skip checking other classes.
+                    if (remainingMatches == 0)
+                    {
+                        break;
+                    }
+                }
+            }
+
+            return remainingMatches == 0;
+        }
+
+
+        protected override void Initialize()
+        {
+            PublishNext(IsMatching());
+            _classes.CollectionChanged += ClassesChanged;
+        }
+
+        protected override void Deinitialize()
+        {
+            _classes.CollectionChanged -= ClassesChanged;
+        }
+
+        private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            if (e.Action != NotifyCollectionChangedAction.Move)
+            {
+                PublishNext(IsMatching());
+            }
+        }
+
+        private bool IsMatching() => AreClassesMatching(_classes, _match);
+    }
+}

+ 32 - 16
src/Avalonia.Styling/Styling/DescendentSelector.cs

@@ -2,24 +2,21 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Collections.Generic;
 using Avalonia.LogicalTree;
 using Avalonia.LogicalTree;
+using Avalonia.Styling.Activators;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
     internal class DescendantSelector : Selector
     internal class DescendantSelector : Selector
     {
     {
         private readonly Selector _parent;
         private readonly Selector _parent;
-        private string _selectorString;
+        private string? _selectorString;
 
 
-        public DescendantSelector(Selector parent)
+        public DescendantSelector(Selector? parent)
         {
         {
-            if (parent == null)
-            {
-                throw new InvalidOperationException("Descendant selector must be preceeded by a selector.");
-            }
-
-            _parent = parent;
+            _parent = parent ?? throw new InvalidOperationException("Descendant selector must be preceeded by a selector.");
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
@@ -29,7 +26,7 @@ namespace Avalonia.Styling
         public override bool InTemplate => _parent.InTemplate;
         public override bool InTemplate => _parent.InTemplate;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override Type TargetType => null;
+        public override Type? TargetType => null;
 
 
         public override string ToString()
         public override string ToString()
         {
         {
@@ -43,8 +40,9 @@ namespace Avalonia.Styling
 
 
         protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
         protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
         {
         {
-            ILogical c = (ILogical)control;
-            List<IObservable<bool>> descendantMatches = new List<IObservable<bool>>();
+            var c = (ILogical)control;
+            IStyleActivator? descendentMatch = null;
+            OrActivator? descendantMatches = null;
 
 
             while (c != null)
             while (c != null)
             {
             {
@@ -56,7 +54,21 @@ namespace Avalonia.Styling
 
 
                     if (match.Result == SelectorMatchResult.Sometimes)
                     if (match.Result == SelectorMatchResult.Sometimes)
                     {
                     {
-                        descendantMatches.Add(match.Activator);
+                        if (descendentMatch is null && descendantMatches is null)
+                        {
+                            descendentMatch = match.Activator;
+                        }
+                        else
+                        {
+                            if (descendantMatches is null)
+                            {
+                                descendantMatches = new OrActivator();
+                                descendantMatches.Add(descendentMatch!);
+                                descendentMatch = null;
+                            }
+
+                            descendantMatches.Add(match.Activator!);
+                        }
                     }
                     }
                     else if (match.IsMatch)
                     else if (match.IsMatch)
                     {
                     {
@@ -65,9 +77,13 @@ namespace Avalonia.Styling
                 }
                 }
             }
             }
 
 
-            if (descendantMatches.Count > 0)
+            if (descendantMatches is object)
+            {
+                return new SelectorMatch(descendantMatches);
+            }
+            else if (descendentMatch is object)
             {
             {
-                return new SelectorMatch(StyleActivator.Or(descendantMatches));
+                return new SelectorMatch(descendentMatch);
             }
             }
             else
             else
             {
             {
@@ -75,6 +91,6 @@ namespace Avalonia.Styling
             }
             }
         }
         }
 
 
-        protected override Selector MovePrevious() => null;
+        protected override Selector? MovePrevious() => null;
     }
     }
 }
 }

+ 13 - 6
src/Avalonia.Styling/Styling/ISetter.cs

@@ -3,6 +3,8 @@
 
 
 using System;
 using System;
 
 
+#nullable enable
+
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
     /// <summary>
     /// <summary>
@@ -11,11 +13,16 @@ namespace Avalonia.Styling
     public interface ISetter
     public interface ISetter
     {
     {
         /// <summary>
         /// <summary>
-        /// Applies the setter to a control.
+        /// Instances a setter on a control.
         /// </summary>
         /// </summary>
-        /// <param name="style">The style that is being applied.</param>
-        /// <param name="control">The control.</param>
-        /// <param name="activator">An optional activator.</param>
-        IDisposable Apply(IStyle style, IStyleable control, IObservable<bool> activator);
+        /// <param name="target">The control.</param>
+        /// <param name="hasActivator">Whether the parent style has an activator.</param>
+        /// <returns>An <see cref="ISetterInstance"/>.</returns>
+        /// <remarks>
+        /// This method should return an <see cref="ISetterInstance"/> which can be used to apply
+        /// the setter to the specified control. Note that it should not apply the setter value 
+        /// until <see cref="ISetterInstance.Activate"/> is called.
+        /// </remarks>
+        ISetterInstance Instance(IStyleable target, bool hasActivator);
     }
     }
-}
+}

+ 20 - 0
src/Avalonia.Styling/Styling/ISetterInstance.cs

@@ -0,0 +1,20 @@
+#nullable enable
+
+namespace Avalonia.Styling
+{
+    /// <summary>
+    /// Represents a setter that has been instanced on a control.
+    /// </summary>
+    public interface ISetterInstance
+    {
+        /// <summary>
+        /// Activates the setter.
+        /// </summary>
+        public void Activate();
+
+        /// <summary>
+        /// Deactivates the setter.
+        /// </summary>
+        public void Deactivate();
+    }
+}

+ 6 - 10
src/Avalonia.Styling/Styling/IStyle.cs

@@ -3,6 +3,8 @@
 
 
 using Avalonia.Controls;
 using Avalonia.Controls;
 
 
+#nullable enable
+
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
     /// <summary>
     /// <summary>
@@ -13,17 +15,11 @@ namespace Avalonia.Styling
         /// <summary>
         /// <summary>
         /// Attaches the style to a control if the style's selector matches.
         /// Attaches the style to a control if the style's selector matches.
         /// </summary>
         /// </summary>
-        /// <param name="control">The control to attach to.</param>
-        /// <param name="container">
-        /// The control that contains this style. May be null.
-        /// </param>
+        /// <param name="target">The control to attach to.</param>
+        /// <param name="host">The element that hosts the style.</param>
         /// <returns>
         /// <returns>
-        /// True if the style can match a control of type <paramref name="control"/>
-        /// (even if it does not match this control specifically); false if the style
-        /// can never match.
+        /// A <see cref="SelectorMatchResult"/> describing how the style matches the control.
         /// </returns>
         /// </returns>
-        bool Attach(IStyleable control, IStyleHost container);
-
-        void Detach();
+        SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host);
     }
     }
 }
 }

+ 22 - 0
src/Avalonia.Styling/Styling/IStyleInstance.cs

@@ -0,0 +1,22 @@
+using System;
+
+#nullable enable
+
+namespace Avalonia.Styling
+{
+    /// <summary>
+    /// Represents a style that has been instanced on a control.
+    /// </summary>
+    public interface IStyleInstance : IDisposable
+    {
+        /// <summary>
+        /// Gets the source style.
+        /// </summary>
+        IStyle Source { get; }
+
+        /// <summary>
+        /// Instructs the style to start acting upon the control.
+        /// </summary>
+        void Start();
+    }
+}

+ 14 - 6
src/Avalonia.Styling/Styling/IStyleable.cs

@@ -4,6 +4,8 @@
 using System;
 using System;
 using Avalonia.Collections;
 using Avalonia.Collections;
 
 
+#nullable enable
+
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
     /// <summary>
     /// <summary>
@@ -11,11 +13,6 @@ namespace Avalonia.Styling
     /// </summary>
     /// </summary>
     public interface IStyleable : IAvaloniaObject, INamed
     public interface IStyleable : IAvaloniaObject, INamed
     {
     {
-        /// <summary>
-        /// Signaled when the control's style should be removed.
-        /// </summary>
-        IObservable<IStyleable> StyleDetach { get; }
-
         /// <summary>
         /// <summary>
         /// Gets the list of classes for the control.
         /// Gets the list of classes for the control.
         /// </summary>
         /// </summary>
@@ -29,6 +26,17 @@ namespace Avalonia.Styling
         /// <summary>
         /// <summary>
         /// Gets the template parent of this element if the control comes from a template.
         /// Gets the template parent of this element if the control comes from a template.
         /// </summary>
         /// </summary>
-        ITemplatedControl TemplatedParent { get; }
+        ITemplatedControl? TemplatedParent { get; }
+
+        /// <summary>
+        /// Notifies the element that a style has been applied.
+        /// </summary>
+        /// <param name="instance">The style instance.</param>
+        void StyleApplied(IStyleInstance instance);
+
+        /// <summary>
+        /// Detaches all styles applied to the element.
+        /// </summary>
+        void DetachStyles();
     }
     }
 }
 }

+ 9 - 7
src/Avalonia.Styling/Styling/NotSelector.cs

@@ -2,7 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Reactive.Linq;
+using Avalonia.Styling.Activators;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -11,16 +13,16 @@ namespace Avalonia.Styling
     /// </summary>
     /// </summary>
     internal class NotSelector : Selector
     internal class NotSelector : Selector
     {
     {
-        private readonly Selector _previous;
+        private readonly Selector? _previous;
         private readonly Selector _argument;
         private readonly Selector _argument;
-        private string _selectorString;
+        private string? _selectorString;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="NotSelector"/> class.
         /// Initializes a new instance of the <see cref="NotSelector"/> class.
         /// </summary>
         /// </summary>
         /// <param name="previous">The previous selector.</param>
         /// <param name="previous">The previous selector.</param>
         /// <param name="argument">The selector to be not-ed.</param>
         /// <param name="argument">The selector to be not-ed.</param>
-        public NotSelector(Selector previous, Selector argument)
+        public NotSelector(Selector? previous, Selector argument)
         {
         {
             _previous = previous;
             _previous = previous;
             _argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument.");
             _argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument.");
@@ -33,7 +35,7 @@ namespace Avalonia.Styling
         public override bool IsCombinator => false;
         public override bool IsCombinator => false;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override Type TargetType => _previous?.TargetType;
+        public override Type? TargetType => _previous?.TargetType;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public override string ToString()
         public override string ToString()
@@ -61,12 +63,12 @@ namespace Avalonia.Styling
                 case SelectorMatchResult.NeverThisType:
                 case SelectorMatchResult.NeverThisType:
                     return SelectorMatch.AlwaysThisType;
                     return SelectorMatch.AlwaysThisType;
                 case SelectorMatchResult.Sometimes:
                 case SelectorMatchResult.Sometimes:
-                    return new SelectorMatch(innerResult.Activator.Select(x => !x));
+                    return new SelectorMatch(new NotActivator(innerResult.Activator!));
                 default:
                 default:
                     throw new InvalidOperationException("Invalid SelectorMatchResult.");
                     throw new InvalidOperationException("Invalid SelectorMatchResult.");
             }
             }
         }
         }
 
 
-        protected override Selector MovePrevious() => _previous;
+        protected override Selector? MovePrevious() => _previous;
     }
     }
 }
 }

+ 39 - 14
src/Avalonia.Styling/Styling/OrSelector.cs

@@ -3,6 +3,9 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using Avalonia.Styling.Activators;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -12,8 +15,8 @@ namespace Avalonia.Styling
     internal class OrSelector : Selector
     internal class OrSelector : Selector
     {
     {
         private readonly IReadOnlyList<Selector> _selectors;
         private readonly IReadOnlyList<Selector> _selectors;
-        private string _selectorString;
-        private Type _targetType;
+        private string? _selectorString;
+        private Type? _targetType;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="OrSelector"/> class.
         /// Initializes a new instance of the <see cref="OrSelector"/> class.
@@ -21,8 +24,15 @@ namespace Avalonia.Styling
         /// <param name="selectors">The selectors to OR.</param>
         /// <param name="selectors">The selectors to OR.</param>
         public OrSelector(IReadOnlyList<Selector> selectors)
         public OrSelector(IReadOnlyList<Selector> selectors)
         {
         {
-            Contract.Requires<ArgumentNullException>(selectors != null);
-            Contract.Requires<ArgumentException>(selectors.Count > 1);
+            if (selectors is null)
+            {
+                throw new ArgumentNullException(nameof(selectors));
+            }
+
+            if (selectors.Count <= 1)
+            {
+                throw new ArgumentException("Need more than one selector to OR.");
+            }
 
 
             _selectors = selectors;
             _selectors = selectors;
         }
         }
@@ -34,7 +44,7 @@ namespace Avalonia.Styling
         public override bool IsCombinator => false;
         public override bool IsCombinator => false;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override Type TargetType
+        public override Type? TargetType
         {
         {
             get
             get
             {
             {
@@ -60,7 +70,8 @@ namespace Avalonia.Styling
 
 
         protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
         protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
         {
         {
-            var activators = new List<IObservable<bool>>();
+            IStyleActivator? activator = null;
+            OrActivator? activators = null;
             var neverThisInstance = false;
             var neverThisInstance = false;
 
 
             foreach (var selector in _selectors)
             foreach (var selector in _selectors)
@@ -76,18 +87,32 @@ namespace Avalonia.Styling
                         neverThisInstance = true;
                         neverThisInstance = true;
                         break;
                         break;
                     case SelectorMatchResult.Sometimes:
                     case SelectorMatchResult.Sometimes:
-                        activators.Add(match.Activator);
+                        if (activator is null && activators is null)
+                        {
+                            activator = match.Activator;
+                        }
+                        else
+                        {
+                            if (activators is null)
+                            {
+                                activators = new OrActivator();
+                                activators.Add(activator!);
+                                activator = null;
+                            }
+
+                            activators.Add(match.Activator!);
+                        }
                         break;
                         break;
                 }
                 }
             }
             }
 
 
-            if (activators.Count > 1)
+            if (activators is object)
             {
             {
-                return new SelectorMatch(StyleActivator.Or(activators));
+                return new SelectorMatch(activators);
             }
             }
-            else if (activators.Count == 1)
+            else if (activator is object)
             {
             {
-                return new SelectorMatch(activators[0]);
+                return new SelectorMatch(activator);
             }
             }
             else if (neverThisInstance)
             else if (neverThisInstance)
             {
             {
@@ -99,11 +124,11 @@ namespace Avalonia.Styling
             }
             }
         }
         }
 
 
-        protected override Selector MovePrevious() => null;
+        protected override Selector? MovePrevious() => null;
 
 
-        private Type EvaluateTargetType()
+        private Type? EvaluateTargetType()
         {
         {
-            var result = default(Type);
+            Type? result = null;
 
 
             foreach (var selector in _selectors)
             foreach (var selector in _selectors)
             {
             {

+ 11 - 14
src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs

@@ -2,8 +2,10 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Reactive.Linq;
 using System.Text;
 using System.Text;
+using Avalonia.Styling.Activators;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -13,14 +15,14 @@ namespace Avalonia.Styling
     /// </summary>
     /// </summary>
     internal class PropertyEqualsSelector : Selector
     internal class PropertyEqualsSelector : Selector
     {
     {
-        private readonly Selector _previous;
+        private readonly Selector? _previous;
         private readonly AvaloniaProperty _property;
         private readonly AvaloniaProperty _property;
-        private readonly object _value;
-        private string _selectorString;
+        private readonly object? _value;
+        private string? _selectorString;
 
 
-        public PropertyEqualsSelector(Selector previous, AvaloniaProperty property, object value)
+        public PropertyEqualsSelector(Selector? previous, AvaloniaProperty property, object? value)
         {
         {
-            Contract.Requires<ArgumentNullException>(property != null);
+            property = property ?? throw new ArgumentNullException(nameof(property));
 
 
             _previous = previous;
             _previous = previous;
             _property = property;
             _property = property;
@@ -33,13 +35,8 @@ namespace Avalonia.Styling
         /// <inheritdoc/>
         /// <inheritdoc/>
         public override bool IsCombinator => false;
         public override bool IsCombinator => false;
 
 
-        /// <summary>
-        /// Gets the name of the control to match.
-        /// </summary>
-        public string Name { get; private set; }
-
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override Type TargetType => _previous?.TargetType;
+        public override Type? TargetType => _previous?.TargetType;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public override string ToString()
         public override string ToString()
@@ -77,7 +74,7 @@ namespace Avalonia.Styling
         {
         {
             if (subscribe)
             if (subscribe)
             {
             {
-                return new SelectorMatch(control.GetObservable(_property).Select(v => Equals(v ?? string.Empty, _value)));
+                return new SelectorMatch(new PropertyEqualsActivator(control, _property, _value));
             }
             }
             else
             else
             {
             {
@@ -86,6 +83,6 @@ namespace Avalonia.Styling
             }
             }
         }
         }
 
 
-        protected override Selector MovePrevious() => _previous;
+        protected override Selector? MovePrevious() => _previous;
     }
     }
 }
 }

+ 48 - 0
src/Avalonia.Styling/Styling/PropertySetterBindingInstance.cs

@@ -0,0 +1,48 @@
+using System;
+using Avalonia.Data;
+
+#nullable enable
+
+namespace Avalonia.Styling
+{
+    internal class PropertySetterBindingInstance : ISetterInstance
+    {
+        private readonly IStyleable _target;
+        private readonly AvaloniaProperty _property;
+        private readonly BindingPriority _priority;
+        private readonly InstancedBinding _binding;
+        private IDisposable? _subscription;
+        private bool _isActive;
+
+        public PropertySetterBindingInstance(
+            IStyleable target,
+            AvaloniaProperty property,
+            BindingPriority priority,
+            IBinding binding)
+        {
+            _target = target;
+            _property = property;
+            _priority = priority;
+            _binding = binding.Initiate(target, property).WithPriority(priority);
+        }
+
+        public void Activate()
+        {
+            if (!_isActive)
+            {
+                _subscription = BindingOperations.Apply(_target, _property, _binding, null);
+                _isActive = true;
+            }
+        }
+
+        public void Deactivate()
+        {
+            if (_isActive)
+            {
+                _subscription?.Dispose();
+                _subscription = null;
+                _isActive = false;
+            }
+        }
+    }
+}

+ 82 - 0
src/Avalonia.Styling/Styling/PropertySetterInstance.cs

@@ -0,0 +1,82 @@
+using System;
+using Avalonia.Data;
+
+#nullable enable
+
+namespace Avalonia.Styling
+{
+    internal class PropertySetterInstance<T> : ISetterInstance
+    {
+        private readonly IStyleable _target;
+        private readonly StyledPropertyBase<T>? _styledProperty;
+        private readonly DirectPropertyBase<T>? _directProperty;
+        private readonly BindingPriority _priority;
+        private readonly T _value;
+        private IDisposable? _subscription;
+        private bool _isActive;
+
+        public PropertySetterInstance(
+            IStyleable target,
+            StyledPropertyBase<T> property,
+            BindingPriority priority,
+            T value)
+        {
+            _target = target;
+            _styledProperty = property;
+            _priority = priority;
+            _value = value;
+        }
+
+        public PropertySetterInstance(
+            IStyleable target,
+            DirectPropertyBase<T> property,
+            BindingPriority priority,
+            T value)
+        {
+            _target = target;
+            _directProperty = property;
+            _priority = priority;
+            _value = value;
+        }
+
+        public void Activate()
+        {
+            if (!_isActive)
+            {
+                if (_styledProperty is object)
+                {
+                    _subscription = _target.SetValue(_styledProperty, _value, _priority);
+                }
+                else
+                {
+                    _target.SetValue(_directProperty!, _value);
+                }
+
+                _isActive = true;
+            }
+        }
+
+        public void Deactivate()
+        {
+            if (_isActive)
+            {
+                if (_subscription is null)
+                {
+                    if (_styledProperty is object)
+                    {
+                        _target.ClearValue(_styledProperty);
+                    }
+                    else
+                    {
+                        _target.ClearValue(_directProperty!);
+                    }
+                }
+                else
+                {
+                    _subscription.Dispose();
+                    _subscription = null;
+                }
+            }
+        }
+    }
+}

+ 31 - 13
src/Avalonia.Styling/Styling/Selector.cs

@@ -2,9 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using Avalonia.Utilities;
+using Avalonia.Styling.Activators;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -30,7 +30,7 @@ namespace Avalonia.Styling
         /// <summary>
         /// <summary>
         /// Gets the target type of the selector, if available.
         /// Gets the target type of the selector, if available.
         /// </summary>
         /// </summary>
-        public abstract Type TargetType { get; }
+        public abstract Type? TargetType { get; }
 
 
         /// <summary>
         /// <summary>
         /// Tries to match the selector with a control.
         /// Tries to match the selector with a control.
@@ -43,8 +43,8 @@ namespace Avalonia.Styling
         /// <returns>A <see cref="SelectorMatch"/>.</returns>
         /// <returns>A <see cref="SelectorMatch"/>.</returns>
         public SelectorMatch Match(IStyleable control, bool subscribe = true)
         public SelectorMatch Match(IStyleable control, bool subscribe = true)
         {
         {
-            ValueSingleOrList<IObservable<bool>> inputs = default;
-
+            IStyleActivator? activator = null;
+            AndActivator? activators = null;
             var selector = this;
             var selector = this;
             var alwaysThisType = true;
             var alwaysThisType = true;
             var hitCombinator = false;
             var hitCombinator = false;
@@ -69,21 +69,39 @@ namespace Avalonia.Styling
                 }
                 }
                 else if (match.Result == SelectorMatchResult.Sometimes)
                 else if (match.Result == SelectorMatchResult.Sometimes)
                 {
                 {
-                    Debug.Assert(match.Activator != null);
+                    if (match.Activator is null)
+                    {
+                        throw new AvaloniaInternalException(
+                            "SelectorMatch returned Sometimes but there is no activator.");
+                    }
+
+                    if (activator is null && activators is null)
+                    {
+                        activator = match.Activator;
+                    }
+                    else
+                    {
+                        if (activators is null)
+                        {
+                            activators = new AndActivator();
+                            activators.Add(activator!);
+                            activator = null;
+                        }
 
 
-                    inputs.Add(match.Activator);
+                        activators.Add(match.Activator);
+                    }
                 }
                 }
 
 
                 selector = selector.MovePrevious();
                 selector = selector.MovePrevious();
             }
             }
 
 
-            if (inputs.HasList)
+            if (activators is object)
             {
             {
-                return new SelectorMatch(StyleActivator.And(inputs.List));
+                return new SelectorMatch(activators);
             }
             }
-            else if (inputs.IsSingle)
+            else if (activator is object)
             {
             {
-                return new SelectorMatch(inputs.Single);
+                return new SelectorMatch(activator);
             }
             }
             else
             else
             {
             {
@@ -107,6 +125,6 @@ namespace Avalonia.Styling
         /// <summary>
         /// <summary>
         /// Moves to the previous selector.
         /// Moves to the previous selector.
         /// </summary>
         /// </summary>
-        protected abstract Selector MovePrevious();
+        protected abstract Selector? MovePrevious();
     }
     }
 }
 }

+ 18 - 11
src/Avalonia.Styling/Styling/SelectorMatch.cs

@@ -2,6 +2,9 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using Avalonia.Styling.Activators;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -21,9 +24,9 @@ namespace Avalonia.Styling
         NeverThisInstance,
         NeverThisInstance,
 
 
         /// <summary>
         /// <summary>
-        /// The selector always matches this type.
+        /// The selector matches this instance based on the <see cref="SelectorMatch.Activator"/>.
         /// </summary>
         /// </summary>
-        AlwaysThisType,
+        Sometimes,
 
 
         /// <summary>
         /// <summary>
         /// The selector always matches this instance, but doesn't always match this type.
         /// The selector always matches this instance, but doesn't always match this type.
@@ -31,9 +34,9 @@ namespace Avalonia.Styling
         AlwaysThisInstance,
         AlwaysThisInstance,
 
 
         /// <summary>
         /// <summary>
-        /// The selector matches this instance based on the <see cref="SelectorMatch.Activator"/>.
+        /// The selector always matches this type.
         /// </summary>
         /// </summary>
-        Sometimes,
+        AlwaysThisType,
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -43,7 +46,7 @@ namespace Avalonia.Styling
     /// A selector match describes whether and how a <see cref="Selector"/> matches a control, and
     /// A selector match describes whether and how a <see cref="Selector"/> matches a control, and
     /// in addition whether the selector can ever match a control of the same type.
     /// in addition whether the selector can ever match a control of the same type.
     /// </remarks>
     /// </remarks>
-    public class SelectorMatch
+    public readonly struct SelectorMatch
     {
     {
         /// <summary>
         /// <summary>
         /// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>.
         /// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>.
@@ -70,20 +73,24 @@ namespace Avalonia.Styling
         /// <see cref="SelectorMatchResult.Sometimes"/> result.
         /// <see cref="SelectorMatchResult.Sometimes"/> result.
         /// </summary>
         /// </summary>
         /// <param name="match">The match activator.</param>
         /// <param name="match">The match activator.</param>
-        public SelectorMatch(IObservable<bool> match)
+        public SelectorMatch(IStyleActivator match)
         {
         {
-            Contract.Requires<ArgumentNullException>(match != null);
+            match = match ?? throw new ArgumentNullException(nameof(match));
 
 
             Result = SelectorMatchResult.Sometimes;
             Result = SelectorMatchResult.Sometimes;
             Activator = match;
             Activator = match;
         }
         }
 
 
-        private SelectorMatch(SelectorMatchResult result) => Result = result;
+        private SelectorMatch(SelectorMatchResult result)
+        {
+            Result = result;
+            Activator = null;
+        }
 
 
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether the match was positive.
         /// Gets a value indicating whether the match was positive.
         /// </summary>
         /// </summary>
-        public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType;
+        public bool IsMatch => Result >= SelectorMatchResult.Sometimes;
 
 
         /// <summary>
         /// <summary>
         /// Gets the result of the match.
         /// Gets the result of the match.
@@ -91,9 +98,9 @@ namespace Avalonia.Styling
         public SelectorMatchResult Result { get; }
         public SelectorMatchResult Result { get; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets an observable which tracks the selector match, in the case of selectors that can
+        /// Gets an activator which tracks the selector match, in the case of selectors that can
         /// change over time.
         /// change over time.
         /// </summary>
         /// </summary>
-        public IObservable<bool> Activator { get; }
+        public IStyleActivator? Activator { get; }
     }
     }
 }
 }

+ 53 - 88
src/Avalonia.Styling/Styling/Setter.cs

@@ -2,11 +2,12 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Reactive.Disposables;
 using Avalonia.Animation;
 using Avalonia.Animation;
 using Avalonia.Data;
 using Avalonia.Data;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
-using Avalonia.Reactive;
+using Avalonia.Utilities;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -17,9 +18,9 @@ namespace Avalonia.Styling
     /// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a
     /// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a
     /// <see cref="AvaloniaObject"/> depending on a condition.
     /// <see cref="AvaloniaObject"/> depending on a condition.
     /// </remarks>
     /// </remarks>
-    public class Setter : ISetter, IAnimationSetter
+    public class Setter : ISetter, IAnimationSetter, IAvaloniaPropertyVisitor<Setter.SetterVisitorData>
     {
     {
-        private object _value;
+        private object? _value;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="Setter"/> class.
         /// Initializes a new instance of the <see cref="Setter"/> class.
@@ -42,11 +43,7 @@ namespace Avalonia.Styling
         /// <summary>
         /// <summary>
         /// Gets or sets the property to set.
         /// Gets or sets the property to set.
         /// </summary>
         /// </summary>
-        public AvaloniaProperty Property
-        {
-            get;
-            set;
-        }
+        public AvaloniaProperty? Property { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the property value.
         /// Gets or sets the property value.
@@ -54,13 +51,9 @@ namespace Avalonia.Styling
         [Content]
         [Content]
         [AssignBinding]
         [AssignBinding]
         [DependsOn(nameof(Property))]
         [DependsOn(nameof(Property))]
-        public object Value
+        public object? Value
         {
         {
-            get
-            {
-                return _value;
-            }
-
+            get => _value;
             set
             set
             {
             {
                 (value as ISetterValue)?.Initialize(this);
                 (value as ISetterValue)?.Initialize(this);
@@ -68,99 +61,71 @@ namespace Avalonia.Styling
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Applies the setter to a control.
-        /// </summary>
-        /// <param name="style">The style that is being applied.</param>
-        /// <param name="control">The control.</param>
-        /// <param name="activator">An optional activator.</param>
-        public IDisposable Apply(IStyle style, IStyleable control, IObservable<bool> activator)
+        public ISetterInstance Instance(IStyleable target, bool hasActivator)
         {
         {
-            Contract.Requires<ArgumentNullException>(control != null);
+            target = target ?? throw new ArgumentNullException(nameof(target));
 
 
-            if (Property == null)
+            if (Property is null)
             {
             {
                 throw new InvalidOperationException("Setter.Property must be set.");
                 throw new InvalidOperationException("Setter.Property must be set.");
             }
             }
 
 
-            var value = Value;
-            var binding = value as IBinding;
+            var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style;
 
 
-            if (binding == null)
+            if (Value is IBinding binding)
             {
             {
-                if (value is ITemplate template)
-                {
-                    bool isPropertyOfTypeITemplate = typeof(ITemplate).IsAssignableFrom(Property.PropertyType);
-
-                    if (!isPropertyOfTypeITemplate)
-                    {
-                        var materialized = template.Build();
-                        value = materialized;
-                    }
-                }
-
-                if (activator == null)
-                {
-                    return control.Bind(Property, ObservableEx.SingleValue(value), BindingPriority.Style);
-                }
-                else
-                {
-                    var description = style?.ToString();
-
-                    var activated = new ActivatedValue(activator, value, description);
-                    return control.Bind(Property, activated, BindingPriority.StyleTrigger);
-                }
+                return new PropertySetterBindingInstance(target, Property, priority, binding);
             }
             }
             else
             else
             {
             {
-                var source = binding.Initiate(control, Property);
+                var value = Value;
 
 
-                if (source != null)
+                if (value is ITemplate template &&
+                    !typeof(ITemplate).IsAssignableFrom(Property.PropertyType))
                 {
                 {
-                    var cloned = Clone(source, source.Mode == BindingMode.Default ? Property.GetMetadata(control.GetType()).DefaultBindingMode : source.Mode, style, activator);
-                    return BindingOperations.Apply(control, Property, cloned, null);
+                    value = template.Build();
                 }
                 }
-            }
 
 
-            return Disposable.Empty;
+                var data = new SetterVisitorData
+                {
+                    target = target,
+                    priority = priority,
+                    value = value,
+                };
+
+                Property.Accept(this, ref data);
+                return data.result!;
+            }
         }
         }
 
 
-        private InstancedBinding Clone(InstancedBinding sourceInstance, BindingMode mode, IStyle style, IObservable<bool> activator)
+        void IAvaloniaPropertyVisitor<SetterVisitorData>.Visit<T>(
+            StyledPropertyBase<T> property,
+            ref SetterVisitorData data)
         {
         {
-            if (activator != null)
-            {
-                var description = style?.ToString();
+            data.result = new PropertySetterInstance<T>(
+                data.target,
+                property,
+                data.priority,
+                (T)data.value);
+        }
 
 
-                switch (mode)
-                {
-                    case BindingMode.OneTime:
-                        if (sourceInstance.Observable != null)
-                        {
-                            var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
-                            return InstancedBinding.OneTime(activated, BindingPriority.StyleTrigger);
-                        }
-                        else
-                        {
-                            var activated = new ActivatedValue(activator, sourceInstance.Value, description);
-                            return InstancedBinding.OneTime(activated, BindingPriority.StyleTrigger);
-                        }
-                    case BindingMode.OneWay:
-                        {
-                            var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
-                            return InstancedBinding.OneWay(activated, BindingPriority.StyleTrigger);
-                        }
-                    default:
-                        {
-                            var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
-                            return new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
-                        }
-                }
+        void IAvaloniaPropertyVisitor<SetterVisitorData>.Visit<T>(
+            DirectPropertyBase<T> property,
+            ref SetterVisitorData data)
+        {
+            data.result = new PropertySetterInstance<T>(
+                data.target,
+                property,
+                data.priority,
+                (T)data.value);
+        }
 
 
-            }
-            else
-            {
-                return sourceInstance.WithPriority(BindingPriority.Style);
-            }
+        private struct SetterVisitorData
+        {
+            public IStyleable target;
+            public BindingPriority priority;
+            public object? value;
+            public ISetterInstance? result;
         }
         }
     }
     }
 }
 }

+ 28 - 137
src/Avalonia.Styling/Styling/Style.cs

@@ -3,12 +3,12 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
 using Avalonia.Animation;
 using Avalonia.Animation;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
 
 
+#nullable enable
+
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
     /// <summary>
     /// <summary>
@@ -16,15 +16,10 @@ namespace Avalonia.Styling
     /// </summary>
     /// </summary>
     public class Style : AvaloniaObject, IStyle, ISetResourceParent
     public class Style : AvaloniaObject, IStyle, ISetResourceParent
     {
     {
-        private static Dictionary<IStyleable, CompositeDisposable> _applied =
-            new Dictionary<IStyleable, CompositeDisposable>();
-        private IResourceNode _parent;
-
-        private CompositeDisposable _subscriptions;
-
-        private IResourceDictionary _resources;
-
-        private IList<IAnimation> _animations;
+        private IResourceNode? _parent;
+        private IResourceDictionary? _resources;
+        private List<ISetter>? _setters;
+        private List<IAnimation>? _animations;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="Style"/> class.
         /// Initializes a new instance of the <see cref="Style"/> class.
@@ -37,13 +32,13 @@ namespace Avalonia.Styling
         /// Initializes a new instance of the <see cref="Style"/> class.
         /// Initializes a new instance of the <see cref="Style"/> class.
         /// </summary>
         /// </summary>
         /// <param name="selector">The style selector.</param>
         /// <param name="selector">The style selector.</param>
-        public Style(Func<Selector, Selector> selector)
+        public Style(Func<Selector?, Selector> selector)
         {
         {
             Selector = selector(null);
             Selector = selector(null);
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
+        public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets a dictionary of style resources.
         /// Gets or sets a dictionary of style resources.
@@ -53,7 +48,7 @@ namespace Avalonia.Styling
             get => _resources ?? (Resources = new ResourceDictionary());
             get => _resources ?? (Resources = new ResourceDictionary());
             set
             set
             {
             {
-                Contract.Requires<ArgumentNullException>(value != null);
+                value = value ?? throw new ArgumentNullException(nameof(value));
 
 
                 var hadResources = false;
                 var hadResources = false;
 
 
@@ -76,117 +71,45 @@ namespace Avalonia.Styling
         /// <summary>
         /// <summary>
         /// Gets or sets the style's selector.
         /// Gets or sets the style's selector.
         /// </summary>
         /// </summary>
-        public Selector Selector { get; set; }
+        public Selector? Selector { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the style's setters.
+        /// Gets the style's setters.
         /// </summary>
         /// </summary>
         [Content]
         [Content]
-        public IList<ISetter> Setters { get; set; } = new List<ISetter>();
+        public IList<ISetter> Setters => _setters ??= new List<ISetter>();
 
 
-        public IList<IAnimation> Animations
-        {
-            get
-            {
-                return _animations ?? (_animations = new List<IAnimation>());
-            }
-        }
-
-        private CompositeDisposable Subscriptions
-        {
-            get
-            {
-                return _subscriptions ?? (_subscriptions = new CompositeDisposable(2));
-            }
-        }
+        /// <summary>
+        /// Gets the style's animations.
+        /// </summary>
+        public IList<IAnimation> Animations => _animations ??= new List<IAnimation>();
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        IResourceNode IResourceNode.ResourceParent => _parent;
+        IResourceNode? IResourceNode.ResourceParent => _parent;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         bool IResourceProvider.HasResources => _resources?.Count > 0;
         bool IResourceProvider.HasResources => _resources?.Count > 0;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public bool Attach(IStyleable control, IStyleHost container)
+        public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
         {
         {
-            if (Selector != null)
-            {
-                var match = Selector.Match(control);
-
-                if (match.IsMatch)
-                {
-                    var controlSubscriptions = GetSubscriptions(control);
-
-                    var animatable = control as Animatable;
-
-                    var setters = Setters;
-                    var settersCount = setters.Count;
-                    var animations = Animations;
-                    var animationsCount = animations.Count;
-
-                    var subs = new CompositeDisposable(settersCount + (animatable != null ? animationsCount : 0) + 1);
-
-                    if (animatable != null)
-                    {
-                        for (var i = 0; i < animationsCount; i++)
-                        {
-                            var animation = animations[i];
-                            var obsMatch = match.Activator;
-
-                            if (match.Result == SelectorMatchResult.AlwaysThisType ||
-                                match.Result == SelectorMatchResult.AlwaysThisInstance)
-                            {
-                                obsMatch = Observable.Return(true);
-                            }
-
-                            var sub = animation.Apply(animatable, null, obsMatch);
-                            subs.Add(sub);
-                        }
-                    }
-
-                    for (var i = 0; i < settersCount; i++)
-                    {
-                        var setter = setters[i];
-                        var sub = setter.Apply(this, control, match.Activator);
-                        subs.Add(sub);
-                    }
+            target = target ?? throw new ArgumentNullException(nameof(target));
 
 
-                    subs.Add(Disposable.Create((subs, Subscriptions) , state =>  state.Subscriptions.Remove(state.subs)));
+            var match = Selector is object ? Selector.Match(target) :
+                target == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
 
 
-                    controlSubscriptions.Add(subs);
-                    Subscriptions.Add(subs);
-                }
-
-                return match.Result != SelectorMatchResult.NeverThisType;
-            }
-            else if (control == container)
+            if (match.IsMatch && _setters is object)
             {
             {
-                var setters = Setters;
-                var settersCount = setters.Count;
-
-                var controlSubscriptions = GetSubscriptions(control);
-
-                var subs = new CompositeDisposable(settersCount + 1);
-
-                for (var i = 0; i < settersCount; i++)
-                {
-                    var setter = setters[i];
-                    var sub = setter.Apply(this, control, null);
-                    subs.Add(sub);
-                }
-
-                subs.Add(Disposable.Create((subs, Subscriptions), state => state.Subscriptions.Remove(state.subs)));
-
-                controlSubscriptions.Add(subs);
-                Subscriptions.Add(subs);
-                return true;
+                var instance = new StyleInstance(this, target, _setters, match.Activator);
+                target.StyleApplied(instance);
+                instance.Start();
             }
             }
 
 
-            return false;
+            return match.Result;
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public bool TryGetResource(object key, out object result)
+        public bool TryGetResource(object key, out object? result)
         {
         {
             result = null;
             result = null;
             return _resources?.TryGetResource(key, out result) ?? false;
             return _resources?.TryGetResource(key, out result) ?? false;
@@ -224,44 +147,12 @@ namespace Avalonia.Styling
 
 
             if (parent == null)
             if (parent == null)
             {
             {
-                Detach();
+                //Detach();
             }
             }
 
 
             _parent = parent;
             _parent = parent;
         }
         }
 
 
-        public void Detach()
-        {
-            _subscriptions?.Dispose();
-            _subscriptions = null;
-        }
-
-        private static CompositeDisposable GetSubscriptions(IStyleable control)
-        {
-            if (!_applied.TryGetValue(control, out var subscriptions))
-            {
-                subscriptions = new CompositeDisposable(3);
-                subscriptions.Add(control.StyleDetach.Subscribe(ControlDetach));
-                _applied.Add(control, subscriptions);
-            }
-
-            return subscriptions;
-        }
-
-        /// <summary>
-        /// Called when a control's <see cref="IStyleable.StyleDetach"/> is signaled to remove
-        /// all applied styles.
-        /// </summary>
-        /// <param name="control">The control.</param>
-        private static void ControlDetach(IStyleable control)
-        {
-            var subscriptions = _applied[control];
-
-            subscriptions.Dispose();
-
-            _applied.Remove(control);
-        }
-
         private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e)
         private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e)
         {
         {
             ResourcesChanged?.Invoke(this, e);
             ResourcesChanged?.Invoke(this, e);

+ 0 - 56
src/Avalonia.Styling/Styling/StyleActivator.cs

@@ -1,56 +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 System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reactive;
-using System.Reactive.Linq;
-
-namespace Avalonia.Styling
-{
-    public enum ActivatorMode
-    {
-        And,
-        Or,
-    }
-
-    public static class StyleActivator
-    {
-        public static IObservable<bool> And(IList<IObservable<bool>> inputs)
-        {
-            if (inputs.Count == 0)
-            {
-                throw new ArgumentException("StyleActivator.And inputs may not be empty.");
-            }
-            else if (inputs.Count == 1)
-            {
-                return inputs[0];
-            }
-            else
-            {
-                return inputs.CombineLatest()
-                    .Select(values => values.All(x => x))
-                    .DistinctUntilChanged();
-            }
-        }
-
-        public static IObservable<bool> Or(IList<IObservable<bool>> inputs)
-        {
-            if (inputs.Count == 0)
-            {
-                throw new ArgumentException("StyleActivator.Or inputs may not be empty.");
-            }
-            else if (inputs.Count == 1)
-            {
-                return inputs[0];
-            }
-            else
-            {
-                return inputs.CombineLatest()
-                    .Select(values => values.Any(x => x))
-                    .DistinctUntilChanged();
-            }
-        }
-    }
-}

+ 81 - 0
src/Avalonia.Styling/Styling/StyleInstance.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Styling.Activators;
+
+#nullable enable
+
+namespace Avalonia.Styling
+{
+    internal class StyleInstance : IStyleInstance, IStyleActivatorSink
+    {
+        private readonly List<ISetterInstance> _setters;
+        private readonly IStyleActivator? _activator;
+        private bool _active;
+
+        public StyleInstance(
+            IStyle source,
+            IStyleable target,
+            IReadOnlyList<ISetter> setters,
+            IStyleActivator? activator = null)
+        {
+            setters = setters ?? throw new ArgumentNullException(nameof(setters));
+
+            Source = source ?? throw new ArgumentNullException(nameof(source));
+            Target = target ?? throw new ArgumentNullException(nameof(target));
+
+            _setters = new List<ISetterInstance>(setters.Count);
+            _activator = activator;
+
+            foreach (var setter in setters)
+            {
+                _setters.Add(setter.Instance(target, activator is object));
+            }
+        }
+
+        public IStyle Source { get; }
+        public IStyleable Target { get; }
+
+        public void Start()
+        {
+            if (_activator == null)
+            {
+                ActivatorChanged(true);
+            }
+            else
+            {
+                _activator.Subscribe(this, 0);
+            }
+        }
+
+        public void Dispose()
+        {
+            ActivatorChanged(false);
+            _activator?.Dispose();
+        }
+
+        private void ActivatorChanged(bool value)
+        {
+            if (_active != value)
+            {
+                _active = value;
+
+                if (_active)
+                {
+                    foreach (var setter in _setters)
+                    {
+                        setter.Activate();
+                    }
+                }
+                else
+                {
+                    foreach (var setter in _setters)
+                    {
+                        setter.Deactivate();
+                    }
+                }
+            }
+        }
+
+        void IStyleActivatorSink.OnNext(bool value, int tag) => ActivatorChanged(value);
+    }
+}

+ 12 - 13
src/Avalonia.Styling/Styling/Styler.cs

@@ -3,35 +3,34 @@
 
 
 using System;
 using System;
 
 
+#nullable enable
+
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
     public class Styler : IStyler
     public class Styler : IStyler
     {
     {
-        public void ApplyStyles(IStyleable control)
+        public void ApplyStyles(IStyleable target)
         {
         {
-            var styleHost = control as IStyleHost;
+            target = target ?? throw new ArgumentNullException(nameof(target));
 
 
-            if (styleHost != null)
+            if (target is IStyleHost styleHost)
             {
             {
-                ApplyStyles(control, styleHost);
+                ApplyStyles(target, styleHost);
             }
             }
         }
         }
 
 
-        private void ApplyStyles(IStyleable control, IStyleHost styleHost)
+        private void ApplyStyles(IStyleable target, IStyleHost host)
         {
         {
-            Contract.Requires<ArgumentNullException>(control != null);
-            Contract.Requires<ArgumentNullException>(styleHost != null);
-
-            var parentContainer = styleHost.StylingParent;
+            var parent = host.StylingParent;
 
 
-            if (parentContainer != null)
+            if (parent != null)
             {
             {
-                ApplyStyles(control, parentContainer);
+                ApplyStyles(target, parent);
             }
             }
 
 
-            if (styleHost.IsStylesInitialized)
+            if (host.IsStylesInitialized)
             {
             {
-                styleHost.Styles.Attach(control, styleHost);
+                host.Styles.TryAttach(target, host);
             }
             }
         }
         }
     }
     }

+ 37 - 45
src/Avalonia.Styling/Styling/Styles.cs

@@ -9,6 +9,8 @@ using System.Linq;
 using Avalonia.Collections;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.Controls;
 
 
+#nullable enable
+
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
     /// <summary>
     /// <summary>
@@ -16,10 +18,10 @@ namespace Avalonia.Styling
     /// </summary>
     /// </summary>
     public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetResourceParent
     public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetResourceParent
     {
     {
-        private IResourceNode _parent;
-        private IResourceDictionary _resources;
-        private AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
-        private Dictionary<Type, List<IStyle>> _cache;
+        private readonly AvaloniaList<IStyle> _styles = new AvaloniaList<IStyle>();
+        private IResourceNode? _parent;
+        private IResourceDictionary? _resources;
+        private Dictionary<Type, List<IStyle>?>? _cache;
 
 
         public Styles()
         public Styles()
         {
         {
@@ -60,6 +62,12 @@ namespace Avalonia.Styling
                 () => { });
                 () => { });
         }
         }
 
 
+        public Styles(IResourceNode parent)
+            : this()
+        {
+            _parent = parent;
+        }
+
         public event NotifyCollectionChangedEventHandler CollectionChanged
         public event NotifyCollectionChangedEventHandler CollectionChanged
         {
         {
             add => _styles.CollectionChanged += value;
             add => _styles.CollectionChanged += value;
@@ -67,7 +75,7 @@ namespace Avalonia.Styling
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
+        public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public int Count => _styles.Count;
         public int Count => _styles.Count;
@@ -83,7 +91,7 @@ namespace Avalonia.Styling
             get => _resources ?? (Resources = new ResourceDictionary());
             get => _resources ?? (Resources = new ResourceDictionary());
             set
             set
             {
             {
-                Contract.Requires<ArgumentNullException>(value != null);
+                value = value ?? throw new ArgumentNullException(nameof(Resources));
 
 
                 var hadResources = false;
                 var hadResources = false;
 
 
@@ -104,7 +112,7 @@ namespace Avalonia.Styling
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        IResourceNode IResourceNode.ResourceParent => _parent;
+        IResourceNode? IResourceNode.ResourceParent => _parent;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         bool ICollection<IStyle>.IsReadOnly => false;
         bool ICollection<IStyle>.IsReadOnly => false;
@@ -119,66 +127,50 @@ namespace Avalonia.Styling
             set => _styles[index] = value;
             set => _styles[index] = value;
         }
         }
 
 
-        /// <summary>
-        /// Attaches the style to a control if the style's selector matches.
-        /// </summary>
-        /// <param name="control">The control to attach to.</param>
-        /// <param name="container">
-        /// The control that contains this style. May be null.
-        /// </param>
-        public bool Attach(IStyleable control, IStyleHost container)
+        /// <inheritdoc/>
+        public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host)
         {
         {
-            if (_cache == null)
-            {
-                _cache = new Dictionary<Type, List<IStyle>>();
-            }
+            _cache ??= new Dictionary<Type, List<IStyle>?>();
 
 
-            if (_cache.TryGetValue(control.StyleKey, out var cached))
+            if (_cache.TryGetValue(target.StyleKey, out var cached))
             {
             {
-                if (cached != null)
+                if (cached is object)
                 {
                 {
                     foreach (var style in cached)
                     foreach (var style in cached)
                     {
                     {
-                        style.Attach(control, container);
+                        style.TryAttach(target, host);
                     }
                     }
 
 
-                    return true;
+                    return SelectorMatchResult.AlwaysThisType;
+                }
+                else
+                {
+                    return SelectorMatchResult.NeverThisType;
                 }
                 }
-
-                return false;
             }
             }
             else
             else
             {
             {
-                List<IStyle> result = null;
+                List<IStyle>? matches = null;
 
 
-                foreach (var style in this)
+                foreach (var child in this)
                 {
                 {
-                    if (style.Attach(control, container))
+                    if (child.TryAttach(target, host) != SelectorMatchResult.NeverThisType)
                     {
                     {
-                        if (result == null)
-                        {
-                            result = new List<IStyle>();
-                        }
-
-                        result.Add(style);
+                        matches ??= new List<IStyle>();
+                        matches.Add(child);
                     }
                     }
                 }
                 }
 
 
-                _cache.Add(control.StyleKey, result);
-                return result != null;
-            }
-        }
-
-        public void Detach()
-        {
-            foreach (IStyle style in this)
-            {
-                style.Detach();
+                _cache.Add(target.StyleKey, matches);
+                
+                return matches is null ?
+                    SelectorMatchResult.NeverThisType :
+                    SelectorMatchResult.AlwaysThisType;
             }
             }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public bool TryGetResource(object key, out object value)
+        public bool TryGetResource(object key, out object? value)
         {
         {
             if (_resources != null && _resources.TryGetResource(key, out value))
             if (_resources != null && _resources.TryGetResource(key, out value))
             {
             {

+ 16 - 93
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@@ -3,11 +3,10 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Reflection;
 using System.Text;
 using System.Text;
-using Avalonia.Collections;
-using Avalonia.Reactive;
+using Avalonia.Styling.Activators;
+
+#nullable enable
 
 
 namespace Avalonia.Styling
 namespace Avalonia.Styling
 {
 {
@@ -17,13 +16,12 @@ namespace Avalonia.Styling
     /// </summary>
     /// </summary>
     internal class TypeNameAndClassSelector : Selector
     internal class TypeNameAndClassSelector : Selector
     {
     {
-        private readonly Selector _previous;
+        private readonly Selector? _previous;
         private readonly Lazy<List<string>> _classes = new Lazy<List<string>>(() => new List<string>());
         private readonly Lazy<List<string>> _classes = new Lazy<List<string>>(() => new List<string>());
-        private Type _targetType;
-        
-        private string _selectorString;
+        private Type? _targetType;
+        private string? _selectorString;
 
 
-        public static TypeNameAndClassSelector OfType(Selector previous, Type targetType)
+        public static TypeNameAndClassSelector OfType(Selector? previous, Type targetType)
         {
         {
             var result = new TypeNameAndClassSelector(previous);
             var result = new TypeNameAndClassSelector(previous);
             result._targetType = targetType;
             result._targetType = targetType;
@@ -32,7 +30,7 @@ namespace Avalonia.Styling
             return result;
             return result;
         }
         }
 
 
-        public static TypeNameAndClassSelector Is(Selector previous, Type targetType)
+        public static TypeNameAndClassSelector Is(Selector? previous, Type targetType)
         {
         {
             var result = new TypeNameAndClassSelector(previous);
             var result = new TypeNameAndClassSelector(previous);
             result._targetType = targetType;
             result._targetType = targetType;
@@ -41,7 +39,7 @@ namespace Avalonia.Styling
             return result;
             return result;
         }
         }
 
 
-        public static TypeNameAndClassSelector ForName(Selector previous, string name)
+        public static TypeNameAndClassSelector ForName(Selector? previous, string name)
         {
         {
             var result = new TypeNameAndClassSelector(previous);
             var result = new TypeNameAndClassSelector(previous);
             result.Name = name;
             result.Name = name;
@@ -49,7 +47,7 @@ namespace Avalonia.Styling
             return result;
             return result;
         }
         }
 
 
-        public static TypeNameAndClassSelector ForClass(Selector previous, string className)
+        public static TypeNameAndClassSelector ForClass(Selector? previous, string className)
         {
         {
             var result = new TypeNameAndClassSelector(previous);
             var result = new TypeNameAndClassSelector(previous);
             result.Classes.Add(className);
             result.Classes.Add(className);
@@ -57,7 +55,7 @@ namespace Avalonia.Styling
             return result;
             return result;
         }
         }
 
 
-        protected TypeNameAndClassSelector(Selector previous)
+        protected TypeNameAndClassSelector(Selector? previous)
         {
         {
             _previous = previous;
             _previous = previous;
         }
         }
@@ -68,10 +66,10 @@ namespace Avalonia.Styling
         /// <summary>
         /// <summary>
         /// Gets the name of the control to match.
         /// Gets the name of the control to match.
         /// </summary>
         /// </summary>
-        public string Name { get; set; }
+        public string? Name { get; set; }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public override Type TargetType => _targetType ?? _previous?.TargetType;
+        public override Type? TargetType => _targetType ?? _previous?.TargetType;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public override bool IsCombinator => false;
         public override bool IsCombinator => false;
@@ -130,12 +128,12 @@ namespace Avalonia.Styling
             {
             {
                 if (subscribe)
                 if (subscribe)
                 {
                 {
-                    var observable = new ClassObserver(control.Classes, _classes.Value);
+                    var observable = new StyleClassActivator(control.Classes, _classes.Value);
 
 
                     return new SelectorMatch(observable);
                     return new SelectorMatch(observable);
                 }
                 }
 
 
-                if (!AreClassesMatching(control.Classes, Classes))
+                if (!StyleClassActivator.AreClassesMatching(control.Classes, Classes))
                 {
                 {
                     return SelectorMatch.NeverThisInstance;
                     return SelectorMatch.NeverThisInstance;
                 }
                 }
@@ -144,7 +142,7 @@ namespace Avalonia.Styling
             return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance;
             return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance;
         }
         }
 
 
-        protected override Selector MovePrevious() => _previous;
+        protected override Selector? MovePrevious() => _previous;
 
 
         private string BuildSelectorString()
         private string BuildSelectorString()
         {
         {
@@ -190,80 +188,5 @@ namespace Avalonia.Styling
 
 
             return builder.ToString();
             return builder.ToString();
         }
         }
-
-        private static bool AreClassesMatching(IReadOnlyList<string> classes, IList<string> toMatch)
-        {
-            int remainingMatches = toMatch.Count;
-            int classesCount = classes.Count;
-
-            // Early bail out - we can't match if control does not have enough classes.
-            if (classesCount < remainingMatches)
-            {
-                return false;
-            }
-
-            for (var i = 0; i < classesCount; i++)
-            {
-                var c = classes[i];
-
-                if (toMatch.Contains(c))
-                {
-                    --remainingMatches;
-
-                    // Already matched so we can skip checking other classes.
-                    if (remainingMatches == 0)
-                    {
-                        break;
-                    }
-                }
-            }
-
-            return remainingMatches == 0;
-        }
-
-        private sealed class ClassObserver : LightweightObservableBase<bool>
-        {
-            private readonly IList<string> _match;
-            private readonly IAvaloniaReadOnlyList<string> _classes;
-            private bool _hasMatch;
-
-            public ClassObserver(IAvaloniaReadOnlyList<string> classes, IList<string> match)
-            {
-                _classes = classes;
-                _match = match;
-            }
-
-            protected override void Deinitialize() => _classes.CollectionChanged -= ClassesChanged;
-
-            protected override void Initialize()
-            {
-                _hasMatch = IsMatching();
-                _classes.CollectionChanged += ClassesChanged;
-            }
-
-            protected override void Subscribed(IObserver<bool> observer, bool first)
-            {
-                observer.OnNext(_hasMatch);
-            }
-
-            private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
-            {
-                if (e.Action != NotifyCollectionChangedAction.Move)
-                {
-                    var hasMatch = IsMatching();
-
-                    if (hasMatch != _hasMatch)
-                    {
-                        PublishNext(hasMatch);
-                        _hasMatch = hasMatch;
-                    }
-                }
-            }
-
-            private bool IsMatching()
-            {
-                return AreClassesMatching(_classes, _match);
-            }
-        }
     }
     }
 }
 }

+ 2 - 17
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@@ -4,6 +4,7 @@
 using Avalonia.Styling;
 using Avalonia.Styling;
 using System;
 using System;
 using Avalonia.Controls;
 using Avalonia.Controls;
+using System.Collections.Generic;
 
 
 namespace Avalonia.Markup.Xaml.Styling
 namespace Avalonia.Markup.Xaml.Styling
 {
 {
@@ -67,23 +68,7 @@ namespace Avalonia.Markup.Xaml.Styling
         IResourceNode IResourceNode.ResourceParent => _parent;
         IResourceNode IResourceNode.ResourceParent => _parent;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public bool Attach(IStyleable control, IStyleHost container)
-        {
-            if (Source != null)
-            {
-                return Loaded.Attach(control, container);
-            }
-
-            return false;
-        }
-
-        public void Detach()
-        {
-            if (Source != null)
-            {
-                Loaded.Detach();
-            }
-        }
+        public SelectorMatchResult TryAttach(IStyleable target, IStyleHost host) => Loaded.TryAttach(target, host);
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value);
         public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value);

+ 1 - 1
tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs

@@ -45,7 +45,7 @@ namespace Avalonia.Benchmarks.Styling
             {
             {
                 _window.Styles.Add(new Style(x => x.OfType<TextBox>().Class("foo").Class("bar").Class("baz"))
                 _window.Styles.Add(new Style(x => x.OfType<TextBox>().Class("foo").Class("bar").Class("baz"))
                 {
                 {
-                    Setters = new[]
+                    Setters =
                     {
                     {
                         new Setter(TextBox.TextProperty, "foo"),
                         new Setter(TextBox.TextProperty, "foo"),
                     }
                     }

+ 3 - 3
tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
 using Avalonia.Controls;
+using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
 using BenchmarkDotNet.Attributes;
 using BenchmarkDotNet.Attributes;
 
 
@@ -34,9 +35,8 @@ namespace Avalonia.Benchmarks.Styling
         {
         {
             var styles = UnitTestApplication.Current.Styles;
             var styles = UnitTestApplication.Current.Styles;
 
 
-            styles.Attach(_control, UnitTestApplication.Current);
-
-            styles.Detach();
+            styles.TryAttach(_control, UnitTestApplication.Current);
+            ((IStyleable)_control).DetachStyles();
         }
         }
 
 
         public void Dispose()
         public void Dispose()

+ 4 - 4
tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@@ -363,7 +363,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                     {
                     {
                         new Style(x => x.OfType<TestTemplatedControl>())
                         new Style(x => x.OfType<TestTemplatedControl>())
                         {
                         {
-                            Setters = new[]
+                            Setters =
                             {
                             {
                                 new Setter(
                                 new Setter(
                                     TemplatedControl.TemplateProperty,
                                     TemplatedControl.TemplateProperty,
@@ -399,7 +399,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                     {
                     {
                         new Style(x => x.OfType<TestTemplatedControl>())
                         new Style(x => x.OfType<TestTemplatedControl>())
                         {
                         {
-                            Setters = new[]
+                            Setters =
                             {
                             {
                                 new Setter(
                                 new Setter(
                                     TemplatedControl.TemplateProperty,
                                     TemplatedControl.TemplateProperty,
@@ -438,7 +438,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                     {
                     {
                         new Style(x => x.OfType<TestTemplatedControl>())
                         new Style(x => x.OfType<TestTemplatedControl>())
                         {
                         {
-                            Setters = new[]
+                            Setters =
                             {
                             {
                                 new Setter(
                                 new Setter(
                                     TemplatedControl.TemplateProperty,
                                     TemplatedControl.TemplateProperty,
@@ -458,7 +458,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                     {
                     {
                         new Style(x => x.OfType<TestTemplatedControl>())
                         new Style(x => x.OfType<TestTemplatedControl>())
                         {
                         {
-                            Setters = new[]
+                            Setters =
                             {
                             {
                                 new Setter(
                                 new Setter(
                                     TemplatedControl.TemplateProperty,
                                     TemplatedControl.TemplateProperty,

+ 1 - 1
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@@ -163,7 +163,7 @@ namespace Avalonia.Controls.UnitTests
                     {
                     {
                         new Style(x => x.OfType<TabItem>())
                         new Style(x => x.OfType<TabItem>())
                         {
                         {
-                            Setters = new[]
+                            Setters =
                             {
                             {
                                 new Setter(TemplatedControl.TemplateProperty, template)
                                 new Setter(TemplatedControl.TemplateProperty, template)
                             }
                             }

+ 1 - 1
tests/Avalonia.Controls.UnitTests/UserControlTests.cs

@@ -25,7 +25,7 @@ namespace Avalonia.Controls.UnitTests
                     {
                     {
                         new Style(x => x.OfType<UserControl>())
                         new Style(x => x.OfType<UserControl>())
                         {
                         {
-                            Setters = new[]
+                            Setters =
                             {
                             {
                                 new Setter(TemplatedControl.TemplateProperty, GetTemplate())
                                 new Setter(TemplatedControl.TemplateProperty, GetTemplate())
                             }
                             }

+ 9 - 1
tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs

@@ -139,7 +139,15 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters
                 get { throw new NotImplementedException(); }
                 get { throw new NotImplementedException(); }
             }
             }
 
 
-            IObservable<IStyleable> IStyleable.StyleDetach { get; }
+            public void DetachStyles()
+            {
+                throw new NotImplementedException();
+            }
+
+            public void StyleApplied(IStyleInstance instance)
+            {
+                throw new NotImplementedException();
+            }
         }
         }
 
 
         private class AttachedOwner
         private class AttachedOwner

+ 1 - 36
tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs

@@ -53,42 +53,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
                     }
                     }
                 };
                 };
 
 
-                setter.Apply(null, control, null);
-                Assert.Equal("foo", control.Text);
-
-                control.Text = "bar";
-                Assert.Equal("bar", data.Foo);
-            }
-        }
-
-        [Fact]
-        public void Setter_With_TwoWay_Binding_And_Activator_Should_Update_Source()
-        {
-            using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
-            {
-                var data = new Data
-                {
-                    Foo = "foo",
-                };
-
-                var control = new TextBox
-                {
-                    DataContext = data,
-                };
-
-                var setter = new Setter
-                {
-                    Property = TextBox.TextProperty,
-                    Value = new Binding
-                    {
-                        Path = "Foo",
-                        Mode = BindingMode.TwoWay
-                    }
-                };
-
-                var activator = Observable.Never<bool>().StartWith(true);
-
-                setter.Apply(null, control, activator);
+                setter.Instance(control, false).Activate();
                 Assert.Equal("foo", control.Text);
                 Assert.Equal("foo", control.Text);
 
 
                 control.Text = "bar";
                 control.Text = "bar";

+ 0 - 71
tests/Avalonia.Styling.UnitTests/ActivatedObservableTests.cs

@@ -1,71 +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 System;
-using System.Collections.Generic;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using Xunit;
-
-namespace Avalonia.Styling.UnitTests
-{
-    public class ActivatedObservableTests
-    {
-        [Fact]
-        public void Should_Produce_Correct_Values()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var source = new BehaviorSubject<object>(1);
-            var target = new ActivatedObservable(activator, source, string.Empty);
-            var result = new List<object>();
-
-            target.Subscribe(x => result.Add(x));
-
-            activator.OnNext(true);
-            source.OnNext(2);
-            activator.OnNext(false);
-            source.OnNext(3);
-            activator.OnNext(true);
-
-            Assert.Equal(
-                new[] 
-                {
-                    AvaloniaProperty.UnsetValue,
-                    1,
-                    2,
-                    AvaloniaProperty.UnsetValue,
-                    3,
-                }, 
-                result);
-        }
-
-        [Fact]
-        public void Should_Complete_When_Source_Completes()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var source = new BehaviorSubject<object>(1);
-            var target = new ActivatedObservable(activator, source, string.Empty);
-            var completed = false;
-
-            target.Subscribe(_ => { }, () => completed = true);
-            source.OnCompleted();
-
-            Assert.True(completed);
-        }
-
-        [Fact]
-        public void Should_Error_When_Source_Errors()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var source = new BehaviorSubject<object>(1);
-            var target = new ActivatedObservable(activator, source, string.Empty);
-            var error = new Exception();
-            var completed = false;
-
-            target.Subscribe(_ => { }, x => completed = true);
-            source.OnError(error);
-
-            Assert.True(completed);
-        }
-    }
-}

+ 0 - 92
tests/Avalonia.Styling.UnitTests/ActivatedSubjectTests.cs

@@ -1,92 +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 System;
-using System.Reactive.Disposables;
-using System.Reactive.Subjects;
-using Xunit;
-
-namespace Avalonia.Styling.UnitTests
-{
-    public class ActivatedSubjectTests
-    {
-        [Fact]
-        public void Should_Set_Values()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var source = new TestSubject();
-            var target = new ActivatedSubject(activator, source, string.Empty);
-
-            target.Subscribe();
-            target.OnNext("bar");
-            Assert.Equal(AvaloniaProperty.UnsetValue, source.Value);
-            activator.OnNext(true);
-            target.OnNext("baz");
-            Assert.Equal("baz", source.Value);
-            activator.OnNext(false);
-            Assert.Equal(AvaloniaProperty.UnsetValue, source.Value);
-            target.OnNext("bax");
-            activator.OnNext(true);
-            Assert.Equal("bax", source.Value);
-        }
-
-        [Fact]
-        public void Should_Invoke_OnCompleted_On_Activator_Completed()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var source = new TestSubject();
-            var target = new ActivatedSubject(activator, source, string.Empty);
-
-            target.Subscribe();
-            activator.OnCompleted();
-
-            Assert.True(source.Completed);
-        }
-
-        [Fact]
-        public void Should_Invoke_OnError_On_Activator_Error()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var source = new TestSubject();
-            var target = new ActivatedSubject(activator, source, string.Empty);
-            var targetError = default(Exception);
-            var error = new Exception();
-
-            target.Subscribe(_ => { }, e => targetError = e);
-            activator.OnError(error);
-
-            Assert.Same(error, source.Error);
-            Assert.Same(error, targetError);
-        }
-
-        private class TestSubject : ISubject<object>
-        {
-            private IObserver<object> _observer;
-
-            public bool Completed { get; set; }
-            public Exception Error { get; set; }
-            public object Value { get; set; } = AvaloniaProperty.UnsetValue;
-
-            public void OnCompleted()
-            {
-                Completed = true;
-            }
-
-            public void OnError(Exception error)
-            {
-                Error = error;
-            }
-
-            public void OnNext(object value)
-            {
-                Value = value;
-            }
-
-            public IDisposable Subscribe(IObserver<object> observer)
-            {
-                _observer = observer;
-                return Disposable.Empty;
-            }
-        }
-    }
-}

+ 0 - 75
tests/Avalonia.Styling.UnitTests/ActivatedValueTests.cs

@@ -1,75 +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 System;
-using System.Collections.Generic;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using Microsoft.Reactive.Testing;
-using Xunit;
-
-namespace Avalonia.Styling.UnitTests
-{
-    public class ActivatedValueTests
-    {
-        [Fact]
-        public void Should_Produce_Correct_Values()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var target = new ActivatedValue(activator, 1, string.Empty);
-            var result = new List<object>();
-
-            target.Subscribe(x => result.Add(x));
-
-            activator.OnNext(true);
-            activator.OnNext(false);
-
-            Assert.Equal(new[] { AvaloniaProperty.UnsetValue, 1, AvaloniaProperty.UnsetValue }, result);
-        }
-
-        [Fact]
-        public void Should_Complete_When_Activator_Completes()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var target = new ActivatedValue(activator, 1, string.Empty);
-            var completed = false;
-
-            target.Subscribe(_ => { }, () => completed = true);
-            activator.OnCompleted();
-
-            Assert.True(completed);
-        }
-
-        [Fact]
-        public void Should_Error_When_Activator_Errors()
-        {
-            var activator = new BehaviorSubject<bool>(false);
-            var target = new ActivatedValue(activator, 1, string.Empty);
-            var error = new Exception();
-            var completed = false;
-
-            target.Subscribe(_ => { }, x => completed = true);
-            activator.OnError(error);
-
-            Assert.True(completed);
-        }
-
-        [Fact]
-        public void Should_Unsubscribe_From_Activator_When_All_Subscriptions_Disposed()
-        {
-            var scheduler = new TestScheduler();
-            var activator1 = scheduler.CreateColdObservable<bool>();
-            var activator2 = scheduler.CreateColdObservable<bool>();
-            var activator = StyleActivator.And(new[] { activator1, activator2 });
-            var target = new ActivatedValue(activator, 1, string.Empty);
-
-            var subscription = target.Subscribe(_ => { });
-            Assert.Equal(1, activator1.Subscriptions.Count);
-            Assert.Equal(Subscription.Infinite, activator1.Subscriptions[0].Unsubscribe);
-
-            subscription.Dispose();
-            Assert.Equal(1, activator1.Subscriptions.Count);
-            Assert.Equal(0, activator1.Subscriptions[0].Unsubscribe);
-        }
-    }
-}

+ 8 - 8
tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs

@@ -36,7 +36,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "foo" },
+                Classes = { "foo" },
             };
             };
 
 
             var target = default(Selector).Class("foo");
             var target = default(Selector).Class("foo");
@@ -51,7 +51,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "bar" },
+                Classes = { "bar" },
             };
             };
 
 
             var target = default(Selector).Class("foo");
             var target = default(Selector).Class("foo");
@@ -66,7 +66,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "foo" },
+                Classes = { "foo" },
                 TemplatedParent = new Mock<ITemplatedControl>().Object,
                 TemplatedParent = new Mock<ITemplatedControl>().Object,
             };
             };
 
 
@@ -83,7 +83,7 @@ namespace Avalonia.Styling.UnitTests
             var control = new Control1();
             var control = new Control1();
 
 
             var target = default(Selector).Class("foo");
             var target = default(Selector).Class("foo");
-            var activator = target.Match(control).Activator;
+            var activator = target.Match(control).Activator.ToObservable();
 
 
             Assert.False(await activator.Take(1));
             Assert.False(await activator.Take(1));
             control.Classes.Add("foo");
             control.Classes.Add("foo");
@@ -95,11 +95,11 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "foo" },
+                Classes = { "foo" },
             };
             };
 
 
             var target = default(Selector).Class("foo");
             var target = default(Selector).Class("foo");
-            var activator = target.Match(control).Activator;
+            var activator = target.Match(control).Activator.ToObservable();
 
 
             Assert.True(await activator.Take(1));
             Assert.True(await activator.Take(1));
             control.Classes.Remove("foo");
             control.Classes.Remove("foo");
@@ -111,7 +111,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1();
             var control = new Control1();
             var target = default(Selector).Class("foo").Class("bar");
             var target = default(Selector).Class("foo").Class("bar");
-            var activator = target.Match(control).Activator;
+            var activator = target.Match(control).Activator.ToObservable();
 
 
             Assert.False(await activator.Take(1));
             Assert.False(await activator.Take(1));
             control.Classes.Add("foo");
             control.Classes.Add("foo");
@@ -128,7 +128,7 @@ namespace Avalonia.Styling.UnitTests
             // Test for #1698
             // Test for #1698
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "foo" },
+                Classes = { "foo" },
             };
             };
 
 
             var target = default(Selector).Class("foo");
             var target = default(Selector).Class("foo");

+ 1 - 1
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@@ -90,7 +90,7 @@ namespace Avalonia.Styling.UnitTests
             child.LogicalParent = parent;
             child.LogicalParent = parent;
 
 
             var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
             var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
-            var activator = selector.Match(child).Activator;
+            var activator = selector.Match(child).Activator.ToObservable();
 
 
             Assert.False(await activator.Take(1));
             Assert.False(await activator.Take(1));
             parent.Classes.Add("foo");
             parent.Classes.Add("foo");

+ 29 - 0
tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs

@@ -85,6 +85,35 @@ namespace Avalonia.Styling.UnitTests
             Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
             Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
         }
         }
 
 
+        [Fact]
+        public void Control_With_Class_Descendent_Of_Control_With_Two_Classes()
+        {
+            var textBlock = new TextBlock();
+            var control = new Button { Content = textBlock };
+
+            control.ApplyTemplate();
+
+            var selector = default(Selector)
+                .OfType<Button>()
+                .Class("foo")
+                .Class("bar")
+                .Descendant()
+                .OfType<TextBlock>()
+                .Class("baz");
+
+            var values = new List<bool>();
+            var match = selector.Match(textBlock);
+
+            Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+            match.Activator.Subscribe(x => values.Add(x));
+
+            Assert.Equal(new[] { false }, values);
+            control.Classes.AddRange(new[] { "foo", "bar" });
+            Assert.Equal(new[] { false }, values);
+            textBlock.Classes.Add("baz");
+            Assert.Equal(new[] { false, true }, values);
+        }
+
         [Fact]
         [Fact]
         public void Named_Class_Template_Child_Of_Control()
         public void Named_Class_Template_Child_Of_Control()
         {
         {

+ 4 - 4
tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs

@@ -41,7 +41,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "foo" },
+                Classes = { "foo" },
             };
             };
 
 
             var target = default(Selector).Not(x => x.Class("foo"));
             var target = default(Selector).Not(x => x.Class("foo"));
@@ -56,7 +56,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "bar" },
+                Classes = { "bar" },
             };
             };
 
 
             var target = default(Selector).Not(x => x.Class("foo"));
             var target = default(Selector).Not(x => x.Class("foo"));
@@ -71,7 +71,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control1
             var control = new Control1
             {
             {
-                Classes = new Classes { "bar" },
+                Classes = { "bar" },
             };
             };
 
 
             var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo"));
             var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo"));
@@ -86,7 +86,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Control2
             var control = new Control2
             {
             {
-                Classes = new Classes { "foo" },
+                Classes = { "foo" },
             };
             };
 
 
             var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo"));
             var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo"));

+ 1 - 1
tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new TextBlock();
             var control = new TextBlock();
             var target = default(Selector).PropertyEquals(TextBlock.TextProperty, "foo");
             var target = default(Selector).PropertyEquals(TextBlock.TextProperty, "foo");
-            var activator = target.Match(control).Activator;
+            var activator = target.Match(control).Activator.ToObservable();
 
 
             Assert.False(await activator.Take(1));
             Assert.False(await activator.Take(1));
             control.Text = "foo";
             control.Text = "foo";

+ 27 - 24
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Styling.UnitTests
             var style = Mock.Of<IStyle>();
             var style = Mock.Of<IStyle>();
             var setter = new Setter(TextBlock.TextProperty, binding);
             var setter = new Setter(TextBlock.TextProperty, binding);
 
 
-            setter.Apply(style, control, null);
+            setter.Instance(control, false).Activate();
 
 
             Assert.Equal("foo", control.Text);
             Assert.Equal("foo", control.Text);
         }
         }
@@ -46,7 +46,7 @@ namespace Avalonia.Styling.UnitTests
             var style = Mock.Of<IStyle>();
             var style = Mock.Of<IStyle>();
             var setter = new Setter(Decorator.ChildProperty, template);
             var setter = new Setter(Decorator.ChildProperty, template);
 
 
-            setter.Apply(style, control, null);
+            setter.Instance(control, false).Activate();
 
 
             Assert.IsType<Canvas>(control.Child);
             Assert.IsType<Canvas>(control.Child);
         }
         }
@@ -62,13 +62,13 @@ namespace Avalonia.Styling.UnitTests
                 RelativeSource = new RelativeSource(RelativeSourceMode.Self),
                 RelativeSource = new RelativeSource(RelativeSourceMode.Self),
             };
             };
             var setter = new Setter(Decorator.TagProperty, binding);
             var setter = new Setter(Decorator.TagProperty, binding);
-            var activator = new BehaviorSubject<bool>(true);
 
 
-            setter.Apply(style, control, activator);
+            var instance = setter.Instance(control, true);
+            instance.Activate();
             Assert.Equal("foobar", control.Tag);
             Assert.Equal("foobar", control.Tag);
 
 
             // Issue #1218 caused TestConverter.ConvertBack to throw here.
             // Issue #1218 caused TestConverter.ConvertBack to throw here.
-            activator.OnNext(false);
+            instance.Deactivate();
             Assert.Null(control.Tag);
             Assert.Null(control.Tag);
         }
         }
 
 
@@ -77,13 +77,14 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Mock<IStyleable>();
             var control = new Mock<IStyleable>();
             var style = Mock.Of<Style>();
             var style = Mock.Of<Style>();
-            var setter = new Setter(TextBlock.TextProperty, "foo");
+            var setter = new Setter(TextBlock.TagProperty, "foo");
 
 
-            setter.Apply(style, control.Object, null);
+            setter.Instance(control.Object, false).Activate();
 
 
-            control.Verify(x => x.Bind(
-                TextBlock.TextProperty,
-                It.IsAny<IObservable<BindingValue<string>>>()));
+            control.Verify(x => x.SetValue(
+                TextBlock.TagProperty,
+                "foo",
+                BindingPriority.Style));
         }
         }
 
 
         [Fact]
         [Fact]
@@ -91,14 +92,15 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Mock<IStyleable>();
             var control = new Mock<IStyleable>();
             var style = Mock.Of<Style>();
             var style = Mock.Of<Style>();
-            var setter = new Setter(TextBlock.TextProperty, "foo");
+            var setter = new Setter(TextBlock.TagProperty, "foo");
             var activator = new Subject<bool>();
             var activator = new Subject<bool>();
 
 
-            setter.Apply(style, control.Object, activator);
+            setter.Instance(control.Object, true).Activate();
 
 
-            control.Verify(x => x.Bind(
-                TextBlock.TextProperty,
-                It.IsAny<IObservable<BindingValue<string>>>()));
+            control.Verify(x => x.SetValue(
+                TextBlock.TagProperty,
+                "foo",
+                BindingPriority.StyleTrigger));
         }
         }
 
 
         [Fact]
         [Fact]
@@ -106,13 +108,14 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Mock<IStyleable>();
             var control = new Mock<IStyleable>();
             var style = Mock.Of<Style>();
             var style = Mock.Of<Style>();
-            var setter = new Setter(TextBlock.TextProperty, CreateMockBinding(TextBlock.TextProperty));
+            var setter = new Setter(TextBlock.TagProperty, CreateMockBinding(TextBlock.TagProperty));
 
 
-            setter.Apply(style, control.Object, null);
+            setter.Instance(control.Object, false).Activate();
 
 
             control.Verify(x => x.Bind(
             control.Verify(x => x.Bind(
-                TextBlock.TextProperty,
-                It.IsAny<IObservable<BindingValue<string>>>()));
+                TextBlock.TagProperty,
+                It.IsAny<IObservable<BindingValue<object>>>(),
+                BindingPriority.Style));
         }
         }
 
 
         [Fact]
         [Fact]
@@ -120,14 +123,14 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             var control = new Mock<IStyleable>();
             var control = new Mock<IStyleable>();
             var style = Mock.Of<Style>();
             var style = Mock.Of<Style>();
-            var setter = new Setter(TextBlock.TextProperty, CreateMockBinding(TextBlock.TextProperty));
-            var activator = new Subject<bool>();
+            var setter = new Setter(TextBlock.TagProperty, CreateMockBinding(TextBlock.TagProperty));
 
 
-            setter.Apply(style, control.Object, activator);
+            setter.Instance(control.Object, true).Activate();
 
 
             control.Verify(x => x.Bind(
             control.Verify(x => x.Bind(
-                TextBlock.TextProperty,
-                It.IsAny<IObservable<BindingValue<string>>>()));
+                TextBlock.TagProperty,
+                It.IsAny<IObservable<BindingValue<object>>>(),
+                BindingPriority.StyleTrigger));
         }
         }
 
 
         private IBinding CreateMockBinding(AvaloniaProperty property)
         private IBinding CreateMockBinding(AvaloniaProperty property)

+ 42 - 0
tests/Avalonia.Styling.UnitTests/StyleActivatorExtensions.cs

@@ -0,0 +1,42 @@
+using System;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Avalonia.Reactive;
+using Avalonia.Styling.Activators;
+
+namespace Avalonia.Styling.UnitTests
+{
+    internal static class StyleActivatorExtensions
+    {
+        public static IDisposable Subscribe(this IStyleActivator activator, Action<bool> action)
+        {
+            return activator.ToObservable().Subscribe(action);
+        }
+
+        public static async Task<bool> Take(this IStyleActivator activator, int value)
+        {
+            return await activator.ToObservable().Take(value);
+        }
+
+        public static IObservable<bool> ToObservable(this IStyleActivator activator)
+        {
+            return new ObservableAdapter(activator);
+        }
+
+        private class ObservableAdapter : LightweightObservableBase<bool>, IStyleActivatorSink
+        {
+            private readonly IStyleActivator _source;
+            private bool _value;
+            
+            public ObservableAdapter(IStyleActivator source) => _source = source;
+            protected override void Initialize() => _source.Subscribe(this);
+            protected override void Deinitialize() => _source.Unsubscribe(this);
+
+            void IStyleActivatorSink.OnNext(bool value, int tag)
+            {
+                _value = value;
+                PublishNext(value);
+            }
+        }
+    }
+}

+ 0 - 169
tests/Avalonia.Styling.UnitTests/StyleActivatorTests.cs

@@ -1,169 +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 System;
-using System.Collections.Generic;
-using System.Reactive;
-using System.Reactive.Linq;
-using Microsoft.Reactive.Testing;
-using Xunit;
-
-namespace Avalonia.Styling.UnitTests
-{
-    public class StyleActivatorTests : ReactiveTest
-    {
-        [Fact]
-        public void Activator_Should_Subscribe_To_Inputs_On_First_Subscription()
-        {
-            var scheduler = new TestScheduler();
-            var source = scheduler.CreateColdObservable<bool>();
-            var target = StyleActivator.And(new[] { source });
-
-            Assert.Equal(0, source.Subscriptions.Count);
-            target.Subscribe(_ => { });
-            Assert.Equal(1, source.Subscriptions.Count);
-        }
-
-        [Fact]
-        public void Activator_Should_Unsubscribe_From_Inputs_After_Last_Subscriber_Completes()
-        {
-            var scheduler = new TestScheduler();
-            var source = scheduler.CreateColdObservable<bool>();
-            var target = StyleActivator.And(new[] { source });
-
-            var dispose = target.Subscribe(_ => { });
-            Assert.Equal(1, source.Subscriptions.Count);
-            Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe);
-
-            dispose.Dispose();
-            Assert.Equal(1, source.Subscriptions.Count);
-            Assert.Equal(0, source.Subscriptions[0].Unsubscribe);
-        }
-
-        [Fact]
-        public void Activator_And_Should_Follow_Single_Input()
-        {
-            var inputs = new[] { new TestSubject<bool>(false) };
-            var target = StyleActivator.And(inputs);
-            var result = new TestObserver<bool>();
-
-            target.Subscribe(result);
-            Assert.False(result.GetValue());
-            inputs[0].OnNext(true);
-            Assert.True(result.GetValue());
-            inputs[0].OnNext(false);
-            Assert.False(result.GetValue());
-            inputs[0].OnNext(true);
-            Assert.True(result.GetValue());
-
-            Assert.Equal(1, inputs[0].SubscriberCount);
-        }
-
-        [Fact]
-        public void Activator_And_Should_AND_Multiple_Inputs()
-        {
-            var inputs = new[]
-            {
-                new TestSubject<bool>(false),
-                new TestSubject<bool>(false),
-                new TestSubject<bool>(true),
-            };
-            var target = StyleActivator.And(inputs);
-            var result = new TestObserver<bool>();
-
-            target.Subscribe(result);
-            Assert.False(result.GetValue());
-            inputs[0].OnNext(true);
-            inputs[1].OnNext(true);
-            Assert.True(result.GetValue());
-            inputs[0].OnNext(false);
-            Assert.False(result.GetValue());
-
-            Assert.Equal(1, inputs[0].SubscriberCount);
-            Assert.Equal(1, inputs[1].SubscriberCount);
-            Assert.Equal(1, inputs[2].SubscriberCount);
-        }
-
-        [Fact]
-        public void Activator_Or_Should_Follow_Single_Input()
-        {
-            var inputs = new[] { new TestSubject<bool>(false) };
-            var target = StyleActivator.Or(inputs);
-            var result = new TestObserver<bool>();
-
-            target.Subscribe(result);
-            Assert.False(result.GetValue());
-            inputs[0].OnNext(true);
-            Assert.True(result.GetValue());
-            inputs[0].OnNext(false);
-            Assert.False(result.GetValue());
-            inputs[0].OnNext(true);
-            Assert.True(result.GetValue());
-
-            Assert.Equal(1, inputs[0].SubscriberCount);
-        }
-
-        [Fact]
-        public void Activator_Or_Should_OR_Multiple_Inputs()
-        {
-            var inputs = new[]
-            {
-                new TestSubject<bool>(false),
-                new TestSubject<bool>(false),
-                new TestSubject<bool>(true),
-            };
-            var target = StyleActivator.Or(inputs);
-            var result = new TestObserver<bool>();
-
-            target.Subscribe(result);
-            Assert.True(result.GetValue());
-            inputs[2].OnNext(false);
-            Assert.False(result.GetValue());
-            inputs[0].OnNext(true);
-            Assert.True(result.GetValue());
-
-            Assert.Equal(1, inputs[0].SubscriberCount);
-            Assert.Equal(1, inputs[1].SubscriberCount);
-            Assert.Equal(1, inputs[2].SubscriberCount);
-        }
-
-        [Fact]
-        public void Activator_Or_Should_Not_Unsubscribe_All_When_Input_Completes_On_False()
-        {
-            var inputs = new[]
-            {
-                new TestSubject<bool>(false),
-                new TestSubject<bool>(false),
-                new TestSubject<bool>(true),
-            };
-            var target = StyleActivator.Or(inputs);
-            var result = new TestObserver<bool>();
-
-            target.Subscribe(result);
-            Assert.True(result.GetValue());
-            inputs[2].OnNext(false);
-            Assert.False(result.GetValue());
-            inputs[2].OnCompleted();
-
-            Assert.Equal(1, inputs[0].SubscriberCount);
-            Assert.Equal(1, inputs[1].SubscriberCount);
-            Assert.Equal(0, inputs[2].SubscriberCount);
-        }
-
-        [Fact]
-        public void Completed_Activator_Should_Signal_OnCompleted()
-        {
-            var inputs = new[]
-            {
-                Observable.Return(false),
-            };
-
-            var target = StyleActivator.Or(inputs);
-            var completed = false;
-
-            target.Subscribe(_ => { }, () => completed = true);
-
-            Assert.True(completed);
-        }
-    }
-}

+ 16 - 42
tests/Avalonia.Styling.UnitTests/StyleTests.cs

@@ -3,7 +3,6 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Reactive.Subjects;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.UnitTests;
 using Avalonia.UnitTests;
 using Xunit;
 using Xunit;
@@ -17,7 +16,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             Style style = new Style(x => x.OfType<Class1>())
             Style style = new Style(x => x.OfType<Class1>())
             {
             {
-                Setters = new[]
+                Setters =
                 {
                 {
                     new Setter(Class1.FooProperty, "Foo"),
                     new Setter(Class1.FooProperty, "Foo"),
                 },
                 },
@@ -25,7 +24,7 @@ namespace Avalonia.Styling.UnitTests
 
 
             var target = new Class1();
             var target = new Class1();
 
 
-            style.Attach(target, null);
+            style.TryAttach(target, null);
 
 
             Assert.Equal("Foo", target.Foo);
             Assert.Equal("Foo", target.Foo);
         }
         }
@@ -35,7 +34,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             Style style = new Style(x => x.OfType<Class1>().Class("foo"))
             Style style = new Style(x => x.OfType<Class1>().Class("foo"))
             {
             {
-                Setters = new[]
+                Setters =
                 {
                 {
                     new Setter(Class1.FooProperty, "Foo"),
                     new Setter(Class1.FooProperty, "Foo"),
                 },
                 },
@@ -43,7 +42,7 @@ namespace Avalonia.Styling.UnitTests
 
 
             var target = new Class1();
             var target = new Class1();
 
 
-            style.Attach(target, null);
+            style.TryAttach(target, null);
             Assert.Equal("foodefault", target.Foo);
             Assert.Equal("foodefault", target.Foo);
             target.Classes.Add("foo");
             target.Classes.Add("foo");
             Assert.Equal("Foo", target.Foo);
             Assert.Equal("Foo", target.Foo);
@@ -56,7 +55,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             Style style = new Style
             Style style = new Style
             {
             {
-                Setters = new[]
+                Setters =
                 {
                 {
                     new Setter(Class1.FooProperty, "Foo"),
                     new Setter(Class1.FooProperty, "Foo"),
                 },
                 },
@@ -64,7 +63,7 @@ namespace Avalonia.Styling.UnitTests
 
 
             var target = new Class1();
             var target = new Class1();
 
 
-            style.Attach(target, target);
+            style.TryAttach(target, target);
 
 
             Assert.Equal("Foo", target.Foo);
             Assert.Equal("Foo", target.Foo);
         }
         }
@@ -74,7 +73,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             Style style = new Style
             Style style = new Style
             {
             {
-                Setters = new[]
+                Setters =
                 {
                 {
                     new Setter(Class1.FooProperty, "Foo"),
                     new Setter(Class1.FooProperty, "Foo"),
                 },
                 },
@@ -83,7 +82,7 @@ namespace Avalonia.Styling.UnitTests
             var target = new Class1();
             var target = new Class1();
             var other = new Class1();
             var other = new Class1();
 
 
-            style.Attach(target, other);
+            style.TryAttach(target, other);
 
 
             Assert.Equal("foodefault", target.Foo);
             Assert.Equal("foodefault", target.Foo);
         }
         }
@@ -93,7 +92,7 @@ namespace Avalonia.Styling.UnitTests
         {
         {
             Style style = new Style(x => x.OfType<Class1>())
             Style style = new Style(x => x.OfType<Class1>())
             {
             {
-                Setters = new[]
+                Setters =
                 {
                 {
                     new Setter(Class1.FooProperty, "Foo"),
                     new Setter(Class1.FooProperty, "Foo"),
                 },
                 },
@@ -104,7 +103,7 @@ namespace Avalonia.Styling.UnitTests
                 Foo = "Original",
                 Foo = "Original",
             };
             };
 
 
-            style.Attach(target, null);
+            style.TryAttach(target, null);
             Assert.Equal("Original", target.Foo);
             Assert.Equal("Original", target.Foo);
         }
         }
 
 
@@ -115,7 +114,7 @@ namespace Avalonia.Styling.UnitTests
             {
             {
                 new Style(x => x.OfType<Class1>().Class("foo"))
                 new Style(x => x.OfType<Class1>().Class("foo"))
                 {
                 {
-                    Setters = new[]
+                    Setters =
                     {
                     {
                         new Setter(Class1.FooProperty, "Foo"),
                         new Setter(Class1.FooProperty, "Foo"),
                     },
                     },
@@ -123,7 +122,7 @@ namespace Avalonia.Styling.UnitTests
 
 
                 new Style(x => x.OfType<Class1>().Class("foo"))
                 new Style(x => x.OfType<Class1>().Class("foo"))
                 {
                 {
-                    Setters = new[]
+                    Setters =
                     {
                     {
                         new Setter(Class1.FooProperty, "Bar"),
                         new Setter(Class1.FooProperty, "Bar"),
                     },
                     },
@@ -135,7 +134,7 @@ namespace Avalonia.Styling.UnitTests
             List<string> values = new List<string>();
             List<string> values = new List<string>();
             target.GetObservable(Class1.FooProperty).Subscribe(x => values.Add(x));
             target.GetObservable(Class1.FooProperty).Subscribe(x => values.Add(x));
 
 
-            styles.Attach(target, null);
+            styles.TryAttach(target, null);
             target.Classes.Add("foo");
             target.Classes.Add("foo");
             target.Classes.Remove("foo");
             target.Classes.Remove("foo");
 
 
@@ -143,13 +142,13 @@ namespace Avalonia.Styling.UnitTests
         }
         }
 
 
         [Fact]
         [Fact]
-        public void Style_Should_Detach_When_Removed_From_Logical_Tree()
+        public void Style_Should_Detach_When_Control_Removed_From_Logical_Tree()
         {
         {
             Border border;
             Border border;
 
 
             var style = new Style(x => x.OfType<Border>())
             var style = new Style(x => x.OfType<Border>())
             {
             {
-                Setters = new[]
+                Setters =
                 {
                 {
                     new Setter(Border.BorderThicknessProperty, new Thickness(4)),
                     new Setter(Border.BorderThicknessProperty, new Thickness(4)),
                 }
                 }
@@ -160,38 +159,13 @@ namespace Avalonia.Styling.UnitTests
                 Child = border = new Border(),
                 Child = border = new Border(),
             };
             };
 
 
-            style.Attach(border, null);
+            style.TryAttach(border, null);
 
 
             Assert.Equal(new Thickness(4), border.BorderThickness);
             Assert.Equal(new Thickness(4), border.BorderThickness);
             root.Child = null;
             root.Child = null;
             Assert.Equal(new Thickness(0), border.BorderThickness);
             Assert.Equal(new Thickness(0), border.BorderThickness);
         }
         }
 
 
-        [Fact]
-        public void Style_Should_Detach_Setters_When_Detach_Is_Called()
-        {
-            Border border;
-
-            var style = new Style(x => x.OfType<Border>())
-            {
-                Setters = new[]
-                {
-                    new Setter(Border.BorderThicknessProperty, new Thickness(4)),
-                }
-            };
-
-            var root = new TestRoot
-            {
-                Child = border = new Border(),
-            };
-
-            style.Attach(border, null);
-
-            Assert.Equal(new Thickness(4), border.BorderThickness);
-            style.Detach();
-            Assert.Equal(new Thickness(0), border.BorderThickness);
-        }
-
         private class Class1 : Control
         private class Class1 : Control
         {
         {
             public static readonly StyledProperty<string> FooProperty =
             public static readonly StyledProperty<string> FooProperty =

+ 5 - 4
tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@@ -352,7 +352,7 @@ namespace Avalonia.Styling.UnitTests
         }
         }
 
 
         [Fact]
         [Fact]
-        public void StyleDetach_Is_Triggered_When_Control_Removed_From_Logical_Tree()
+        public void StyleInstance_Is_Disposed_When_Control_Removed_From_Logical_Tree()
         {
         {
             using (AvaloniaLocator.EnterScope())
             using (AvaloniaLocator.EnterScope())
             {
             {
@@ -361,11 +361,12 @@ namespace Avalonia.Styling.UnitTests
 
 
                 root.Child = child;
                 root.Child = child;
 
 
-                bool styleDetachTriggered = false;
-                ((IStyleable)child).StyleDetach.Subscribe(_ => styleDetachTriggered = true);
+                var styleInstance = new Mock<IStyleInstance>();
+                ((IStyleable)child).StyleApplied(styleInstance.Object);
+
                 root.Child = null;
                 root.Child = null;
 
 
-                Assert.True(styleDetachTriggered);
+                styleInstance.Verify(x => x.Dispose(), Times.Once);
             }
             }
         }
         }