Browse Source
* Replace "Active Styles" dev tools analysis with "Active Value Frames" * Remove old diagnostic methods from public API (breaking change) * Show full style selectors * Avoid unnecessary value setters by checking if color was actually changed * Run UpdateStyles from the dispatcher to fix flickering issue * Fix buildpull/15676/head
committed by
GitHub
18 changed files with 408 additions and 226 deletions
@ -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; |
|||
} |
|||
} |
|||
@ -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<ValueEntryDiagnostic> Values { get; } |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Diagnostics; |
|||
|
|||
internal class LocalValueFrameDiagnostic : IValueFrameDiagnostic |
|||
{ |
|||
public LocalValueFrameDiagnostic(IEnumerable<ValueEntryDiagnostic> 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<ValueEntryDiagnostic> Values { get; } |
|||
} |
|||
@ -1,21 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Diagnostics |
|||
{ |
|||
/// <summary>
|
|||
/// Contains information about style related diagnostics of a control.
|
|||
/// </summary>
|
|||
public class StyleDiagnostics |
|||
{ |
|||
/// <summary>
|
|||
/// Currently applied styles.
|
|||
/// </summary>
|
|||
public IReadOnlyList<AppliedStyle> AppliedStyles { get; } |
|||
|
|||
public StyleDiagnostics(IReadOnlyList<AppliedStyle> appliedStyles) |
|||
{ |
|||
AppliedStyles = appliedStyles; |
|||
} |
|||
} |
|||
} |
|||
@ -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<ValueEntryDiagnostic> 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<string>(); |
|||
|
|||
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); |
|||
} |
|||
} |
|||
@ -1,17 +1,16 @@ |
|||
namespace Avalonia.Diagnostics |
|||
namespace Avalonia.Diagnostics; |
|||
|
|||
/// <summary>
|
|||
/// Defines diagnostic extensions on <see cref="StyledElement"/>s.
|
|||
/// </summary>
|
|||
public static class StyledElementExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Defines diagnostic extensions on <see cref="StyledElement"/>s.
|
|||
/// Gets a style diagnostics for a <see cref="StyledElement"/>.
|
|||
/// </summary>
|
|||
public static class StyledElementExtensions |
|||
/// <param name="styledElement">The element.</param>
|
|||
public static ValueStoreDiagnostic GetValueStoreDiagnostic(this StyledElement styledElement) |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a style diagnostics for a <see cref="StyledElement"/>.
|
|||
/// </summary>
|
|||
/// <param name="styledElement">The element.</param>
|
|||
public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement) |
|||
{ |
|||
return styledElement.GetStyleDiagnosticsInternal(); |
|||
} |
|||
return styledElement.GetValueStore().GetStoreDiagnostic(); |
|||
} |
|||
} |
|||
|
|||
@ -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<ValueEntryDiagnostic> 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()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Diagnostics; |
|||
|
|||
public class ValueStoreDiagnostic |
|||
{ |
|||
/// <summary>
|
|||
/// Currently applied frames.
|
|||
/// </summary>
|
|||
public IReadOnlyList<IValueFrameDiagnostic> AppliedFrames { get; } |
|||
|
|||
internal ValueStoreDiagnostic(IReadOnlyList<IValueFrameDiagnostic> appliedFrames) |
|||
{ |
|||
AppliedFrames = appliedFrames; |
|||
} |
|||
} |
|||
@ -1,43 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class StyleViewModel : ViewModelBase |
|||
{ |
|||
private readonly AppliedStyle _styleInstance; |
|||
private bool _isActive; |
|||
private bool _isVisible; |
|||
|
|||
public StyleViewModel(AppliedStyle styleInstance, string name, List<SetterViewModel> setters) |
|||
{ |
|||
_styleInstance = styleInstance; |
|||
IsVisible = true; |
|||
Name = name; |
|||
Setters = setters; |
|||
|
|||
Update(); |
|||
} |
|||
|
|||
public bool IsActive |
|||
{ |
|||
get => _isActive; |
|||
set => RaiseAndSetIfChanged(ref _isActive, value); |
|||
} |
|||
|
|||
public bool IsVisible |
|||
{ |
|||
get => _isVisible; |
|||
set => RaiseAndSetIfChanged(ref _isVisible, value); |
|||
} |
|||
|
|||
public string Name { get; } |
|||
|
|||
public List<SetterViewModel> Setters { get; } |
|||
|
|||
public void Update() |
|||
{ |
|||
IsActive = _styleInstance.IsActive; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.Input.Platform; |
|||
using Avalonia.Markup.Xaml.MarkupExtensions; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class ValueFrameViewModel : ViewModelBase |
|||
{ |
|||
private readonly IValueFrameDiagnostic _valueFrame; |
|||
private bool _isActive; |
|||
private bool _isVisible; |
|||
|
|||
public ValueFrameViewModel(StyledElement styledElement, IValueFrameDiagnostic valueFrame, IClipboard? clipboard) |
|||
{ |
|||
_valueFrame = valueFrame; |
|||
IsVisible = true; |
|||
|
|||
Description = (_valueFrame.Type, _valueFrame.Description) switch |
|||
{ |
|||
(IValueFrameDiagnostic.FrameType.Local, _) => "Local Values " + _valueFrame.Description, |
|||
(IValueFrameDiagnostic.FrameType.Template, _) => "Template " + _valueFrame.Description, |
|||
(IValueFrameDiagnostic.FrameType.Theme, _) => "Theme " + _valueFrame.Description, |
|||
(_, {Length:>0}) => _valueFrame.Description, |
|||
_ => _valueFrame.Priority.ToString() |
|||
}; |
|||
|
|||
Setters = new List<SetterViewModel>(); |
|||
|
|||
foreach (var (setterProperty, setterValue) in valueFrame.Values) |
|||
{ |
|||
var resourceInfo = GetResourceInfo(setterValue); |
|||
|
|||
SetterViewModel setterVm; |
|||
|
|||
if (resourceInfo.HasValue) |
|||
{ |
|||
var resourceKey = resourceInfo.Value.resourceKey; |
|||
var resourceValue = styledElement.FindResource(resourceKey); |
|||
|
|||
setterVm = new ResourceSetterViewModel(setterProperty, resourceKey, resourceValue, |
|||
resourceInfo.Value.isDynamic, clipboard); |
|||
} |
|||
else |
|||
{ |
|||
var isBinding = IsBinding(setterValue); |
|||
|
|||
if (isBinding) |
|||
{ |
|||
setterVm = new BindingSetterViewModel(setterProperty, setterValue, clipboard); |
|||
} |
|||
else |
|||
{ |
|||
setterVm = new SetterViewModel(setterProperty, setterValue, clipboard); |
|||
} |
|||
} |
|||
Setters.Add(setterVm); |
|||
} |
|||
|
|||
Update(); |
|||
} |
|||
|
|||
public bool IsActive |
|||
{ |
|||
get => _isActive; |
|||
set => RaiseAndSetIfChanged(ref _isActive, value); |
|||
} |
|||
|
|||
public bool IsVisible |
|||
{ |
|||
get => _isVisible; |
|||
set => RaiseAndSetIfChanged(ref _isVisible, value); |
|||
} |
|||
|
|||
public string? Description { get; } |
|||
|
|||
public List<SetterViewModel> Setters { get; } |
|||
|
|||
public void Update() |
|||
{ |
|||
IsActive = _valueFrame.IsActive; |
|||
} |
|||
|
|||
private static (object resourceKey, bool isDynamic)? GetResourceInfo(object? value) |
|||
{ |
|||
if (value is StaticResourceExtension staticResource |
|||
&& staticResource.ResourceKey != null) |
|||
{ |
|||
return (staticResource.ResourceKey, false); |
|||
} |
|||
else if (value is DynamicResourceExtension dynamicResource |
|||
&& dynamicResource.ResourceKey != null) |
|||
{ |
|||
return (dynamicResource.ResourceKey, true); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private static bool IsBinding(object? value) |
|||
{ |
|||
switch (value) |
|||
{ |
|||
case Binding: |
|||
case CompiledBindingExtension: |
|||
case TemplateBinding: |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue