Browse Source

General UX improvements.

Dariusz Komosinski 4 years ago
parent
commit
e428d5601a

+ 21 - 0
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();
+        }
+    }
+}

+ 100 - 127
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<SetterViewModel> 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<SetterViewModel> 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<object, List<PropertyViewModel>> _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<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();

+ 51 - 0
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;
+            }
+        }
+    }
+}

+ 17 - 0
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;
+        }
+    }
+}

+ 35 - 0
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;
+        }
+    }
+}

+ 43 - 0
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<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;
+        }
+    }
+}

+ 7 - 0
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;
+                    }
                 }
             }
         }

+ 60 - 10
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">
 
   <UserControl.Resources>
@@ -11,6 +12,7 @@
     <SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" />
     <SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" />
     <SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" />
+    <conv:BoolToOpacityConverter x:Key="BoolToOpacity" Opacity="0.6"/>
   </UserControl.Resources>
 
   <UserControl.Styles>
@@ -149,29 +151,77 @@
         </Canvas>
       </Grid>
 
-      <TextBlock Grid.Row="2" Text="Styles" Margin="4" />
+      <Grid Grid.Row="2" Margin="4" RowDefinitions="Auto,Auto">
+        <TextBlock Grid.Row="0" Text="Styles"  />
+        <TextBox Grid.Row="1" Margin="2" Watermark="Filter" Text="{Binding StyleFilter}" />
+      </Grid>
 
-      <ScrollViewer Grid.Row="3" HorizontalScrollBarVisibility="Disabled">
+        <ScrollViewer Grid.Row="3" HorizontalScrollBarVisibility="Disabled">
         <ItemsControl Items="{Binding AppliedStyles}" >
           <ItemsControl.ItemTemplate>
             <DataTemplate>
               <Border BorderThickness="0,0,0,1" BorderBrush="#6C6C6C">
-                <Expander IsExpanded="True" Margin="0" Padding="8,0" ContentTransition="{x:Null}" IsVisible="{Binding IsActive}" >
+                <Border.IsVisible>
+                  <MultiBinding Converter="{x:Static BoolConverters.And}">
+                    <Binding Path="IsActive" />
+                    <Binding Path="IsVisible" />
+                  </MultiBinding>
+                </Border.IsVisible>
+                <Expander IsExpanded="True" Margin="0" Padding="8,0" ContentTransition="{x:Null}" >
                   <Expander.Header>
                     <TextBlock Grid.Row="0" Text="{Binding Name}" />
                   </Expander.Header>
+
                   <ItemsControl Margin="20,0,0,0" Grid.Row="1" Items="{Binding Setters}">
-                    <ItemsControl.ItemTemplate>
-                      <DataTemplate>
+                    <ItemsControl.DataTemplates>
+
+                      <DataTemplate DataType="IBrush">
+                        <StackPanel Orientation="Horizontal" Spacing="2">
+                          <Border BorderThickness="1" BorderBrush="Black" Background="{Binding}" Width="8" Height="8"/>
+                          <TextBlock Text="{Binding}" />
+                        </StackPanel>
+                      </DataTemplate>
+
+                      <DataTemplate DataType="Color">
                         <StackPanel Orientation="Horizontal" Spacing="2">
-                          <TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
-                          <TextBlock Text=":" />
-                          <Border IsVisible="{Binding IsSpecialKind}" Height="8" Width="8" VerticalAlignment="Center" Background="{Binding KindColor}"/>
-                          <TextBlock Text="{Binding Value}" />
+                          <Border BorderThickness="1" BorderBrush="Black" Width="8" Height="8">
+                            <Border.Background>
+                              <SolidColorBrush Color="{Binding}" />
+                            </Border.Background>
+                          </Border>
+                          <TextBlock Text="{Binding}" />
                         </StackPanel>
                       </DataTemplate>
-                    </ItemsControl.ItemTemplate>
+
+                      <DataTemplate DataType="vm:ResourceSetterViewModel">
+                        <Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
+                          <StackPanel Orientation="Horizontal" Spacing="2" HorizontalAlignment="Left">
+                            <TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
+                            <TextBlock Text=":" />
+                            <ContentControl Content="{Binding Value}"/>
+                            <TextBlock>(</TextBlock>
+                            <Ellipse Height="8" Width="8" VerticalAlignment="Center" Fill="{Binding Tint}"/>
+                            <TextBlock FontStyle="Italic" Text="{Binding Key}" />
+                            <TextBlock>)</TextBlock>
+                          </StackPanel>
+                          <Rectangle Height="1" Fill="#6C6C6C" IsVisible="{Binding !IsActive}" />
+                        </Panel>
+                      </DataTemplate>
+
+                      <DataTemplate DataType="vm:SetterViewModel">
+                        <Panel Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacity}}" IsVisible="{Binding IsVisible}" HorizontalAlignment="Left">
+                          <StackPanel Orientation="Horizontal" Spacing="2">
+                            <TextBlock Text="{Binding Name}" FontWeight="SemiBold" />
+                            <TextBlock Text=":" />
+                            <ContentControl Content="{Binding Value}"/>
+                          </StackPanel>
+                          <Rectangle Height="1" Fill="#6C6C6C" VerticalAlignment="Center" IsVisible="{Binding !IsActive}" />
+                        </Panel>
+                      </DataTemplate>
+
+                    </ItemsControl.DataTemplates>
                   </ItemsControl>
+
                 </Expander>
               </Border>
             </DataTemplate>