Browse Source

Merge branch 'master' into fixes/4293-listbox-remove-item-selection-2

danwalmsley 5 years ago
parent
commit
b8ff0459c1
100 changed files with 987 additions and 708 deletions
  1. 1 1
      .gitmodules
  2. 27 0
      Avalonia.sln
  3. 1 0
      Directory.Build.props
  4. 1 1
      dirs.proj
  5. 12 4
      native/Avalonia.Native/src/OSX/Screens.mm
  6. 1 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  7. 5 5
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  8. 6 0
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  9. 10 6
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  10. 17 14
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  11. 1 1
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  12. 0 1
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  13. 3 3
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  14. 5 0
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  15. 10 11
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  16. 2 2
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  17. 0 2
      src/Avalonia.Controls/Repeater/ElementFactory.cs
  18. 0 1
      src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs
  19. 21 8
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  20. 3 3
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  21. 5 7
      src/Avalonia.Controls/Templates/IDataTemplate.cs
  22. 25 0
      src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs
  23. 1 1
      src/Avalonia.Controls/TextBox.cs
  24. 10 15
      src/Avalonia.Controls/TickBar.cs
  25. 2 2
      src/Avalonia.Controls/TopLevel.cs
  26. 1 1
      src/Avalonia.Controls/Window.cs
  27. 4 3
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  28. 8 2
      src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs
  29. 21 7
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json
  30. 1 0
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  31. 4 3
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  32. 2 1
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  33. 0 2
      src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs
  34. 5 4
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  35. 0 15
      src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs
  36. 1 1
      src/Avalonia.Native/PopupImpl.cs
  37. 5 3
      src/Avalonia.Native/WindowImplBase.cs
  38. 2 0
      src/Avalonia.Themes.Default/TextBox.xaml
  39. 51 56
      src/Avalonia.Themes.Fluent/TextBox.xaml
  40. 10 5
      src/Avalonia.Visuals/Media/Color.cs
  41. 1 14
      src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs
  42. 1 2
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  43. 23 0
      src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
  44. 18 0
      src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs
  45. 196 307
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  46. 51 16
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  47. 31 14
      src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
  48. 150 7
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  49. 9 2
      src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
  50. 0 5
      src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs
  51. 3 4
      src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs
  52. 33 0
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
  53. 37 0
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
  54. 1 1
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
  55. 0 1
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  56. 8 8
      src/Avalonia.Visuals/Media/TextWrapping.cs
  57. 2 2
      src/Avalonia.X11/X11NativeControlHost.cs
  58. 22 25
      src/Avalonia.X11/X11Window.cs
  59. 2 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  60. 14 0
      src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
  61. 42 0
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
  62. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  63. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  64. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  65. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  66. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  67. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
  68. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
  69. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  70. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  71. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
  72. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
  73. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  74. 17 12
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  75. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
  76. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
  77. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
  78. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs
  79. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
  80. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  81. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  82. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
  83. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
  84. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
  85. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  86. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  87. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs
  88. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  89. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  90. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
  91. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs
  92. 12 0
      src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props
  93. 0 0
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  94. 0 36
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  95. 20 47
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  96. 1 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  97. 1 2
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  98. 7 4
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  99. 0 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  100. 1 1
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs

+ 1 - 1
.gitmodules

@@ -2,5 +2,5 @@
 	path = nukebuild/Numerge
 	url = https://github.com/kekekeks/Numerge.git
 [submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
-	path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
+	path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
 	url = https://github.com/kekekeks/XamlX.git

+ 27 - 0
Avalonia.sln

@@ -211,6 +211,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Av
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -1998,6 +2000,30 @@ Global
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
 		{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.Build.0 = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.Build.0 = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2056,6 +2082,7 @@ Global
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
+		{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 1 - 0
Directory.Build.props

@@ -1,5 +1,6 @@
 <Project>
   <PropertyGroup>
       <PackageOutputPath Condition="'$(PackageOutputPath)' == ''">$(MSBuildThisFileDirectory)build-intermediate/nuget</PackageOutputPath>
+      <AvaloniaPreviewerNetCoreToolPath>$(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll</AvaloniaPreviewerNetCoreToolPath>
   </PropertyGroup>
 </Project>

+ 1 - 1
dirs.proj

@@ -6,7 +6,7 @@
     <ProjectReference Include="packages/**/*.*proj" />
     <ProjectReference Remove="**/*.shproj" />
     <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
-    <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
+    <ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github/**/*.*proj" />
   </ItemGroup>
   <!--<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">-->
   <ItemGroup>

+ 12 - 4
native/Avalonia.Native/src/OSX/Screens.mm

@@ -4,6 +4,14 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
 {
     public:
     FORWARD_IUNKNOWN()
+    
+    private:
+    CGFloat PrimaryDisplayHeight()
+    {
+      return NSMaxY([[[NSScreen screens] firstObject] frame]);
+    }
+    
+public:
     virtual HRESULT GetScreenCount (int* ret) override
     {
         @autoreleasepool
@@ -25,15 +33,15 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
             
             auto screen = [[NSScreen screens] objectAtIndex:index];
             
-            ret->Bounds.X = [screen frame].origin.x;
-            ret->Bounds.Y = [screen frame].origin.y;
             ret->Bounds.Height = [screen frame].size.height;
             ret->Bounds.Width = [screen frame].size.width;
+            ret->Bounds.X = [screen frame].origin.x;
+            ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height;
             
-            ret->WorkingArea.X = [screen visibleFrame].origin.x;
-            ret->WorkingArea.Y = [screen visibleFrame].origin.y;
             ret->WorkingArea.Height = [screen visibleFrame].size.height;
             ret->WorkingArea.Width = [screen visibleFrame].size.width;
+            ret->WorkingArea.X = [screen visibleFrame].origin.x;
+            ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height;
             
             ret->PixelDensity = [screen backingScaleFactor];
             

+ 1 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _view.Visibility = ViewStates.Visible;
         }
 
-        public double Scaling => 1;
+        public double RenderScaling => 1;
 
         void Draw()
         {

+ 5 - 5
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -17,14 +17,14 @@
       <Compile Include="../Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaResourceXamlInfo.cs">
         <Link>Shared/AvaloniaResourceXamlInfo.cs</Link>
       </Compile>
-      <Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/**/*.cs">
+      <Compile Include="../Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/**/*.cs">
         <Link>XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
       <Compile Remove="external/cecil/**/*.*" />
-      <Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX\**\*.cs">
+      <Compile Include="../Markup/Avalonia.Markup.Xaml.Loader/xamlil.github\src\XamlX\**\*.cs">
         <Link>XamlIl/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
-      <Compile Include="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX.IL.Cecil\**\*.cs">
+      <Compile Include="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX.IL.Cecil\**\*.cs">
         <Link>XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
       <Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs">
@@ -57,8 +57,8 @@
       <Compile Include="../Avalonia.Base/Utilities/StyleClassParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
-      <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
-      <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
+      <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\**\obj\**\*.cs" />
+      <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
       <PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />
       <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
     </ItemGroup>

+ 6 - 0
src/Avalonia.Controls.DataGrid/DataGrid.cs

@@ -3922,6 +3922,12 @@ namespace Avalonia.Controls
                     dataGridCell: editingCell);
 
                 EditingRow.InvalidateDesiredHeight();
+                var column = editingCell.OwningColumn;
+                if (column.Width.IsSizeToCells || column.Width.IsAuto)
+                {// Invalidate desired width and force recalculation
+                    column.SetWidthDesiredValue(0);
+                    EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width);
+                }
             }
 
             // We're done, so raise the CellEditEnded event

+ 10 - 6
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@@ -88,7 +88,7 @@ namespace Avalonia.Controls
             AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), 
                 x => x.SelectedDate, (x, v) => x.SelectedDate = v);
 
-        //Template Items
+        // Template Items
         private Button _flyoutButton;
         private TextBlock _dayText;
         private TextBlock _monthText;
@@ -359,10 +359,14 @@ namespace Avalonia.Controls
                 }
             }
 
-            Grid.SetColumn(_spacer1, 1);
-            Grid.SetColumn(_spacer2, 3);
-            _spacer1.IsVisible = columnIndex > 1;
-            _spacer2.IsVisible = columnIndex > 2;
+            var isSpacer1Visible = columnIndex > 1;
+            var isSpacer2Visible = columnIndex > 2;
+            // ternary conditional operator is used to make sure grid cells will be validated
+            Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
+            Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+
+            _spacer1.IsVisible = isSpacer1Visible;
+            _spacer2.IsVisible = isSpacer2Visible;
         }
 
         private void SetSelectedDateText()
@@ -398,7 +402,7 @@ namespace Avalonia.Controls
 
             var deltaY = _presenter.GetOffsetForPopup();
 
-            //The extra 5 px I think is related to default popup placement behavior
+            // The extra 5 px I think is related to default popup placement behavior
             _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
                 Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
                  Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);

+ 17 - 14
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@@ -77,7 +77,7 @@ namespace Avalonia.Controls
             DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => 
             x.YearVisible, (x, v) => x.YearVisible = v);
 
-        //Template Items
+        // Template Items
         private Grid _pickerContainer;
         private Button _acceptButton;
         private Button _dismissButton;
@@ -107,7 +107,7 @@ namespace Avalonia.Controls
         private bool _yearVisible = true;
         private DateTimeOffset _syncDate;
 
-        private GregorianCalendar _calendar;
+        private readonly GregorianCalendar _calendar;
         private bool _suppressUpdateSelection;
 
         public DatePickerPresenter()
@@ -234,7 +234,7 @@ namespace Avalonia.Controls
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
             base.OnApplyTemplate(e);
-            //These are requirements, so throw if not found
+            // These are requirements, so throw if not found
             _pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
             _monthHost = e.NameScope.Get<Panel>("MonthHost");
             _dayHost = e.NameScope.Get<Panel>("DayHost");
@@ -326,7 +326,7 @@ namespace Avalonia.Controls
         /// </summary>
         private void InitPicker()
         {
-            //OnApplyTemplate must've been called before we can init here...
+            // OnApplyTemplate must've been called before we can init here...
             if (_pickerContainer == null)
                 return;
 
@@ -344,12 +344,11 @@ namespace Avalonia.Controls
 
             SetGrid();
 
-            //Date should've been set when we reach this point
+            // Date should've been set when we reach this point
             var dt = Date;
             if (DayVisible)
             {
-                GregorianCalendar gc = new GregorianCalendar();
-                var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month);
+                var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month);
                 _daySelector.MaximumValue = maxDays;
                 _daySelector.MinimumValue = 1;
                 _daySelector.SelectedValue = dt.Day;
@@ -407,10 +406,14 @@ namespace Avalonia.Controls
                 }
             }
 
-            Grid.SetColumn(_spacer1, 1);
-            Grid.SetColumn(_spacer2, 3);
-            _spacer1.IsVisible = columnIndex > 1;
-            _spacer2.IsVisible = columnIndex > 2;
+            var isSpacer1Visible = columnIndex > 1;
+            var isSpacer2Visible = columnIndex > 2;
+            // ternary conditional operator is used to make sure grid cells will be validated
+            Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0);
+            Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0);
+
+            _spacer1.IsVisible = isSpacer1Visible;
+            _spacer2.IsVisible = isSpacer2Visible;
         }
 
         private void SetInitialFocus()
@@ -433,12 +436,12 @@ namespace Avalonia.Controls
             }
         }
 
-        private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void OnDismissButtonClicked(object sender, RoutedEventArgs e)
         {
             OnDismiss();
         }
 
-        private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        private void OnAcceptButtonClicked(object sender, RoutedEventArgs e)
         {
             Date = _syncDate;
             OnConfirmed();
@@ -471,7 +474,7 @@ namespace Avalonia.Controls
 
             _syncDate = newDate;
 
-            //We don't need to update the days if not displaying day, not february
+            // We don't need to update the days if not displaying day, not february
             if (!DayVisible || _syncDate.Month != 2)
                 return;
 

+ 1 - 1
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
             }
         }
 
-        public double Scaling
+        public double RenderScaling
         {
             get { return _scaling; }
             set

+ 0 - 1
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators
             private readonly IDataTemplate _inner;
             public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
             public IControl Build(object param) => _inner.Build(param);
-            public bool SupportsRecycling => _inner.SupportsRecycling;
             public bool Match(object data) => _inner.Match(data);
             public InstancedBinding ItemsSelector(object item) => null;
         }

+ 3 - 3
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -23,10 +23,10 @@ namespace Avalonia.Platform
         Size ClientSize { get; }
 
         /// <summary>
-        /// Gets the scaling factor for the toplevel.
+        /// Gets the scaling factor for the toplevel. This is used for rendering.
         /// </summary>
-        double Scaling { get; }
-
+        double RenderScaling { get; }
+        
         /// <summary>
         /// The list of native platform's surfaces that can be consumed by rendering subsystems.
         /// </summary>

+ 5 - 0
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@@ -13,6 +13,11 @@ namespace Avalonia.Platform
         /// Hides the window.
         /// </summary>
         void Hide();
+        
+        /// <summary>
+        /// Gets the scaling factor for Window positioning and sizing.
+        /// </summary>
+        double DesktopScaling { get; }
 
         /// <summary>
         /// Gets the position of the window in device pixels.

+ 10 - 11
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters
 
         private IControl _child;
         private bool _createdChild;
-        private IDataTemplate _dataTemplate;
+        private IRecyclingDataTemplate _recyclingDataTemplate;
         private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
 
         /// <summary>
@@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters
         protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
         {
             base.OnAttachedToLogicalTree(e);
-            _dataTemplate = null;
+            _recyclingDataTemplate = null;
             _createdChild = false;
             InvalidateMeasure();
         }
@@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters
             {
                 var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
 
-                // We have content and it isn't a control, so if the new data template is the same
-                // as the old data template, try to recycle the existing child control to display
-                // the new data.
-                if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
+                if (dataTemplate is IRecyclingDataTemplate rdt)
                 {
-                    newChild = oldChild;
+                    var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null;
+                    newChild = rdt.Build(content, toRecycle);
+                    _recyclingDataTemplate = rdt;
                 }
                 else
                 {
-                    _dataTemplate = dataTemplate;
-                    newChild = _dataTemplate.Build(content);
+                    newChild = dataTemplate.Build(content);
+                    _recyclingDataTemplate = null;
                 }
             }
             else
             {
-                _dataTemplate = null;
+                _recyclingDataTemplate = null;
             }
 
             return newChild;
@@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters
                 LogicalChildren.Remove(Child);
                 ((ISetInheritanceParent)Child).SetParent(Child.Parent);
                 Child = null;
-                _dataTemplate = null;
+                _recyclingDataTemplate = null;
             }
 
             InvalidateMeasure();

+ 2 - 2
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs

@@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
 
         public void MoveAndResize(Point devicePoint, Size virtualSize)
         {
-            _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
+            _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.RenderScaling);
         }
 
-        public virtual double Scaling => _parent.Scaling;
+        public virtual double Scaling => _parent.DesktopScaling;
     }
 }

+ 0 - 2
src/Avalonia.Controls/Repeater/ElementFactory.cs

@@ -4,8 +4,6 @@ namespace Avalonia.Controls
 {
     public abstract class ElementFactory : IElementFactory
     {
-        bool IDataTemplate.SupportsRecycling => false;
-
         public IControl Build(object data)
         {
             return GetElementCore(new ElementFactoryGetArgs { Data = data });

+ 0 - 1
src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs

@@ -13,7 +13,6 @@ namespace Avalonia.Controls
 
         public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate;
 
-        public bool SupportsRecycling => false;
         public IControl Build(object param) => GetElement(null, param);
         public bool Match(object data) => _dataTemplate.Match(data);
 

+ 21 - 8
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates
     /// <summary>
     /// Builds a control for a piece of data.
     /// </summary>
-    public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate
+    public class FuncDataTemplate : FuncTemplate<object, IControl>, IRecyclingDataTemplate
     {
         /// <summary>
         /// The default data template used in the case where no matching data template is found.
@@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates
                 },
                 true);
 
-        /// <summary>
-        /// The implementation of the <see cref="Match"/> method.
-        /// </summary>
         private readonly Func<object, bool> _match;
+        private readonly bool _supportsRecycling;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="FuncDataTemplate"/> class.
@@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates
             Contract.Requires<ArgumentNullException>(match != null);
 
             _match = match;
-            SupportsRecycling = supportsRecycling;
+            _supportsRecycling = supportsRecycling;
         }
 
-        /// <inheritdoc/>
-        public bool SupportsRecycling { get; }
-
         /// <summary>
         /// Checks to see if this data template matches the specified data.
         /// </summary>
@@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates
             return _match(data);
         }
 
+        /// <summary>
+        /// Creates or recycles a control to display the specified data.
+        /// </summary>
+        /// <param name="data">The data to display.</param>
+        /// <param name="existing">An optional control to recycle.</param>
+        /// <returns>
+        /// The <paramref name="existing"/> control if supplied and applicable to
+        /// <paramref name="data"/>, otherwise a new control.
+        /// </returns>
+        /// <remarks>
+        /// The caller should ensure that any control passed to <paramref name="existing"/>
+        /// originated from the same data template.
+        /// </remarks>
+        public IControl Build(object data, IControl existing)
+        {
+            return _supportsRecycling && existing is object ? existing : Build(data);
+        }
+
         /// <summary>
         /// Determines of an object is of the specified type.
         /// </summary>

+ 3 - 3
src/Avalonia.Controls/Templates/FuncTemplate`2.cs

@@ -1,5 +1,7 @@
 using System;
 
+#nullable enable
+
 namespace Avalonia.Controls.Templates
 {
     /// <summary>
@@ -18,9 +20,7 @@ namespace Avalonia.Controls.Templates
         /// <param name="func">The function used to create the control.</param>
         public FuncTemplate(Func<TParam, INameScope, TControl> func)
         {
-            Contract.Requires<ArgumentNullException>(func != null);
-
-            _func = func;
+            _func = func ?? throw new ArgumentNullException(nameof(func));
         }
 
         /// <summary>

+ 5 - 7
src/Avalonia.Controls/Templates/IDataTemplate.cs

@@ -1,3 +1,7 @@
+using System;
+
+#nullable enable
+
 namespace Avalonia.Controls.Templates
 {
     /// <summary>
@@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates
     /// </summary>
     public interface IDataTemplate : ITemplate<object, IControl>
     {
-        /// <summary>
-        /// Gets a value indicating whether the data template supports recycling of the generated
-        /// control.
-        /// </summary>
-        bool SupportsRecycling { get; }
-
         /// <summary>
         /// Checks to see if this data template matches the specified data.
         /// </summary>
@@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates
         /// </returns>
         bool Match(object data);
     }
-}
+}

+ 25 - 0
src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs

@@ -0,0 +1,25 @@
+#nullable enable
+
+namespace Avalonia.Controls.Templates
+{
+    /// <summary>
+    /// An <see cref="IDataTemplate"/> that supports recycling existing elements.
+    /// </summary>
+    public interface IRecyclingDataTemplate : IDataTemplate
+    {
+        /// <summary>
+        /// Creates or recycles a control to display the specified data.
+        /// </summary>
+        /// <param name="data">The data to display.</param>
+        /// <param name="existing">An optional control to recycle.</param>
+        /// <returns>
+        /// The <paramref name="existing"/> control if supplied and applicable to
+        /// <paramref name="data"/>, otherwise a new control.
+        /// </returns>
+        /// <remarks>
+        /// The caller should ensure that any control passed to <paramref name="existing"/>
+        /// originated from the same data template.
+        /// </remarks>
+        IControl Build(object data, IControl? existing);
+    }
+}

+ 1 - 1
src/Avalonia.Controls/TextBox.cs

@@ -134,7 +134,7 @@ namespace Avalonia.Controls
                 {
                     if (acceptsReturn)
                     {
-                        return wrapping == TextWrapping.NoWrap ?
+                        return wrapping != TextWrapping.Wrap ?
                             ScrollBarVisibility.Auto :
                             ScrollBarVisibility.Disabled;
                     }

+ 10 - 15
src/Avalonia.Controls/TickBar.cs

@@ -39,11 +39,15 @@ namespace Avalonia.Controls
     {
         static TickBar()
         {
-            AffectsRender<TickBar>(ReservedSpaceProperty,
+            AffectsRender<TickBar>(FillProperty,
+                                   IsDirectionReversedProperty,
+                                   ReservedSpaceProperty,
                                    MaximumProperty,
                                    MinimumProperty,
                                    OrientationProperty,
-                                   TickFrequencyProperty);
+                                   PlacementProperty,
+                                   TickFrequencyProperty,
+                                   TicksProperty);
         }
 
         public TickBar() : base()
@@ -137,7 +141,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// The Ticks property contains collection of value of type Double which
         /// are the logical positions use to draw the ticks.
-        /// The property value is a <see cref="DoubleCollection" />.
+        /// The property value is a <see cref="AvaloniaList{T}" />.
         /// </summary>
         public AvaloniaList<double> Ticks
         {
@@ -169,7 +173,6 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<TickBarPlacement> PlacementProperty =
             AvaloniaProperty.Register<TickBar, TickBarPlacement>(nameof(Placement), 0d);
 
-
         /// <summary>
         /// Placement property specified how the Tick will be placed.
         /// This property affects the way ticks are drawn.
@@ -189,7 +192,7 @@ namespace Avalonia.Controls
 
         /// <summary>
         /// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or
-        /// tob and bottom spacing (for vertical orienation).
+        /// top and bottom spacing (for vertical orienation).
         /// The space on both sides of TickBar is half of specified ReservedSpace.
         /// This property has type of <see cref="Rect" />.
         /// </summary>
@@ -201,7 +204,7 @@ namespace Avalonia.Controls
 
         /// <summary>
         /// Draw ticks.
-        /// Ticks can be draw in 8 diffrent ways depends on Placment property and IsDirectionReversed property.
+        /// Ticks can be draw in 8 different ways depends on Placement property and IsDirectionReversed property.
         ///
         /// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and
         /// SelectionStart and SelectionEnd are valid.
@@ -211,9 +214,7 @@ namespace Avalonia.Controls
         ///
         /// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size.
         ///
-        /// Brush that use to fill ticks is specified by Shape.Fill property.
-        ///
-        /// Pen that use to draw ticks is specified by Shape.Pen property.
+        /// Brush that use to fill ticks is specified by Fill property.
         /// </summary>
         public override void Render(DrawingContext dc)
         {
@@ -222,7 +223,6 @@ namespace Avalonia.Controls
             var tickLen = 0.0d;   // Height for Primary Tick (for Mininum and Maximum value)
             var tickLen2 = 0.0d;  // Height for Secondary Tick
             var logicalToPhysical = 1.0;
-            var progression = 1.0d;
             var startPoint = new Point();
             var endPoint = new Point();
             var rSpace = Orientation == Orientation.Horizontal ? ReservedSpace.Width : ReservedSpace.Height;
@@ -242,7 +242,6 @@ namespace Avalonia.Controls
                     startPoint = new Point(halfReservedSpace, size.Height);
                     endPoint = new Point(halfReservedSpace + size.Width, size.Height);
                     logicalToPhysical = size.Width / range;
-                    progression = 1;
                     break;
 
                 case TickBarPlacement.Bottom:
@@ -255,7 +254,6 @@ namespace Avalonia.Controls
                     startPoint = new Point(halfReservedSpace, 0d);
                     endPoint = new Point(halfReservedSpace + size.Width, 0d);
                     logicalToPhysical = size.Width / range;
-                    progression = 1;
                     break;
 
                 case TickBarPlacement.Left:
@@ -269,7 +267,6 @@ namespace Avalonia.Controls
                     startPoint = new Point(size.Width, size.Height + halfReservedSpace);
                     endPoint = new Point(size.Width, halfReservedSpace);
                     logicalToPhysical = size.Height / range * -1;
-                    progression = -1;
                     break;
 
                 case TickBarPlacement.Right:
@@ -282,7 +279,6 @@ namespace Avalonia.Controls
                     startPoint = new Point(0d, size.Height + halfReservedSpace);
                     endPoint = new Point(0d, halfReservedSpace);
                     logicalToPhysical = size.Height / range * -1;
-                    progression = -1;
                     break;
             };
 
@@ -291,7 +287,6 @@ namespace Avalonia.Controls
             // Invert direciton of the ticks
             if (IsDirectionReversed)
             {
-                progression *= -progression;
                 logicalToPhysical *= -1;
 
                 // swap startPoint & endPoint

+ 2 - 2
src/Avalonia.Controls/TopLevel.cs

@@ -280,10 +280,10 @@ namespace Avalonia.Controls
         }
 
         /// <inheritdoc/>
-        double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
+        double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1;
 
         /// <inheritdoc/>
-        double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1;
+        double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1;
 
         IStyleHost IStyleHost.StylingParent => _globalStyles;
 

+ 1 - 1
src/Avalonia.Controls/Window.cs

@@ -818,7 +818,7 @@ namespace Avalonia.Controls
 
         private void SetWindowStartupLocation(IWindowBaseImpl owner = null)
         {
-            var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1;
+            var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1;
 
             // TODO: We really need non-client size here.
             var rect = new PixelRect(

+ 4 - 3
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@@ -18,10 +18,11 @@ namespace Avalonia.DesignerSupport
             Control control;
             using (PlatformManager.DesignerMode())
             {
-                var loader = new AvaloniaXamlLoader() {IsDesignMode = true};
+                var loader = AvaloniaLocator.Current.GetService<AvaloniaXamlLoader.IRuntimeXamlLoader>();
                 var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
 
-
+                if (loader == null)
+                    throw new XamlLoadException("Runtime XAML loader is not registered");
                 
                 Uri baseUri = null;
                 if (assemblyPath != null)
@@ -34,7 +35,7 @@ namespace Avalonia.DesignerSupport
                 }
 
                 var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null;
-                var loaded = loader.Load(stream, localAsm, null, baseUri);
+                var loaded = loader.Load(stream, localAsm, null, baseUri, true);
                 var style = loaded as IStyle;
                 if (style != null)
                 {

+ 8 - 2
src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs

@@ -9,12 +9,14 @@ namespace Avalonia.DesignerSupport.Remote
 {
     class FileWatcherTransport : IAvaloniaRemoteTransportConnection, ITransportWithEnforcedMethod
     {
+        private readonly string _appPath;
         private string _path;
         private string _lastContents;
         private bool _disposed;
 
-        public FileWatcherTransport(Uri file)
+        public FileWatcherTransport(Uri file, string appPath)
         {
+            _appPath = appPath;
             _path = file.LocalPath;
         }
 
@@ -73,7 +75,11 @@ namespace Avalonia.DesignerSupport.Remote
                 {
                     Console.WriteLine("Triggering XAML update");
                     _lastContents = data;
-                    _onMessage?.Invoke(this, new UpdateXamlMessage { Xaml = data });
+                    _onMessage?.Invoke(this, new UpdateXamlMessage
+                    {
+                        Xaml = data,
+                        AssemblyPath = _appPath
+                    });
                 }
 
                 await Task.Delay(100);

+ 21 - 7
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json

@@ -3564,12 +3564,14 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -3584,17 +3586,20 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -3711,7 +3716,8 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -3723,6 +3729,7 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -3737,6 +3744,7 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -3744,12 +3752,14 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -3768,6 +3778,7 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -3848,7 +3859,8 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -3860,6 +3872,7 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -3981,6 +3994,7 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",

+ 1 - 0
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@@ -36,6 +36,7 @@ namespace Avalonia.DesignerSupport.Remote
         {
         }
 
+        public double DesktopScaling => 1.0;
         public PixelPoint Position { get; set; }
         public Action<PixelPoint> PositionChanged { get; set; }
         public Action Deactivated { get; set; }

+ 4 - 3
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@@ -112,8 +112,9 @@ namespace Avalonia.DesignerSupport.Remote
             return rv;
         }
 
-        static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport)
+        static IAvaloniaRemoteTransportConnection CreateTransport(CommandLineArgs args)
         {
+            var transport = args.Transport;
             if (transport.Scheme == "tcp-bson")
             {
                 return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result;
@@ -121,7 +122,7 @@ namespace Avalonia.DesignerSupport.Remote
 
             if (transport.Scheme == "file")
             {
-                return new FileWatcherTransport(transport);
+                return new FileWatcherTransport(transport, args.AppPath);
             }
             PrintUsage();
             return null;
@@ -160,7 +161,7 @@ namespace Avalonia.DesignerSupport.Remote
         public static void Main(string[] cmdline)
         {
             var args = ParseCommandLineArgs(cmdline);
-            var transport = CreateTransport(args.Transport);
+            var transport = CreateTransport(args);
             if (transport is ITransportWithEnforcedMethod enforcedMethod)
                 args.Method = enforcedMethod.PreviewerMethod;
             var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath));

+ 2 - 1
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -21,7 +21,8 @@ namespace Avalonia.DesignerSupport.Remote
         public IPlatformHandle Handle { get; }
         public Size MaxAutoSizeHint { get; }
         public Size ClientSize { get; }
-        public double Scaling { get; } = 1.0;
+        public double RenderScaling { get; } = 1.0;
+        public double DesktopScaling => 1.0;
         public IEnumerable<object> Surfaces { get; }
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }

+ 0 - 2
src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs

@@ -7,8 +7,6 @@ namespace Avalonia.Diagnostics
 {
     internal class ViewLocator : IDataTemplate
     {
-        public bool SupportsRecycling => false;
-
         public IControl Build(object data)
         {
             var name = data.GetType().FullName.Replace("ViewModel", "View");

+ 5 - 4
src/Avalonia.Headless/HeadlessWindowImpl.cs

@@ -41,7 +41,8 @@ namespace Avalonia.Headless
         }
 
         public Size ClientSize { get; set; }
-        public double Scaling { get; } = 1;
+        public double RenderScaling { get; } = 1;
+        public double DesktopScaling => RenderScaling;
         public IEnumerable<object> Surfaces { get; }
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }
@@ -62,9 +63,9 @@ namespace Avalonia.Headless
 
         public IInputRoot InputRoot { get; set; }
 
-        public Point PointToClient(PixelPoint point) => point.ToPoint(Scaling);
+        public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling);
 
-        public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, Scaling);
+        public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);
 
         public void SetCursor(IPlatformHandle cursor)
         {
@@ -201,7 +202,7 @@ namespace Avalonia.Headless
 
         public ILockedFramebuffer Lock()
         {
-            var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, Scaling), new Vector(96, 96) * Scaling);
+            var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, RenderScaling), new Vector(96, 96) * RenderScaling);
             var fb = bmp.Lock();
             return new FramebufferProxy(fb, () =>
             {

+ 0 - 15
src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs

@@ -1,15 +0,0 @@
-using Avalonia.Controls.Primitives.PopupPositioning;
-using Avalonia.Platform;
-
-namespace Avalonia.Native
-{
-    class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper
-    {
-        public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize)
-        {
-
-        }
-
-        public override double Scaling => 1;
-    }
-}

+ 1 - 1
src/Avalonia.Native/PopupImpl.cs

@@ -26,7 +26,7 @@ namespace Avalonia.Native
                 var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
                 Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context);
             }
-            PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize));
+            PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
         }
 
         private void MoveResize(PixelPoint position, Size size, double scaling)

+ 5 - 3
src/Avalonia.Native/WindowImplBase.cs

@@ -81,7 +81,7 @@ namespace Avalonia.Native
                 _glSurface = new GlPlatformSurface(window, _glContext);
             Screen = new ScreenImpl(screens);
             _savedLogicalSize = ClientSize;
-            _savedScaling = Scaling;
+            _savedScaling = RenderScaling;
             _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
 
             var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
@@ -369,7 +369,9 @@ namespace Avalonia.Native
             _native.SetTopMost(value);
         }
 
-        public double Scaling => _native?.GetScaling() ?? 1;
+        public double RenderScaling => _native?.GetScaling() ?? 1;
+
+        public double DesktopScaling => 1;
 
         public Action Deactivated { get; set; }
         public Action Activated { get; set; }
@@ -432,7 +434,7 @@ namespace Avalonia.Native
 
                 TransparencyLevel = transparencyLevel;
 
-                _native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur);
+                _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur);
                 TransparencyLevelChanged?.Invoke(TransparencyLevel);
             }
         }

+ 2 - 0
src/Avalonia.Themes.Default/TextBox.xaml

@@ -40,6 +40,8 @@
                   <TextBlock Name="watermark"
                              Opacity="0.5"
                              Text="{TemplateBinding Watermark}"
+                             TextAlignment="{TemplateBinding TextAlignment}"
+                             TextWrapping="{TemplateBinding TextWrapping}"
                              IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
                   <TextPresenter Name="PART_TextPresenter"
                                  Text="{TemplateBinding Text, Mode=TwoWay}"

+ 51 - 56
src/Avalonia.Themes.Fluent/TextBox.xaml

@@ -10,71 +10,65 @@
     <Setter Property="SelectionBrush" Value="{DynamicResource TextControlSelectionHighlightColor}" />
     <Setter Property="BorderThickness" Value="{DynamicResource TextControlBorderThemeThickness}" />    
     <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
-    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
-    <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
     <Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
     <Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
     <Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
     <Setter Property="Template">
       <ControlTemplate>
-        <Grid RowDefinitions="Auto, *, Auto" ColumnDefinitions="*, Auto">
+        <DockPanel>
 
           <!-- TODO bind Content -> Header and ContentTemplate -> HeaderTemplate -->
           <ContentPresenter x:Name="HeaderContentPresenter"
-                            Grid.Row="0"
-                            Grid.Column="0"
-                            Grid.ColumnSpan="2"
+                            DockPanel.Dock="Top"
                             TextBlock.FontWeight="Normal"
                             TextBlock.Foreground="{DynamicResource TextControlHeaderForeground}"
                             IsVisible="False"
                             Margin="{DynamicResource TextBoxTopHeaderMargin}" />
-          
-          <Border Name="border"
-                  Grid.Row="1"
-                  Grid.Column="0"
-                  Grid.RowSpan="1"
-                  Grid.ColumnSpan="2"
-                  Background="{TemplateBinding Background}"
-                  BorderBrush="{TemplateBinding BorderBrush}"
-                  BorderThickness="{TemplateBinding BorderThickness}"
-                  CornerRadius="{DynamicResource ControlCornerRadius}"
-                  Margin="{TemplateBinding BorderThickness}"
-                  MinWidth="{TemplateBinding MinWidth}"
-                  MinHeight="{TemplateBinding MinHeight}">
-          </Border>
-
-          <Border Padding="{TemplateBinding Padding}"
-            Grid.Row="1"
-            Grid.Column="0"
-            Margin="{TemplateBinding BorderThickness}">
-            <DockPanel>
-              <TextBlock Name="floatingWatermark"
-                         Foreground="{DynamicResource SystemAccentColor}"
-                         FontSize="{DynamicResource FontSizeSmall}"
-                         Text="{TemplateBinding Watermark}"
-                         DockPanel.Dock="Top">
-                <TextBlock.IsVisible>
-                  <MultiBinding Converter="{x:Static BoolConverters.And}">
-                    <Binding RelativeSource="{RelativeSource TemplatedParent}"
-                             Path="UseFloatingWatermark"/>
-                    <Binding RelativeSource="{RelativeSource TemplatedParent}"
-                             Path="Text"
-                             Converter="{x:Static StringConverters.IsNotNullOrEmpty}"/>
-                  </MultiBinding>
-                </TextBlock.IsVisible>
-              </TextBlock>
-
-              <DataValidationErrors>
-                <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
-                              VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
-
-                  <Panel>
-                    <TextBlock Name="watermark"
+
+          <Panel>
+            <Border
+              Name="border"
+              Background="{TemplateBinding Background}"
+              BorderBrush="{TemplateBinding BorderBrush}"
+              BorderThickness="{TemplateBinding BorderThickness}"
+              CornerRadius="{DynamicResource ControlCornerRadius}"
+              MinWidth="{TemplateBinding MinWidth}"
+              MinHeight="{TemplateBinding MinHeight}">
+            </Border>
+
+            <Border
+              Padding="{TemplateBinding Padding}"
+              Margin="{TemplateBinding BorderThickness}">
+              <DockPanel>
+                <TextBlock Name="floatingWatermark"
+                           Foreground="{DynamicResource SystemAccentColor}"
+                           FontSize="{DynamicResource FontSizeSmall}"
+                           Text="{TemplateBinding Watermark}"
+                           DockPanel.Dock="Top">
+                  <TextBlock.IsVisible>
+                      <MultiBinding Converter="{x:Static BoolConverters.And}">
+                        <Binding RelativeSource="{RelativeSource TemplatedParent}"
+                               Path="UseFloatingWatermark"/>
+                        <Binding RelativeSource="{RelativeSource TemplatedParent}"
+                               Path="Text"
+                               Converter="{x:Static StringConverters.IsNotNullOrEmpty}"/>
+                      </MultiBinding>
+                  </TextBlock.IsVisible>
+                </TextBlock>
+
+                <DataValidationErrors>
+                  <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
+                                VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
+
+                    <Panel>
+                      <TextBlock Name="watermark"
                                Opacity="0.5"
                                Text="{TemplateBinding Watermark}"
+                               TextAlignment="{TemplateBinding TextAlignment}"
+                               TextWrapping="{TemplateBinding TextWrapping}"
                                IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
-                    <!-- TODO eliminate this margin... text layout issue? -->
-                    <TextPresenter Name="PART_TextPresenter"
+                      <!-- TODO eliminate this margin... text layout issue? -->
+                      <TextPresenter Name="PART_TextPresenter"
                                    Margin="0 1 0 0"
                                    Text="{TemplateBinding Text, Mode=TwoWay}"
                                    CaretIndex="{TemplateBinding CaretIndex}"
@@ -86,12 +80,13 @@
                                    SelectionBrush="{TemplateBinding SelectionBrush}"
                                    SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
                                    CaretBrush="{TemplateBinding CaretBrush}"/>
-                  </Panel>
-                </ScrollViewer>
-              </DataValidationErrors>
-            </DockPanel>
-          </Border>
-        </Grid>
+                    </Panel>
+                  </ScrollViewer>
+                </DataValidationErrors>
+              </DockPanel>
+            </Border>
+          </Panel>
+        </DockPanel>
       </ControlTemplate>
     </Setter>
   </Style>

+ 10 - 5
src/Avalonia.Visuals/Media/Color.cs

@@ -89,6 +89,11 @@ namespace Avalonia.Media
         /// <returns>The <see cref="Color"/>.</returns>
         public static Color Parse(string s)
         {
+            if (s is null)
+            {
+                throw new ArgumentNullException(nameof(s));
+            }
+
             if (TryParse(s, out Color color))
             {
                 return color;
@@ -120,14 +125,16 @@ namespace Avalonia.Media
         /// <returns>The status of the operation.</returns>
         public static bool TryParse(string s, out Color color)
         {
-            if (s == null)
+            color = default;
+
+            if (s is null)
             {
-                throw new ArgumentNullException(nameof(s));
+                return false;
             }
 
             if (s.Length == 0)
             {
-                throw new FormatException();
+                return false;
             }
 
             if (s[0] == '#' && TryParseInternal(s.AsSpan(), out color))
@@ -144,8 +151,6 @@ namespace Avalonia.Media
                 return true;
             }
 
-            color = default;
-
             return false;
         }
 

+ 1 - 14
src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs

@@ -4,14 +4,12 @@
     {
         private TextAlignment _textAlignment;
         private TextWrapping _textWrapping;
-        private TextTrimming _textTrimming;
         private double _lineHeight;
 
         public GenericTextParagraphProperties(
             TextRunProperties defaultTextRunProperties,
             TextAlignment textAlignment = TextAlignment.Left,
-            TextWrapping textWrapping = TextWrapping.WrapWithOverflow,
-            TextTrimming textTrimming = TextTrimming.None,
+            TextWrapping textWrapping = TextWrapping.NoWrap,
             double lineHeight = 0)
         {
             DefaultTextRunProperties = defaultTextRunProperties;
@@ -20,8 +18,6 @@
 
             _textWrapping = textWrapping;
 
-            _textTrimming = textTrimming;
-
             _lineHeight = lineHeight;
         }
 
@@ -31,8 +27,6 @@
 
         public override TextWrapping TextWrapping => _textWrapping;
 
-        public override TextTrimming TextTrimming => _textTrimming;
-
         public override double LineHeight => _lineHeight;
 
         /// <summary>
@@ -50,13 +44,6 @@
         {
             _textWrapping = textWrapping;
         }
-        /// <summary>
-        /// Set text trimming
-        /// </summary>
-        internal void SetTextTrimming(TextTrimming textTrimming)
-        {
-            _textTrimming = textTrimming;
-        }
 
         /// <summary>
         /// Set line height

+ 1 - 2
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@@ -1,5 +1,4 @@
-using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Utilities;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media.TextFormatting
 {

+ 23 - 0
src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs

@@ -0,0 +1,23 @@
+namespace Avalonia.Media.TextFormatting
+{
+    /// <summary>
+    /// Properties of text collapsing
+    /// </summary>
+    public abstract class TextCollapsingProperties
+    {
+        /// <summary>
+        /// Gets the width in which the collapsible range is constrained to
+        /// </summary>
+        public abstract double Width { get; }
+
+        /// <summary>
+        /// Gets the text run that is used as collapsing symbol
+        /// </summary>
+        public abstract TextRun Symbol { get; }
+
+        /// <summary>
+        /// Gets the style of collapsing
+        /// </summary>
+        public abstract TextCollapsingStyle Style { get; }
+    }
+}

+ 18 - 0
src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs

@@ -0,0 +1,18 @@
+namespace Avalonia.Media.TextFormatting
+{
+    /// <summary>
+    /// Text collapsing style
+    /// </summary>
+    public enum TextCollapsingStyle
+    {
+        /// <summary>
+        /// Collapse trailing characters
+        /// </summary>
+        TrailingCharacter,
+
+        /// <summary>
+        /// Collapse trailing words
+        /// </summary>
+        TrailingWord,
+    }
+}

+ 196 - 307
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@@ -1,52 +1,194 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using Avalonia.Media.TextFormatting.Unicode;
-using Avalonia.Platform;
-using Avalonia.Utilities;
 
 namespace Avalonia.Media.TextFormatting
 {
     internal class TextFormatterImpl : TextFormatter
     {
-        private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
-
         /// <inheritdoc cref="TextFormatter.FormatLine"/>
         public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
             TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null)
         {
-            var textTrimming = paragraphProperties.TextTrimming;
             var textWrapping = paragraphProperties.TextWrapping;
-            TextLine textLine = null;
 
             var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak);
 
             var textRange = GetTextRange(textRuns);
 
-            if (textTrimming != TextTrimming.None)
+            TextLine textLine;
+
+            switch (textWrapping)
+            {
+                case TextWrapping.NoWrap:
+                    {
+                        var textLineMetrics =
+                            TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties);
+
+                        textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak);
+                        break;
+                    }
+                case TextWrapping.WrapWithOverflow:
+                case TextWrapping.Wrap:
+                    {
+                        textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
+                        break;
+                    }
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+
+            return textLine;
+        }
+
+        /// <summary>
+        /// Measures the number of characters that fits into available width.
+        /// </summary>
+        /// <param name="textCharacters">The text run.</param>
+        /// <param name="availableWidth">The available width.</param>
+        /// <returns></returns>
+        internal static int MeasureCharacters(ShapedTextCharacters textCharacters, double availableWidth)
+        {
+            var glyphRun = textCharacters.GlyphRun;
+
+            if (glyphRun.Bounds.Width < availableWidth)
             {
-                textLine = PerformTextTrimming(textRuns, textRange, paragraphWidth, paragraphProperties);
+                return glyphRun.Characters.Length;
+            }
+
+            var glyphCount = 0;
+
+            var currentWidth = 0.0;
+
+            if (glyphRun.GlyphAdvances.IsEmpty)
+            {
+                var glyphTypeface = glyphRun.GlyphTypeface;
+
+                for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
+                {
+                    var glyph = glyphRun.GlyphIndices[i];
+
+                    var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
+
+                    if (currentWidth + advance > availableWidth)
+                    {
+                        break;
+                    }
+
+                    currentWidth += advance;
+
+                    glyphCount++;
+                }
             }
             else
             {
-                switch (textWrapping)
+                foreach (var advance in glyphRun.GlyphAdvances)
                 {
-                    case TextWrapping.NoWrap:
-                        {
-                            var textLineMetrics =
-                                TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties);
+                    if (currentWidth + advance > availableWidth)
+                    {
+                        break;
+                    }
 
-                            textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak);
-                            break;
+                    currentWidth += advance;
+
+                    glyphCount++;
+                }
+            }
+
+            if (glyphCount == glyphRun.GlyphIndices.Length)
+            {
+                return glyphRun.Characters.Length;
+            }
+
+            if (glyphRun.GlyphClusters.IsEmpty)
+            {
+                return glyphCount;
+            }
+
+            var firstCluster = glyphRun.GlyphClusters[0];
+
+            var lastCluster = glyphRun.GlyphClusters[glyphCount];
+
+            return lastCluster - firstCluster;
+        }
+
+        /// <summary>
+        /// Split a sequence of runs into two segments at specified length.
+        /// </summary>
+        /// <param name="textRuns">The text run's.</param>
+        /// <param name="length">The length to split at.</param>
+        /// <returns>The split text runs.</returns>
+        internal static SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextCharacters> textRuns, int length)
+        {
+            var currentLength = 0;
+
+            for (var i = 0; i < textRuns.Count; i++)
+            {
+                var currentRun = textRuns[i];
+
+                if (currentLength + currentRun.GlyphRun.Characters.Length < length)
+                {
+                    currentLength += currentRun.GlyphRun.Characters.Length;
+                    continue;
+                }
+
+                var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
+
+                var first = new ShapedTextCharacters[firstCount];
+
+                if (firstCount > 1)
+                {
+                    for (var j = 0; j < i; j++)
+                    {
+                        first[j] = textRuns[j];
+                    }
+                }
+
+                var secondCount = textRuns.Count - firstCount;
+
+                if (currentLength + currentRun.GlyphRun.Characters.Length == length)
+                {
+                    var second = new ShapedTextCharacters[secondCount];
+
+                    var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
+
+                    if (secondCount > 0)
+                    {
+                        for (var j = 0; j < secondCount; j++)
+                        {
+                            second[j] = textRuns[i + j + offset];
                         }
-                    case TextWrapping.WrapWithOverflow:
-                    case TextWrapping.Wrap:
+                    }
+
+                    first[i] = currentRun;
+
+                    return new SplitTextRunsResult(first, second);
+                }
+                else
+                {
+                    secondCount++;
+
+                    var second = new ShapedTextCharacters[secondCount];
+
+                    if (secondCount > 0)
+                    {
+                        for (var j = 1; j < secondCount; j++)
                         {
-                            textLine = PerformTextWrapping(textRuns, textRange, paragraphWidth, paragraphProperties);
-                            break;
+                            second[j] = textRuns[i + j];
                         }
+                    }
+
+                    var split = currentRun.Split(length - currentLength);
+
+                    first[i] = split.First;
+
+                    second[0] = split.Second;
+
+                    return new SplitTextRunsResult(first, second);
                 }
             }
 
-            return textLine;
+            return new SplitTextRunsResult(textRuns, null);
         }
 
         /// <summary>
@@ -174,87 +316,6 @@ namespace Avalonia.Media.TextFormatting
             return false;
         }
 
-        /// <summary>
-        /// Performs text trimming and returns a trimmed line.
-        /// </summary>
-        /// <param name="textRuns">The text runs to perform the trimming on.</param>
-        /// <param name="textRange">The text range that is covered by the text runs.</param>
-        /// <param name="paragraphWidth">A <see cref="double"/> value that specifies the width of the paragraph that the line fills.</param>
-        /// <param name="paragraphProperties">A <see cref="TextParagraphProperties"/> value that represents paragraph properties,
-        /// such as TextWrapping, TextAlignment, or TextStyle.</param>
-        /// <returns></returns>
-        private static TextLine PerformTextTrimming(IReadOnlyList<ShapedTextCharacters> textRuns, TextRange textRange,
-            double paragraphWidth, TextParagraphProperties paragraphProperties)
-        {
-            var textTrimming = paragraphProperties.TextTrimming;
-            var availableWidth = paragraphWidth;
-            var currentWidth = 0.0;
-            var runIndex = 0;
-
-            while (runIndex < textRuns.Count)
-            {
-                var currentRun = textRuns[runIndex];
-
-                currentWidth += currentRun.GlyphRun.Bounds.Width;
-
-                if (currentWidth > availableWidth)
-                {
-                    var ellipsisRun = CreateEllipsisRun(currentRun.Properties);
-
-                    var measuredLength = MeasureText(currentRun, availableWidth - ellipsisRun.GlyphRun.Bounds.Width);
-
-                    if (textTrimming == TextTrimming.WordEllipsis)
-                    {
-                        if (measuredLength < textRange.End)
-                        {
-                            var currentBreakPosition = 0;
-
-                            var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
-                            while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
-                            {
-                                var nextBreakPosition = lineBreaker.Current.PositionWrap;
-
-                                if (nextBreakPosition == 0)
-                                {
-                                    break;
-                                }
-
-                                if (nextBreakPosition > measuredLength)
-                                {
-                                    break;
-                                }
-
-                                currentBreakPosition = nextBreakPosition;
-                            }
-
-                            measuredLength = currentBreakPosition;
-                        }
-                    }
-
-                    var splitResult = SplitTextRuns(textRuns, measuredLength);
-
-                    var trimmedRuns = new List<ShapedTextCharacters>(splitResult.First.Count + 1);
-
-                    trimmedRuns.AddRange(splitResult.First);
-
-                    trimmedRuns.Add(ellipsisRun);
-
-                    var textLineMetrics =
-                        TextLineMetrics.Create(trimmedRuns, textRange, paragraphWidth, paragraphProperties);
-
-                    return new TextLineImpl(trimmedRuns, textLineMetrics);
-                }
-
-                availableWidth -= currentRun.GlyphRun.Bounds.Width;
-
-                runIndex++;
-            }
-
-            return new TextLineImpl(textRuns,
-                TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
-        }
-
         /// <summary>
         /// Performs text wrapping returns a list of text lines.
         /// </summary>
@@ -269,7 +330,7 @@ namespace Avalonia.Media.TextFormatting
             var availableWidth = paragraphWidth;
             var currentWidth = 0.0;
             var runIndex = 0;
-            var length = 0;
+            var currentLength = 0;
 
             while (runIndex < textRuns.Count)
             {
@@ -277,60 +338,55 @@ namespace Avalonia.Media.TextFormatting
 
                 if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
                 {
-                    var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth);
+                    var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
+
+                    var breakFound = false;
+
+                    var currentBreakPosition = 0;
 
                     if (measuredLength < currentRun.Text.Length)
                     {
-                        if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
-                        {
-                            var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(measuredLength));
+                        var lineBreaker = new LineBreakEnumerator(currentRun.Text);
 
-                            if (lineBreaker.MoveNext())
-                            {
-                                measuredLength += lineBreaker.Current.PositionWrap;
-                            }
-                            else
-                            {
-                                measuredLength = currentRun.Text.Length;
-                            }
-                        }
-                        else
+                        while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
                         {
-                            var currentBreakPosition = -1;
+                            var nextBreakPosition = lineBreaker.Current.PositionWrap;
 
-                            var lineBreaker = new LineBreakEnumerator(currentRun.Text);
-
-                            while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+                            if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
                             {
-                                var nextBreakPosition = lineBreaker.Current.PositionWrap;
+                                break;
+                            }
 
-                                if (nextBreakPosition == 0)
-                                {
-                                    break;
-                                }
+                            breakFound = lineBreaker.Current.Required ||
+                                         lineBreaker.Current.PositionWrap != currentRun.Text.Length;
 
-                                if (nextBreakPosition > measuredLength)
-                                {
-                                    break;
-                                }
+                            currentBreakPosition = nextBreakPosition;
+                        }
+                    }
 
-                                currentBreakPosition = nextBreakPosition;
-                            }
+                    if (breakFound)
+                    {
+                        measuredLength = currentBreakPosition;
+                    }
+                    else
+                    {
+                        if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow)
+                        {
+                            var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(currentBreakPosition));
 
-                            if (currentBreakPosition != -1)
+                            if (lineBreaker.MoveNext())
                             {
-                                measuredLength = currentBreakPosition;
+                                measuredLength = currentBreakPosition + lineBreaker.Current.PositionWrap;
                             }
-
                         }
                     }
 
-                    length += measuredLength;
+                    currentLength += measuredLength;
 
-                    var splitResult = SplitTextRuns(textRuns, length);
+                    var splitResult = SplitTextRuns(textRuns, currentLength);
 
                     var textLineMetrics = TextLineMetrics.Create(splitResult.First,
-                        new TextRange(textRange.Start, length), paragraphWidth, paragraphProperties);
+                        new TextRange(textRange.Start, currentLength), paragraphWidth, paragraphProperties);
 
                     var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ?
                         new TextLineBreak(splitResult.Second) :
@@ -341,7 +397,7 @@ namespace Avalonia.Media.TextFormatting
 
                 currentWidth += currentRun.GlyphRun.Bounds.Width;
 
-                length += currentRun.GlyphRun.Characters.Length;
+                currentLength += currentRun.GlyphRun.Characters.Length;
 
                 runIndex++;
             }
@@ -350,94 +406,6 @@ namespace Avalonia.Media.TextFormatting
                 TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties));
         }
 
-        /// <summary>
-        /// Measures the number of characters that fits into available width.
-        /// </summary>
-        /// <param name="textCharacters">The text run.</param>
-        /// <param name="availableWidth">The available width.</param>
-        /// <returns></returns>
-        private static int MeasureText(ShapedTextCharacters textCharacters, double availableWidth)
-        {
-            var glyphRun = textCharacters.GlyphRun;
-
-            if (glyphRun.Bounds.Width < availableWidth)
-            {
-                return glyphRun.Characters.Length;
-            }
-
-            var glyphCount = 0;
-
-            var currentWidth = 0.0;
-
-            if (glyphRun.GlyphAdvances.IsEmpty)
-            {
-                var glyphTypeface = glyphRun.GlyphTypeface;
-
-                for (var i = 0; i < glyphRun.GlyphClusters.Length; i++)
-                {
-                    var glyph = glyphRun.GlyphIndices[i];
-
-                    var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
-
-                    if (currentWidth + advance > availableWidth)
-                    {
-                        break;
-                    }
-
-                    currentWidth += advance;
-
-                    glyphCount++;
-                }
-            }
-            else
-            {
-                for (var i = 0; i < glyphRun.GlyphAdvances.Length; i++)
-                {
-                    var advance = glyphRun.GlyphAdvances[i];
-
-                    if (currentWidth + advance > availableWidth)
-                    {
-                        break;
-                    }
-
-                    currentWidth += advance;
-
-                    glyphCount++;
-                }
-            }
-
-            if (glyphCount == glyphRun.GlyphIndices.Length)
-            {
-                return glyphRun.Characters.Length;
-            }
-
-            if (glyphRun.GlyphClusters.IsEmpty)
-            {
-                return glyphCount;
-            }
-
-            var firstCluster = glyphRun.GlyphClusters[0];
-
-            var lastCluster = glyphRun.GlyphClusters[glyphCount];
-
-            return lastCluster - firstCluster;
-        }
-
-        /// <summary>
-        /// Creates an ellipsis.
-        /// </summary>
-        /// <param name="properties">The text run properties.</param>
-        /// <returns></returns>
-        private static ShapedTextCharacters CreateEllipsisRun(TextRunProperties properties)
-        {
-            var formatterImpl = AvaloniaLocator.Current.GetService<ITextShaperImpl>();
-
-            var glyphRun = formatterImpl.ShapeText(s_ellipsis, properties.Typeface, properties.FontRenderingEmSize,
-                properties.CultureInfo);
-
-            return new ShapedTextCharacters(glyphRun, properties);
-        }
-
         /// <summary>
         /// Gets the text range that is covered by the text runs.
         /// </summary>
@@ -464,86 +432,7 @@ namespace Avalonia.Media.TextFormatting
             return new TextRange(start, end - start);
         }
 
-        /// <summary>
-        /// Split a sequence of runs into two segments at specified length.
-        /// </summary>
-        /// <param name="textRuns">The text run's.</param>
-        /// <param name="length">The length to split at.</param>
-        /// <returns>The split text runs.</returns>
-        private static SplitTextRunsResult SplitTextRuns(IReadOnlyList<ShapedTextCharacters> textRuns, int length)
-        {
-            var currentLength = 0;
-
-            for (var i = 0; i < textRuns.Count; i++)
-            {
-                var currentRun = textRuns[i];
-
-                if (currentLength + currentRun.GlyphRun.Characters.Length < length)
-                {
-                    currentLength += currentRun.GlyphRun.Characters.Length;
-                    continue;
-                }
-
-                var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
-
-                var first = new ShapedTextCharacters[firstCount];
-
-                if (firstCount > 1)
-                {
-                    for (var j = 0; j < i; j++)
-                    {
-                        first[j] = textRuns[j];
-                    }
-                }
-
-                var secondCount = textRuns.Count - firstCount;
-
-                if (currentLength + currentRun.GlyphRun.Characters.Length == length)
-                {
-                    var second = new ShapedTextCharacters[secondCount];
-
-                    var offset = currentRun.GlyphRun.Characters.Length > 1 ? 1 : 0;
-
-                    if (secondCount > 0)
-                    {
-                        for (var j = 0; j < secondCount; j++)
-                        {
-                            second[j] = textRuns[i + j + offset];
-                        }
-                    }
-
-                    first[i] = currentRun;
-
-                    return new SplitTextRunsResult(first, second);
-                }
-                else
-                {
-                    secondCount++;
-
-                    var second = new ShapedTextCharacters[secondCount];
-
-                    if (secondCount > 0)
-                    {
-                        for (var j = 1; j < secondCount; j++)
-                        {
-                            second[j] = textRuns[i + j];
-                        }
-                    }
-
-                    var split = currentRun.Split(length - currentLength);
-
-                    first[i] = split.First;
-
-                    second[0] = split.Second;
-
-                    return new SplitTextRunsResult(first, second);
-                }
-            }
-
-            return new SplitTextRunsResult(textRuns, null);
-        }
-
-        private readonly struct SplitTextRunsResult
+        internal readonly struct SplitTextRunsResult
         {
             public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second)
             {

+ 51 - 16
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@@ -1,9 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using Avalonia.Media.TextFormatting.Unicode;
 using Avalonia.Utilities;
-using Avalonia.Platform;
 
 namespace Avalonia.Media.TextFormatting
 {
@@ -17,6 +15,7 @@ namespace Avalonia.Media.TextFormatting
         private readonly ReadOnlySlice<char> _text;
         private readonly TextParagraphProperties _paragraphProperties;
         private readonly IReadOnlyList<ValueSpan<TextRunProperties>> _textStyleOverrides;
+        private readonly TextTrimming _textTrimming;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TextLayout" /> class.
@@ -54,9 +53,11 @@ namespace Avalonia.Media.TextFormatting
                 new ReadOnlySlice<char>(text.AsMemory());
 
             _paragraphProperties =
-                CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textTrimming,
+                CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
                     textDecorations, lineHeight);
 
+            _textTrimming = textTrimming;
+
             _textStyleOverrides = textStyleOverrides;
 
             LineHeight = lineHeight;
@@ -143,18 +144,16 @@ namespace Avalonia.Media.TextFormatting
         /// <param name="foreground">The foreground.</param>
         /// <param name="textAlignment">The text alignment.</param>
         /// <param name="textWrapping">The text wrapping.</param>
-        /// <param name="textTrimming">The text trimming.</param>
         /// <param name="textDecorations">The text decorations.</param>
         /// <param name="lineHeight">The height of each line of text.</param>
         /// <returns></returns>
         private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize,
-            IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, TextTrimming textTrimming,
+            IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping,
             TextDecorationCollection textDecorations, double lineHeight)
         {
             var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground);
 
-            return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, textTrimming,
-                lineHeight);
+            return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, lineHeight);
         }
 
         /// <summary>
@@ -214,25 +213,44 @@ namespace Avalonia.Media.TextFormatting
                 var textSource = new FormattedTextSource(_text,
                     _paragraphProperties.DefaultTextRunProperties, _textStyleOverrides);
 
-                TextLineBreak previousLineBreak = null;
+                TextLine previousLine = null;
 
-                while (currentPosition < _text.Length && (MaxLines == 0 || textLines.Count < MaxLines))
+                while (currentPosition < _text.Length)
                 {
                     var textLine = TextFormatter.Current.FormatLine(textSource, currentPosition, MaxWidth,
-                        _paragraphProperties, previousLineBreak);
+                        _paragraphProperties, previousLine?.LineBreak);
 
-                    previousLineBreak = textLine.LineBreak;
+                    currentPosition += textLine.TextRange.Length;
 
-                    textLines.Add(textLine);
+                    if (textLines.Count > 0)
+                    {
+                        if (textLines.Count == MaxLines || !double.IsPositiveInfinity(MaxHeight) &&
+                            height + textLine.LineMetrics.Size.Height > MaxHeight)
+                        {
+                            if (previousLine?.LineBreak != null && _textTrimming != TextTrimming.None)
+                            {
+                                var collapsedLine =
+                                    previousLine.Collapse(GetCollapsingProperties(MaxWidth));
 
-                    UpdateBounds(textLine, ref width, ref height);
+                                textLines[textLines.Count - 1] = collapsedLine;
+                            }
+
+                            break;
+                        }
+                    }
+
+                    var hasOverflowed = textLine.LineMetrics.HasOverflowed;
 
-                    if (!double.IsPositiveInfinity(MaxHeight) && height > MaxHeight)
+                    if (hasOverflowed && _textTrimming != TextTrimming.None)
                     {
-                        break;
+                        textLine = textLine.Collapse(GetCollapsingProperties(MaxWidth));
                     }
 
-                    currentPosition += textLine.TextRange.Length;
+                    textLines.Add(textLine);
+
+                    UpdateBounds(textLine, ref width, ref height);
+
+                    previousLine = textLine;
 
                     if (currentPosition != _text.Length || textLine.LineBreak == null)
                     {
@@ -250,6 +268,23 @@ namespace Avalonia.Media.TextFormatting
             }
         }
 
+        /// <summary>
+        /// Gets the <see cref="TextCollapsingProperties"/> for current text trimming mode.
+        /// </summary>
+        /// <param name="width">The collapsing width.</param>
+        /// <returns>The <see cref="TextCollapsingProperties"/>.</returns>
+        private TextCollapsingProperties GetCollapsingProperties(double width)
+        {
+            return _textTrimming switch
+            {
+                TextTrimming.CharacterEllipsis => new TextTrailingCharacterEllipsis(width,
+                    _paragraphProperties.DefaultTextRunProperties),
+                TextTrimming.WordEllipsis => new TextTrailingWordEllipsis(width,
+                    _paragraphProperties.DefaultTextRunProperties),
+                _ => throw new ArgumentOutOfRangeException(),
+            };
+        }
+
         private readonly struct FormattedTextSource : ITextSource
         {
             private readonly ReadOnlySlice<char> _text;

+ 31 - 14
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@@ -39,6 +39,14 @@ namespace Avalonia.Media.TextFormatting
         /// </returns>
         public abstract TextLineBreak LineBreak { get; }
 
+        /// <summary>
+        /// Gets a value that indicates whether the line is collapsed.
+        /// </summary>
+        /// <returns>
+        /// <c>true</c>, if the line is collapsed; otherwise, <c>false</c>.
+        /// </returns>
+        public abstract bool HasCollapsed { get; }
+
         /// <summary>
         /// Draws the <see cref="TextLine"/> at the given origin.
         /// </summary>
@@ -47,40 +55,49 @@ namespace Avalonia.Media.TextFormatting
         public abstract void Draw(DrawingContext drawingContext, Point origin);
 
         /// <summary>
-        /// Client to get the character hit corresponding to the specified 
-        /// distance from the beginning of the line.
+        /// Create a collapsed line based on collapsed text properties.
+        /// </summary>
+        /// <param name="collapsingPropertiesList">A list of <see cref="TextCollapsingProperties"/>
+        /// objects that represent the collapsed text properties.</param>
+        /// <returns>
+        /// A <see cref="TextLine"/> value that represents a collapsed line that can be displayed.
+        /// </returns>
+        public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList);
+
+        /// <summary>
+        /// Gets the character hit corresponding to the specified distance from the beginning of the line.
         /// </summary>
-        /// <param name="distance">distance in text flow direction from the beginning of the line</param>
-        /// <returns>The <see cref="CharacterHit"/></returns>
+        /// <param name="distance">A <see cref="double"/> value that represents the distance from the beginning of the line.</param>
+        /// <returns>The <see cref="CharacterHit"/> object at the specified distance from the beginning of the line.</returns>
         public abstract CharacterHit GetCharacterHitFromDistance(double distance);
 
         /// <summary>
-        /// Client to get the distance from the beginning of the line from the specified 
+        /// Gets the distance from the beginning of the line to the specified character hit.
         /// <see cref="CharacterHit"/>.
         /// </summary>
-        /// <param name="characterHit"><see cref="CharacterHit"/> of the character to query the distance.</param>
-        /// <returns>Distance in text flow direction from the beginning of the line.</returns>
+        /// <param name="characterHit">The <see cref="CharacterHit"/> object whose distance you want to query.</param>
+        /// <returns>A <see cref="double"/> that represents the distance from the beginning of the line.</returns>
         public abstract double GetDistanceFromCharacterHit(CharacterHit characterHit);
 
         /// <summary>
-        /// Client to get the next <see cref="CharacterHit"/> for caret navigation.
+        /// Gets the next character hit for caret navigation.
         /// </summary>
         /// <param name="characterHit">The current <see cref="CharacterHit"/>.</param>
         /// <returns>The next <see cref="CharacterHit"/>.</returns>
         public abstract CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit);
 
         /// <summary>
-        /// Client to get the previous character hit for caret navigation
+        /// Gets the previous character hit for caret navigation.
         /// </summary>
-        /// <param name="characterHit">the current character hit</param>
-        /// <returns>The previous <see cref="CharacterHit"/></returns>
+        /// <param name="characterHit">The current <see cref="CharacterHit"/>.</param>
+        /// <returns>The previous <see cref="CharacterHit"/>.</returns>
         public abstract CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit);
 
         /// <summary>
-        /// Client to get the previous character hit after backspacing
+        /// Gets the previous character hit after backspacing.
         /// </summary>
-        /// <param name="characterHit">the current character hit</param>
-        /// <returns>The <see cref="CharacterHit"/> after backspacing</returns>
+        /// <param name="characterHit">The current <see cref="CharacterHit"/>.</param>
+        /// <returns>The <see cref="CharacterHit"/> after backspacing.</returns>
         public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit);
 
         /// <summary>

+ 150 - 7
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@@ -1,4 +1,6 @@
 using System.Collections.Generic;
+using Avalonia.Media.TextFormatting.Unicode;
+using Avalonia.Platform;
 
 namespace Avalonia.Media.TextFormatting
 {
@@ -7,11 +9,12 @@ namespace Avalonia.Media.TextFormatting
         private readonly IReadOnlyList<ShapedTextCharacters> _textRuns;
 
         public TextLineImpl(IReadOnlyList<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics,
-            TextLineBreak lineBreak = null)
+            TextLineBreak lineBreak = null, bool hasCollapsed = false)
         {
             _textRuns = textRuns;
             LineMetrics = lineMetrics;
             LineBreak = lineBreak;
+            HasCollapsed = hasCollapsed;
         }
 
         /// <inheritdoc/>
@@ -26,6 +29,9 @@ namespace Avalonia.Media.TextFormatting
         /// <inheritdoc/>
         public override TextLineBreak LineBreak { get; }
 
+        /// <inheritdoc/>
+        public override bool HasCollapsed { get; }
+
         /// <inheritdoc/>
         public override void Draw(DrawingContext drawingContext, Point origin)
         {
@@ -41,6 +47,99 @@ namespace Avalonia.Media.TextFormatting
             }
         }
 
+        /// <inheritdoc/>
+        public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
+        {
+            if (collapsingPropertiesList == null || collapsingPropertiesList.Length == 0)
+            {
+                return this;
+            }
+
+            var collapsingProperties = collapsingPropertiesList[0];
+            var runIndex = 0;
+            var currentWidth = 0.0;
+            var textRange = TextRange;
+            var collapsedLength = 0;
+            TextLineMetrics textLineMetrics;
+
+            var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol);
+
+            var availableWidth = collapsingProperties.Width - shapedSymbol.Bounds.Width;
+
+            while (runIndex < _textRuns.Count)
+            {
+                var currentRun = _textRuns[runIndex];
+
+                currentWidth += currentRun.GlyphRun.Bounds.Width;
+
+                if (currentWidth > availableWidth)
+                {
+                    var measuredLength = TextFormatterImpl.MeasureCharacters(currentRun, availableWidth);
+
+                    var currentBreakPosition = 0;
+
+                    if (measuredLength < textRange.End)
+                    {
+                        var lineBreaker = new LineBreakEnumerator(currentRun.Text);
+
+                        while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
+                        {
+                            var nextBreakPosition = lineBreaker.Current.PositionWrap;
+
+                            if (nextBreakPosition == 0)
+                            {
+                                break;
+                            }
+
+                            if (nextBreakPosition > measuredLength)
+                            {
+                                break;
+                            }
+
+                            currentBreakPosition = nextBreakPosition;
+                        }
+                    }
+
+                    if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord)
+                    {
+                        measuredLength = currentBreakPosition;
+                    }
+
+                    collapsedLength += measuredLength;
+
+                    var splitResult = TextFormatterImpl.SplitTextRuns(_textRuns, collapsedLength);
+
+                    var shapedTextCharacters = new List<ShapedTextCharacters>(splitResult.First.Count + 1);
+
+                    shapedTextCharacters.AddRange(splitResult.First);
+
+                    shapedTextCharacters.Add(shapedSymbol);
+
+                    textRange = new TextRange(textRange.Start, collapsedLength);
+
+                    var shapedWidth = GetShapedWidth(shapedTextCharacters);
+
+                    textLineMetrics = new TextLineMetrics(new Size(shapedWidth, LineMetrics.Size.Height),
+                        LineMetrics.TextBaseline, textRange, false);
+
+                    return new TextLineImpl(shapedTextCharacters, textLineMetrics, LineBreak, true);
+                }
+
+                availableWidth -= currentRun.GlyphRun.Bounds.Width;
+
+                collapsedLength += currentRun.GlyphRun.Characters.Length;
+
+                runIndex++;
+            }
+
+            textLineMetrics =
+                new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Bounds.Width),
+                    LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed);
+
+            return new TextLineImpl(new List<ShapedTextCharacters>(_textRuns) { shapedSymbol }, textLineMetrics, null,
+                true);
+        }
+
         /// <inheritdoc/>
         public override CharacterHit GetCharacterHitFromDistance(double distance)
         {
@@ -82,7 +181,7 @@ namespace Avalonia.Media.TextFormatting
                 return nextCharacterHit;
             }
 
-            return new CharacterHit(TextRange.End); // Can't move, we're after the last character
+            return characterHit; // Can't move, we're after the last character
         }
 
         /// <inheritdoc/>
@@ -93,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
                 return previousCharacterHit;
             }
 
-            return new CharacterHit(TextRange.Start); // Can't move, we're before the first character
+            return characterHit; // Can't move, we're before the first character
         }
 
         /// <inheritdoc/>
@@ -148,9 +247,13 @@ namespace Avalonia.Media.TextFormatting
             {
                 var run = _textRuns[runIndex];
 
-                nextCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
+                var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
 
-                if (codepointIndex <= nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
+                nextCharacterHit = characterHit.TrailingLength != 0 ?
+                    foundCharacterHit :
+                    new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
+
+                if (nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex)
                 {
                     return true;
                 }
@@ -184,9 +287,13 @@ namespace Avalonia.Media.TextFormatting
             {
                 var run = _textRuns[runIndex];
 
-                previousCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
+                var foundCharacterHit = run.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
+
+                previousCharacterHit = characterHit.TrailingLength != 0 ?
+                    foundCharacterHit :
+                    new CharacterHit(foundCharacterHit.FirstCharacterIndex);
 
-                if (previousCharacterHit.FirstCharacterIndex < codepointIndex)
+                if (previousCharacterHit.FirstCharacterIndex < characterHit.FirstCharacterIndex)
                 {
                     return true;
                 }
@@ -230,5 +337,41 @@ namespace Avalonia.Media.TextFormatting
 
             return runIndex;
         }
+
+        /// <summary>
+        /// Creates a shaped symbol.
+        /// </summary>
+        /// <param name="textRun">The symbol run to shape.</param>
+        /// <returns>
+        /// The shaped symbol.
+        /// </returns>
+        internal static ShapedTextCharacters CreateShapedSymbol(TextRun textRun)
+        {
+            var formatterImpl = AvaloniaLocator.Current.GetService<ITextShaperImpl>();
+
+            var glyphRun = formatterImpl.ShapeText(textRun.Text, textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize,
+                textRun.Properties.CultureInfo);
+
+            return new ShapedTextCharacters(glyphRun, textRun.Properties);
+        }
+        
+        /// <summary>
+        /// Gets the shaped width of specified shaped text characters.
+        /// </summary>
+        /// <param name="shapedTextCharacters">The shaped text characters.</param>
+        /// <returns>
+        /// The shaped width.
+        /// </returns>
+        private static double GetShapedWidth(IReadOnlyList<ShapedTextCharacters> shapedTextCharacters)
+        {
+            var shapedWidth = 0.0;
+
+            for (var i = 0; i < shapedTextCharacters.Count; i++)
+            {
+                shapedWidth += shapedTextCharacters[i].Bounds.Width;
+            }
+
+            return shapedWidth;
+        }
     }
 }

+ 9 - 2
src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs

@@ -9,11 +9,12 @@ namespace Avalonia.Media.TextFormatting
     /// </summary>
     public readonly struct TextLineMetrics
     {
-        public TextLineMetrics(Size size, double textBaseline, TextRange textRange)
+        public TextLineMetrics(Size size, double textBaseline, TextRange textRange, bool hasOverflowed)
         {
             Size = size;
             TextBaseline = textBaseline;
             TextRange = textRange;
+            HasOverflowed = hasOverflowed;
         }
 
         /// <summary>
@@ -37,6 +38,12 @@ namespace Avalonia.Media.TextFormatting
         /// </summary>
         public double TextBaseline { get; }
 
+        /// <summary>
+        /// Gets a boolean value that indicates whether content of the line overflows 
+        /// the specified paragraph width.
+        /// </summary>
+        public bool HasOverflowed { get; }
+
         /// <summary>
         /// Creates the text line metrics.
         /// </summary>
@@ -83,7 +90,7 @@ namespace Avalonia.Media.TextFormatting
                     descent - ascent + lineGap :
                     paragraphProperties.LineHeight);
 
-            return new TextLineMetrics(size, -ascent, textRange);
+            return new TextLineMetrics(size, -ascent, textRange, size.Width > paragraphWidth);
         }
     }
 }

+ 0 - 5
src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs

@@ -26,11 +26,6 @@
         /// </summary>
         public abstract TextWrapping TextWrapping { get; }
 
-        /// <summary>
-        /// Gets the text trimming.
-        /// </summary>
-        public abstract TextTrimming TextTrimming { get; }
-
         /// <summary>
         /// Paragraph's line height
         /// </summary>

+ 3 - 4
src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs

@@ -4,12 +4,11 @@ using System.Globalization;
 namespace Avalonia.Media.TextFormatting
 {
     /// <summary>
-    /// Properties that can change from one run to the next, such as typeface or foreground brush.
+    /// Provides a set of properties, such as typeface or foreground brush, that can be applied to a TextRun object. This is an abstract class.
     /// </summary>
     /// <remarks>
-    /// The client provides a concrete implementation of this abstract run properties class. This
-    /// allows client to implement their run properties the way that fits with their run formatting
-    /// store.
+    /// The text layout client provides a concrete implementation of this abstract class.
+    /// This enables the client to implement text run properties in a way that corresponds with the associated formatting store.
     /// </remarks>
     public abstract class TextRunProperties : IEquatable<TextRunProperties>
     {

+ 33 - 0
src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs

@@ -0,0 +1,33 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+    /// <summary>
+    /// a collapsing properties to collapse whole line toward the end
+    /// at character granularity and with ellipsis being the collapsing symbol
+    /// </summary>
+    public class TextTrailingCharacterEllipsis : TextCollapsingProperties
+    {
+        private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
+
+        /// <summary>
+        /// Construct a text trailing character ellipsis collapsing properties
+        /// </summary>
+        /// <param name="width">width in which collapsing is constrained to</param>
+        /// <param name="textRunProperties">text run properties of ellispis symbol</param>
+        public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProperties)
+        {
+            Width = width;
+            Symbol = new TextCharacters(s_ellipsis, textRunProperties);
+        }
+
+        /// <inheritdoc/>
+        public sealed override double Width { get; }
+
+        /// <inheritdoc/>
+        public sealed override TextRun Symbol { get; }
+
+        /// <inheritdoc/>
+        public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingCharacter;
+    }
+}

+ 37 - 0
src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs

@@ -0,0 +1,37 @@
+using Avalonia.Utilities;
+
+namespace Avalonia.Media.TextFormatting
+{
+    /// <summary>
+    /// a collapsing properties to collapse whole line toward the end
+    /// at word granularity and with ellipsis being the collapsing symbol
+    /// </summary>
+    public class TextTrailingWordEllipsis : TextCollapsingProperties
+    {
+        private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
+
+        /// <summary>
+        /// Construct a text trailing word ellipsis collapsing properties
+        /// </summary>
+        /// <param name="width">width in which collapsing is constrained to</param>
+        /// <param name="textRunProperties">text run properties of ellispis symbol</param>
+        public TextTrailingWordEllipsis(
+            double width,
+            TextRunProperties textRunProperties
+        )
+        {
+            Width = width;
+            Symbol = new TextCharacters(s_ellipsis, textRunProperties);
+        }
+
+
+        /// <inheritdoc/>
+        public sealed override double Width { get; }
+
+        /// <inheritdoc/>
+        public sealed override TextRun Symbol { get; }
+
+        /// <inheritdoc/>
+        public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingWord;
+    }
+}

+ 1 - 1
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs

@@ -112,7 +112,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
         {
             count = 1;
 
-            if (index > text.End)
+            if (index > text.Length)
             {
                 return ReplacementCodepoint;
             }

+ 0 - 1
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

@@ -109,7 +109,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
                 {
                     case PairBreakType.DI: // Direct break
                         shouldBreak = true;
-                        _lastPos = _pos;
                         break;
 
                     case PairBreakType.IN: // possible indirect break

+ 8 - 8
src/Avalonia.Visuals/Media/TextWrapping.cs

@@ -5,13 +5,6 @@ namespace Avalonia.Media
     /// </summary>
     public enum TextWrapping
     {
-        /// <summary>
-        /// Line-breaking occurs if the line overflows the available block width.
-        /// However, a line may overflow the block width if the line breaking algorithm
-        /// cannot determine a break opportunity, as in the case of a very long word.
-        /// </summary>
-        WrapWithOverflow,
-
         /// <summary>
         /// Text should not wrap.
         /// </summary>
@@ -20,6 +13,13 @@ namespace Avalonia.Media
         /// <summary>
         /// Text can wrap.
         /// </summary>
-        Wrap
+        Wrap, 
+        
+        /// <summary>
+        /// Line-breaking occurs if the line overflows the available block width.
+        /// However, a line may overflow the block width if the line breaking algorithm
+        /// cannot determine a break opportunity, as in the case of a very long word.
+        /// </summary>
+        WrapWithOverflow
     }
 }

+ 2 - 2
src/Avalonia.X11/X11NativeControlHost.cs

@@ -167,7 +167,7 @@ namespace Avalonia.X11
                     XUnmapWindow(_display, _holder.Handle);
                 }
 
-                size *= _attachedTo.Window.Scaling;
+                size *= _attachedTo.Window.RenderScaling;
                 XResizeWindow(_display, _child.Handle,
                     Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
             }
@@ -179,7 +179,7 @@ namespace Avalonia.X11
                 CheckDisposed();
                 if (_attachedTo == null)
                     throw new InvalidOperationException("The control isn't currently attached to a toplevel");
-                bounds *= _attachedTo.Window.Scaling;
+                bounds *= _attachedTo.Window.RenderScaling;
                 
                 var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
                     Math.Max(1, (int)bounds.Height));

+ 22 - 25
src/Avalonia.X11/X11Window.cs

@@ -163,7 +163,7 @@ namespace Avalonia.X11
             var surfaces = new List<object>
             {
                 new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, 
-                   depth, () => Scaling)
+                   depth, () => RenderScaling)
             };
             
             if (egl != null)
@@ -217,7 +217,7 @@ namespace Avalonia.X11
                 }
             }
 
-            public double Scaling => _window.Scaling;
+            public double Scaling => _window.RenderScaling;
         }
 
         void UpdateMotifHints()
@@ -284,9 +284,9 @@ namespace Avalonia.X11
             XSetWMNormalHints(_x11.Display, _handle, ref hints);
         }
 
-        public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling);
+        public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling);
 
-        public double Scaling
+        public double RenderScaling
         {
             get
             {
@@ -296,6 +296,8 @@ namespace Avalonia.X11
             }
             private set => _scaling = value;
         }
+        
+        public double DesktopScaling => RenderScaling;
 
         public IEnumerable<object> Surfaces { get; }
         public Action<RawInputEventArgs> Input { get; set; }
@@ -538,14 +540,14 @@ namespace Avalonia.X11
                 {
                     var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
                         .FirstOrDefault(m => m.Bounds.Contains(Position));
-                    newScaling = monitor?.PixelDensity ?? Scaling;
+                    newScaling = monitor?.PixelDensity ?? RenderScaling;
                 }
 
-                if (Scaling != newScaling)
+                if (RenderScaling != newScaling)
                 {
                     var oldScaledSize = ClientSize;
-                    Scaling = newScaling;
-                    ScalingChanged?.Invoke(Scaling);
+                    RenderScaling = newScaling;
+                    ScalingChanged?.Invoke(RenderScaling);
                     SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
                     if(!skipResize)
                         Resize(oldScaledSize, true);
@@ -707,9 +709,9 @@ namespace Avalonia.X11
         private void ScheduleInput(RawInputEventArgs args)
         {
             if (args is RawPointerEventArgs mouse)
-                mouse.Position = mouse.Position / Scaling;
+                mouse.Position = mouse.Position / RenderScaling;
             if (args is RawDragEvent drag)
-                drag.Location = drag.Location / Scaling;
+                drag.Location = drag.Location / RenderScaling;
             
             _lastEvent = new InputEventContainer() {Event = args};
             _inputQueue.Enqueue(_lastEvent);
@@ -760,11 +762,7 @@ namespace Avalonia.X11
 
         public void Dispose()
         {
-            if (_handle != IntPtr.Zero)
-            {
-                XDestroyWindow(_x11.Display, _handle);
-                Cleanup();
-            }
+            Cleanup();            
         }
 
         void Cleanup()
@@ -787,8 +785,7 @@ namespace Avalonia.X11
             }
             
             if (_useRenderWindow && _renderHandle != IntPtr.Zero)
-            {
-                XDestroyWindow(_x11.Display, _renderHandle);
+            {                
                 _renderHandle = IntPtr.Zero;
             }
         }
@@ -821,11 +818,11 @@ namespace Avalonia.X11
 
         public void Hide() => XUnmapWindow(_x11.Display, _handle);
         
-        public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
+        public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / RenderScaling, (point.Y - Position.Y) / RenderScaling);
 
         public PixelPoint PointToScreen(Point point) => new PixelPoint(
-            (int)(point.X * Scaling + Position.X),
-            (int)(point.Y * Scaling + Position.Y));
+            (int)(point.X * RenderScaling + Position.X),
+            (int)(point.Y * RenderScaling + Position.Y));
         
         public void SetSystemDecorations(SystemDecorations enabled)
         {
@@ -845,7 +842,7 @@ namespace Avalonia.X11
             Resize(size, true);
         }
 
-        PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling));
+        PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * RenderScaling), (int)(size.Height * RenderScaling));
         
         void Resize(Size clientSize, bool force)
         {
@@ -1025,13 +1022,13 @@ namespace Avalonia.X11
         {
             _scaledMinMaxSize = (minSize, maxSize);
             var min = new PixelSize(
-                (int)(minSize.Width < 1 ? 1 : minSize.Width * Scaling),
-                (int)(minSize.Height < 1 ? 1 : minSize.Height * Scaling));
+                (int)(minSize.Width < 1 ? 1 : minSize.Width * RenderScaling),
+                (int)(minSize.Height < 1 ? 1 : minSize.Height * RenderScaling));
 
             const int maxDim = MaxWindowDimension;
             var max = new PixelSize(
-                (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * Scaling)),
-                (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * Scaling)));
+                (int)(maxSize.Width > maxDim ? maxDim : Math.Max(min.Width, maxSize.Width * RenderScaling)),
+                (int)(maxSize.Height > maxDim ? maxDim : Math.Max(min.Height, maxSize.Height * RenderScaling)));
             
             _minMaxSize = (min, max);
             UpdateSizeHints(null);

+ 2 - 2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -65,7 +65,7 @@ namespace Avalonia.LinuxFramebuffer
         public IMouseDevice MouseDevice => new MouseDevice();
         public IPopupImpl CreatePopup() => null;
 
-        public double Scaling => _outputBackend.Scaling;
+        public double RenderScaling => _outputBackend.Scaling;
         public IEnumerable<object> Surfaces { get; }
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }
@@ -77,7 +77,7 @@ namespace Avalonia.LinuxFramebuffer
         public Action Closed { get; set; }
         public Action LostFocus { get; set; }
 
-        public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling);
+        public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling);
 
         public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
 

+ 14 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <IsPackable>true</IsPackable>
+  </PropertyGroup>
+  <Import Project="IncludeXamlIlSre.props" />
+  <ItemGroup>
+    <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
+  </ItemGroup>
+</Project>

+ 42 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs

@@ -0,0 +1,42 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using Avalonia.Markup.Xaml.XamlIl;
+// ReSharper disable CheckNamespace
+
+namespace Avalonia.Markup.Xaml
+{
+    public static class AvaloniaRuntimeXamlLoader
+    {
+        /// <summary>
+        /// Loads XAML from a string.
+        /// </summary>
+        /// <param name="xaml">The string containing the XAML.</param>
+        /// <param name="localAssembly">Default assembly for clr-namespace:</param>
+        /// <param name="rootInstance">
+        /// The optional instance into which the XAML should be loaded.
+        /// </param>
+        /// <returns>The loaded object.</returns>
+        public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false)
+        {
+            Contract.Requires<ArgumentNullException>(xaml != null);
+
+            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+            {
+                return Load(stream, localAssembly, rootInstance, uri, designMode);
+            }
+        }
+
+        public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null,
+            bool designMode = false)
+            => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode);
+
+        public static object Parse(string xaml, Assembly localAssembly = null)
+            => Load(xaml, localAssembly);
+
+        public static T Parse<T>(string xaml, Assembly localAssembly = null)
+            => (T)Parse(xaml, localAssembly);
+            
+    }
+}

+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs → src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs


+ 17 - 12
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@@ -1,11 +1,6 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
-using System.Text;
-using Avalonia.Markup.Parsers;
-using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
-using Avalonia.Utilities;
 using XamlX;
 using XamlX.Ast;
 using XamlX.Transform;
@@ -129,12 +124,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
             if (itemsCollectionType != null)
             {
-                var elementType = itemsCollectionType
-                    .GetAllInterfaces()
-                    .FirstOrDefault(i =>
-                        i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true)
-                    .GenericArguments[0];
-                return new AvaloniaXamlIlDataContextTypeMetadataNode(on, elementType);
+                foreach (var i in GetAllInterfacesIncludingSelf(itemsCollectionType))
+                {
+                    if (i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true)
+                    {
+                        return new AvaloniaXamlIlDataContextTypeMetadataNode(on, i.GenericArguments[0]);
+                    }
+                }
             }
             // We can't infer the collection type and the currently calculated type is definitely wrong.
             // Notify the user that we were unable to infer the data context type if they use a compiled binding.
@@ -165,6 +161,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 
             return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
         }
+
+        private static IEnumerable<IXamlType> GetAllInterfacesIncludingSelf(IXamlType type)
+        {
+            if (type.IsInterface)
+                yield return type;
+
+            foreach (var i in type.GetAllInterfaces())
+                yield return i;
+        }
     }
 
     [DebuggerDisplay("DataType = {DataContextType}")]

+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/XNameTransformer.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs


+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs → src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs


+ 12 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/IncludeXamlIlSre.props

@@ -0,0 +1,12 @@
+<Project>
+  <ItemGroup>
+    <None Remove="$(MSBuildThisFileDirectory)\xamlil.github\**\*.*" />
+    <Content Remove="$(MSBuildThisFileDirectory)\xamlil.github\**\*.*" />
+    <Compile Remove="$(MSBuildThisFileDirectory)\xamlil.github\**\*.*" />
+    <Compile Include="$(MSBuildThisFileDirectory)\xamlil.github\src\XamlX\**\*.cs" />
+    <Compile Remove="$(MSBuildThisFileDirectory)\xamlil.github\**\obj\**\*.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)\..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)\..\Avalonia.Markup.Xaml\Parsers\PropertyParser.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)\..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
+  </ItemGroup>
+</Project>

+ 0 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github → src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github


+ 0 - 36
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -45,44 +45,10 @@
         <Compile Include="Templates\Template.cs" />
         <Compile Include="Templates\TemplateContent.cs" />
         <Compile Include="Templates\TreeDataTemplate.cs" />
-        <Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompilerConfiguration.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlAvaloniaPropertyResolver.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathParser.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlConstructorServiceProviderTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDataContextTypeTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDesignPropertiesTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlCompiledBindingsMetadataRemover.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlMetadataRemover.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlPropertyPathTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlResolveByNameMarkupExtensionReplacer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlRootObjectScopeTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformInstanceAttachedProperties.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlWellKnownTypes.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\XamlIlAvaloniaPropertyHelper.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompiler.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlLanguage.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AddNameScopeRegistration.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlSetterTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\IgnoredDirectivesTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlSelectorTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\XNameTransformer.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\XamlIlBindingPathHelper.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\XamlIlClrPropertyInfoHelper.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\XamlIlPropertyInfoAccessorFactoryEmitter.cs" />
         <Compile Include="XamlIl\Runtime\IAvaloniaXamlIlParentStackProvider.cs" />
         <Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
         <Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
         <Compile Include="XamlLoadException.cs" />
-        <Compile Include="XamlIl\xamlil.github\src\XamlX\**\*.cs" />
-        <Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlX.Il.Cecil\**\*.cs" />
-        <Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" />
-        <Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
         <Compile Include="..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
         <Compile Include="XamlTypes.cs" />
     </ItemGroup>
@@ -96,8 +62,6 @@
     <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
     <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\Avalonia.Markup\Avalonia.Markup.csproj" />
-    <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
-    <PackageReference Condition="$(UseCecil) == true" Include="Mono.Cecil" Version="0.10.3" />
   </ItemGroup>
   <Import Project="..\..\..\build\Rx.props" />
 </Project>

+ 20 - 47
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@@ -10,10 +10,13 @@ namespace Avalonia.Markup.Xaml
     /// <summary>
     /// Loads XAML for a avalonia application.
     /// </summary>
-    public class AvaloniaXamlLoader
+    public static class AvaloniaXamlLoader
     {
-        public bool IsDesignMode { get; set; }
-
+        public interface IRuntimeXamlLoader
+        {
+            object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode);
+        }
+        
         /// <summary>
         /// Loads the XAML into a Avalonia component.
         /// </summary>
@@ -32,7 +35,7 @@ namespace Avalonia.Markup.Xaml
         /// A base URI to use if <paramref name="uri"/> is relative.
         /// </param>
         /// <returns>The loaded object.</returns>
-        public object Load(Uri uri, Uri baseUri = null)
+        public static object Load(Uri uri, Uri baseUri = null)
         {
             Contract.Requires<ArgumentNullException>(uri != null);
 
@@ -55,52 +58,22 @@ namespace Avalonia.Markup.Xaml
                 if (compiledResult != null)
                     return compiledResult;
             }
-            
-            
-            var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
-            using (var stream = asset.stream)
-            {
-                var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
-                return Load(stream, asset.assembly, null, absoluteUri);
-            }
-        }
-        
-        /// <summary>
-        /// Loads XAML from a string.
-        /// </summary>
-        /// <param name="xaml">The string containing the XAML.</param>
-        /// <param name="localAssembly">Default assembly for clr-namespace:</param>
-        /// <param name="rootInstance">
-        /// The optional instance into which the XAML should be loaded.
-        /// </param>
-        /// <returns>The loaded object.</returns>
-        public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null)
-        {
-            Contract.Requires<ArgumentNullException>(xaml != null);
 
-            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+            // This is intended for unit-tests only
+            var runtimeLoader = AvaloniaLocator.Current.GetService<IRuntimeXamlLoader>();
+            if (runtimeLoader != null)
             {
-                return Load(stream, localAssembly, rootInstance);
+                var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
+                using (var stream = asset.stream)
+                {
+                    var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
+                    return runtimeLoader.Load(stream, asset.assembly, null, absoluteUri, false);
+                }
             }
-        }
 
-        /// <summary>
-        /// Loads XAML from a stream.
-        /// </summary>
-        /// <param name="stream">The stream containing the XAML.</param>
-        /// <param name="localAssembly">Default assembly for clr-namespace</param>
-        /// <param name="rootInstance">
-        /// The optional instance into which the XAML should be loaded.
-        /// </param>
-        /// <param name="uri">The URI of the XAML</param>
-        /// <returns>The loaded object.</returns>
-        public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null) 
-            => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, IsDesignMode);
-
-        public static object Parse(string xaml, Assembly localAssembly = null)
-            => new AvaloniaXamlLoader().Load(xaml, localAssembly);
-
-        public static T Parse<T>(string xaml, Assembly localAssembly = null)
-            => (T)Parse(xaml, localAssembly);
+            throw new XamlLoadException(
+                $"No precompiled XAML found for {uri} (baseUri: {baseUri}), make sure to specify x:Class and include your XAML file as AvaloniaResource");
+        }
+        
     }
 }

+ 1 - 2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@@ -25,8 +25,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
                 if (_loaded == null)
                 {
                     _isLoading = true;
-                    var loader = new AvaloniaXamlLoader();
-                    _loaded = (IResourceDictionary)loader.Load(Source, _baseUri);
+                    _loaded = (IResourceDictionary)AvaloniaXamlLoader.Load(Source, _baseUri);
                     _isLoading = false;
                 }
 

+ 1 - 2
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@@ -51,8 +51,7 @@ namespace Avalonia.Markup.Xaml.Styling
                 if (_loaded == null)
                 {
                     _isLoading = true;
-                    var loader = new AvaloniaXamlLoader();
-                    var loaded = (IStyle)loader.Load(Source, _baseUri);
+                    var loaded = (IStyle)AvaloniaXamlLoader.Load(Source, _baseUri);
                     _loaded = new[] { loaded };
                     _isLoading = false;
                 }

+ 7 - 4
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@@ -5,7 +5,7 @@ using Avalonia.Metadata;
 
 namespace Avalonia.Markup.Xaml.Templates
 {
-    public class DataTemplate : IDataTemplate
+    public class DataTemplate : IRecyclingDataTemplate
     {
         public Type DataType { get; set; }
 
@@ -14,8 +14,6 @@ namespace Avalonia.Markup.Xaml.Templates
         [TemplateContent]
         public object Content { get; set; }
 
-        public bool SupportsRecycling { get; set; } = true;
-
         public bool Match(object data)
         {
             if (DataType == null)
@@ -28,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates
             }
         }
 
-        public IControl Build(object data) => TemplateContent.Load(Content).Control;
+        public IControl Build(object data) => Build(data, null);
+
+        public IControl Build(object data, IControl existing)
+        {
+            return existing ?? TemplateContent.Load(Content).Control;
+        }
     }
 }

+ 0 - 2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@@ -18,8 +18,6 @@ namespace Avalonia.Markup.Xaml.Templates
         [AssignBinding]
         public Binding ItemsSource { get; set; }
 
-        public bool SupportsRecycling { get; set; } = true;
-
         public bool Match(object data)
         {
             if (DataType == null)

+ 1 - 1
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@@ -570,7 +570,7 @@ namespace Avalonia.Skia
                 
                 float constraint = -1;
 
-                if (_wrapping != TextWrapping.NoWrap)
+                if (_wrapping == TextWrapping.Wrap)
                 {
                     constraint = widthConstraint <= 0 ? MAX_LINE_WIDTH : widthConstraint;
                     if (constraint > MAX_LINE_WIDTH)

Some files were not shown because too many files changed in this diff