Browse Source

Merge branch 'master' into issues/4306

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

2
.gitmodules

@ -2,5 +2,5 @@
path = nukebuild/Numerge path = nukebuild/Numerge
url = https://github.com/kekekeks/Numerge.git url = https://github.com/kekekeks/Numerge.git
[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"] [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 url = https://github.com/kekekeks/XamlX.git

27
Avalonia.sln

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

1
Directory.Build.props

@ -1,5 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<PackageOutputPath Condition="'$(PackageOutputPath)' == ''">$(MSBuildThisFileDirectory)build-intermediate/nuget</PackageOutputPath> <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> </PropertyGroup>
</Project> </Project>

2
dirs.proj

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

16
native/Avalonia.Native/src/OSX/Screens.mm

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

13
native/Avalonia.Native/src/OSX/window.mm

@ -1291,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->UpdateCursor(); _parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size]; auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height; if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
[self updateRenderTarget]; {
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); _lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
}
} }
- (void)updateLayer - (void)updateLayer

8
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -10,7 +10,13 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="16"> Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8"> <StackPanel Orientation="Vertical" Spacing="8">
<ListBox Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoScrollToSelectedItem="True" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox> <ListBox Items="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
SelectedItems="{Binding SelectedItems}"
AutoScrollToSelectedItem="True"
SelectionMode="{Binding SelectionMode}"
Width="250"
Height="350"/>
<Button Command="{Binding AddItemCommand}">Add</Button> <Button Command="{Binding AddItemCommand}">Add</Button>

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

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

10
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

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

6
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3922,6 +3922,12 @@ namespace Avalonia.Controls
dataGridCell: editingCell); dataGridCell: editingCell);
EditingRow.InvalidateDesiredHeight(); 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 // We're done, so raise the CellEditEnded event

16
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

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

31
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

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

2
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; } get { return _scaling; }
set set

1
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

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

6
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

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

5
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

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

21
src/Avalonia.Controls/Presenters/ContentPresenter.cs

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

4
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs

@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public void MoveAndResize(Point devicePoint, Size virtualSize) 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;
} }
} }

20
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives
} }
} }
foreach (var i in e.SelectedIndices) if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0)
{ {
Mark(i.GetAt(0), true); foreach (var i in e.SelectedIndices)
} {
Mark(i.GetAt(0), true);
}
foreach (var i in e.DeselectedIndices) foreach (var i in e.DeselectedIndices)
{
Mark(i.GetAt(0), false);
}
}
else if (e.DeselectedItems.Count > 0)
{ {
Mark(i.GetAt(0), false); // (De)selected indices being empty means that a selected item was removed from
// the Items (it can't tell us the index of the item because the index is no longer
// valid). In this case, we just update the selection state of all containers.
UpdateContainerSelection();
} }
var newSelectedIndex = SelectedIndex; var newSelectedIndex = SelectedIndex;

2
src/Avalonia.Controls/Repeater/ElementFactory.cs

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

1
src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs

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

29
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates
/// <summary> /// <summary>
/// Builds a control for a piece of data. /// Builds a control for a piece of data.
/// </summary> /// </summary>
public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate public class FuncDataTemplate : FuncTemplate<object, IControl>, IRecyclingDataTemplate
{ {
/// <summary> /// <summary>
/// The default data template used in the case where no matching data template is found. /// The default data template used in the case where no matching data template is found.
@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates
}, },
true); true);
/// <summary>
/// The implementation of the <see cref="Match"/> method.
/// </summary>
private readonly Func<object, bool> _match; private readonly Func<object, bool> _match;
private readonly bool _supportsRecycling;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FuncDataTemplate"/> class. /// Initializes a new instance of the <see cref="FuncDataTemplate"/> class.
@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates
Contract.Requires<ArgumentNullException>(match != null); Contract.Requires<ArgumentNullException>(match != null);
_match = match; _match = match;
SupportsRecycling = supportsRecycling; _supportsRecycling = supportsRecycling;
} }
/// <inheritdoc/>
public bool SupportsRecycling { get; }
/// <summary> /// <summary>
/// Checks to see if this data template matches the specified data. /// Checks to see if this data template matches the specified data.
/// </summary> /// </summary>
@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates
return _match(data); 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> /// <summary>
/// Determines of an object is of the specified type. /// Determines of an object is of the specified type.
/// </summary> /// </summary>

6
src/Avalonia.Controls/Templates/FuncTemplate`2.cs

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

12
src/Avalonia.Controls/Templates/IDataTemplate.cs

@ -1,3 +1,7 @@
using System;
#nullable enable
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
/// <summary> /// <summary>
@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates
/// </summary> /// </summary>
public interface IDataTemplate : ITemplate<object, IControl> 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> /// <summary>
/// Checks to see if this data template matches the specified data. /// Checks to see if this data template matches the specified data.
/// </summary> /// </summary>
@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates
/// </returns> /// </returns>
bool Match(object data); bool Match(object data);
} }
} }

25
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);
}
}

2
src/Avalonia.Controls/TextBox.cs

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

25
src/Avalonia.Controls/TickBar.cs

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

4
src/Avalonia.Controls/TopLevel.cs

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

2
src/Avalonia.Controls/Window.cs

@ -818,7 +818,7 @@ namespace Avalonia.Controls
private void SetWindowStartupLocation(IWindowBaseImpl owner = null) 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. // TODO: We really need non-client size here.
var rect = new PixelRect( var rect = new PixelRect(

7
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

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

10
src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs

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

28
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json

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

1
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 PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; } public Action<PixelPoint> PositionChanged { get; set; }
public Action Deactivated { get; set; } public Action Deactivated { get; set; }

7
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

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

3
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -21,7 +21,8 @@ namespace Avalonia.DesignerSupport.Remote
public IPlatformHandle Handle { get; } public IPlatformHandle Handle { get; }
public Size MaxAutoSizeHint { get; } public Size MaxAutoSizeHint { get; }
public Size ClientSize { 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 IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; } public Action<Rect> Paint { get; set; }

2
src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs

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

9
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -41,7 +41,8 @@ namespace Avalonia.Headless
} }
public Size ClientSize { get; set; } 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 IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; } public Action<Rect> Paint { get; set; }
@ -62,9 +63,9 @@ namespace Avalonia.Headless
public IInputRoot InputRoot { get; set; } 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) public void SetCursor(IPlatformHandle cursor)
{ {
@ -201,7 +202,7 @@ namespace Avalonia.Headless
public ILockedFramebuffer Lock() 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(); var fb = bmp.Lock();
return new FramebufferProxy(fb, () => return new FramebufferProxy(fb, () =>
{ {

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;
}
}

2
src/Avalonia.Native/PopupImpl.cs

@ -26,7 +26,7 @@ namespace Avalonia.Native
var context = _opts.UseGpu ? glFeature?.DeferredContext : null; var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context); 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) private void MoveResize(PixelPoint position, Size size, double scaling)

8
src/Avalonia.Native/WindowImplBase.cs

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

2
src/Avalonia.Themes.Default/TextBox.xaml

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

107
src/Avalonia.Themes.Fluent/TextBox.xaml

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

15
src/Avalonia.Visuals/Media/Color.cs

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

15
src/Avalonia.Visuals/Media/TextFormatting/GenericTextParagraphProperties.cs

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

3
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 namespace Avalonia.Media.TextFormatting
{ {

23
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
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,
}
}

503
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.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
internal class TextFormatterImpl : TextFormatter internal class TextFormatterImpl : TextFormatter
{ {
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
/// <inheritdoc cref="TextFormatter.FormatLine"/> /// <inheritdoc cref="TextFormatter.FormatLine"/>
public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth,
TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null) TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null)
{ {
var textTrimming = paragraphProperties.TextTrimming;
var textWrapping = paragraphProperties.TextWrapping; var textWrapping = paragraphProperties.TextWrapping;
TextLine textLine = null;
var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak); var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, previousLineBreak, out var nextLineBreak);
var textRange = GetTextRange(textRuns); 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 else
{ {
switch (textWrapping) foreach (var advance in glyphRun.GlyphAdvances)
{ {
case TextWrapping.NoWrap: if (currentWidth + advance > availableWidth)
{ {
var textLineMetrics = break;
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties); }
textLine = new TextLineImpl(textRuns, textLineMetrics, nextLineBreak); currentWidth += advance;
break;
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); second[j] = textRuns[i + j];
break;
} }
}
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> /// <summary>
@ -174,87 +316,6 @@ namespace Avalonia.Media.TextFormatting
return false; 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> /// <summary>
/// Performs text wrapping returns a list of text lines. /// Performs text wrapping returns a list of text lines.
/// </summary> /// </summary>
@ -269,7 +330,7 @@ namespace Avalonia.Media.TextFormatting
var availableWidth = paragraphWidth; var availableWidth = paragraphWidth;
var currentWidth = 0.0; var currentWidth = 0.0;
var runIndex = 0; var runIndex = 0;
var length = 0; var currentLength = 0;
while (runIndex < textRuns.Count) while (runIndex < textRuns.Count)
{ {
@ -277,60 +338,55 @@ namespace Avalonia.Media.TextFormatting
if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth) 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 (measuredLength < currentRun.Text.Length)
{ {
if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) var lineBreaker = new LineBreakEnumerator(currentRun.Text);
{
var lineBreaker = new LineBreakEnumerator(currentRun.Text.Skip(measuredLength));
if (lineBreaker.MoveNext()) while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
measuredLength += lineBreaker.Current.PositionWrap;
}
else
{
measuredLength = currentRun.Text.Length;
}
}
else
{ {
var currentBreakPosition = -1; var nextBreakPosition = lineBreaker.Current.PositionWrap;
var lineBreaker = new LineBreakEnumerator(currentRun.Text); if (nextBreakPosition == 0 || nextBreakPosition > measuredLength)
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{ {
var nextBreakPosition = lineBreaker.Current.PositionWrap; break;
}
if (nextBreakPosition == 0) breakFound = lineBreaker.Current.Required ||
{ lineBreaker.Current.PositionWrap != currentRun.Text.Length;
break;
}
if (nextBreakPosition > measuredLength) currentBreakPosition = nextBreakPosition;
{ }
break; }
}
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, 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 ? var lineBreak = splitResult.Second != null && splitResult.Second.Count > 0 ?
new TextLineBreak(splitResult.Second) : new TextLineBreak(splitResult.Second) :
@ -341,7 +397,7 @@ namespace Avalonia.Media.TextFormatting
currentWidth += currentRun.GlyphRun.Bounds.Width; currentWidth += currentRun.GlyphRun.Bounds.Width;
length += currentRun.GlyphRun.Characters.Length; currentLength += currentRun.GlyphRun.Characters.Length;
runIndex++; runIndex++;
} }
@ -350,94 +406,6 @@ namespace Avalonia.Media.TextFormatting
TextLineMetrics.Create(textRuns, textRange, paragraphWidth, paragraphProperties)); 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> /// <summary>
/// Gets the text range that is covered by the text runs. /// Gets the text range that is covered by the text runs.
/// </summary> /// </summary>
@ -464,86 +432,7 @@ namespace Avalonia.Media.TextFormatting
return new TextRange(start, end - start); return new TextRange(start, end - start);
} }
/// <summary> internal readonly struct SplitTextRunsResult
/// 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
{ {
public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second) public SplitTextRunsResult(IReadOnlyList<ShapedTextCharacters> first, IReadOnlyList<ShapedTextCharacters> second)
{ {

67
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -1,9 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
@ -17,6 +15,7 @@ namespace Avalonia.Media.TextFormatting
private readonly ReadOnlySlice<char> _text; private readonly ReadOnlySlice<char> _text;
private readonly TextParagraphProperties _paragraphProperties; private readonly TextParagraphProperties _paragraphProperties;
private readonly IReadOnlyList<ValueSpan<TextRunProperties>> _textStyleOverrides; private readonly IReadOnlyList<ValueSpan<TextRunProperties>> _textStyleOverrides;
private readonly TextTrimming _textTrimming;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TextLayout" /> class. /// Initializes a new instance of the <see cref="TextLayout" /> class.
@ -54,9 +53,11 @@ namespace Avalonia.Media.TextFormatting
new ReadOnlySlice<char>(text.AsMemory()); new ReadOnlySlice<char>(text.AsMemory());
_paragraphProperties = _paragraphProperties =
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, textTrimming, CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
textDecorations, lineHeight); textDecorations, lineHeight);
_textTrimming = textTrimming;
_textStyleOverrides = textStyleOverrides; _textStyleOverrides = textStyleOverrides;
LineHeight = lineHeight; LineHeight = lineHeight;
@ -143,18 +144,16 @@ namespace Avalonia.Media.TextFormatting
/// <param name="foreground">The foreground.</param> /// <param name="foreground">The foreground.</param>
/// <param name="textAlignment">The text alignment.</param> /// <param name="textAlignment">The text alignment.</param>
/// <param name="textWrapping">The text wrapping.</param> /// <param name="textWrapping">The text wrapping.</param>
/// <param name="textTrimming">The text trimming.</param>
/// <param name="textDecorations">The text decorations.</param> /// <param name="textDecorations">The text decorations.</param>
/// <param name="lineHeight">The height of each line of text.</param> /// <param name="lineHeight">The height of each line of text.</param>
/// <returns></returns> /// <returns></returns>
private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, 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) TextDecorationCollection textDecorations, double lineHeight)
{ {
var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground); var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground);
return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, textTrimming, return new GenericTextParagraphProperties(textRunStyle, textAlignment, textWrapping, lineHeight);
lineHeight);
} }
/// <summary> /// <summary>
@ -214,25 +213,44 @@ namespace Avalonia.Media.TextFormatting
var textSource = new FormattedTextSource(_text, var textSource = new FormattedTextSource(_text,
_paragraphProperties.DefaultTextRunProperties, _textStyleOverrides); _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, 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) 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 struct FormattedTextSource : ITextSource
{ {
private readonly ReadOnlySlice<char> _text; private readonly ReadOnlySlice<char> _text;

45
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@ -39,6 +39,14 @@ namespace Avalonia.Media.TextFormatting
/// </returns> /// </returns>
public abstract TextLineBreak LineBreak { get; } 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> /// <summary>
/// Draws the <see cref="TextLine"/> at the given origin. /// Draws the <see cref="TextLine"/> at the given origin.
/// </summary> /// </summary>
@ -47,40 +55,49 @@ namespace Avalonia.Media.TextFormatting
public abstract void Draw(DrawingContext drawingContext, Point origin); public abstract void Draw(DrawingContext drawingContext, Point origin);
/// <summary> /// <summary>
/// Client to get the character hit corresponding to the specified /// Create a collapsed line based on collapsed text properties.
/// distance from the beginning of the line. /// </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> /// </summary>
/// <param name="distance">distance in text flow direction from the beginning of the line</param> /// <param name="distance">A <see cref="double"/> value that represents the distance from the beginning of the line.</param>
/// <returns>The <see cref="CharacterHit"/></returns> /// <returns>The <see cref="CharacterHit"/> object at the specified distance from the beginning of the line.</returns>
public abstract CharacterHit GetCharacterHitFromDistance(double distance); public abstract CharacterHit GetCharacterHitFromDistance(double distance);
/// <summary> /// <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"/>. /// <see cref="CharacterHit"/>.
/// </summary> /// </summary>
/// <param name="characterHit"><see cref="CharacterHit"/> of the character to query the distance.</param> /// <param name="characterHit">The <see cref="CharacterHit"/> object whose distance you want to query.</param>
/// <returns>Distance in text flow direction from the beginning of the line.</returns> /// <returns>A <see cref="double"/> that represents the distance from the beginning of the line.</returns>
public abstract double GetDistanceFromCharacterHit(CharacterHit characterHit); public abstract double GetDistanceFromCharacterHit(CharacterHit characterHit);
/// <summary> /// <summary>
/// Client to get the next <see cref="CharacterHit"/> for caret navigation. /// Gets the next character hit for caret navigation.
/// </summary> /// </summary>
/// <param name="characterHit">The current <see cref="CharacterHit"/>.</param> /// <param name="characterHit">The current <see cref="CharacterHit"/>.</param>
/// <returns>The next <see cref="CharacterHit"/>.</returns> /// <returns>The next <see cref="CharacterHit"/>.</returns>
public abstract CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit); public abstract CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit);
/// <summary> /// <summary>
/// Client to get the previous character hit for caret navigation /// Gets the previous character hit for caret navigation.
/// </summary> /// </summary>
/// <param name="characterHit">the current character hit</param> /// <param name="characterHit">The current <see cref="CharacterHit"/>.</param>
/// <returns>The previous <see cref="CharacterHit"/></returns> /// <returns>The previous <see cref="CharacterHit"/>.</returns>
public abstract CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit); public abstract CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit);
/// <summary> /// <summary>
/// Client to get the previous character hit after backspacing /// Gets the previous character hit after backspacing.
/// </summary> /// </summary>
/// <param name="characterHit">the current character hit</param> /// <param name="characterHit">The current <see cref="CharacterHit"/>.</param>
/// <returns>The <see cref="CharacterHit"/> after backspacing</returns> /// <returns>The <see cref="CharacterHit"/> after backspacing.</returns>
public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit); public abstract CharacterHit GetBackspaceCaretCharacterHit(CharacterHit characterHit);
/// <summary> /// <summary>

157
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -1,4 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
@ -7,11 +9,12 @@ namespace Avalonia.Media.TextFormatting
private readonly IReadOnlyList<ShapedTextCharacters> _textRuns; private readonly IReadOnlyList<ShapedTextCharacters> _textRuns;
public TextLineImpl(IReadOnlyList<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics, public TextLineImpl(IReadOnlyList<ShapedTextCharacters> textRuns, TextLineMetrics lineMetrics,
TextLineBreak lineBreak = null) TextLineBreak lineBreak = null, bool hasCollapsed = false)
{ {
_textRuns = textRuns; _textRuns = textRuns;
LineMetrics = lineMetrics; LineMetrics = lineMetrics;
LineBreak = lineBreak; LineBreak = lineBreak;
HasCollapsed = hasCollapsed;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -26,6 +29,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/> /// <inheritdoc/>
public override TextLineBreak LineBreak { get; } public override TextLineBreak LineBreak { get; }
/// <inheritdoc/>
public override bool HasCollapsed { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin) 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/> /// <inheritdoc/>
public override CharacterHit GetCharacterHitFromDistance(double distance) public override CharacterHit GetCharacterHitFromDistance(double distance)
{ {
@ -82,7 +181,7 @@ namespace Avalonia.Media.TextFormatting
return nextCharacterHit; 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/> /// <inheritdoc/>
@ -93,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
return previousCharacterHit; 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/> /// <inheritdoc/>
@ -148,9 +247,13 @@ namespace Avalonia.Media.TextFormatting
{ {
var run = _textRuns[runIndex]; 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; return true;
} }
@ -184,9 +287,13 @@ namespace Avalonia.Media.TextFormatting
{ {
var run = _textRuns[runIndex]; 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; return true;
} }
@ -230,5 +337,41 @@ namespace Avalonia.Media.TextFormatting
return runIndex; 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;
}
} }
} }

11
src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs

@ -9,11 +9,12 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
public readonly struct TextLineMetrics public readonly struct TextLineMetrics
{ {
public TextLineMetrics(Size size, double textBaseline, TextRange textRange) public TextLineMetrics(Size size, double textBaseline, TextRange textRange, bool hasOverflowed)
{ {
Size = size; Size = size;
TextBaseline = textBaseline; TextBaseline = textBaseline;
TextRange = textRange; TextRange = textRange;
HasOverflowed = hasOverflowed;
} }
/// <summary> /// <summary>
@ -37,6 +38,12 @@ namespace Avalonia.Media.TextFormatting
/// </summary> /// </summary>
public double TextBaseline { get; } 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> /// <summary>
/// Creates the text line metrics. /// Creates the text line metrics.
/// </summary> /// </summary>
@ -83,7 +90,7 @@ namespace Avalonia.Media.TextFormatting
descent - ascent + lineGap : descent - ascent + lineGap :
paragraphProperties.LineHeight); paragraphProperties.LineHeight);
return new TextLineMetrics(size, -ascent, textRange); return new TextLineMetrics(size, -ascent, textRange, size.Width > paragraphWidth);
} }
} }
} }

5
src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs

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

7
src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs

@ -4,12 +4,11 @@ using System.Globalization;
namespace Avalonia.Media.TextFormatting namespace Avalonia.Media.TextFormatting
{ {
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The client provides a concrete implementation of this abstract run properties class. This /// The text layout client provides a concrete implementation of this abstract class.
/// allows client to implement their run properties the way that fits with their run formatting /// This enables the client to implement text run properties in a way that corresponds with the associated formatting store.
/// store.
/// </remarks> /// </remarks>
public abstract class TextRunProperties : IEquatable<TextRunProperties> public abstract class TextRunProperties : IEquatable<TextRunProperties>
{ {

33
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
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;
}
}

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs

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

1
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

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

16
src/Avalonia.Visuals/Media/TextWrapping.cs

@ -5,13 +5,6 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public enum TextWrapping 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> /// <summary>
/// Text should not wrap. /// Text should not wrap.
/// </summary> /// </summary>
@ -20,6 +13,13 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Text can wrap. /// Text can wrap.
/// </summary> /// </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
} }
} }

4
src/Avalonia.X11/X11NativeControlHost.cs

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

47
src/Avalonia.X11/X11Window.cs

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

4
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

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

14
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
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
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs → src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

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

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

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

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

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

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

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

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

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

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

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

29
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.Diagnostics;
using System.Linq; using System.Linq;
using System.Text;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Utilities;
using XamlX; using XamlX;
using XamlX.Ast; using XamlX.Ast;
using XamlX.Transform; using XamlX.Transform;
@ -129,12 +124,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (itemsCollectionType != null) if (itemsCollectionType != null)
{ {
var elementType = itemsCollectionType foreach (var i in GetAllInterfacesIncludingSelf(itemsCollectionType))
.GetAllInterfaces() {
.FirstOrDefault(i => if (i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true)
i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true) {
.GenericArguments[0]; return new AvaloniaXamlIlDataContextTypeMetadataNode(on, i.GenericArguments[0]);
return new AvaloniaXamlIlDataContextTypeMetadataNode(on, elementType); }
}
} }
// We can't infer the collection type and the currently calculated type is definitely wrong. // 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. // 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); 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}")] [DebuggerDisplay("DataType = {DataContextType}")]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

12
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
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github → src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

36
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -45,44 +45,10 @@
<Compile Include="Templates\Template.cs" /> <Compile Include="Templates\Template.cs" />
<Compile Include="Templates\TemplateContent.cs" /> <Compile Include="Templates\TemplateContent.cs" />
<Compile Include="Templates\TreeDataTemplate.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\IAvaloniaXamlIlParentStackProvider.cs" />
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" /> <Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" /> <Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
<Compile Include="XamlLoadException.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="..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
<Compile Include="XamlTypes.cs" /> <Compile Include="XamlTypes.cs" />
</ItemGroup> </ItemGroup>
@ -96,8 +62,6 @@
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" /> <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\Avalonia.Markup\Avalonia.Markup.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> </ItemGroup>
<Import Project="..\..\..\build\Rx.props" /> <Import Project="..\..\..\build\Rx.props" />
</Project> </Project>

67
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -10,10 +10,13 @@ namespace Avalonia.Markup.Xaml
/// <summary> /// <summary>
/// Loads XAML for a avalonia application. /// Loads XAML for a avalonia application.
/// </summary> /// </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> /// <summary>
/// Loads the XAML into a Avalonia component. /// Loads the XAML into a Avalonia component.
/// </summary> /// </summary>
@ -32,7 +35,7 @@ namespace Avalonia.Markup.Xaml
/// A base URI to use if <paramref name="uri"/> is relative. /// A base URI to use if <paramref name="uri"/> is relative.
/// </param> /// </param>
/// <returns>The loaded object.</returns> /// <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); Contract.Requires<ArgumentNullException>(uri != null);
@ -55,52 +58,22 @@ namespace Avalonia.Markup.Xaml
if (compiledResult != null) if (compiledResult != null)
return compiledResult; 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> throw new XamlLoadException(
/// Loads XAML from a stream. $"No precompiled XAML found for {uri} (baseUri: {baseUri}), make sure to specify x:Class and include your XAML file as AvaloniaResource");
/// </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);
} }
} }

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

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

3
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

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

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

Loading…
Cancel
Save