diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs new file mode 100644 index 0000000000..63ac3ab62f --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace Avalonia.Diagnostics.Converters +{ + internal class BoolToOpacityConverter : IValueConverter + { + public double Opacity { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool)value ? 1d : Opacity; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 6c622ad6c3..e6758d680c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -9,128 +9,17 @@ using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Markup.Xaml.MarkupExtensions; -using Avalonia.Media; using Avalonia.Styling; using Avalonia.VisualTree; namespace Avalonia.Diagnostics.ViewModels { - internal class StyleViewModel : ViewModelBase - { - private readonly IStyleInstance _styleInstance; - private bool _isActive; - - public StyleViewModel(IStyleInstance styleInstance, string name, List setters) - { - _styleInstance = styleInstance; - IsActive = styleInstance.IsActive; - Name = name; - Setters = setters; - } - - public bool IsActive - { - get => _isActive; - set => RaiseAndSetIfChanged(ref _isActive, value); - } - - public string Name { get; } - - public List Setters { get; } - - public void Update() - { - IsActive = _styleInstance.IsActive; - } - } - - internal enum ValueKind - { - Regular, - Resource - } - - internal class SetterViewModel : ViewModelBase - { - public string Name { get; } - - public object Value { get; } - - public ValueKind Kind { get; } - - public bool IsSpecialKind => Kind != ValueKind.Regular; - - public IBrush KindColor { get; } - - public SetterViewModel(string name, object value, ValueKind kind) - { - Name = name; - Value = value; - Kind = kind; - - if (Kind == ValueKind.Resource) - { - KindColor = Brushes.Brown; - } - else - { - KindColor = Brushes.Transparent; - } - } - } - - internal class PseudoClassesViewModel : ViewModelBase - { - private readonly StyledElement _source; - private readonly IPseudoClasses _pseudoClasses; - private bool _isActive; - private bool _isUpdating; - - public PseudoClassesViewModel(string name, StyledElement source) - { - Name = name; - _source = source; - _pseudoClasses = _source.Classes; - - Update(); - } - - public string Name { get; } - - public bool IsActive - { - get => _isActive; - set - { - RaiseAndSetIfChanged(ref _isActive, value); - - if (!_isUpdating) - { - _pseudoClasses.Set(Name, value); - } - } - } - - public void Update() - { - try - { - _isUpdating = true; - - IsActive = _source.Classes.Contains(Name); - } - finally - { - _isUpdating = false; - } - } - } - internal class ControlDetailsViewModel : ViewModelBase, IDisposable { private readonly IVisual _control; private readonly IDictionary> _propertyIndex; private AvaloniaPropertyViewModel _selectedProperty; + private string _styleFilter; public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) { @@ -196,32 +85,46 @@ namespace Avalonia.Diagnostics.ViewModels { var setterValue = regularSetter.Value; - ValueKind kind = ValueKind.Regular; + var resourceInfo = GetResourceInfo(setterValue); + + SetterViewModel setterVm; - if (setterValue is DynamicResourceExtension dynResource) + if (resourceInfo.HasValue) { - var resolved = styledElement.FindResource(dynResource.ResourceKey); - - if (resolved != null) - { - setterValue = $"{resolved} ({dynResource.ResourceKey})"; - } - else - { - setterValue = dynResource.ResourceKey; - } - - kind = ValueKind.Resource; + 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(new SetterViewModel(regularSetter.Property?.Name ?? "?", setterValue, kind)); + 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; } @@ -237,9 +140,46 @@ namespace Avalonia.Diagnostics.ViewModels get => _selectedProperty; set => RaiseAndSetIfChanged(ref _selectedProperty, value); } + + public string StyleFilter + { + get => _styleFilter; + set => RaiseAndSetIfChanged(ref _styleFilter, value); + } public ControlLayoutViewModel Layout { get; } + protected override void OnPropertyChanged(PropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + + if (e.PropertyName == nameof(StyleFilter)) + { + UpdateStyleFilters(); + } + } + + 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) @@ -333,6 +273,39 @@ namespace Avalonia.Diagnostics.ViewModels style.Update(); } + var propertyBuckets = new Dictionary>(); + + 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 { setter }; + + propertyBuckets.Add(setter.Property, setters); + } + } + } + foreach (var pseudoClass in PseudoClasses) { pseudoClass.Update(); diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PseudoClassesViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PseudoClassesViewModel.cs new file mode 100644 index 0000000000..dd2452333e --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PseudoClassesViewModel.cs @@ -0,0 +1,51 @@ +using Avalonia.Controls; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal class PseudoClassesViewModel : ViewModelBase + { + private readonly IPseudoClasses _pseudoClasses; + private readonly StyledElement _source; + private bool _isActive; + private bool _isUpdating; + + public PseudoClassesViewModel(string name, StyledElement source) + { + Name = name; + _source = source; + _pseudoClasses = _source.Classes; + + Update(); + } + + public string Name { get; } + + public bool IsActive + { + get => _isActive; + set + { + RaiseAndSetIfChanged(ref _isActive, value); + + if (!_isUpdating) + { + _pseudoClasses.Set(Name, value); + } + } + } + + public void Update() + { + try + { + _isUpdating = true; + + IsActive = _source.Classes.Contains(Name); + } + finally + { + _isUpdating = false; + } + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs new file mode 100644 index 0000000000..fedfa1c0b4 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs @@ -0,0 +1,17 @@ +using Avalonia.Media; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal class ResourceSetterViewModel : SetterViewModel + { + public object Key { get; } + + public IBrush Tint { get; } + + public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object resourceValue, bool isDynamic) : base(property, resourceValue) + { + Key = resourceKey; + Tint = isDynamic ? Brushes.Orange : Brushes.Brown; + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs new file mode 100644 index 0000000000..691817ccd1 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs @@ -0,0 +1,35 @@ +namespace Avalonia.Diagnostics.ViewModels +{ + internal class SetterViewModel : ViewModelBase + { + private bool _isActive; + private bool _isVisible; + + public AvaloniaProperty Property { get; } + + public string Name { get; } + + public object Value { get; } + + public bool IsActive + { + get => _isActive; + set => RaiseAndSetIfChanged(ref _isActive, value); + } + + public bool IsVisible + { + get => _isVisible; + set => RaiseAndSetIfChanged(ref _isVisible, value); + } + + public SetterViewModel(AvaloniaProperty property, object value) + { + Property = property; + Name = property.Name; + Value = value; + IsActive = true; + IsVisible = true; + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs new file mode 100644 index 0000000000..06e2409800 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Avalonia.Styling; + +namespace Avalonia.Diagnostics.ViewModels +{ + internal class StyleViewModel : ViewModelBase + { + private readonly IStyleInstance _styleInstance; + private bool _isActive; + private bool _isVisible; + + public StyleViewModel(IStyleInstance styleInstance, string name, List 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 Setters { get; } + + public void Update() + { + IsActive = _styleInstance.IsActive; + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index bd65a3b06b..6b779cd6ac 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -31,11 +31,18 @@ namespace Avalonia.Diagnostics.ViewModels get => _selectedNode; private set { + var oldDetails = Details; + if (RaiseAndSetIfChanged(ref _selectedNode, value)) { Details = value != null ? new ControlDetailsViewModel(this, value.Visual) : null; + + if (Details != null && oldDetails != null) + { + Details.StyleFilter = oldDetails.StyleFilter; + } } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 9b341c1388..7b5fbbfb22 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters" xmlns:local="clr-namespace:Avalonia.Diagnostics.Views" + xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" x:Class="Avalonia.Diagnostics.Views.ControlDetailsView"> @@ -11,6 +12,7 @@ + @@ -149,29 +151,77 @@ - + + + + - + - + + + + + + + + - - + + + + + + + + + + - - - - + + + + + + - + + + + + + + + ( + + + ) + + + + + + + + + + + + + + + + + +