From bd77de553b6220fa77c74c18b68baad3490da06e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 19 May 2021 17:12:34 +0100 Subject: [PATCH] Merge pull request #5950 from MarchingCube/devtools-layout-alignment Allow for easily changing layout alignment --- .../Controls/ThicknessEditor.axaml | 87 +++++ .../{Views => Controls}/ThicknessEditor.cs | 14 +- .../Converters/EnumToCheckedConverter.cs | 25 ++ .../Diagnostics/DevToolsOptions.cs | 4 +- .../ViewModels/ControlLayoutViewModel.cs | 101 +++-- .../Diagnostics/Views/ControlDetailsView.xaml | 353 ++++++------------ .../Views/ControlDetailsView.xaml.cs | 109 ------ .../Views/LayoutExplorerView.axaml | 210 +++++++++++ .../Views/LayoutExplorerView.axaml.cs | 128 +++++++ .../Diagnostics/Views/MainWindow.xaml | 1 + .../Diagnostics/Views/TreePageView.xaml | 2 +- 11 files changed, 640 insertions(+), 394 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml rename src/Avalonia.Diagnostics/Diagnostics/{Views => Controls}/ThicknessEditor.cs (98%) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml new file mode 100644 index 00000000000..c202d137781 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs similarity index 98% rename from src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs rename to src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs index 2f03a23fdf3..e5b3b080e24 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.cs @@ -2,7 +2,7 @@ using Avalonia.Data; using Avalonia.Media; -namespace Avalonia.Diagnostics.Views +namespace Avalonia.Diagnostics.Controls { internal class ThicknessEditor : ContentControl { @@ -35,12 +35,6 @@ internal class ThicknessEditor : ContentControl public static readonly StyledProperty HighlightProperty = AvaloniaProperty.Register(nameof(Highlight)); - public IBrush Highlight - { - get => GetValue(HighlightProperty); - set => SetValue(HighlightProperty, value); - } - private Thickness _thickness; private string _header; private bool _isPresent = true; @@ -92,6 +86,12 @@ public double Bottom set => SetAndRaise(BottomProperty, ref _bottom, value); } + public IBrush Highlight + { + get => GetValue(HighlightProperty); + set => SetValue(HighlightProperty, value); + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs b/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs new file mode 100644 index 00000000000..8d10981ba78 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Converters/EnumToCheckedConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using Avalonia.Data; +using Avalonia.Data.Converters; + +namespace Avalonia.Diagnostics.Converters +{ + internal class EnumToCheckedConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return Equals(value, parameter); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool isChecked && isChecked) + { + return parameter; + } + + return BindingOperations.DoNothing; + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index ee461922072..f9978f3b7ef 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -19,8 +19,8 @@ public class DevToolsOptions public bool ShowAsChildWindow { get; set; } = true; /// - /// Gets or sets the initial size of the DevTools window. The default value is 1024x512. + /// Gets or sets the initial size of the DevTools window. The default value is 1280x720. /// - public Size Size { get; set; } = new Size(1024, 512); + public Size Size { get; set; } = new Size(1280, 720); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs index ef227f7374f..b0718bc6ce7 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs @@ -10,27 +10,58 @@ 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 HorizontalAlignment _horizontalAlignment; + private Thickness _marginThickness; + private Thickness _paddingThickness; private bool _updatingFromControl; + private VerticalAlignment _verticalAlignment; + private double _width; + private string _widthConstraint; + + 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); + } + + HorizontalAlignment = ao.GetValue(Layoutable.HorizontalAlignmentProperty); + VerticalAlignment = ao.GetValue(Layoutable.VerticalAlignmentProperty); + } + + UpdateSize(); + UpdateSizeConstraints(); + } 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; @@ -61,35 +92,21 @@ public string HeightConstraint private set => RaiseAndSetIfChanged(ref _heightConstraint, value); } - public bool HasPadding { get; } - - public bool HasBorder { get; } - - public ControlLayoutViewModel(IVisual control) + public HorizontalAlignment HorizontalAlignment { - _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); + get => _horizontalAlignment; + private set => RaiseAndSetIfChanged(ref _horizontalAlignment, value); + } - if (HasPadding) - { - PaddingThickness = ao.GetValue(Decorator.PaddingProperty); - } + public VerticalAlignment VerticalAlignment + { + get => _verticalAlignment; + private set => RaiseAndSetIfChanged(ref _verticalAlignment, value); + } - if (HasBorder) - { - BorderThickness = ao.GetValue(Border.BorderThicknessProperty); - } - } + public bool HasPadding { get; } - UpdateSize(); - UpdateSizeConstraints(); - } + public bool HasBorder { get; } private void UpdateSizeConstraints() { @@ -151,6 +168,14 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs e) { ao.SetValue(Border.BorderThicknessProperty, BorderThickness); } + else if (e.PropertyName == nameof(HorizontalAlignment)) + { + ao.SetValue(Layoutable.HorizontalAlignmentProperty, HorizontalAlignment); + } + else if (e.PropertyName == nameof(VerticalAlignment)) + { + ao.SetValue(Layoutable.VerticalAlignmentProperty, VerticalAlignment); + } } } @@ -171,15 +196,15 @@ public void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventAr 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 || @@ -187,6 +212,14 @@ public void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventAr { UpdateSizeConstraints(); } + else if (e.Property == Layoutable.HorizontalAlignmentProperty) + { + HorizontalAlignment = ao.GetValue(Layoutable.HorizontalAlignmentProperty); + } + else if (e.Property == Layoutable.VerticalAlignmentProperty) + { + VerticalAlignment = ao.GetValue(Layoutable.VerticalAlignmentProperty); + } } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 50534e77b87..ab651b2a065 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -7,88 +7,10 @@ x:Name="Main"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -130,184 +52,133 @@ - - + - - - - - + + + + - + - - - + - - - + - - - - - - - - - - - - - + + + + + - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - + + - - - - - - - - - - - - - - ( - - - ) + + + + - - - + - - - - - - - - + - - - + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs index c9568509f6d..c6bd5a18aa1 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs @@ -1,24 +1,10 @@ -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(); @@ -27,101 +13,6 @@ public ControlDetailsView() 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/LayoutExplorerView.axaml b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml new file mode 100644 index 00000000000..af6c84a76a2 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs new file mode 100644 index 00000000000..d4a09f72960 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs @@ -0,0 +1,128 @@ +using System; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Diagnostics.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.VisualTree; + +namespace Avalonia.Diagnostics.Views +{ + internal class LayoutExplorerView : UserControl + { + private readonly ThicknessEditor _borderArea; + private readonly ThicknessEditor _paddingArea; + private readonly Rectangle _horizontalSizeBegin; + private readonly Rectangle _horizontalSizeEnd; + private readonly Rectangle _verticalSizeBegin; + private readonly Rectangle _verticalSizeEnd; + private readonly Grid _layoutRoot; + private readonly Border _horizontalSize; + private readonly Border _verticalSize; + private readonly Border _contentArea; + + public LayoutExplorerView() + { + InitializeComponent(); + + _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 InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + 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/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 7dd4ed0832b..7628ee2d8c2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -15,6 +15,7 @@ + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml index fd631f0dffb..a5328716fca 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" x:Class="Avalonia.Diagnostics.Views.TreePageView"> - +