@ -1,8 +1,15 @@
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Collections.Specialized ;
using System.ComponentModel ;
using System.Linq ;
using System.Reflection ;
using Avalonia.Collections ;
using Avalonia.Controls ;
using Avalonia.Controls.Metadata ;
using Avalonia.Markup.Xaml.MarkupExtensions ;
using Avalonia.Styling ;
using Avalonia.VisualTree ;
namespace Avalonia.Diagnostics.ViewModels
@ -12,6 +19,10 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly IVisual _ control ;
private readonly IDictionary < object , List < PropertyViewModel > > _ propertyIndex ;
private AvaloniaPropertyViewModel _ selectedProperty ;
private string _ styleFilter ;
private bool _ snapshotStyles ;
private bool _ showInactiveStyles ;
private string _ styleStatus ;
public ControlDetailsViewModel ( TreePageViewModel treePage , IVisual control )
{
@ -43,20 +54,160 @@ namespace Avalonia.Diagnostics.ViewModels
{
ao . PropertyChanged + = ControlPropertyChanged ;
}
AppliedStyles = new ObservableCollection < StyleViewModel > ( ) ;
PseudoClasses = new ObservableCollection < PseudoClassViewModel > ( ) ;
if ( control is StyledElement styledElement )
{
styledElement . Classes . CollectionChanged + = OnClassesChanged ;
var pseudoClassAttributes = styledElement . GetType ( ) . GetCustomAttributes < PseudoClassesAttribute > ( true ) ;
foreach ( var classAttribute in pseudoClassAttributes )
{
foreach ( var className in classAttribute . PseudoClasses )
{
PseudoClasses . Add ( new PseudoClassViewModel ( className , styledElement ) ) ;
}
}
var styleDiagnostics = styledElement . GetStyleDiagnostics ( ) ;
foreach ( var appliedStyle in styleDiagnostics . AppliedStyles )
{
var styleSource = appliedStyle . Source ;
var setters = new List < SetterViewModel > ( ) ;
if ( styleSource is Style style )
{
foreach ( var setter in style . Setters )
{
if ( setter is Setter regularSetter )
{
var setterValue = regularSetter . Value ;
var resourceInfo = GetResourceInfo ( setterValue ) ;
SetterViewModel setterVm ;
if ( resourceInfo . HasValue )
{
var resourceKey = resourceInfo . Value . resourceKey ;
var resourceValue = styledElement . FindResource ( resourceKey ) ;
setterVm = new ResourceSetterViewModel ( regularSetter . Property , resourceKey , resourceValue , resourceInfo . Value . isDynamic ) ;
}
else
{
setterVm = new SetterViewModel ( regularSetter . Property , setterValue ) ;
}
setters . Add ( setterVm ) ;
}
}
AppliedStyles . Add ( new StyleViewModel ( appliedStyle , style . Selector ? . ToString ( ) ? ? "No selector" , setters ) ) ;
}
}
UpdateStyles ( ) ;
}
}
private ( object resourceKey , bool isDynamic ) ? GetResourceInfo ( object value )
{
if ( value is StaticResourceExtension staticResource )
{
return ( staticResource . ResourceKey , false ) ;
}
else if ( value is DynamicResourceExtension dynamicResource )
{
return ( dynamicResource . ResourceKey , true ) ;
}
return null ;
}
public TreePageViewModel TreePage { get ; }
public DataGridCollectionView PropertiesView { get ; }
public ObservableCollection < StyleViewModel > AppliedStyles { get ; }
public ObservableCollection < PseudoClassViewModel > PseudoClasses { get ; }
public AvaloniaPropertyViewModel SelectedProperty
{
get = > _ selectedProperty ;
set = > RaiseAndSetIfChanged ( ref _ selectedProperty , value ) ;
}
public string StyleFilter
{
get = > _ styleFilter ;
set = > RaiseAndSetIfChanged ( ref _ styleFilter , value ) ;
}
public bool SnapshotStyles
{
get = > _ snapshotStyles ;
set = > RaiseAndSetIfChanged ( ref _ snapshotStyles , value ) ;
}
public bool ShowInactiveStyles
{
get = > _ showInactiveStyles ;
set = > RaiseAndSetIfChanged ( ref _ showInactiveStyles , value ) ;
}
public string StyleStatus
{
get = > _ styleStatus ;
set = > RaiseAndSetIfChanged ( ref _ styleStatus , value ) ;
}
public ControlLayoutViewModel Layout { get ; }
protected override void OnPropertyChanged ( PropertyChangedEventArgs e )
{
base . OnPropertyChanged ( e ) ;
if ( e . PropertyName = = nameof ( StyleFilter ) )
{
UpdateStyleFilters ( ) ;
}
else if ( e . PropertyName = = nameof ( SnapshotStyles ) )
{
if ( ! SnapshotStyles )
{
UpdateStyles ( ) ;
}
}
}
private void UpdateStyleFilters ( )
{
var filter = StyleFilter ;
bool hasFilter = ! string . IsNullOrEmpty ( filter ) ;
foreach ( var style in AppliedStyles )
{
var hasVisibleSetter = false ;
foreach ( var setter in style . Setters )
{
setter . IsVisible =
! hasFilter | | setter . Name . IndexOf ( filter , StringComparison . OrdinalIgnoreCase ) > = 0 ;
hasVisibleSetter | = setter . IsVisible ;
}
style . IsVisible = hasVisibleSetter ;
}
}
public void Dispose ( )
{
if ( _ control is INotifyPropertyChanged inpc )
@ -68,6 +219,11 @@ namespace Avalonia.Diagnostics.ViewModels
{
ao . PropertyChanged - = ControlPropertyChanged ;
}
if ( _ control is StyledElement se )
{
se . Classes . CollectionChanged - = OnClassesChanged ;
}
}
private IEnumerable < PropertyViewModel > GetAvaloniaProperties ( object o )
@ -129,6 +285,74 @@ namespace Avalonia.Diagnostics.ViewModels
property . Update ( ) ;
}
}
if ( ! SnapshotStyles )
{
UpdateStyles ( ) ;
}
}
private void OnClassesChanged ( object sender , NotifyCollectionChangedEventArgs e )
{
if ( ! SnapshotStyles )
{
UpdateStyles ( ) ;
}
}
private void UpdateStyles ( )
{
int activeCount = 0 ;
foreach ( var style in AppliedStyles )
{
style . Update ( ) ;
if ( style . IsActive )
{
activeCount + + ;
}
}
var propertyBuckets = new Dictionary < AvaloniaProperty , List < SetterViewModel > > ( ) ;
foreach ( var style in AppliedStyles )
{
if ( ! style . IsActive )
{
continue ;
}
foreach ( var setter in style . Setters )
{
if ( propertyBuckets . TryGetValue ( setter . Property , out var setters ) )
{
foreach ( var otherSetter in setters )
{
otherSetter . IsActive = false ;
}
setter . IsActive = true ;
setters . Add ( setter ) ;
}
else
{
setter . IsActive = true ;
setters = new List < SetterViewModel > { setter } ;
propertyBuckets . Add ( setter . Property , setters ) ;
}
}
}
foreach ( var pseudoClass in PseudoClasses )
{
pseudoClass . Update ( ) ;
}
StyleStatus = $"Styles ({activeCount}/{AppliedStyles.Count} active)" ;
}
private bool FilterProperty ( object arg )