diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 27aab099fa..a47a716d3d 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -1015,6 +1015,18 @@
baseline/netstandard2.0/Avalonia.Base.dll
target/netstandard2.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Diagnostics.AppliedStyle
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Diagnostics.StyleDiagnostics
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0001
T:Avalonia.Input.FocusManager.<GetFocusScopeAncestors>d__18
@@ -1075,6 +1087,12 @@
baseline/netstandard2.0/Avalonia.Dialogs.dll
target/netstandard2.0/Avalonia.Dialogs.dll
+
+ CP0002
+ M:Avalonia.Diagnostics.StyledElementExtensions.GetStyleDiagnostics(Avalonia.StyledElement)
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0006
M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache
diff --git a/src/Avalonia.Base/Diagnostics/AppliedStyle.cs b/src/Avalonia.Base/Diagnostics/AppliedStyle.cs
deleted file mode 100644
index 90390a85da..0000000000
--- a/src/Avalonia.Base/Diagnostics/AppliedStyle.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Avalonia.Styling;
-
-namespace Avalonia.Diagnostics
-{
- public sealed class AppliedStyle
- {
- private readonly IStyleInstance _instance;
-
- internal AppliedStyle(IStyleInstance instance)
- {
- _instance = instance;
- }
-
- public bool HasActivator => _instance.HasActivator;
- public bool IsActive => _instance.IsActive;
- public StyleBase Style => (StyleBase)_instance.Source;
- }
-}
diff --git a/src/Avalonia.Base/Diagnostics/IValueFrameDiagnostic.cs b/src/Avalonia.Base/Diagnostics/IValueFrameDiagnostic.cs
new file mode 100644
index 0000000000..df48efe04e
--- /dev/null
+++ b/src/Avalonia.Base/Diagnostics/IValueFrameDiagnostic.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using Avalonia.Data;
+using Avalonia.Metadata;
+
+namespace Avalonia.Diagnostics;
+
+public record ValueEntryDiagnostic(AvaloniaProperty Property, object? Value);
+
+[Unstable]
+[NotClientImplementable]
+public interface IValueFrameDiagnostic
+{
+ public enum FrameType
+ {
+ Unknown = 0,
+ Local,
+ Theme,
+ Style,
+ Template
+ }
+
+ string? Description { get; }
+ FrameType Type { get; }
+ bool IsActive { get; }
+ BindingPriority Priority { get; }
+ IEnumerable Values { get; }
+}
diff --git a/src/Avalonia.Base/Diagnostics/LocalValueFrameDiagnostic.cs b/src/Avalonia.Base/Diagnostics/LocalValueFrameDiagnostic.cs
new file mode 100644
index 0000000000..96d72c1480
--- /dev/null
+++ b/src/Avalonia.Base/Diagnostics/LocalValueFrameDiagnostic.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using Avalonia.Data;
+
+namespace Avalonia.Diagnostics;
+
+internal class LocalValueFrameDiagnostic : IValueFrameDiagnostic
+{
+ public LocalValueFrameDiagnostic(IEnumerable values)
+ {
+ Values = values;
+ }
+
+ public string? Description => null;
+ public IValueFrameDiagnostic.FrameType Type => IValueFrameDiagnostic.FrameType.Local;
+ public bool IsActive => true;
+ public BindingPriority Priority => BindingPriority.LocalValue;
+ public IEnumerable Values { get; }
+}
diff --git a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs b/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs
deleted file mode 100644
index 90326891c6..0000000000
--- a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Collections.Generic;
-using Avalonia.Styling;
-
-namespace Avalonia.Diagnostics
-{
- ///
- /// Contains information about style related diagnostics of a control.
- ///
- public class StyleDiagnostics
- {
- ///
- /// Currently applied styles.
- ///
- public IReadOnlyList AppliedStyles { get; }
-
- public StyleDiagnostics(IReadOnlyList appliedStyles)
- {
- AppliedStyles = appliedStyles;
- }
- }
-}
diff --git a/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs b/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs
new file mode 100644
index 0000000000..03d901cee2
--- /dev/null
+++ b/src/Avalonia.Base/Diagnostics/StyleValueFrameDiagnostic.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using Avalonia.Data;
+using Avalonia.PropertyStore;
+using Avalonia.Styling;
+
+namespace Avalonia.Diagnostics;
+
+internal class StyleValueFrameDiagnostic : IValueFrameDiagnostic
+{
+ private readonly StyleInstance _styleInstance;
+
+ internal StyleValueFrameDiagnostic(StyleInstance styleInstance)
+ {
+ _styleInstance = styleInstance;
+ }
+
+ public string? Description => _styleInstance.Source switch
+ {
+ Style s => GetFullSelector(s),
+ ControlTheme t => t.TargetType?.Name,
+ _ => null
+ };
+
+ public IValueFrameDiagnostic.FrameType Type => _styleInstance.Source switch
+ {
+ Style => IValueFrameDiagnostic.FrameType.Style,
+ ControlTheme => IValueFrameDiagnostic.FrameType.Theme,
+ _ => IValueFrameDiagnostic.FrameType.Unknown
+ };
+
+ public bool IsActive => _styleInstance.IsActive();
+ public BindingPriority Priority => _styleInstance.FramePriority.ToBindingPriority();
+ public IEnumerable Values
+ {
+ get
+ {
+ foreach (var setter in ((StyleBase)_styleInstance.Source!).Setters)
+ {
+ if (setter is Setter { Property: not null } regularSetter)
+ {
+ yield return new ValueEntryDiagnostic(regularSetter.Property, regularSetter.Value);
+ }
+ }
+ }
+ }
+
+ private string GetFullSelector(Style? style)
+ {
+ var selectors = new Stack();
+
+ while (style is not null)
+ {
+ if (style.Selector is not null)
+ {
+ selectors.Push(style.Selector.ToString());
+ }
+
+ style = style.Parent as Style;
+ }
+
+ return string.Concat(selectors);
+ }
+}
diff --git a/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs b/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs
index d7bcc1aa47..32bbe78218 100644
--- a/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs
+++ b/src/Avalonia.Base/Diagnostics/StyledElementExtensions.cs
@@ -1,17 +1,16 @@
-namespace Avalonia.Diagnostics
+namespace Avalonia.Diagnostics;
+
+///
+/// Defines diagnostic extensions on s.
+///
+public static class StyledElementExtensions
{
///
- /// Defines diagnostic extensions on s.
+ /// Gets a style diagnostics for a .
///
- public static class StyledElementExtensions
+ /// The element.
+ public static ValueStoreDiagnostic GetValueStoreDiagnostic(this StyledElement styledElement)
{
- ///
- /// Gets a style diagnostics for a .
- ///
- /// The element.
- public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement)
- {
- return styledElement.GetStyleDiagnosticsInternal();
- }
+ return styledElement.GetValueStore().GetStoreDiagnostic();
}
}
diff --git a/src/Avalonia.Base/Diagnostics/ValueFrameDiagnostic.cs b/src/Avalonia.Base/Diagnostics/ValueFrameDiagnostic.cs
new file mode 100644
index 0000000000..9b28cf0cdb
--- /dev/null
+++ b/src/Avalonia.Base/Diagnostics/ValueFrameDiagnostic.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using Avalonia.Data;
+using Avalonia.PropertyStore;
+using Avalonia.Styling;
+
+namespace Avalonia.Diagnostics;
+
+internal sealed class ValueFrameDiagnostic : IValueFrameDiagnostic
+{
+ private readonly ValueFrame _valueFrame;
+
+ internal ValueFrameDiagnostic(ValueFrame valueFrame)
+ {
+ _valueFrame = valueFrame;
+ }
+
+ public string? Description => (_valueFrame.Owner?.Owner as StyledElement)?.StyleKey.Name;
+
+ public IValueFrameDiagnostic.FrameType Type => IValueFrameDiagnostic.FrameType.Template;
+
+ public bool IsActive => _valueFrame.IsActive();
+ public BindingPriority Priority => _valueFrame.FramePriority.ToBindingPriority();
+ public IEnumerable Values
+ {
+ get
+ {
+ for (var i = 0; i < _valueFrame.EntryCount; i++)
+ {
+ var entry = _valueFrame.GetEntry(i);
+ if (entry.HasValue())
+ {
+ yield return new ValueEntryDiagnostic(entry.Property, entry.GetValue());
+ }
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Diagnostics/ValueStoreDiagnostic.cs b/src/Avalonia.Base/Diagnostics/ValueStoreDiagnostic.cs
new file mode 100644
index 0000000000..e4880cdc76
--- /dev/null
+++ b/src/Avalonia.Base/Diagnostics/ValueStoreDiagnostic.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using Avalonia.Styling;
+
+namespace Avalonia.Diagnostics;
+
+public class ValueStoreDiagnostic
+{
+ ///
+ /// Currently applied frames.
+ ///
+ public IReadOnlyList AppliedFrames { get; }
+
+ internal ValueStoreDiagnostic(IReadOnlyList appliedFrames)
+ {
+ AppliedFrames = appliedFrames;
+ }
+}
diff --git a/src/Avalonia.Base/PropertyStore/FramePriority.cs b/src/Avalonia.Base/PropertyStore/FramePriority.cs
index 950a8375f2..a77bbe211b 100644
--- a/src/Avalonia.Base/PropertyStore/FramePriority.cs
+++ b/src/Avalonia.Base/PropertyStore/FramePriority.cs
@@ -28,6 +28,12 @@ namespace Avalonia.PropertyStore
return (FramePriority)(p * 3 + (int)type);
}
+ public static BindingPriority ToBindingPriority(this FramePriority priority)
+ {
+ var p = (int)priority / 3;
+ return p == 0 ? BindingPriority.Animation : (BindingPriority)p;
+ }
+
public static bool IsType(this FramePriority priority, FrameType type)
{
return (FrameType)((int)priority % 3) == type;
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index 35406993c7..e443a11781 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -836,6 +836,40 @@ namespace Avalonia.PropertyStore
}
}
+ public ValueStoreDiagnostic GetStoreDiagnostic()
+ {
+ var frames = new List();
+
+ var effectiveLocalValues = new List(_effectiveValues.Count);
+ for (var i = 0; i < _effectiveValues.Count; i++)
+ {
+ if (_effectiveValues.GetValue(i) is { } effectiveValue
+ && effectiveValue.Priority == BindingPriority.LocalValue)
+ {
+ effectiveLocalValues.Add(new ValueEntryDiagnostic(effectiveValue.Property, effectiveValue.Value));
+ }
+ }
+
+ if (effectiveLocalValues.Count > 0)
+ {
+ frames.Add(new LocalValueFrameDiagnostic(effectiveLocalValues));
+ }
+
+ foreach (var frame in Frames)
+ {
+ if (frame is StyleInstance { Source: StyleBase } styleInstance)
+ {
+ frames.Add(new StyleValueFrameDiagnostic(styleInstance));
+ }
+ else
+ {
+ frames.Add(new ValueFrameDiagnostic(frame));
+ }
+ }
+
+ return new ValueStoreDiagnostic(frames);
+ }
+
private int InsertFrame(ValueFrame frame)
{
Debug.Assert(!_frames.Contains(frame));
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index 07ea2f69bb..1f0c00ef63 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -420,19 +420,6 @@ namespace Avalonia
}
}
- internal StyleDiagnostics GetStyleDiagnosticsInternal()
- {
- var styles = new List();
-
- foreach (var frame in GetValueStore().Frames)
- {
- if (frame is IStyleInstance style)
- styles.Add(new(style));
- }
-
- return new StyleDiagnostics(styles);
- }
-
///
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs
index 268e42cff3..40412a6373 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/BrushEditor.axaml.cs
@@ -23,7 +23,14 @@ namespace Avalonia.Diagnostics.Controls
public BrushEditor()
{
FlyoutBase.SetAttachedFlyout(this, new Flyout { Content = _colorView });
- _colorView.ColorChanged += (_, e) => Brush = new ImmutableSolidColorBrush(e.NewColor);
+ _colorView.ColorChanged += (_, e) =>
+ {
+ // Avoid unnecessary value setters by checking if color was actually changed.
+ if (Brush is null || Brush is not ISolidColorBrush oldSolidBrush || oldSolidBrush.Color != e.NewColor)
+ {
+ Brush = new ImmutableSolidColorBrush(e.NewColor);
+ }
+ };
clearHandler = (s, e) => Brush = default;
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index d57d3088f6..957f71292d 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -11,6 +11,7 @@ using Avalonia.Controls.Metadata;
using Avalonia.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Styling;
+using Avalonia.Threading;
namespace Avalonia.Diagnostics.ViewModels
{
@@ -21,9 +22,9 @@ namespace Avalonia.Diagnostics.ViewModels
private IDictionary