From 177d18463a5fb35c318f67a8fe74f1efd1167c8f Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sat, 7 Nov 2020 16:17:21 +0100 Subject: [PATCH] Add support for visualizing layout properties. --- .../ViewModels/ControlDetailsViewModel.cs | 6 + .../ViewModels/ControlLayoutViewModel.cs | 150 ++++++++++++++++++ .../Diagnostics/Views/ControlDetailsView.xaml | 145 +++++++++++++---- .../Diagnostics/Views/ThicknessEditor.cs | 117 ++++++++++++++ 4 files changed, 387 insertions(+), 31 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index d4b988acd4..fa41eacbeb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -32,6 +32,8 @@ namespace Avalonia.Diagnostics.ViewModels view.Filter = FilterProperty; PropertiesView = view; + Layout = new ControlLayoutViewModel(control); + if (control is INotifyPropertyChanged inpc) { inpc.PropertyChanged += ControlPropertyChanged; @@ -52,6 +54,8 @@ namespace Avalonia.Diagnostics.ViewModels get => _selectedProperty; set => RaiseAndSetIfChanged(ref _selectedProperty, value); } + + public ControlLayoutViewModel Layout { get; } public void Dispose() { @@ -112,6 +116,8 @@ namespace Avalonia.Diagnostics.ViewModels property.Update(); } } + + Layout.ControlPropertyChanged(sender, e); } private void ControlPropertyChanged(object sender, PropertyChangedEventArgs e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs new file mode 100644 index 0000000000..0173f19358 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -0,0 +1,150 @@ +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 string _sizeText; + + 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 string SizeText + { + get => _sizeText; + private set => RaiseAndSetIfChanged(ref _sizeText, 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(); + } + + private bool _updatingFromControl; + + 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; + + var updateSize = false; + + if (e.Property == Visual.BoundsProperty) + { + updateSize = true; + } + 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); + + updateSize = true; + } + } + } + + if (updateSize) + { + UpdateSize(); + } + } + finally + { + _updatingFromControl = false; + } + } + + private void UpdateSize() + { + var size = _control.Bounds; + + size.Deflate(BorderThickness); + + SizeText = $"{size.Width} × {size.Height}"; + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 8aad5fffd8..1487cb6dde 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -1,38 +1,121 @@  - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs new file mode 100644 index 0000000000..58ea91c2fa --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs @@ -0,0 +1,117 @@ +using Avalonia.Controls; +using Avalonia.Data; + +namespace Avalonia.Diagnostics.Views +{ + internal class ThicknessEditor : ContentControl + { + public static readonly DirectProperty ThicknessProperty = + AvaloniaProperty.RegisterDirect(nameof(Thickness), o => o.Thickness, + (o, v) => o.Thickness = v, defaultBindingMode: BindingMode.TwoWay); + + public static readonly DirectProperty HeaderProperty = + AvaloniaProperty.RegisterDirect(nameof(Header), o => o.Header, + (o, v) => o.Header = v); + + public static readonly DirectProperty IsPresentProperty = + AvaloniaProperty.RegisterDirect(nameof(Header), o => o.IsPresent, + (o, v) => o.IsPresent = v); + + public static readonly DirectProperty LeftProperty = + AvaloniaProperty.RegisterDirect(nameof(Left), o => o.Left, (o, v) => o.Left = v); + + public static readonly DirectProperty TopProperty = + AvaloniaProperty.RegisterDirect(nameof(Top), o => o.Top, (o, v) => o.Top = v); + + public static readonly DirectProperty RightProperty = + AvaloniaProperty.RegisterDirect(nameof(Right), o => o.Right, + (o, v) => o.Right = v); + + public static readonly DirectProperty BottomProperty = + AvaloniaProperty.RegisterDirect(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(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ThicknessProperty) + { + try + { + _isUpdatingThickness = true; + + var value = change.NewValue.GetValueOrDefault(); + + 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); + } + } + } +}