diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 59a61e3424..f2e90bb6d7 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -177,7 +177,7 @@ namespace Avalonia
{
return new IndexerDescriptor
{
- Priority = BindingPriority.TemplatedParent,
+ Priority = BindingPriority.Template,
Property = property,
};
}
diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs
index dd1654f53c..5fd5aae43b 100644
--- a/src/Avalonia.Base/Data/BindingPriority.cs
+++ b/src/Avalonia.Base/Data/BindingPriority.cs
@@ -1,7 +1,9 @@
+using System;
+
namespace Avalonia.Data
{
///
- /// The priority of a binding.
+ /// The priority of a value or binding.
///
public enum BindingPriority
{
@@ -16,23 +18,22 @@ namespace Avalonia.Data
LocalValue = 0,
///
- /// A triggered style binding.
+ /// A triggered style value.
///
///
/// A style trigger is a selector such as .class which overrides a
- /// binding. In this way, a basic control can have
- /// for example a Background from the templated parent which changes when the
- /// control has the :pointerover class.
+ /// value. In this way, a control can have, e.g. a Background from
+ /// the template which changes when the control has the :pointerover class.
///
StyleTrigger,
///
- /// A binding to a property on the templated parent.
+ /// A value from the control's template.
///
- TemplatedParent,
+ Template,
///
- /// A style binding.
+ /// A style value.
///
Style,
@@ -42,8 +43,11 @@ namespace Avalonia.Data
Inherited,
///
- /// The binding is uninitialized.
+ /// The value is uninitialized.
///
Unset = int.MaxValue,
+
+ [Obsolete("Use Template priority")]
+ TemplatedParent = Template,
}
}
diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 527b63292d..39edab3fc6 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Logging;
+using Avalonia.Styling;
using Avalonia.VisualTree;
#nullable enable
@@ -719,9 +720,9 @@ namespace Avalonia.Layout
return finalSize;
}
- protected sealed override void InvalidateStyles()
+ internal sealed override void InvalidateStyles(bool recurse)
{
- base.InvalidateStyles();
+ base.InvalidateStyles(recurse);
InvalidateMeasure();
}
@@ -795,6 +796,12 @@ namespace Avalonia.Layout
base.OnVisualParentChanged(oldParent, newParent);
}
+ private protected override void OnControlThemeChanged()
+ {
+ base.OnControlThemeChanged();
+ InvalidateMeasure();
+ }
+
///
/// Called when the layout manager raises a LayoutUpdated event.
///
diff --git a/src/Avalonia.Base/PropertyStore/FramePriority.cs b/src/Avalonia.Base/PropertyStore/FramePriority.cs
new file mode 100644
index 0000000000..950a8375f2
--- /dev/null
+++ b/src/Avalonia.Base/PropertyStore/FramePriority.cs
@@ -0,0 +1,36 @@
+using System.Diagnostics;
+using Avalonia.Data;
+
+namespace Avalonia.PropertyStore
+{
+ internal enum FramePriority : sbyte
+ {
+ Animation,
+ AnimationTemplatedParentTheme,
+ AnimationTheme,
+ StyleTrigger,
+ StyleTriggerTemplatedParentTheme,
+ StyleTriggerTheme,
+ Template,
+ TemplateTemplatedParentTheme,
+ TemplateTheme,
+ Style,
+ StyleTemplatedParentTheme,
+ StyleTheme,
+ }
+
+ internal static class FramePriorityExtensions
+ {
+ public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style)
+ {
+ Debug.Assert(priority != BindingPriority.LocalValue);
+ var p = (int)(priority > 0 ? priority : priority + 1);
+ return (FramePriority)(p * 3 + (int)type);
+ }
+
+ public static bool IsType(this FramePriority priority, FrameType type)
+ {
+ return (FrameType)((int)priority % 3) == type;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
index 1d886e7501..50d5333b9f 100644
--- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
+++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
@@ -7,11 +7,11 @@ namespace Avalonia.PropertyStore
/// Holds values in a set by one of the SetValue or AddBinding
/// overloads with non-LocalValue priority.
///
- internal class ImmediateValueFrame : ValueFrame
+ internal sealed class ImmediateValueFrame : ValueFrame
{
public ImmediateValueFrame(BindingPriority priority)
+ : base(priority, FrameType.Style)
{
- Priority = priority;
}
public TypedBindingEntry AddBinding(
diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs
index 5ada4b3c84..7a9d1bb13a 100644
--- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs
@@ -1,13 +1,18 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Utilities;
-using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
+ internal enum FrameType
+ {
+ Style,
+ TemplatedParentTheme,
+ Theme,
+ }
+
internal abstract class ValueFrame
{
private List? _entries;
@@ -15,11 +20,18 @@ namespace Avalonia.PropertyStore
private ValueStore? _owner;
private bool _isShared;
+ protected ValueFrame(BindingPriority priority, FrameType type)
+ {
+ Priority = priority;
+ FramePriority = priority.ToFramePriority(type);
+ }
+
public int EntryCount => _index.Count;
public bool IsActive => GetIsActive(out _);
public ValueStore? Owner => !_isShared ? _owner :
throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame");
- public BindingPriority Priority { get; protected set; }
+ public BindingPriority Priority { get; }
+ public FramePriority FramePriority { get; }
public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property);
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index d858e30212..92e5288255 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -2,10 +2,12 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using Avalonia.Data;
using Avalonia.Diagnostics;
-using Avalonia.Logging;
+using Avalonia.Styling;
using Avalonia.Utilities;
+using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@@ -580,6 +582,53 @@ namespace Avalonia.PropertyStore
return false;
}
+ public void RemoveFrames(FrameType type)
+ {
+ var removed = false;
+
+ for (var i = _frames.Count - 1; i >= 0; --i)
+ {
+ var frame = _frames[i];
+
+ if (frame is not ImmediateValueFrame && frame.FramePriority.IsType(type))
+ {
+ _frames.RemoveAt(i);
+ frame.Dispose();
+ removed = true;
+ }
+ }
+
+ if (removed)
+ {
+ ++_frameGeneration;
+ ReevaluateEffectiveValues();
+ }
+ }
+
+
+ public void RemoveFrames(IReadOnlyList styles)
+ {
+ var removed = false;
+
+ for (var i = _frames.Count - 1; i >= 0; --i)
+ {
+ var frame = _frames[i];
+
+ if (frame is StyleInstance style && styles.Contains(style.Source))
+ {
+ _frames.RemoveAt(i);
+ frame.Dispose();
+ removed = true;
+ }
+ }
+
+ if (removed)
+ {
+ ++_frameGeneration;
+ ReevaluateEffectiveValues();
+ }
+ }
+
public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property)
{
object? value;
@@ -612,7 +661,7 @@ namespace Avalonia.PropertyStore
{
Debug.Assert(!_frames.Contains(frame));
- var index = BinarySearchFrame(frame.Priority);
+ var index = BinarySearchFrame(frame.FramePriority);
_frames.Insert(index, frame);
++_frameGeneration;
frame.SetOwner(this);
@@ -626,7 +675,7 @@ namespace Avalonia.PropertyStore
{
Debug.Assert(priority != BindingPriority.LocalValue);
- var index = BinarySearchFrame(priority);
+ var index = BinarySearchFrame(priority.ToFramePriority());
if (index > 0 && _frames[index - 1] is ImmediateValueFrame f &&
f.Priority == priority &&
@@ -914,7 +963,7 @@ namespace Avalonia.PropertyStore
}
}
- private int BinarySearchFrame(BindingPriority priority)
+ private int BinarySearchFrame(FramePriority priority)
{
var lo = 0;
var hi = _frames.Count - 1;
@@ -923,7 +972,7 @@ namespace Avalonia.PropertyStore
while (lo <= hi)
{
var i = lo + ((hi - lo) >> 1);
- var order = priority - _frames[i].Priority;
+ var order = priority - _frames[i].FramePriority;
if (order <= 0)
{
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index c72f398fd9..187edd8335 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
+using System.Diagnostics;
using System.Linq;
using Avalonia.Animation;
using Avalonia.Collections;
@@ -11,6 +12,7 @@ using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.LogicalTree;
+using Avalonia.PropertyStore;
using Avalonia.Styling;
namespace Avalonia
@@ -69,10 +71,10 @@ namespace Avalonia
private IAvaloniaList? _logicalChildren;
private IResourceDictionary? _resources;
private Styles? _styles;
- private bool _styled;
+ private bool _stylesApplied;
+ private bool _themeApplied;
private ITemplatedControl? _templatedParent;
private bool _dataContextUpdating;
- private bool _hasPromotedTheme;
private ControlTheme? _implicitTheme;
///
@@ -141,7 +143,7 @@ namespace Avalonia
set
{
- if (_styled)
+ if (_stylesApplied)
{
throw new InvalidOperationException("Cannot set Name : styled element already styled.");
}
@@ -353,38 +355,33 @@ namespace Avalonia
///
public bool ApplyStyling()
{
- if (_initCount == 0 && !_styled)
+ if (_initCount == 0 && (!_stylesApplied || !_themeApplied))
{
- var hasPromotedTheme = _hasPromotedTheme;
-
GetValueStore().BeginStyling();
try
{
- ApplyControlTheme();
- ApplyStyles(this);
+ if (!_themeApplied)
+ {
+ ApplyControlTheme();
+ _themeApplied = true;
+ }
+
+ if (!_stylesApplied)
+ {
+ ApplyStyles(this);
+ _stylesApplied = true;
+ }
}
finally
{
- _styled = true;
GetValueStore().EndStyling();
}
-
- if (hasPromotedTheme)
- {
- _hasPromotedTheme = false;
- ClearValue(ThemeProperty);
- }
}
- return _styled;
+ return _stylesApplied;
}
- ///
- /// Detaches all styles from the element and queues a restyle.
- ///
- protected virtual void InvalidateStyles() => DetachStyles();
-
protected void InitializeIfNeeded()
{
if (_initCount == 0 && !IsInitialized)
@@ -506,17 +503,16 @@ namespace Avalonia
};
}
- void IStyleable.DetachStyles() => DetachStyles();
-
void IStyleHost.StylesAdded(IReadOnlyList styles)
{
- InvalidateStylesOnThisAndDescendents();
+ if (HasSettersOrAnimations(styles))
+ InvalidateStyles(recurse: true);
}
void IStyleHost.StylesRemoved(IReadOnlyList styles)
{
- var allStyles = RecurseStyles(styles);
- DetachStylesFromThisAndDescendents(allStyles);
+ if (FlattenStyles(styles) is { } allStyles)
+ DetachStyles(allStyles);
}
protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
@@ -615,31 +611,25 @@ namespace Avalonia
if (change.Property == ThemeProperty)
{
- var (oldValue, newValue) = change.GetOldAndNewValue();
-
- // Changing the theme detaches all styles, meaning that if the theme property was
- // set via a style, it will get cleared! To work around this, if the value was
- // applied at less than local value priority then promote the value to local value
- // priority until styling is re-applied.
- if (change.Priority > BindingPriority.LocalValue)
- {
- Theme = newValue;
- _hasPromotedTheme = true;
- }
- else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue)
- {
- _hasPromotedTheme = false;
- }
-
- InvalidateStyles();
-
- if (oldValue is not null)
- DetachControlThemeFromTemplateChildren(oldValue);
+ OnControlThemeChanged();
+ _themeApplied = false;
}
}
- internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme)
+ private protected virtual void OnControlThemeChanged()
{
+ var values = GetValueStore();
+ values.BeginStyling();
+ try { values.RemoveFrames(FrameType.Theme); }
+ finally { values.EndStyling(); }
+ }
+
+ internal virtual void OnTemplatedParentControlThemeChanged()
+ {
+ var values = GetValueStore();
+ values.BeginStyling();
+ try { values.RemoveFrames(FrameType.TemplatedParentTheme); }
+ finally { values.EndStyling(); }
}
internal ControlTheme? GetEffectiveTheme()
@@ -667,6 +657,23 @@ namespace Avalonia
return null;
}
+ internal virtual void InvalidateStyles(bool recurse)
+ {
+ var values = GetValueStore();
+ values.BeginStyling();
+ try { values.RemoveFrames(FrameType.Style); }
+ finally { values.EndStyling(); }
+
+ _stylesApplied = false;
+
+ if (recurse && GetInheritanceChildren() is { } children)
+ {
+ var childCount = children.Count;
+ for (var i = 0; i < childCount; ++i)
+ (children[i] as StyledElement)?.InvalidateStyles(recurse);
+ }
+ }
+
private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted)
{
if (o is StyledElement element)
@@ -736,26 +743,28 @@ namespace Avalonia
var theme = GetEffectiveTheme();
if (theme is not null)
- ApplyControlTheme(theme);
+ ApplyControlTheme(theme, FrameType.Theme);
if (TemplatedParent is StyledElement styleableParent &&
styleableParent.GetEffectiveTheme() is { } parentTheme)
{
- ApplyControlTheme(parentTheme);
+ ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme);
}
}
- private void ApplyControlTheme(ControlTheme theme)
+ private void ApplyControlTheme(ControlTheme theme, FrameType type)
{
+ Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme);
+
if (theme.BasedOn is ControlTheme basedOn)
- ApplyControlTheme(basedOn);
+ ApplyControlTheme(basedOn, type);
- theme.TryAttach(this, null);
+ theme.TryAttach(this, type);
if (theme.HasChildren)
{
foreach (var child in theme.Children)
- ApplyStyle(child, null);
+ ApplyStyle(child, null, type);
}
}
@@ -768,17 +777,17 @@ namespace Avalonia
if (host.IsStylesInitialized)
{
foreach (var style in host.Styles)
- ApplyStyle(style, host);
+ ApplyStyle(style, host, FrameType.Style);
}
}
- private void ApplyStyle(IStyle style, IStyleHost? host)
+ private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type)
{
if (style is Style s)
- s.TryAttach(this, host);
+ s.TryAttach(this, host, type);
foreach (var child in style.Children)
- ApplyStyle(child, host);
+ ApplyStyle(child, host, type);
}
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
@@ -824,7 +833,7 @@ namespace Avalonia
{
_logicalRoot = null;
_implicitTheme = null;
- DetachStyles();
+ InvalidateStyles(recurse: false);
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);
@@ -886,70 +895,81 @@ namespace Avalonia
}
}
- private void DetachStyles(IReadOnlyList? styles = null)
+ private void DetachStyles(IReadOnlyList