Browse Source

General UX improvements.

pull/5537/head
Dariusz Komosinski 6 years ago
parent
commit
e428d5601a
  1. 21
      src/Avalonia.Diagnostics/Diagnostics/Converters/BoolToOpacityConverter.cs
  2. 227
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  3. 51
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PseudoClassesViewModel.cs
  4. 17
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
  5. 35
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs
  6. 43
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs
  7. 7
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  8. 70
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

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

227
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
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
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
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
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
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;
}
}
}
}

70
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>

Loading…
Cancel
Save