Browse Source

WIP setup for inspecting styles.

pull/5537/head
Dariusz Komosinski 5 years ago
parent
commit
188faa07c5
  1. 208
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  2. 49
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  3. 30
      src/Avalonia.Styling/StyledElement.cs
  4. 2
      src/Avalonia.Styling/Styling/IStyleInstance.cs
  5. 9
      src/Avalonia.Styling/Styling/StyleInstance.cs

208
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -1,12 +1,131 @@
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.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;
@ -43,12 +162,76 @@ namespace Avalonia.Diagnostics.ViewModels
{
ao.PropertyChanged += ControlPropertyChanged;
}
AppliedStyles = new ObservableCollection<StyleViewModel>();
PseudoClasses = new ObservableCollection<PseudoClassesViewModel>();
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 PseudoClassesViewModel(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;
ValueKind kind = ValueKind.Regular;
if (setterValue is DynamicResourceExtension dynResource)
{
var resolved = styledElement.FindResource(dynResource.ResourceKey);
if (resolved != null)
{
setterValue = $"{resolved} ({dynResource.ResourceKey})";
}
else
{
setterValue = dynResource.ResourceKey;
}
kind = ValueKind.Resource;
}
setters.Add(new SetterViewModel(regularSetter.Property?.Name ?? "?", setterValue, kind));
}
}
AppliedStyles.Add(new StyleViewModel(appliedStyle, style.Selector?.ToString() ?? "No selector", setters));
}
}
}
}
public TreePageViewModel TreePage { get; }
public DataGridCollectionView PropertiesView { get; }
public ObservableCollection<StyleViewModel> AppliedStyles { get; }
public ObservableCollection<PseudoClassesViewModel> PseudoClasses { get; }
public AvaloniaPropertyViewModel SelectedProperty
{
get => _selectedProperty;
@ -68,6 +251,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 +317,26 @@ namespace Avalonia.Diagnostics.ViewModels
property.Update();
}
}
UpdateStyles();
}
private void OnClassesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateStyles();
}
private void UpdateStyles()
{
foreach (var style in AppliedStyles)
{
style.Update();
}
foreach (var pseudoClass in PseudoClasses)
{
pseudoClass.Update();
}
}
private bool FilterProperty(object arg)

49
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -105,7 +105,7 @@
<GridSplitter Grid.Column="1" />
<Grid Grid.Column="2" RowDefinitions="Auto,*" >
<Grid Grid.Column="2" RowDefinitions="Auto,*, Auto,*,Auto" >
<TextBlock Grid.Row="0" Text="Layout Visualizer" Margin="4" />
<Grid Grid.Row="1" x:Name="LayoutRoot" Margin="8,0,8,8" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto">
@ -148,7 +148,52 @@
<Rectangle x:Name="VerticalSizeEnd" />
</Canvas>
</Grid>
<TextBlock Grid.Row="2" Text="Styles" Margin="4" />
<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}" >
<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>
<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}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Expander Header="Pseudo Classes" Grid.Row="4">
<ItemsControl Items="{Binding PseudoClasses}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Margin="2" Content="{Binding Name}" IsChecked="{Binding IsActive, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</Grid>
</Grid>

30
src/Avalonia.Styling/StyledElement.cs

@ -16,6 +16,24 @@ using Avalonia.Styling;
namespace Avalonia
{
public class StyleDiagnostics
{
public IReadOnlyList<IStyleInstance> AppliedStyles { get; }
public StyleDiagnostics(IReadOnlyList<IStyleInstance> appliedStyles)
{
AppliedStyles = appliedStyles;
}
}
public static class StyledElementExtensions
{
public static StyleDiagnostics GetStyleDiagnostics(this StyledElement styledElement)
{
return styledElement.GetStyleDiagnosticsInternal();
}
}
/// <summary>
/// Extends an <see cref="Animatable"/> with the following features:
///
@ -356,6 +374,18 @@ namespace Avalonia
}
}
internal StyleDiagnostics GetStyleDiagnosticsInternal()
{
IReadOnlyList<IStyleInstance>? appliedStyles = _appliedStyles;
if (appliedStyles is null)
{
appliedStyles = Array.Empty<IStyleInstance>();
}
return new StyleDiagnostics(appliedStyles);
}
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{

2
src/Avalonia.Styling/Styling/IStyleInstance.cs

@ -14,6 +14,8 @@ namespace Avalonia.Styling
/// </summary>
IStyle Source { get; }
bool IsActive { get; }
/// <summary>
/// Instructs the style to start acting upon the control.
/// </summary>

9
src/Avalonia.Styling/Styling/StyleInstance.cs

@ -17,7 +17,6 @@ namespace Avalonia.Styling
private readonly List<IDisposable>? _animations;
private readonly IStyleActivator? _activator;
private readonly Subject<bool>? _animationTrigger;
private bool _active;
public StyleInstance(
IStyle source,
@ -29,6 +28,7 @@ namespace Avalonia.Styling
Source = source ?? throw new ArgumentNullException(nameof(source));
Target = target ?? throw new ArgumentNullException(nameof(target));
_activator = activator;
IsActive = _activator is null;
if (setters is object)
{
@ -56,6 +56,7 @@ namespace Avalonia.Styling
}
}
public bool IsActive { get; private set; }
public IStyle Source { get; }
public IStyleable Target { get; }
@ -104,15 +105,15 @@ namespace Avalonia.Styling
private void ActivatorChanged(bool value)
{
if (_active != value)
if (IsActive != value)
{
_active = value;
IsActive = value;
_animationTrigger?.OnNext(value);
if (_setters is object)
{
if (_active)
if (IsActive)
{
foreach (var setter in _setters)
{

Loading…
Cancel
Save