Selaa lähdekoodia

Merge branch 'master' into feature/formattedTextBuildGeometry

Max Katz 3 vuotta sitten
vanhempi
sitoutus
fc0ef7f376

+ 11 - 16
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@@ -9,42 +9,37 @@
     <ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
     <AndroidPackageFormat>apk</AndroidPackageFormat>
     <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
+    <RuntimeIdentifiers>android-arm64;android-x64</RuntimeIdentifiers>
   </PropertyGroup>
-  <ItemGroup>
-    <None Remove="Assets\AboutAssets.txt" />
-  </ItemGroup>
   <ItemGroup>
     <AndroidResource Include="..\..\build\Assets\Icon.png">
       <Link>Resources\drawable\Icon.png</Link>
     </AndroidResource>
   </ItemGroup>
 
-  <PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
-    <DebugSymbols>False</DebugSymbols>
-    <UseInterpreter>False</UseInterpreter>
+  <PropertyGroup Condition="'$(RunAOTCompilation)'=='' and '$(Configuration)'=='Release' and '$(TF_BUILD)'==''">
     <RunAOTCompilation>True</RunAOTCompilation>
+  </PropertyGroup>
+  
+  <PropertyGroup Condition="'$(RunAOTCompilation)'=='True'">
     <EnableLLVM>True</EnableLLVM>
     <AndroidAotAdditionalArguments>no-write-symbols,nodebug</AndroidAotAdditionalArguments>
     <AndroidAotMode>Hybrid</AndroidAotMode>
     <AndroidGenerateJniMarshalMethods>True</AndroidGenerateJniMarshalMethods>
   </PropertyGroup>
 
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
-    <EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
-    <RunAOTCompilation>False</RunAOTCompilation>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-    <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
+  <PropertyGroup Condition="'$(AndroidEnableProfiler)'=='True'">
+    <IsEmulator Condition="'$(IsEmulator)' == ''">True</IsEmulator>
+    <DebugSymbols>True</DebugSymbols>
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
-    <PackageReference Include="Xamarin.AndroidX.Lifecycle.ViewModel" Version="2.3.1.3" />
+    <AndroidEnvironment Condition="'$(IsEmulator)'=='True'" Include="environment.emulator.txt" />
+    <AndroidEnvironment Condition="'$(IsEmulator)'!='True'" Include="environment.device.txt" />
   </ItemGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
-</Project>
+</Project>

+ 2 - 1
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
-	<application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
+  <application android:label="ControlCatalog.Android" android:icon="@drawable/Icon"></application>
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 </manifest>

+ 1 - 0
samples/ControlCatalog.Android/environment.device.txt

@@ -0,0 +1 @@
+DOTNET_DiagnosticPorts=127.0.0.1:9000,suspend

+ 1 - 0
samples/ControlCatalog.Android/environment.emulator.txt

@@ -0,0 +1 @@
+DOTNET_DiagnosticPorts=10.0.2.2:9001,suspend

+ 11 - 3
src/Avalonia.Controls/GridSplitter.cs

@@ -221,7 +221,8 @@ namespace Avalonia.Controls
                     ShowsPreview = showsPreview,
                     ResizeDirection = resizeDirection,
                     SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
-                    ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection)
+                    ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection),
+                    Scaling = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1,
                 };
 
                 // Store the rows and columns to resize on drag events.
@@ -630,13 +631,17 @@ namespace Avalonia.Controls
             {
                 double actualLength1 = GetActualLength(definition1);
                 double actualLength2 = GetActualLength(definition2);
+                double pixelLength = 1 / _resizeData.Scaling;
+                double epsilon = pixelLength + LayoutHelper.LayoutEpsilon;
 
                 // When splitting, Check to see if the total pixels spanned by the definitions 
-                // is the same as before starting resize. If not cancel the drag.
+                // is the same as before starting resize. If not cancel the drag. We need to account for
+                // layout rounding here, so ignore differences of less than a device pixel to avoid problems
+                // that WPF has, such as https://stackoverflow.com/questions/28464843.
                 if (_resizeData.SplitBehavior == SplitBehavior.Split &&
                     !MathUtilities.AreClose(
                         actualLength1 + actualLength2,
-                        _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength, LayoutHelper.LayoutEpsilon))
+                        _resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength, epsilon))
                 {
                     CancelResize();
 
@@ -798,6 +803,9 @@ namespace Avalonia.Controls
             // The minimum of Width/Height of Splitter.  Used to ensure splitter 
             // isn't hidden by resizing a row/column smaller than the splitter.
             public double SplitterLength;
+
+            // The current layout scaling factor.
+            public double Scaling;
         }
     }
 

+ 17 - 0
src/Avalonia.Controls/Templates/DataTemplates.cs

@@ -1,3 +1,4 @@
+using System;
 using Avalonia.Collections;
 
 namespace Avalonia.Controls.Templates
@@ -13,6 +14,22 @@ namespace Avalonia.Controls.Templates
         public DataTemplates()
         {
             ResetBehavior = ResetBehavior.Remove;
+            
+            Validate += ValidateDataTemplate;
+        }
+
+        private static void ValidateDataTemplate(IDataTemplate template)
+        {
+            var valid = template switch
+            {
+                ITypedDataTemplate typed => typed.DataType is not null,
+                _ => true
+            };
+            
+            if (!valid)
+            {
+                throw new InvalidOperationException("DataTemplate inside of DataTemplates must have a DataType set. Set DataType property or use ItemTemplate with single template instead.");
+            }
         }
     }
 }

+ 10 - 0
src/Avalonia.Controls/Templates/ITypedDataTemplate.cs

@@ -0,0 +1,10 @@
+using System;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls.Templates;
+
+public interface ITypedDataTemplate : IDataTemplate
+{
+    [DataType]
+    Type? DataType { get; }
+}

+ 3 - 1
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@@ -35,7 +35,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
             Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
             
-            
             // Targeted
             InsertBefore<PropertyReferenceResolver>(
                 new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
@@ -57,6 +56,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
             );
 
+            InsertAfter<TypeReferenceResolver>(
+                new XDataTypeTransformer());
+
             // After everything else
             InsertBefore<NewObjectTransformer>(
                 new AddNameScopeRegistration(),

+ 73 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XDataTypeTransformer.cs

@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    internal class XDataTypeTransformer : IXamlAstTransformer
+    {
+        private const string DataTypePropertyName = "DataType";
+        
+        /// <summary>
+        /// Converts x:DataType directives to regular DataType assignments if property with Avalonia.Metadata.DataTypeAttribute exists.
+        /// </summary>
+        /// <returns></returns>
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (node is XamlAstObjectNode on)
+            {
+                for (var c = 0; c < on.Children.Count; c++)
+                {
+                    var ch = on.Children[c];
+                    if (ch is XamlAstXmlDirective { Namespace: XamlNamespaces.Xaml2006, Name: DataTypePropertyName } d)
+                    {
+                        if (on.Children.OfType<XamlAstXamlPropertyValueNode>()
+                            .Any(p => ((XamlAstNamePropertyReference)p.Property)?.Name == DataTypePropertyName))
+                        {
+                            // Break iteration if any DataType property was already set by user code.
+                            break;
+                        }
+                        
+                        var templateDataTypeAttribute = context.GetAvaloniaTypes().DataTypeAttribute;
+
+                        var clrType = (on.Type as XamlAstClrTypeReference)?.Type;
+                        if (clrType is null)
+                        {
+                            break;
+                        }
+
+                        // Technically it's possible to map "x:DataType" to a property with [DataType] attribute regardless of its name,
+                        // but we go explicitly strict here and check the name as well.
+                        var (declaringType, dataTypeProperty) = GetAllProperties(clrType)
+                            .FirstOrDefault(t => t.property.Name == DataTypePropertyName && t.property.CustomAttributes
+                                .Any(a => a.Type == templateDataTypeAttribute));
+                       
+                        if (dataTypeProperty is not null)
+                        {
+                            on.Children[c] = new XamlAstXamlPropertyValueNode(d,
+                                new XamlAstNamePropertyReference(d,
+                                    new XamlAstClrTypeReference(ch, declaringType, false), dataTypeProperty.Name,
+                                    on.Type),
+                                d.Values);
+                        }
+                    }
+                }
+            }
+
+            return node;
+        }
+        
+        private static IEnumerable<(IXamlType declaringType, IXamlProperty property)> GetAllProperties(IXamlType t)
+        {
+            foreach (var p in t.Properties)
+                yield return (t, p);
+            if(t.BaseType!=null)
+                foreach (var tuple in GetAllProperties(t.BaseType))
+                    yield return tuple;
+        }
+    }
+}

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@@ -5,7 +5,7 @@ using Avalonia.Metadata;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
-    public class DataTemplate : IRecyclingDataTemplate
+    public class DataTemplate : IRecyclingDataTemplate, ITypedDataTemplate
     {
         [DataType]
         public Type DataType { get; set; }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@@ -9,7 +9,7 @@ using Avalonia.Metadata;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
-    public class TreeDataTemplate : ITreeDataTemplate
+    public class TreeDataTemplate : ITreeDataTemplate, ITypedDataTemplate
     {
         [DataType]
         public Type DataType { get; set; }

+ 4 - 3
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@@ -17,6 +17,7 @@ using Avalonia.Markup.Xaml.Templates;
 using Avalonia.Media;
 using Avalonia.Metadata;
 using Avalonia.UnitTests;
+using JetBrains.Annotations;
 using XamlX;
 using Xunit;
 
@@ -413,11 +414,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
         x:DataType='local:TestDataContext'>
     <ItemsControl Items='{CompiledBinding ListProperty}' Name='target'>
-        <ItemsControl.DataTemplates>
+        <ItemsControl.ItemTemplate>
             <DataTemplate>
                 <TextBlock Text='{CompiledBinding}' Name='textBlock' />
             </DataTemplate>
-        </ItemsControl.DataTemplates>
+        </ItemsControl.ItemTemplate>
     </ItemsControl>
 </Window>";
                 var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
@@ -1527,7 +1528,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
         [TemplateContent]
         public object Content { get; set; }
 
-        public bool Match(object data) => FancyDataType.IsInstanceOfType(data);
+        public bool Match(object data) => FancyDataType?.IsInstanceOfType(data) ?? true;
 
         public IControl Build(object data) => TemplateContent.Load(Content)?.Control;
     }

+ 4 - 4
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlBindingTests.cs

@@ -74,18 +74,18 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
 <Window xmlns='https://github.com/avaloniaui'>
     <DockPanel>
         <TabStrip Name='strip' DockPanel.Dock='Top' Items='{Binding Items}' SelectedIndex='0'>
-          <TabStrip.DataTemplates>
+          <TabStrip.ItemTemplate>
             <DataTemplate>
               <TextBlock Text='{Binding Header}'/>
             </DataTemplate>
-          </TabStrip.DataTemplates>
+          </TabStrip.ItemTemplate>
         </TabStrip>
         <Carousel Name='carousel' Items='{Binding Items}' SelectedIndex='{Binding #strip.SelectedIndex}'>
-          <Carousel.DataTemplates>
+          <Carousel.ItemTemplate>
             <DataTemplate>
               <TextBlock Text='{Binding Detail}'/>
             </DataTemplate>
-          </Carousel.DataTemplates>
+          </Carousel.ItemTemplate>
         </Carousel>
     </DockPanel>
 </Window>";

+ 113 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@@ -1,5 +1,11 @@
+using System;
+using System.Linq;
 using Avalonia.Controls;
 using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Templates;
+using Avalonia.Markup.Xaml.Templates;
+using Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;
+using Avalonia.Metadata;
 using Avalonia.UnitTests;
 using Xunit;
 
@@ -89,6 +95,93 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
             }
         }
 
+        [Fact]
+        public void XDataType_Should_Be_Assigned_To_Clr_Property()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:sys='clr-namespace:System;assembly=netstandard'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <Window.DataTemplates>
+        <DataTemplate x:DataType='sys:String'>
+            <Canvas Name='foo'/>
+        </DataTemplate>
+    </Window.DataTemplates>
+    <ContentControl Name='target' Content='Foo'/>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var target = window.FindControl<ContentControl>("target");
+                var template = (DataTemplate)window.DataTemplates.First();
+                
+                window.ApplyTemplate();
+                target.ApplyTemplate();
+                ((ContentPresenter)target.Presenter).UpdateChild();
+                
+                Assert.Equal(typeof(string), template.DataType);
+                Assert.IsType<Canvas>(target.Presenter.Child);
+            }
+        }
+        
+        [Fact]
+        public void XDataType_Should_Be_Ignored_If_DataType_Already_Set()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:sys='clr-namespace:System;assembly=netstandard'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <Window.DataTemplates>
+        <DataTemplate DataType='sys:String' x:DataType='UserControl'>
+            <Canvas Name='foo'/>
+        </DataTemplate>
+    </Window.DataTemplates>
+    <ContentControl Name='target' Content='Foo'/>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var target = window.FindControl<ContentControl>("target");
+
+                window.ApplyTemplate();
+                target.ApplyTemplate();
+                ((ContentPresenter)target.Presenter).UpdateChild();
+
+                Assert.IsType<Canvas>(target.Presenter.Child);
+            }
+        }
+        
+        [Fact]
+        public void XDataType_Should_Be_Ignored_If_DataType_Has_Non_Standard_Name()
+        {
+            // We don't want DataType to be mapped to FancyDataType, avoid possible confusion.
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:sys='clr-namespace:System;assembly=netstandard'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <ContentControl Name='target' Content='Foo'>
+        <ContentControl.ContentTemplate>
+            <local:CustomDataTemplate x:DataType='local:TestDataContext'>
+                <TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
+            </local:CustomDataTemplate>
+        </ContentControl.ContentTemplate>
+    </ContentControl>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var target = window.FindControl<ContentControl>("target");
+                
+                window.ApplyTemplate();
+                target.ApplyTemplate();
+                ((ContentPresenter)target.Presenter).UpdateChild();
+
+                var dataTemplate = (CustomDataTemplate)target.ContentTemplate;
+                Assert.Null(dataTemplate.FancyDataType);
+            }
+        }
+        
         [Fact]
         public void Can_Set_DataContext_In_DataTemplate()
         {
@@ -132,5 +225,25 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
                 Assert.Same(viewModel.Child.Child, canvas.DataContext);
             }
         }
+        
+        [Fact]
+        public void DataTemplates_Without_Type_Should_Throw()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:sys='clr-namespace:System;assembly=netstandard'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <Window.DataTemplates>
+        <DataTemplate>
+            <Canvas Name='foo'/>
+        </DataTemplate>
+    </Window.DataTemplates>
+    <ContentControl Name='target' Content='Foo'/>
+</Window>";
+                Assert.Throws<InvalidOperationException>(() => (Window)AvaloniaRuntimeXamlLoader.Load(xaml));
+            }
+        }
     }
 }

+ 18 - 1
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TreeDataTemplateTests.cs

@@ -14,12 +14,29 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
         {
             using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
             {
-                var xaml = "<DataTemplates xmlns='https://github.com/avaloniaui'><TreeDataTemplate ItemsSource='{Binding}'/></DataTemplates>";
+                var xaml = "<DataTemplates xmlns='https://github.com/avaloniaui'><TreeDataTemplate DataType='Control' ItemsSource='{Binding}'/></DataTemplates>";
                 var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
                 var template = (TreeDataTemplate)(templates.First());
 
                 Assert.IsType<Binding>(template.ItemsSource);
             }                
         }
+        
+        [Fact]
+        public void XDataType_Should_Be_Assigned_To_Clr_Property()
+        {
+            using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
+            {
+                var xaml = @"
+<DataTemplates xmlns='https://github.com/avaloniaui'
+               xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
+    <TreeDataTemplate x:DataType='x:String' />
+</DataTemplates>";
+                var templates = (DataTemplates)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var template = (TreeDataTemplate)(templates.First());
+
+                Assert.Equal(typeof(string), template.DataType);
+            }                
+        }
     }
 }