diff --git a/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject b/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject
index 319cd523ce..95a483b433 100644
--- a/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject
@@ -1,5 +1,3 @@
-
- True
-
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/Avalonia.MicroCom.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution
index afce1018ec..b97a8e54f5 100644
--- a/Avalonia.v3.ncrunchsolution
+++ b/Avalonia.v3.ncrunchsolution
@@ -4,6 +4,8 @@
tests\TestFiles\**.*
src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll
src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.dll
+ src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.Rocks.dll
+ src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.Pdb.dll
True
diff --git a/Directory.Build.props b/Directory.Build.props
index b41f8c488e..c6610695c4 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,5 +2,7 @@
$(MSBuildThisFileDirectory)build-intermediate/nuget
$(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll
+
+ false
diff --git a/Documentation/build.md b/Documentation/build.md
index 8c2ef57b54..2f59146a48 100644
--- a/Documentation/build.md
+++ b/Documentation/build.md
@@ -30,7 +30,7 @@ The build process needs [Xcode](https://developer.apple.com/xcode/) to build the
Linux operating systems ship with their own respective package managers however we will use [Homebrew](https://brew.sh/) to manage packages on macOS. To install follow the instructions [here](https://docs.brew.sh/Installation).
-### Install CastXML
+### Install CastXML (pre Nov 2020)
Avalonia requires [CastXML](https://github.com/CastXML/CastXML) for XML processing during the build process. The easiest way to install this is via the operating system's package managers, such as below.
@@ -61,6 +61,7 @@ git submodule update --init --recursive
### Build native libraries (macOS only)
On macOS it is necessary to build and manually install the respective native libraries using [Xcode](https://developer.apple.com/xcode/). The steps to get this working correctly are:
+- (for revisions after 2 Nov 2020) Run `./build.sh GenerateCppHeaders` to generate `avalonia-native.h` from `avn.idl`
- Navigate to the Avalonia/native/Avalonia.Native/src/OSX folder and open the `Avalonia.Native.OSX.xcodeproj` project
- Build the library via the Product->Build menu. This will generate binaries in your local path under ~/Library/Developer/Xcode/DerivedData/Avalonia.Native.OSX-*guid* where "guid" is uniquely generated every time you build.
- Manually install the native library by copying it from the build artifacts folder into the shared dynamic library path:
diff --git a/build/MicroCom.targets b/build/MicroCom.targets
new file mode 100644
index 0000000000..3a07950616
--- /dev/null
+++ b/build/MicroCom.targets
@@ -0,0 +1,33 @@
+
+
+
+
+
+ false
+ all
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_AvaloniaPatchComInterop>true
+
+
+
diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props
index d8e86e917e..f74ab07e31 100644
--- a/build/ReactiveUI.props
+++ b/build/ReactiveUI.props
@@ -1,5 +1,5 @@
-
+
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index ecc01b4aab..97647a1c59 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -141,6 +141,7 @@ partial class Build : NukeBuild
Target CompileNative => _ => _
.DependsOn(Clean)
+ .DependsOn(GenerateCppHeaders)
.OnlyWhenStatic(() => EnvironmentInfo.IsOsx)
.Executes(() =>
{
diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs
index ec9649bc64..31071768c9 100644
--- a/src/Avalonia.Controls/ContextMenu.cs
+++ b/src/Avalonia.Controls/ContextMenu.cs
@@ -236,7 +236,7 @@ namespace Avalonia.Controls
///
/// Opens the menu.
///
- public override void Open() => Open(null);
+ public override void Open() => throw new NotSupportedException();
///
/// Opens a context menu on the specified control.
diff --git a/src/Avalonia.Controls/Selection/SelectionModel.cs b/src/Avalonia.Controls/Selection/SelectionModel.cs
index 054974e4f1..6ae53a4d59 100644
--- a/src/Avalonia.Controls/Selection/SelectionModel.cs
+++ b/src/Avalonia.Controls/Selection/SelectionModel.cs
@@ -443,6 +443,7 @@ namespace Avalonia.Controls.Selection
}
if ((e.Action == NotifyCollectionChangedAction.Remove && e.OldStartingIndex <= oldSelectedIndex) ||
+ (e.Action == NotifyCollectionChangedAction.Replace && e.OldStartingIndex == oldSelectedIndex) ||
e.Action == NotifyCollectionChangedAction.Reset)
{
RaisePropertyChanged(nameof(SelectedItem));
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index b2bd5ab2e5..09742412d9 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -378,10 +378,11 @@ namespace Avalonia.Controls
{
if (!this.IsVisualAncestorOf(element))
{
- IControl result = _selectedItem != null ?
+ var result = _selectedItem != null ?
ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) :
ItemContainerGenerator.ContainerFromIndex(0);
- return (true, result);
+
+ return (result != null, result); // SelectedItem may not be in the treeview.
}
return (true, null);
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..fd2e4c3355
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
@@ -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 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)
+ {
+ 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;
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
index 8aad5fffd8..2e0b6813ba 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
@@ -1,38 +1,155 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 0000000000..c7611c8c46
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ThicknessEditor.cs
@@ -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