diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs index 2ee8df78ef..fd2e4c3355 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -11,7 +11,10 @@ namespace Avalonia.Diagnostics.ViewModels private Thickness _marginThickness; private Thickness _borderThickness; private Thickness _paddingThickness; - private string _sizeText; + private double _width; + private double _height; + private string _widthConstraint; + private string _heightConstraint; private bool _updatingFromControl; public Thickness MarginThickness @@ -32,10 +35,28 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _paddingThickness, value); } - public string SizeText + public double Width { - get => _sizeText; - private set => RaiseAndSetIfChanged(ref _sizeText, value); + 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; } @@ -65,6 +86,29 @@ namespace Avalonia.Diagnostics.ViewModels } UpdateSize(); + UpdateSizeConstraints(); + } + + private void UpdateSizeConstraints() + { + if (_control is IAvaloniaObject ao) + { + string CreateConstraintInfo(StyledProperty minProperty, StyledProperty 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) @@ -99,11 +143,9 @@ namespace Avalonia.Diagnostics.ViewModels { _updatingFromControl = true; - var updateSize = false; - if (e.Property == Visual.BoundsProperty) { - updateSize = true; + UpdateSize(); } else { @@ -120,16 +162,16 @@ namespace Avalonia.Diagnostics.ViewModels else if (e.Property == Border.BorderThicknessProperty) { BorderThickness = ao.GetValue(Border.BorderThicknessProperty); - - updateSize = true; + } + else if (e.Property == Layoutable.MinWidthProperty || + e.Property == Layoutable.MaxWidthProperty || + e.Property == Layoutable.MinHeightProperty || + e.Property == Layoutable.MaxHeightProperty) + { + UpdateSizeConstraints(); } } } - - if (updateSize) - { - UpdateSize(); - } } finally { @@ -141,9 +183,8 @@ namespace Avalonia.Diagnostics.ViewModels { var size = _control.Bounds; - size.Deflate(BorderThickness); - - SizeText = $"{size.Width} × {size.Height}"; + Width = size.Width; + Height = size.Height; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 1487cb6dde..42e64d59ae 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -106,15 +106,48 @@ - - - - - - + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs index c6bd5a18aa..c9568509f6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs @@ -1,10 +1,24 @@ +using System; using Avalonia.Controls; +using Avalonia.Controls.Shapes; using Avalonia.Markup.Xaml; +using Avalonia.VisualTree; namespace Avalonia.Diagnostics.Views { internal class ControlDetailsView : UserControl { + private ThicknessEditor _borderArea; + private ThicknessEditor _paddingArea; + private Rectangle _horizontalSizeBegin; + private Rectangle _horizontalSizeEnd; + private Rectangle _verticalSizeBegin; + private Rectangle _verticalSizeEnd; + private Grid _layoutRoot; + private Border _horizontalSize; + private Border _verticalSize; + private Border _contentArea; + public ControlDetailsView() { InitializeComponent(); @@ -13,6 +27,101 @@ namespace Avalonia.Diagnostics.Views private void InitializeComponent() { AvaloniaXamlLoader.Load(this); + + _borderArea = this.FindControl("BorderArea"); + _paddingArea = this.FindControl("PaddingArea"); + + _horizontalSizeBegin = this.FindControl("HorizontalSizeBegin"); + _horizontalSizeEnd = this.FindControl("HorizontalSizeEnd"); + _verticalSizeBegin = this.FindControl("VerticalSizeBegin"); + _verticalSizeEnd = this.FindControl("VerticalSizeEnd"); + + _horizontalSize = this.FindControl("HorizontalSize"); + _verticalSize = this.FindControl("VerticalSize"); + + _contentArea = this.FindControl("ContentArea"); + + _layoutRoot = this.FindControl("LayoutRoot"); + + void SubscribeToBounds(Visual visual) + { + visual.GetPropertyChangedObservable(TransformedBoundsProperty) + .Subscribe(UpdateSizeGuidelines); + } + + SubscribeToBounds(_borderArea); + SubscribeToBounds(_paddingArea); + SubscribeToBounds(_contentArea); + } + + private void UpdateSizeGuidelines(AvaloniaPropertyChangedEventArgs e) + { + void UpdateGuidelines(Visual area) + { + if (area.TransformedBounds is TransformedBounds bounds) + { + // Horizontal guideline + { + var sizeArea = TranslateToRoot((_horizontalSize.TransformedBounds ?? default).Bounds.BottomLeft, + _horizontalSize); + + var start = TranslateToRoot(bounds.Bounds.BottomLeft, area); + + SetPosition(_horizontalSizeBegin, start); + + var end = TranslateToRoot(bounds.Bounds.BottomRight, area); + + SetPosition(_horizontalSizeEnd, end.WithX(end.X - 1)); + + var height = sizeArea.Y - start.Y + 2; + + _horizontalSizeBegin.Height = height; + _horizontalSizeEnd.Height = height; + } + + // Vertical guideline + { + var sizeArea = TranslateToRoot((_verticalSize.TransformedBounds ?? default).Bounds.TopRight, _verticalSize); + + var start = TranslateToRoot(bounds.Bounds.TopRight, area); + + SetPosition(_verticalSizeBegin, start); + + var end = TranslateToRoot(bounds.Bounds.BottomRight, area); + + SetPosition(_verticalSizeEnd, end.WithY(end.Y - 1)); + + var width = sizeArea.X - start.X + 2; + + _verticalSizeBegin.Width = width; + _verticalSizeEnd.Width = width; + } + } + } + + Point TranslateToRoot(Point point, IVisual from) + { + return from.TranslatePoint(point, _layoutRoot) ?? default; + } + + static void SetPosition(Rectangle rect, Point start) + { + Canvas.SetLeft(rect, start.X); + Canvas.SetTop(rect, start.Y); + } + + if (_borderArea.IsPresent) + { + UpdateGuidelines(_borderArea); + } + else if (_paddingArea.IsPresent) + { + UpdateGuidelines(_paddingArea); + } + else + { + UpdateGuidelines(_contentArea); + } } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs index 58ea91c2fa..c7611c8c46 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs @@ -1,8 +1,21 @@ 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(ConvertToDecoration); + + private static TextDecorationCollection ConvertToDecoration(object arg) + { + return arg != null ? TextDecorations.Underline : null; + } + } + internal class ThicknessEditor : ContentControl { public static readonly DirectProperty ThicknessProperty =