committed by
GitHub
6 changed files with 584 additions and 32 deletions
@ -0,0 +1,190 @@ |
|||
using System.ComponentModel; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Layout; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Diagnostics.ViewModels |
|||
{ |
|||
internal class ControlLayoutViewModel : ViewModelBase |
|||
{ |
|||
private readonly IVisual _control; |
|||
private Thickness _marginThickness; |
|||
private Thickness _borderThickness; |
|||
private Thickness _paddingThickness; |
|||
private double _width; |
|||
private double _height; |
|||
private string _widthConstraint; |
|||
private string _heightConstraint; |
|||
private bool _updatingFromControl; |
|||
|
|||
public Thickness MarginThickness |
|||
{ |
|||
get => _marginThickness; |
|||
set => RaiseAndSetIfChanged(ref _marginThickness, value); |
|||
} |
|||
|
|||
public Thickness BorderThickness |
|||
{ |
|||
get => _borderThickness; |
|||
set => RaiseAndSetIfChanged(ref _borderThickness, value); |
|||
} |
|||
|
|||
public Thickness PaddingThickness |
|||
{ |
|||
get => _paddingThickness; |
|||
set => RaiseAndSetIfChanged(ref _paddingThickness, value); |
|||
} |
|||
|
|||
public double Width |
|||
{ |
|||
get => _width; |
|||
private set => RaiseAndSetIfChanged(ref _width, value); |
|||
} |
|||
|
|||
public double Height |
|||
{ |
|||
get => _height; |
|||
private set => RaiseAndSetIfChanged(ref _height, value); |
|||
} |
|||
|
|||
public string WidthConstraint |
|||
{ |
|||
get => _widthConstraint; |
|||
private set => RaiseAndSetIfChanged(ref _widthConstraint, value); |
|||
} |
|||
|
|||
public string HeightConstraint |
|||
{ |
|||
get => _heightConstraint; |
|||
private set => RaiseAndSetIfChanged(ref _heightConstraint, value); |
|||
} |
|||
|
|||
public bool HasPadding { get; } |
|||
|
|||
public bool HasBorder { get; } |
|||
|
|||
public ControlLayoutViewModel(IVisual control) |
|||
{ |
|||
_control = control; |
|||
|
|||
HasPadding = AvaloniaPropertyRegistry.Instance.IsRegistered(control, Decorator.PaddingProperty); |
|||
HasBorder = AvaloniaPropertyRegistry.Instance.IsRegistered(control, Border.BorderThicknessProperty); |
|||
|
|||
if (control is AvaloniaObject ao) |
|||
{ |
|||
MarginThickness = ao.GetValue(Layoutable.MarginProperty); |
|||
|
|||
if (HasPadding) |
|||
{ |
|||
PaddingThickness = ao.GetValue(Decorator.PaddingProperty); |
|||
} |
|||
|
|||
if (HasBorder) |
|||
{ |
|||
BorderThickness = ao.GetValue(Border.BorderThicknessProperty); |
|||
} |
|||
} |
|||
|
|||
UpdateSize(); |
|||
UpdateSizeConstraints(); |
|||
} |
|||
|
|||
private void UpdateSizeConstraints() |
|||
{ |
|||
if (_control is IAvaloniaObject ao) |
|||
{ |
|||
string CreateConstraintInfo(StyledProperty<double> minProperty, StyledProperty<double> maxProperty) |
|||
{ |
|||
if (ao.IsSet(minProperty) || ao.IsSet(maxProperty)) |
|||
{ |
|||
var minValue = ao.GetValue(minProperty); |
|||
var maxValue = ao.GetValue(maxProperty); |
|||
|
|||
return $"{minValue} < size < {maxValue}"; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
WidthConstraint = CreateConstraintInfo(Layoutable.MinWidthProperty, Layoutable.MaxWidthProperty); |
|||
HeightConstraint = CreateConstraintInfo(Layoutable.MinHeightProperty, Layoutable.MaxHeightProperty); |
|||
} |
|||
} |
|||
|
|||
protected override void OnPropertyChanged(PropertyChangedEventArgs e) |
|||
{ |
|||
base.OnPropertyChanged(e); |
|||
|
|||
if (_updatingFromControl) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (_control is AvaloniaObject ao) |
|||
{ |
|||
if (e.PropertyName == nameof(MarginThickness)) |
|||
{ |
|||
ao.SetValue(Layoutable.MarginProperty, MarginThickness); |
|||
} |
|||
else if (HasPadding && e.PropertyName == nameof(PaddingThickness)) |
|||
{ |
|||
ao.SetValue(Decorator.PaddingProperty, PaddingThickness); |
|||
} |
|||
else if (HasBorder && e.PropertyName == nameof(BorderThickness)) |
|||
{ |
|||
ao.SetValue(Border.BorderThicknessProperty, BorderThickness); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
try |
|||
{ |
|||
_updatingFromControl = true; |
|||
|
|||
if (e.Property == Visual.BoundsProperty) |
|||
{ |
|||
UpdateSize(); |
|||
} |
|||
else |
|||
{ |
|||
if (_control is IAvaloniaObject ao) |
|||
{ |
|||
if (e.Property == Layoutable.MarginProperty) |
|||
{ |
|||
MarginThickness = ao.GetValue(Layoutable.MarginProperty); |
|||
} |
|||
else if (e.Property == Decorator.PaddingProperty) |
|||
{ |
|||
PaddingThickness = ao.GetValue(Decorator.PaddingProperty); |
|||
} |
|||
else if (e.Property == Border.BorderThicknessProperty) |
|||
{ |
|||
BorderThickness = ao.GetValue(Border.BorderThicknessProperty); |
|||
} |
|||
else if (e.Property == Layoutable.MinWidthProperty || |
|||
e.Property == Layoutable.MaxWidthProperty || |
|||
e.Property == Layoutable.MinHeightProperty || |
|||
e.Property == Layoutable.MaxHeightProperty) |
|||
{ |
|||
UpdateSizeConstraints(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
_updatingFromControl = false; |
|||
} |
|||
} |
|||
|
|||
private void UpdateSize() |
|||
{ |
|||
var size = _control.Bounds; |
|||
|
|||
Width = size.Width; |
|||
Height = size.Height; |
|||
} |
|||
} |
|||
} |
|||
@ -1,38 +1,155 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters" |
|||
xmlns:local="clr-namespace:Avalonia.Diagnostics.Views" |
|||
x:Class="Avalonia.Diagnostics.Views.ControlDetailsView"> |
|||
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,*"> |
|||
|
|||
<TextBox Grid.Row="0" Grid.Column="0" |
|||
BorderThickness="0" |
|||
Text="{Binding TreePage.PropertyFilter}" |
|||
Watermark="Filter properties" /> |
|||
|
|||
<CheckBox Grid.Row="0" |
|||
Grid.Column="1" |
|||
Content="Regex" |
|||
IsChecked="{Binding TreePage.UseRegexFilter}"/> |
|||
|
|||
<DataGrid Items="{Binding PropertiesView}" |
|||
Grid.Row="1" Grid.ColumnSpan="2" |
|||
BorderThickness="0" |
|||
RowBackground="Transparent" |
|||
SelectedItem="{Binding SelectedProperty, Mode=TwoWay}" |
|||
CanUserResizeColumns="true"> |
|||
<DataGrid.Columns> |
|||
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True" /> |
|||
<DataGridTextColumn Header="Value" Binding="{Binding Value}" /> |
|||
<DataGridTextColumn Header="Type" Binding="{Binding Type}" /> |
|||
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True" /> |
|||
</DataGrid.Columns> |
|||
|
|||
<DataGrid.Styles> |
|||
<Style Selector="DataGridRow TextBox"> |
|||
<Setter Property="SelectionBrush" Value="LightBlue"/> |
|||
</Style> |
|||
</DataGrid.Styles> |
|||
</DataGrid> |
|||
|
|||
<UserControl.Resources> |
|||
<SolidColorBrush x:Key="ThicknessBorderBrush" Color="#666666" /> |
|||
<SolidColorBrush x:Key="SizeGuidelineBrush" Color="#333333" /> |
|||
<SolidColorBrush x:Key="MarginBackgroundBrush" Color="#D78965" /> |
|||
<SolidColorBrush x:Key="BorderBackgroundBrush" Color="#E3C381" /> |
|||
<SolidColorBrush x:Key="PaddingBackgroundBrush" Color="#B8C47F" /> |
|||
<SolidColorBrush x:Key="SizeBackgroundBrush" Color="#88B2BD" /> |
|||
</UserControl.Resources> |
|||
|
|||
<UserControl.Styles> |
|||
<Style Selector="local|ThicknessEditor"> |
|||
<Setter Property="HorizontalContentAlignment" Value="Center" /> |
|||
<Setter Property="VerticalContentAlignment" Value="Center" /> |
|||
<Setter Property="BorderThickness" Value="1" /> |
|||
<Setter Property="BorderBrush" Value="{StaticResource ThicknessBorderBrush}" /> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<Border |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}"> |
|||
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="Auto,*,Auto"> |
|||
<Grid.Styles> |
|||
<Style Selector="TextBox.thickness-edit"> |
|||
<Setter Property="Background" Value="Transparent" /> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
<Setter Property="Margin" Value="2" /> |
|||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|||
<Setter Property="VerticalAlignment" Value="Stretch" /> |
|||
<Setter Property="HorizontalContentAlignment" Value="Center" /> |
|||
<Setter Property="VerticalContentAlignment" Value="Center" /> |
|||
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Disabled" /> |
|||
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Disabled" /> |
|||
<Setter Property="IsVisible" Value="{Binding $parent[local:ThicknessEditor].IsPresent}" /> |
|||
</Style> |
|||
</Grid.Styles> |
|||
<TextBlock IsVisible="{TemplateBinding IsPresent}" Margin="4,0,0,0" Text="{TemplateBinding Header}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" /> |
|||
<TextBox Grid.Row="1" Grid.Column="0" Text="{Binding Left, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> |
|||
<TextBox x:Name="Right" Grid.Row="0" Grid.Column="1" Text="{Binding Top, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> |
|||
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Right, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> |
|||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Bottom, RelativeSource={RelativeSource TemplatedParent}}" Classes="thickness-edit" /> |
|||
<ContentPresenter Grid.Row="1" Grid.Column="1" |
|||
Name="PART_ContentPresenter" |
|||
ContentTemplate="{TemplateBinding ContentTemplate}" |
|||
Content="{TemplateBinding Content}" |
|||
Padding="{TemplateBinding Padding}" |
|||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
|||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" /> |
|||
</Grid> |
|||
</Border> |
|||
|
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
<Style Selector="local|ThicknessEditor[IsPresent=False]"> |
|||
<Setter Property="BorderThickness" Value="0" /> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
|
|||
<Grid ColumnDefinitions="*,Auto,280"> |
|||
|
|||
<Grid Grid.Column="0" ColumnDefinitions="*,Auto,Auto,Auto" RowDefinitions="Auto,*"> |
|||
|
|||
<TextBox Grid.Row="0" |
|||
Grid.Column="0" |
|||
BorderThickness="0" |
|||
Text="{Binding TreePage.PropertyFilter}" |
|||
Watermark="Filter properties" /> |
|||
|
|||
<CheckBox Grid.Row="0" |
|||
Grid.Column="1" |
|||
Margin="0,0,4,0" |
|||
Content="Regex" |
|||
IsChecked="{Binding TreePage.UseRegexFilter}" /> |
|||
|
|||
<DataGrid Items="{Binding PropertiesView}" |
|||
Grid.Row="1" |
|||
Grid.Column="0" |
|||
Grid.ColumnSpan="2" |
|||
BorderThickness="0" |
|||
RowBackground="Transparent" |
|||
SelectedItem="{Binding SelectedProperty, Mode=TwoWay}" |
|||
CanUserResizeColumns="true"> |
|||
<DataGrid.Columns> |
|||
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True" /> |
|||
<DataGridTextColumn Header="Value" Binding="{Binding Value}" /> |
|||
<DataGridTextColumn Header="Type" Binding="{Binding Type}" /> |
|||
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True" /> |
|||
</DataGrid.Columns> |
|||
|
|||
<DataGrid.Styles> |
|||
<Style Selector="DataGridRow TextBox"> |
|||
<Setter Property="SelectionBrush" Value="LightBlue" /> |
|||
</Style> |
|||
</DataGrid.Styles> |
|||
</DataGrid> |
|||
|
|||
</Grid> |
|||
|
|||
<GridSplitter Grid.Column="1" /> |
|||
|
|||
<Grid Grid.Column="2" RowDefinitions="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"> |
|||
|
|||
<Border x:Name="VerticalSize" Grid.Row="0" Grid.Column="1" > |
|||
<TextBlock VerticalAlignment="Center" FontWeight="Bold" |
|||
TextDecorations="{Binding Layout.HeightConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}" |
|||
Text="{Binding Layout.Height}" |
|||
ToolTip.Tip="{Binding Layout.HeightConstraint}" /> |
|||
</Border> |
|||
|
|||
<Border x:Name="HorizontalSize" Grid.Row="1" Grid.Column="0" > |
|||
<TextBlock HorizontalAlignment="Center" FontWeight="Bold" |
|||
TextDecorations="{Binding Layout.WidthConstraint, Converter={x:Static local:Converters.HasConstraintConverter}}" |
|||
Text="{Binding Layout.Width}" |
|||
ToolTip.Tip="{Binding Layout.WidthConstraint}" /> |
|||
</Border> |
|||
|
|||
<local:ThicknessEditor Grid.Row="0" Grid.Column="0" Header="margin" VerticalAlignment="Top" HorizontalAlignment="Center" Background="{StaticResource MarginBackgroundBrush}" Thickness="{Binding Layout.MarginThickness}"> |
|||
<local:ThicknessEditor x:Name="BorderArea" Header="border" Background="{StaticResource BorderBackgroundBrush}" Thickness="{Binding Layout.BorderThickness}" IsPresent="{Binding Layout.HasBorder}"> |
|||
<local:ThicknessEditor x:Name="PaddingArea" Header="padding" Background="{StaticResource PaddingBackgroundBrush}" Thickness="{Binding Layout.PaddingThickness}" IsPresent="{Binding Layout.HasPadding}"> |
|||
<Border x:Name="ContentArea" BorderThickness="1" BorderBrush="{StaticResource ThicknessBorderBrush}" MinWidth="100" MinHeight="16" Background="{StaticResource SizeBackgroundBrush}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"> |
|||
<TextBlock Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="content" /> |
|||
</Border> |
|||
</local:ThicknessEditor> |
|||
</local:ThicknessEditor> |
|||
</local:ThicknessEditor> |
|||
|
|||
<Canvas Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2"> |
|||
<Canvas.Styles> |
|||
<Style Selector="Rectangle"> |
|||
<Setter Property="StrokeDashArray" Value="1,3" /> |
|||
<Setter Property="Stroke" Value="{StaticResource SizeGuidelineBrush}" /> |
|||
<Setter Property="StrokeThickness" Value="1" /> |
|||
</Style> |
|||
</Canvas.Styles> |
|||
<Rectangle x:Name="HorizontalSizeBegin" /> |
|||
<Rectangle x:Name="HorizontalSizeEnd" /> |
|||
<Rectangle x:Name="VerticalSizeBegin" /> |
|||
<Rectangle x:Name="VerticalSizeEnd" /> |
|||
</Canvas> |
|||
</Grid> |
|||
|
|||
</Grid> |
|||
</Grid> |
|||
|
|||
</UserControl> |
|||
|
|||
@ -0,0 +1,130 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.Data.Converters; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Diagnostics.Views |
|||
{ |
|||
internal static class Converters |
|||
{ |
|||
public static IValueConverter HasConstraintConverter = |
|||
new FuncValueConverter<object, TextDecorationCollection>(ConvertToDecoration); |
|||
|
|||
private static TextDecorationCollection ConvertToDecoration(object arg) |
|||
{ |
|||
return arg != null ? TextDecorations.Underline : null; |
|||
} |
|||
} |
|||
|
|||
internal class ThicknessEditor : ContentControl |
|||
{ |
|||
public static readonly DirectProperty<ThicknessEditor, Thickness> ThicknessProperty = |
|||
AvaloniaProperty.RegisterDirect<ThicknessEditor, Thickness>(nameof(Thickness), o => o.Thickness, |
|||
(o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
public static readonly DirectProperty<ThicknessEditor, string> HeaderProperty = |
|||
AvaloniaProperty.RegisterDirect<ThicknessEditor, string>(nameof(Header), o => o.Header, |
|||
(o, v) => o.Header = v); |
|||
|
|||
public static readonly DirectProperty<ThicknessEditor, bool> IsPresentProperty = |
|||
AvaloniaProperty.RegisterDirect<ThicknessEditor, bool>(nameof(Header), o => o.IsPresent, |
|||
(o, v) => o.IsPresent = v); |
|||
|
|||
public static readonly DirectProperty<ThicknessEditor, double> LeftProperty = |
|||
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Left), o => o.Left, (o, v) => o.Left = v); |
|||
|
|||
public static readonly DirectProperty<ThicknessEditor, double> TopProperty = |
|||
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Top), o => o.Top, (o, v) => o.Top = v); |
|||
|
|||
public static readonly DirectProperty<ThicknessEditor, double> RightProperty = |
|||
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Right), o => o.Right, |
|||
(o, v) => o.Right = v); |
|||
|
|||
public static readonly DirectProperty<ThicknessEditor, double> BottomProperty = |
|||
AvaloniaProperty.RegisterDirect<ThicknessEditor, double>(nameof(Bottom), o => o.Bottom, |
|||
(o, v) => o.Bottom = v); |
|||
|
|||
|
|||
private Thickness _thickness; |
|||
private string _header; |
|||
private bool _isPresent = true; |
|||
private double _left; |
|||
private double _top; |
|||
private double _right; |
|||
private double _bottom; |
|||
|
|||
private bool _isUpdatingThickness; |
|||
|
|||
public Thickness Thickness |
|||
{ |
|||
get => _thickness; |
|||
set => SetAndRaise(ThicknessProperty, ref _thickness, value); |
|||
} |
|||
|
|||
public string Header |
|||
{ |
|||
get => _header; |
|||
set => SetAndRaise(HeaderProperty, ref _header, value); |
|||
} |
|||
|
|||
public bool IsPresent |
|||
{ |
|||
get => _isPresent; |
|||
set => SetAndRaise(IsPresentProperty, ref _isPresent, value); |
|||
} |
|||
|
|||
public double Left |
|||
{ |
|||
get => _left; |
|||
set => SetAndRaise(LeftProperty, ref _left, value); |
|||
} |
|||
|
|||
public double Top |
|||
{ |
|||
get => _top; |
|||
set => SetAndRaise(TopProperty, ref _top, value); |
|||
} |
|||
|
|||
public double Right |
|||
{ |
|||
get => _right; |
|||
set => SetAndRaise(RightProperty, ref _right, value); |
|||
} |
|||
|
|||
public double Bottom |
|||
{ |
|||
get => _bottom; |
|||
set => SetAndRaise(BottomProperty, ref _bottom, value); |
|||
} |
|||
|
|||
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change) |
|||
{ |
|||
base.OnPropertyChanged(change); |
|||
|
|||
if (change.Property == ThicknessProperty) |
|||
{ |
|||
try |
|||
{ |
|||
_isUpdatingThickness = true; |
|||
|
|||
var value = change.NewValue.GetValueOrDefault<Thickness>(); |
|||
|
|||
Left = value.Left; |
|||
Top = value.Top; |
|||
Right = value.Right; |
|||
Bottom = value.Bottom; |
|||
} |
|||
finally |
|||
{ |
|||
_isUpdatingThickness = false; |
|||
} |
|||
} |
|||
else if (!_isUpdatingThickness && |
|||
(change.Property == LeftProperty || change.Property == TopProperty || |
|||
change.Property == RightProperty || change.Property == BottomProperty)) |
|||
{ |
|||
Thickness = new Thickness(Left, Top, Right, Bottom); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue