Browse Source

Merge branch 'master' into headless-platform

pull/2847/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
8ce7e2a38d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .ncrunch/NativeEmbedSample.v3.ncrunchproject
  2. 8
      Avalonia.sln
  3. 5
      native/Avalonia.Native/inc/avalonia-native.h
  4. 21
      native/Avalonia.Native/src/OSX/controlhost.mm
  5. 33
      native/Avalonia.Native/src/OSX/window.mm
  6. 2
      nukebuild/Numerge
  7. 4
      packages/Avalonia/Avalonia.csproj
  8. 10
      packages/Avalonia/AvaloniaBuildTasks.props
  9. 22
      packages/Avalonia/AvaloniaBuildTasks.targets
  10. 18
      packages/Avalonia/AvaloniaItemSchema.xaml
  11. 2
      readme.md
  12. 26
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  13. 9
      samples/interop/NativeEmbedSample/MainWindow.xaml
  14. 1
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  15. 6
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  16. 4
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  17. 3
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  18. 2
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  19. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  20. 89
      src/Avalonia.Controls/NativeControlHost.cs
  21. 4
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  22. 164
      src/Avalonia.Controls/ProgressBar.cs
  23. 67
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  24. 2
      src/Avalonia.Controls/SelectionModel.cs
  25. 9
      src/Avalonia.Controls/TopLevel.cs
  26. 9
      src/Avalonia.Controls/Utils/IEnumerableUtils.cs
  27. 4
      src/Avalonia.Controls/Window.cs
  28. 2
      src/Avalonia.Controls/WindowBase.cs
  29. 10
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  30. 29
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  31. 23
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  32. 14
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  33. 78
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  34. 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  35. 37
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  36. 22
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  37. 24
      src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs
  38. 24
      src/Avalonia.Layout/ILayoutManager.cs
  39. 7
      src/Avalonia.Layout/ILayoutable.cs
  40. 205
      src/Avalonia.Layout/LayoutManager.cs
  41. 9
      src/Avalonia.Layout/LayoutQueue.cs
  42. 70
      src/Avalonia.Layout/Layoutable.cs
  43. 9
      src/Avalonia.Native/NativeControlHostImpl.cs
  44. 12
      src/Avalonia.Native/PopupImpl.cs
  45. 2
      src/Avalonia.Native/WindowImplBase.cs
  46. 12
      src/Avalonia.Themes.Default/ProgressBar.xaml
  47. 43
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  48. 45
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  49. 9
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  50. 8
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  51. 1
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  52. 23
      src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
  53. 44
      src/Avalonia.Themes.Fluent/FocusAdorner.xaml
  54. 191
      src/Avalonia.Themes.Fluent/ProgressBar.xaml
  55. 16
      src/Avalonia.Themes.Fluent/ToolTip.xaml
  56. 61
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  57. BIN
      src/Avalonia.Visuals/Assets/GraphemeBreak.trie
  58. BIN
      src/Avalonia.Visuals/Assets/UnicodeData.trie
  59. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
  60. 65
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
  61. 34
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
  62. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
  63. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  64. 178
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs
  65. 10
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs
  66. 15
      src/Avalonia.Visuals/Rect.cs
  67. 22
      src/Avalonia.X11/X11NativeControlHost.cs
  68. 12
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  69. 19
      src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
  70. 2
      tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs
  71. 2
      tests/Avalonia.Benchmarks/Layout/Measure.cs
  72. 2
      tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs
  73. 2
      tests/Avalonia.Controls.UnitTests/GridTests.cs
  74. 2
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  75. 9
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  76. 27
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  77. 6
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  78. 25
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  79. 48
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  80. 4
      tests/Avalonia.Layout.UnitTests/LayoutableTests.cs
  81. 424
      tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs
  82. 20
      tests/Avalonia.LeakTests/ControlTests.cs
  83. 4
      tests/Avalonia.UnitTests/TestRoot.cs
  84. 3
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs
  85. 67
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
  86. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
  87. 83
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
  88. 2
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
  89. 85
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs
  90. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
  91. 25
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
  92. 181
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs
  93. 8
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs
  94. 6
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  95. 10
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

5
.ncrunch/NativeEmbedSample.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

8
Avalonia.sln

@ -201,9 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject EndProject
@ -215,8 +215,8 @@ 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
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13

5
native/Avalonia.Native/inc/avalonia-native.h

@ -278,6 +278,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0; virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
virtual HRESULT TakeFocusFromChildren() = 0;
}; };
AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
@ -493,8 +494,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
virtual void* GetParentHandle() = 0; virtual void* GetParentHandle() = 0;
virtual HRESULT InitializeWithChildHandle(void* child) = 0; virtual HRESULT InitializeWithChildHandle(void* child) = 0;
virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0; virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
virtual void MoveTo(float x, float y, float width, float height) = 0; virtual void ShowInBounds(float x, float y, float width, float height) = 0;
virtual void Hide() = 0; virtual void HideWithSize(float width, float height) = 0;
virtual void ReleaseChild() = 0; virtual void ReleaseChild() = 0;
}; };

21
native/Avalonia.Native/src/OSX/controlhost.mm

@ -97,7 +97,7 @@ public:
return S_OK; return S_OK;
}; };
virtual void MoveTo(float x, float y, float width, float height) override virtual void ShowInBounds(float x, float y, float width, float height) override
{ {
if(_child == nil) if(_child == nil)
return; return;
@ -106,7 +106,7 @@ public:
IAvnNativeControlHostTopLevelAttachment* slf = this; IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef(); slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
slf->MoveTo(x, y, width, height); slf->ShowInBounds(x, y, width, height);
slf->Release(); slf->Release();
}); });
return; return;
@ -122,9 +122,24 @@ public:
[[_holder superview] setNeedsDisplay:true]; [[_holder superview] setNeedsDisplay:true];
} }
virtual void Hide() override virtual void HideWithSize(float width, float height) override
{ {
if(_child == nil)
return;
if(AvnInsidePotentialDeadlock::IsInside())
{
IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{
slf->HideWithSize(width, height);
slf->Release();
});
return;
}
NSRect frame = {0, 0, width, height};
[_holder setHidden: true]; [_holder setHidden: true];
[_child setFrame: frame];
} }
virtual void ReleaseChild() override virtual void ReleaseChild() override

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

@ -116,10 +116,15 @@ public:
{ {
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
UpdateStyle(); UpdateStyle();
if(ShouldTakeFocusOnShow())
[Window makeKeyAndOrderFront:Window]; {
[NSApp activateIgnoringOtherApps:YES]; [Window makeKeyAndOrderFront:Window];
[NSApp activateIgnoringOtherApps:YES];
}
else
{
[Window orderFront: Window];
}
[Window setTitle:_lastTitle]; [Window setTitle:_lastTitle];
_shown = true; _shown = true;
@ -128,6 +133,11 @@ public:
} }
} }
virtual bool ShouldTakeFocusOnShow()
{
return true;
}
virtual HRESULT Hide () override virtual HRESULT Hide () override
{ {
@autoreleasepool @autoreleasepool
@ -774,6 +784,15 @@ private:
} }
} }
virtual HRESULT TakeFocusFromChildren () override
{
if(Window == nil)
return S_OK;
if([Window isKeyWindow])
[Window makeFirstResponder: View];
return S_OK;
}
void EnterFullScreenMode () void EnterFullScreenMode ()
{ {
_fullScreenActive = true; _fullScreenActive = true;
@ -1858,7 +1877,6 @@ private:
WindowEvents = events; WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel]; [Window setLevel:NSPopUpMenuWindowLevel];
} }
protected: protected:
virtual NSWindowStyleMask GetStyle() override virtual NSWindowStyleMask GetStyle() override
{ {
@ -1876,6 +1894,11 @@ protected:
return S_OK; return S_OK;
} }
} }
public:
virtual bool ShouldTakeFocusOnShow() override
{
return false;
}
}; };
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl) extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)

2
nukebuild/Numerge

@ -1 +1 @@
Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8 Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5

4
packages/Avalonia/Avalonia.csproj

@ -41,6 +41,10 @@
<Pack>true</Pack> <Pack>true</Pack>
<PackagePath>build\</PackagePath> <PackagePath>build\</PackagePath>
</Content> </Content>
<Content Include="AvaloniaItemSchema.xaml">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
</Content>
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\SharedVersion.props" /> <Import Project="..\..\build\SharedVersion.props" />
<Import Project="..\..\build\NetFX.props" /> <Import Project="..\..\build\NetFX.props" />

10
packages/Avalonia/AvaloniaBuildTasks.props

@ -1,3 +1,11 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<AvailableItemName Include="AvaloniaXaml" />
<AvailableItemName Include="AvaloniaResource" />
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)AvaloniaItemSchema.xaml" />
</ItemGroup>
<ItemGroup Condition="'$(EnableDefaultItems)'=='True'">
<AvaloniaXaml Include="**\*.axaml" SubType="Designer" />
<AvaloniaXaml Include="**\*.paml" SubType="Designer" />
</ItemGroup>
</Project> </Project>

22
packages/Avalonia/AvaloniaBuildTasks.targets

@ -4,6 +4,20 @@
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild> <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
<AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance> <AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
</PropertyGroup> </PropertyGroup>
<!-- Unfortunately we have to update default items in .targets since custom nuget props are improted before Microsoft.NET.Sdk.DefaultItems.props -->
<ItemGroup Condition="'$(EnableDefaultItems)'=='True'">
<Compile Update="**\*.paml.cs">
<DependentUpon>%(Filename)</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="**\*.axaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
<SubType>Code</SubType>
</Compile>
<None Remove="**\*.axaml" />
<None Remove="**\*.paml" />
</ItemGroup>
<UsingTask TaskName="GenerateAvaloniaResourcesTask" <UsingTask TaskName="GenerateAvaloniaResourcesTask"
AssemblyFile="$(AvaloniaBuildTasksLocation)" AssemblyFile="$(AvaloniaBuildTasksLocation)"
@ -31,9 +45,12 @@
<Target Name="GenerateAvaloniaResources" <Target Name="GenerateAvaloniaResources"
BeforeTargets="CoreCompile;CoreResGen" BeforeTargets="CoreCompile;CoreResGen"
Inputs="@(AvaloniaResource);$(MSBuildAllProjects)" Inputs="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)"
Outputs="$(AvaloniaResourcesTemporaryFilePath)" Outputs="$(AvaloniaResourcesTemporaryFilePath)"
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)"> DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
<ItemGroup>
<AvaloniaResource Include="@(AvaloniaXaml)"/>
</ItemGroup>
<GenerateAvaloniaResourcesTask <GenerateAvaloniaResourcesTask
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'" Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
Output="$(AvaloniaResourcesTemporaryFilePath)" Output="$(AvaloniaResourcesTemporaryFilePath)"
@ -79,5 +96,6 @@
<ItemGroup> <ItemGroup>
<UpToDateCheckInput Include="@(AvaloniaResource)" /> <UpToDateCheckInput Include="@(AvaloniaResource)" />
<UpToDateCheckInput Include="@(AvaloniaXaml)" />
</ItemGroup> </ItemGroup>
</Project> </Project>

18
packages/Avalonia/AvaloniaItemSchema.xaml

@ -0,0 +1,18 @@
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties">
<ContentType
Name="AvaloniaXaml"
DisplayName="Avalonia XAML"
ItemType="AvaloniaXaml">
<NameValuePair Name="DependentFileExtensions" Value=".cs" />
<NameValuePair Name="DefaultMetadata_SubType" Value="Designer" />
</ContentType>
<ItemType Name="AvaloniaXaml" DisplayName="Avalonia XAML" />
<FileExtension Name=".axaml" ContentType="AvaloniaXaml" />
<FileExtension Name=".paml" ContentType="AvaloniaXaml" />
<ContentType
Name="AvaloniaResource"
DisplayName="Avalonia Resource"
ItemType="AvaloniaResource"
/>
</ProjectSchemaDefinitions>

2
readme.md

@ -68,7 +68,7 @@ Avalonia is licenced under the [MIT licence](licence.md).
## Contributors ## Contributors
This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)]. This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing)].
<a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a> <a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
### Backers ### Backers

26
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -1,29 +1,19 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ProgressBarPage">
<StackPanel Orientation="Vertical" Spacing="4"> <StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ProgressBar</TextBlock> <TextBlock Classes="h1">ProgressBar</TextBlock>
<TextBlock Classes="h2">A progress bar control</TextBlock> <TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel> <StackPanel>
<CheckBox <CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" />
x:Name="showProgress" <CheckBox x:Name="isIndeterminate" Margin="10,16,0,0" Content="Toggle Indeterminate" />
Margin="10,16,0,0" <StackPanel Orientation="Horizontal" Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16">
Content="Show Progress Text" />
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Spacing="16"> <StackPanel Spacing="16">
<ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" /> <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
<ProgressBar IsIndeterminate="True"/>
</StackPanel> </StackPanel>
<ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" /> <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
<ProgressBar Orientation="Vertical" IsIndeterminate="True" />
</StackPanel> </StackPanel>
<StackPanel Margin="16"> <StackPanel Margin="16">
<Slider Name="hprogress" Maximum="100" Value="40"/> <Slider Name="hprogress" Maximum="100" Value="40" />
<Slider Name="vprogress" Maximum="100" Value="60"/> <Slider Name="vprogress" Maximum="100" Value="60" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

9
samples/interop/NativeEmbedSample/MainWindow.xaml

@ -20,7 +20,16 @@
<DockPanel DockPanel.Dock="Top"> <DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button> <Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button> <Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
<Border DockPanel.Dock="Right" Background="#c0c0c0">
<ToolTip.Tip>
<ToolTip>
<TextBlock>Text</TextBlock>
</ToolTip>
</ToolTip.Tip>
<TextBlock>Tooltip</TextBlock>
</Border>
<TextBox Text="Lorem ipsum dolor sit amet"/> <TextBox Text="Lorem ipsum dolor sit amet"/>
</DockPanel> </DockPanel>
<Grid ColumnDefinitions="*,5,*"> <Grid ColumnDefinitions="*,5,*">
<DockPanel> <DockPanel>

1
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -140,6 +140,7 @@ namespace Avalonia.Collections
} }
} }
[Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>( public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
this IAvaloniaReadOnlyList<TSource> collection, this IAvaloniaReadOnlyList<TSource> collection,
Func<TSource, TDerived> select) Func<TSource, TDerived> select)

6
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -14,7 +14,7 @@ namespace Avalonia.Threading
private readonly DispatcherPriority _priority; private readonly DispatcherPriority _priority;
private TimeSpan _interval; private TimeSpan _interval;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class. /// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary> /// </summary>
@ -154,6 +154,8 @@ namespace Avalonia.Threading
TimeSpan interval, TimeSpan interval,
DispatcherPriority priority = DispatcherPriority.Normal) DispatcherPriority priority = DispatcherPriority.Normal)
{ {
interval = (interval != TimeSpan.Zero) ? interval : TimeSpan.FromTicks(1);
var timer = new DispatcherTimer(priority) { Interval = interval }; var timer = new DispatcherTimer(priority) { Interval = interval };
timer.Tick += (s, e) => timer.Tick += (s, e) =>
@ -197,7 +199,7 @@ namespace Avalonia.Threading
} }
} }
/// <summary> /// <summary>
/// Raises the <see cref="Tick"/> event on the dispatcher thread. /// Raises the <see cref="Tick"/> event on the dispatcher thread.

4
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@ -107,7 +107,7 @@ namespace Avalonia.Build.Tasks
foreach (var s in sources.ToList()) foreach (var s in sources.ToList())
{ {
if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml")) if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml"))
{ {
XamlFileInfo info; XamlFileInfo info;
try try
@ -150,7 +150,7 @@ namespace Avalonia.Build.Tasks
BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance); BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance);
foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml"))) foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml") || r.ItemSpec.EndsWith(".axaml")))
BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec, BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec,
"XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work"); "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
var resources = BuildResourceSources(); var resources = BuildResourceSources();

3
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -25,7 +25,8 @@ namespace Avalonia.Build.Tasks
public static partial class XamlCompilerTaskExecutor public static partial class XamlCompilerTaskExecutor
{ {
static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
|| r.Name.ToLowerInvariant().EndsWith(".paml"); || r.Name.ToLowerInvariant().EndsWith(".paml")
|| r.Name.ToLowerInvariant().EndsWith(".axaml");
public class CompileResult public class CompileResult
{ {

2
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -25,7 +25,7 @@ namespace Avalonia.Controls.Embedding
{ {
EnsureInitialized(); EnsureInitialized();
ApplyTemplate(); ApplyTemplate();
LayoutManager.ExecuteInitialLayoutPass(this); LayoutManager.ExecuteInitialLayoutPass();
} }
private void EnsureInitialized() private void EnsureInitialized()

2
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
{ {
EnsureInitialized(); EnsureInitialized();
ApplyTemplate(); ApplyTemplate();
LayoutManager.ExecuteInitialLayoutPass(this); LayoutManager.ExecuteInitialLayoutPass();
} }
private void EnsureInitialized() private void EnsureInitialized()

89
src/Avalonia.Controls/NativeControlHost.cs

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.LogicalTree;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -12,14 +14,18 @@ namespace Avalonia.Controls
private INativeControlHostControlTopLevelAttachment _attachment; private INativeControlHostControlTopLevelAttachment _attachment;
private IPlatformHandle _nativeControlHandle; private IPlatformHandle _nativeControlHandle;
private bool _queuedForDestruction; private bool _queuedForDestruction;
private bool _queuedForMoveResize;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
static NativeControlHost() static NativeControlHost()
{ {
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged); IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
} }
private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) public NativeControlHost()
=> host.UpdateHost(); {
_propertyChangedHandler = PropertyChangedHandler;
}
private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost(); => host.UpdateHost();
@ -27,21 +33,46 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = e.Root as TopLevel; _currentRoot = e.Root as TopLevel;
var visual = (IVisual)this;
while (visual != _currentRoot)
{
if (visual is Visual v)
{
v.PropertyChanged += _propertyChangedHandler;
_propertyChangedSubscriptions.Add(v);
}
visual = visual.GetVisualParent();
}
UpdateHost(); UpdateHost();
} }
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.IsEffectiveValueChange && e.Property == BoundsProperty)
EnqueueForMoveResize();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = null; _currentRoot = null;
if (_propertyChangedSubscriptions != null)
{
foreach (var v in _propertyChangedSubscriptions)
v.PropertyChanged -= _propertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
UpdateHost(); UpdateHost();
} }
void UpdateHost() private void UpdateHost()
{ {
_queuedForMoveResize = false;
_currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost; _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
var needsAttachment = _currentHost != null; var needsAttachment = _currentHost != null;
var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
if (needsAttachment) if (needsAttachment)
{ {
@ -93,22 +124,46 @@ namespace Avalonia.Controls
} }
} }
if (needsShow) if (_attachment?.AttachedTo != _currentHost)
_attachment?.ShowInBounds(TransformedBounds.Value); return;
else if (needsAttachment)
_attachment?.Hide(); TryUpdateNativeControlPosition();
}
private Rect? GetAbsoluteBounds()
{
var bounds = Bounds;
var position = this.TranslatePoint(bounds.Position, _currentRoot);
if (position == null)
return null;
return new Rect(position.Value, bounds.Size);
}
void EnqueueForMoveResize()
{
if(_queuedForMoveResize)
return;
_queuedForMoveResize = true;
Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render);
} }
public bool TryUpdateNativeControlPosition() public bool TryUpdateNativeControlPosition()
{ {
var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue; if (_currentHost == null)
return false;
var bounds = GetAbsoluteBounds();
var needsShow = IsEffectivelyVisible && bounds.HasValue;
if(needsShow) if (needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value); _attachment?.ShowInBounds(bounds.Value);
return needsShow; else
_attachment?.HideWithSize(Bounds.Size);
return false;
} }
void CheckDestruction() private void CheckDestruction()
{ {
_queuedForDestruction = false; _queuedForDestruction = false;
if (_currentRoot == null) if (_currentRoot == null)
@ -117,10 +172,12 @@ namespace Avalonia.Controls
protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{ {
if (_currentHost == null)
throw new InvalidOperationException();
return _currentHost.CreateDefaultChild(parent); return _currentHost.CreateDefaultChild(parent);
} }
void DestroyNativeControl() private void DestroyNativeControl()
{ {
if (_nativeControlHandle != null) if (_nativeControlHandle != null)
{ {

4
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform
{ {
INativeControlHostImpl AttachedTo { get; set; } INativeControlHostImpl AttachedTo { get; set; }
bool IsCompatibleWith(INativeControlHostImpl host); bool IsCompatibleWith(INativeControlHostImpl host);
void Hide(); void HideWithSize(Size size);
void ShowInBounds(TransformedBounds transformedBounds); void ShowInBounds(Rect rect);
} }
public interface ITopLevelImplWithNativeControlHost public interface ITopLevelImplWithNativeControlHost

164
src/Avalonia.Controls/ProgressBar.cs

@ -1,8 +1,7 @@
using System; using System;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -11,6 +10,92 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class ProgressBar : RangeBase public class ProgressBar : RangeBase
{ {
public class ProgressBarTemplateProperties : AvaloniaObject
{
private double _container2Width;
private double _containerWidth;
private double _containerAnimationStartPosition;
private double _containerAnimationEndPosition;
private double _container2AnimationStartPosition;
private double _container2AnimationEndPosition;
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(ContainerAnimationStartPosition),
p => p.ContainerAnimationStartPosition,
(p, o) => p.ContainerAnimationStartPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(ContainerAnimationEndPosition),
p => p.ContainerAnimationEndPosition,
(p, o) => p.ContainerAnimationEndPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(Container2AnimationStartPosition),
p => p.Container2AnimationStartPosition,
(p, o) => p.Container2AnimationStartPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(Container2AnimationEndPosition),
p => p.Container2AnimationEndPosition,
(p, o) => p.Container2AnimationEndPosition = o);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2WidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(Container2Width),
p => p.Container2Width,
(p, o) => p.Container2Width = o);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerWidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(ContainerWidth),
p => p.ContainerWidth,
(p, o) => p.ContainerWidth = o);
public double ContainerAnimationStartPosition
{
get => _containerAnimationStartPosition;
set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value);
}
public double ContainerAnimationEndPosition
{
get => _containerAnimationEndPosition;
set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value);
}
public double Container2AnimationStartPosition
{
get => _container2AnimationStartPosition;
set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value);
}
public double Container2Width
{
get => _container2Width;
set => SetAndRaise(Container2WidthProperty, ref _container2Width, value);
}
public double ContainerWidth
{
get => _containerWidth;
set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value);
}
public double Container2AnimationEndPosition
{
get => _container2AnimationEndPosition;
set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value);
}
}
private double _indeterminateStartingOffset;
private double _indeterminateEndingOffset;
private Border _indicator;
public static readonly StyledProperty<bool> IsIndeterminateProperty = public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate)); AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
@ -20,19 +105,33 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal); AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
private static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty = [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>( AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(IndeterminateStartingOffset), nameof(IndeterminateStartingOffset),
p => p.IndeterminateStartingOffset, p => p.IndeterminateStartingOffset,
(p, o) => p.IndeterminateStartingOffset = o); (p, o) => p.IndeterminateStartingOffset = o);
private static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty = [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>( AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(IndeterminateEndingOffset), nameof(IndeterminateEndingOffset),
p => p.IndeterminateEndingOffset, p => p.IndeterminateEndingOffset,
(p, o) => p.IndeterminateEndingOffset = o); (p, o) => p.IndeterminateEndingOffset = o);
private Border _indicator; [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public double IndeterminateStartingOffset
{
get => _indeterminateStartingOffset;
set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
}
[Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public double IndeterminateEndingOffset
{
get => _indeterminateEndingOffset;
set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
}
static ProgressBar() static ProgressBar()
{ {
@ -45,6 +144,8 @@ namespace Avalonia.Controls
UpdatePseudoClasses(IsIndeterminate, Orientation); UpdatePseudoClasses(IsIndeterminate, Orientation);
} }
public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties();
public bool IsIndeterminate public bool IsIndeterminate
{ {
get => GetValue(IsIndeterminateProperty); get => GetValue(IsIndeterminateProperty);
@ -62,19 +163,6 @@ namespace Avalonia.Controls
get => GetValue(OrientationProperty); get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value); set => SetValue(OrientationProperty, value);
} }
private double _indeterminateStartingOffset;
private double IndeterminateStartingOffset
{
get => _indeterminateStartingOffset;
set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
}
private double _indeterminateEndingOffset;
private double IndeterminateEndingOffset
{
get => _indeterminateEndingOffset;
set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
@ -111,21 +199,33 @@ namespace Avalonia.Controls
{ {
if (IsIndeterminate) if (IsIndeterminate)
{ {
if (Orientation == Orientation.Horizontal) // Pulled from ModernWPF.
{
var width = bounds.Width / 5.0;
IndeterminateStartingOffset = -width;
_indicator.Width = width;
IndeterminateEndingOffset = bounds.Width;
} var dim = Orientation == Orientation.Horizontal ? bounds.Width : bounds.Height;
else var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
{ var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
var height = bounds.Height / 5.0;
IndeterminateStartingOffset = -bounds.Height; TemplateProperties.ContainerWidth = barIndicatorWidth;
_indicator.Height = height; TemplateProperties.Container2Width = barIndicatorWidth2;
IndeterminateEndingOffset = height;
} TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
// Remove these properties when we switch to fluent as default and removed the old one.
IndeterminateStartingOffset = -(dim / 5d);
IndeterminateEndingOffset = dim;
var padding = Padding;
var rectangle = new RectangleGeometry(
new Rect(
padding.Left,
padding.Top,
bounds.Width - (padding.Right + padding.Left),
bounds.Height - (padding.Bottom + padding.Top)
));
} }
else else
{ {

67
src/Avalonia.Controls/Repeater/ViewportManager.cs

@ -49,8 +49,8 @@ namespace Avalonia.Controls
// For non-virtualizing layouts, we do not need to keep // For non-virtualizing layouts, we do not need to keep
// updating viewports and invalidating measure often. So when // updating viewports and invalidating measure often. So when
// a non virtualizing layout is used, we stop doing all that work. // a non virtualizing layout is used, we stop doing all that work.
bool _managingViewportDisabled; private bool _managingViewportDisabled;
private IDisposable _effectiveViewportChangedRevoker; private bool _effectiveViewportChangedSubscribed;
private bool _layoutUpdatedSubscribed; private bool _layoutUpdatedSubscribed;
public ViewportManager(ItemsRepeater owner) public ViewportManager(ItemsRepeater owner)
@ -228,11 +228,15 @@ namespace Avalonia.Controls
_pendingViewportShift = default; _pendingViewportShift = default;
_unshiftableShift = default; _unshiftableShift = default;
_effectiveViewportChangedRevoker?.Dispose(); if (_managingViewportDisabled && _effectiveViewportChangedSubscribed)
if (!_managingViewportDisabled)
{ {
_effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner); _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged;
_effectiveViewportChangedSubscribed = false;
}
else if (!_managingViewportDisabled && !_effectiveViewportChangedSubscribed)
{
_owner.EffectiveViewportChanged += OnEffectiveViewportChanged;
_effectiveViewportChangedSubscribed = true;
} }
} }
@ -340,6 +344,11 @@ namespace Avalonia.Controls
// Note that the element being brought into view could be a descendant. // Note that the element being brought into view could be a descendant.
var targetChild = GetImmediateChildOfRepeater((IControl)args.TargetObject); var targetChild = GetImmediateChildOfRepeater((IControl)args.TargetObject);
if (targetChild is null)
{
return;
}
// Make sure that only the target child can be the anchor during the bring into view operation. // Make sure that only the target child can be the anchor during the bring into view operation.
foreach (var child in _owner.Children) foreach (var child in _owner.Children)
{ {
@ -373,7 +382,7 @@ namespace Avalonia.Controls
if (parent == null) if (parent == null)
{ {
throw new InvalidOperationException("OnBringIntoViewRequested called with args.target element not under the ItemsRepeater that recieved the call"); return null;
} }
return targetChild; return targetChild;
@ -415,15 +424,15 @@ namespace Avalonia.Controls
_scroller = null; _scroller = null;
} }
_effectiveViewportChangedRevoker?.Dispose(); _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged;
_effectiveViewportChangedRevoker = null; _effectiveViewportChangedSubscribed = false;
_ensuredScroller = false; _ensuredScroller = false;
} }
private void OnEffectiveViewportChanged(Rect effectiveViewport) private void OnEffectiveViewportChanged(object sender, EffectiveViewportChangedEventArgs e)
{ {
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId);
UpdateViewport(effectiveViewport); UpdateViewport(e.EffectiveViewport);
_pendingViewportShift = default; _pendingViewportShift = default;
_unshiftableShift = default; _unshiftableShift = default;
@ -468,8 +477,8 @@ namespace Avalonia.Controls
} }
else if (!_managingViewportDisabled) else if (!_managingViewportDisabled)
{ {
_effectiveViewportChangedRevoker?.Dispose(); _owner.EffectiveViewportChanged += OnEffectiveViewportChanged;
_effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner); _effectiveViewportChangedSubscribed = true;
} }
_ensuredScroller = true; _ensuredScroller = true;
@ -529,38 +538,6 @@ namespace Avalonia.Controls
} }
} }
private IDisposable SubscribeToEffectiveViewportChanged(IControl control)
{
// HACK: This is a bit of a hack. We need the effective viewport of the ItemsRepeater -
// we can get this from TransformedBounds, but this property is updated after layout has
// run, which is too late. Instead, for now lets just hook into an internal event on
// ScrollContentPresenter to find out what the offset and viewport will be after arrange
// and use those values. Note that this doesn't handle nested ScrollViewers at all, but
// it's enough to get scrolling to non-uniformly sized items working for now.
//
// UWP uses the EffectiveViewportChanged event (which I think was implemented specially
// for this case): we need to implement that in Avalonia, but the semantics of it aren't
// clear to me. Hopefully the source for this event will be released with WinUI 3.
if (control.VisualParent is ScrollContentPresenter scp)
{
scp.PreArrange += ScrollContentPresenterPreArrange;
return Disposable.Create(() => scp.PreArrange -= ScrollContentPresenterPreArrange);
}
return Disposable.Empty;
}
private void ScrollContentPresenterPreArrange(object sender, VectorEventArgs e)
{
var scp = (ScrollContentPresenter)sender;
var effectiveViewport = new Rect((Point)scp.Offset, new Size(e.Vector.X, e.Vector.Y));
if (effectiveViewport != _visibleWindow)
{
OnEffectiveViewportChanged(effectiveViewport);
}
}
private class ScrollerInfo private class ScrollerInfo
{ {
public ScrollerInfo(ScrollViewer scroller) public ScrollerInfo(ScrollViewer scroller)

2
src/Avalonia.Controls/SelectionModel.cs

@ -189,8 +189,6 @@ namespace Avalonia.Controls
} }
set set
{ {
var isSelected = IsSelectedWithPartialAt(value);
if (!IsSelectedAt(value) || SelectedItems.Count > 1) if (!IsSelectedAt(value) || SelectedItems.Count > 1)
{ {
using var operation = new Operation(this); using var operation = new Operation(this);

9
src/Avalonia.Controls/TopLevel.cs

@ -318,7 +318,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Creates the layout manager for this <see cref="TopLevel" />. /// Creates the layout manager for this <see cref="TopLevel" />.
/// </summary> /// </summary>
protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(); protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
/// <summary> /// <summary>
/// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>. /// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
@ -340,6 +340,9 @@ namespace Avalonia.Controls
_globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved; _globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved;
} }
Renderer?.Dispose();
Renderer = null;
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
@ -349,8 +352,8 @@ namespace Avalonia.Controls
(this as IInputRoot).MouseDevice?.TopLevelClosed(this); (this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null; PlatformImpl = null;
OnClosed(EventArgs.Empty); OnClosed(EventArgs.Empty);
Renderer?.Dispose();
Renderer = null; LayoutManager?.Dispose();
} }
/// <summary> /// <summary>

9
src/Avalonia.Controls/Utils/IEnumerableUtils.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Avalonia.Controls.Utils namespace Avalonia.Controls.Utils
@ -15,12 +16,14 @@ namespace Avalonia.Controls.Utils
{ {
if (items != null) if (items != null)
{ {
var collection = items as ICollection; if (items is ICollection collection)
if (collection != null)
{ {
return collection.Count; return collection.Count;
} }
else if (items is IReadOnlyCollection<object> readOnly)
{
return readOnly.Count;
}
else else
{ {
return Enumerable.Count(items.Cast<object>()); return Enumerable.Count(items.Cast<object>());

4
src/Avalonia.Controls/Window.cs

@ -519,7 +519,7 @@ namespace Avalonia.Controls
} }
} }
LayoutManager.ExecuteInitialLayoutPass(this); LayoutManager.ExecuteInitialLayoutPass();
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
@ -592,7 +592,7 @@ namespace Avalonia.Controls
} }
} }
LayoutManager.ExecuteInitialLayoutPass(this); LayoutManager.ExecuteInitialLayoutPass();
var result = new TaskCompletionSource<TResult>(); var result = new TaskCompletionSource<TResult>();

2
src/Avalonia.Controls/WindowBase.cs

@ -162,7 +162,7 @@ namespace Avalonia.Controls
if (!_hasExecutedInitialLayoutPass) if (!_hasExecutedInitialLayoutPass)
{ {
LayoutManager.ExecuteInitialLayoutPass(this); LayoutManager.ExecuteInitialLayoutPass();
_hasExecutedInitialLayoutPass = true; _hasExecutedInitialLayoutPass = true;
} }
PlatformImpl?.Show(); PlatformImpl?.Show();

10
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -45,7 +45,15 @@ namespace Avalonia.Diagnostics
window.Closed += DevToolsClosed; window.Closed += DevToolsClosed;
s_open.Add(root, window); s_open.Add(root, window);
window.Show();
if (root is Window inspectedWindow)
{
window.Show(inspectedWindow);
}
else
{
window.Show();
}
} }
return Disposable.Create(() => window?.Close()); return Disposable.Create(() => window?.Close());

29
src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -9,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
public LogicalTreeNode(ILogical logical, TreeNode parent) public LogicalTreeNode(ILogical logical, TreeNode parent)
: base((Control)logical, parent) : base((Control)logical, parent)
{ {
Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this)); Children = new LogicalTreeNodeCollection(this, logical);
} }
public static LogicalTreeNode[] Create(object control) public static LogicalTreeNode[] Create(object control)
@ -17,5 +18,31 @@ namespace Avalonia.Diagnostics.ViewModels
var logical = control as ILogical; var logical = control as ILogical;
return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null; return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null;
} }
internal class LogicalTreeNodeCollection : TreeNodeCollection
{
private readonly ILogical _control;
private IDisposable _subscription;
public LogicalTreeNodeCollection(TreeNode owner, ILogical control)
: base(owner)
{
_control = control;
}
public override void Dispose()
{
base.Dispose();
_subscription?.Dispose();
}
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
_subscription = _control.LogicalChildren.ForEachItem(
(i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)),
(i, item) => nodes.RemoveAt(i),
() => nodes.Clear());
}
}
} }
} }

23
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.Models; using Avalonia.Diagnostics.Models;
using Avalonia.Input; using Avalonia.Input;
@ -12,6 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly TreePageViewModel _logicalTree; private readonly TreePageViewModel _logicalTree;
private readonly TreePageViewModel _visualTree; private readonly TreePageViewModel _visualTree;
private readonly EventsPageViewModel _events; private readonly EventsPageViewModel _events;
private readonly IDisposable _pointerOverSubscription;
private ViewModelBase _content; private ViewModelBase _content;
private int _selectedTab; private int _selectedTab;
private string _focusedControl; private string _focusedControl;
@ -25,16 +27,9 @@ namespace Avalonia.Diagnostics.ViewModels
_events = new EventsPageViewModel(root); _events = new EventsPageViewModel(root);
UpdateFocusedControl(); UpdateFocusedControl();
KeyboardDevice.Instance.PropertyChanged += (s, e) => KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
{
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{
UpdateFocusedControl();
}
};
SelectedTab = 0; SelectedTab = 0;
root.GetObservable(TopLevel.PointerOverElementProperty) _pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty)
.Subscribe(x => PointerOverElement = x?.GetType().Name); .Subscribe(x => PointerOverElement = x?.GetType().Name);
Console = new ConsoleViewModel(UpdateConsoleContext); Console = new ConsoleViewModel(UpdateConsoleContext);
} }
@ -129,6 +124,8 @@ namespace Avalonia.Diagnostics.ViewModels
public void Dispose() public void Dispose()
{ {
KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged;
_pointerOverSubscription.Dispose();
_logicalTree.Dispose(); _logicalTree.Dispose();
_visualTree.Dispose(); _visualTree.Dispose();
} }
@ -137,5 +134,13 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
} }
private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{
UpdateFocusedControl();
}
}
} }
} }

14
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -3,15 +3,15 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreeNode : ViewModelBase internal class TreeNode : ViewModelBase, IDisposable
{ {
private IDisposable _classesSubscription;
private string _classes; private string _classes;
private bool _isExpanded; private bool _isExpanded;
@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels
x => control.Classes.CollectionChanged -= x) x => control.Classes.CollectionChanged -= x)
.TakeUntil(removed); .TakeUntil(removed);
classesChanged.Select(_ => Unit.Default) _classesSubscription = classesChanged.Select(_ => Unit.Default)
.StartWith(Unit.Default) .StartWith(Unit.Default)
.Subscribe(_ => .Subscribe(_ =>
{ {
@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public IAvaloniaReadOnlyList<TreeNode> Children public TreeNodeCollection Children
{ {
get; get;
protected set; protected set;
@ -104,6 +104,12 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public void Dispose()
{
_classesSubscription.Dispose();
Children.Dispose();
}
private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item) private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item)
{ {
var count = collection.Count; var count = collection.Count;

78
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs

@ -0,0 +1,78 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using Avalonia.Collections;
namespace Avalonia.Diagnostics.ViewModels
{
internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable
{
private AvaloniaList<TreeNode> _inner;
public TreeNodeCollection(TreeNode owner) => Owner = owner;
public TreeNode this[int index]
{
get
{
EnsureInitialized();
return _inner[index];
}
}
public int Count
{
get
{
EnsureInitialized();
return _inner.Count;
}
}
protected TreeNode Owner { get; }
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _inner.CollectionChanged += value;
remove => _inner.CollectionChanged -= value;
}
public event PropertyChangedEventHandler PropertyChanged
{
add => _inner.PropertyChanged += value;
remove => _inner.PropertyChanged -= value;
}
public virtual void Dispose()
{
if (_inner is object)
{
foreach (var node in _inner)
{
node.Dispose();
}
}
}
public IEnumerator<TreeNode> GetEnumerator()
{
EnsureInitialized();
return _inner.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
protected abstract void Initialize(AvaloniaList<TreeNode> nodes);
private void EnsureInitialized()
{
if (_inner is null)
{
_inner = new AvaloniaList<TreeNode>();
Initialize(_inner);
}
}
}
}

10
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -62,7 +62,15 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public void Dispose() => _details?.Dispose(); public void Dispose()
{
foreach (var node in Nodes)
{
node.Dispose();
}
_details?.Dispose();
}
public TreeNode FindNode(IControl control) public TreeNode FindNode(IControl control)
{ {

37
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -9,16 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
public VisualTreeNode(IVisual visual, TreeNode parent) public VisualTreeNode(IVisual visual, TreeNode parent)
: base(visual, parent) : base(visual, parent)
{ {
var host = visual as IVisualTreeHost; Children = new VisualTreeNodeCollection(this, visual);
if (host?.Root == null)
{
Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
}
else
{
Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
}
if ((Visual is IStyleable styleable)) if ((Visual is IStyleable styleable))
{ {
@ -33,5 +25,30 @@ namespace Avalonia.Diagnostics.ViewModels
var visual = control as IVisual; var visual = control as IVisual;
return visual != null ? new[] { new VisualTreeNode(visual, null) } : null; return visual != null ? new[] { new VisualTreeNode(visual, null) } : null;
} }
internal class VisualTreeNodeCollection : TreeNodeCollection
{
private readonly IVisual _control;
private IDisposable _subscription;
public VisualTreeNodeCollection(TreeNode owner, IVisual control)
: base(owner)
{
_control = control;
}
public override void Dispose()
{
_subscription?.Dispose();
}
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
_subscription = _control.VisualChildren.ForEachItem(
(i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
(i, item) => nodes.RemoveAt(i),
() => nodes.Clear());
}
}
} }
} }

22
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -14,8 +14,8 @@ namespace Avalonia.Diagnostics.Views
{ {
internal class MainWindow : Window, IStyleHost internal class MainWindow : Window, IStyleHost
{ {
private readonly IDisposable _keySubscription;
private TopLevel _root; private TopLevel _root;
private IDisposable _keySubscription;
public MainWindow() public MainWindow()
{ {
@ -33,8 +33,22 @@ namespace Avalonia.Diagnostics.Views
{ {
if (_root != value) if (_root != value)
{ {
if (_root != null)
{
_root.Closed -= RootClosed;
}
_root = value; _root = value;
DataContext = new MainViewModel(value);
if (_root != null)
{
_root.Closed += RootClosed;
DataContext = new MainViewModel(value);
}
else
{
DataContext = null;
}
} }
} }
} }
@ -45,6 +59,8 @@ namespace Avalonia.Diagnostics.Views
{ {
base.OnClosed(e); base.OnClosed(e);
_keySubscription.Dispose(); _keySubscription.Dispose();
_root.Closed -= RootClosed;
_root = null;
((MainViewModel)DataContext)?.Dispose(); ((MainViewModel)DataContext)?.Dispose();
} }
@ -70,5 +86,7 @@ namespace Avalonia.Diagnostics.Views
} }
} }
} }
private void RootClosed(object sender, EventArgs e) => Close();
} }
} }

24
src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs

@ -0,0 +1,24 @@
using System;
namespace Avalonia.Layout
{
/// <summary>
/// Provides data for the <see cref="Layoutable.EffectiveViewportChanged"/> event.
/// </summary>
public class EffectiveViewportChangedEventArgs : EventArgs
{
public EffectiveViewportChangedEventArgs(Rect effectiveViewport)
{
EffectiveViewport = effectiveViewport;
}
/// <summary>
/// Gets the <see cref="Rect"/> representing the effective viewport.
/// </summary>
/// <remarks>
/// The viewport is expressed in coordinates relative to the control that the event is
/// raised on.
/// </remarks>
public Rect EffectiveViewport { get; }
}
}

24
src/Avalonia.Layout/ILayoutManager.cs

@ -7,7 +7,7 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Manages measuring and arranging of controls. /// Manages measuring and arranging of controls.
/// </summary> /// </summary>
public interface ILayoutManager public interface ILayoutManager : IDisposable
{ {
/// <summary> /// <summary>
/// Raised when the layout manager completes a layout pass. /// Raised when the layout manager completes a layout pass.
@ -35,6 +35,15 @@ namespace Avalonia.Layout
/// </remarks> /// </remarks>
void ExecuteLayoutPass(); void ExecuteLayoutPass();
/// <summary>
/// Executes the initial layout pass on a layout root.
/// </summary>
/// <remarks>
/// You should not usually need to call this method explictly, the layout root will call
/// it to carry out the initial layout of the control.
/// </remarks>
void ExecuteInitialLayoutPass();
/// <summary> /// <summary>
/// Executes the initial layout pass on a layout root. /// Executes the initial layout pass on a layout root.
/// </summary> /// </summary>
@ -43,6 +52,19 @@ namespace Avalonia.Layout
/// You should not usually need to call this method explictly, the layout root will call /// You should not usually need to call this method explictly, the layout root will call
/// it to carry out the initial layout of the control. /// it to carry out the initial layout of the control.
/// </remarks> /// </remarks>
[Obsolete("Call ExecuteInitialLayoutPass without parameter")]
void ExecuteInitialLayoutPass(ILayoutRoot root); void ExecuteInitialLayoutPass(ILayoutRoot root);
/// <summary>
/// Registers a control as wanting to receive effective viewport notifications.
/// </summary>
/// <param name="control">The control.</param>
void RegisterEffectiveViewportListener(ILayoutable control);
/// <summary>
/// Registers a control as no longer wanting to receive effective viewport notifications.
/// </summary>
/// <param name="control">The control.</param>
void UnregisterEffectiveViewportListener(ILayoutable control);
} }
} }

7
src/Avalonia.Layout/ILayoutable.cs

@ -111,5 +111,12 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
/// <param name="control">The child control.</param> /// <param name="control">The child control.</param>
void ChildDesiredSizeChanged(ILayoutable control); void ChildDesiredSizeChanged(ILayoutable control);
/// <summary>
/// Used by the <see cref="LayoutManager"/> to notify the control that its effective
/// viewport is changed.
/// </summary>
/// <param name="e">The viewport information.</param>
void EffectiveViewportChanged(EffectiveViewportChangedEventArgs e);
} }
} }

205
src/Avalonia.Layout/LayoutManager.cs

@ -1,7 +1,10 @@
using System; using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
#nullable enable #nullable enable
@ -10,16 +13,21 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Manages measuring and arranging of controls. /// Manages measuring and arranging of controls.
/// </summary> /// </summary>
public class LayoutManager : ILayoutManager public class LayoutManager : ILayoutManager, IDisposable
{ {
private const int MaxPasses = 3;
private readonly ILayoutRoot _owner;
private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid); private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid); private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
private readonly Action _executeLayoutPass; private readonly Action _executeLayoutPass;
private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners;
private bool _disposed;
private bool _queued; private bool _queued;
private bool _running; private bool _running;
public LayoutManager() public LayoutManager(ILayoutRoot owner)
{ {
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
_executeLayoutPass = ExecuteLayoutPass; _executeLayoutPass = ExecuteLayoutPass;
} }
@ -31,6 +39,11 @@ namespace Avalonia.Layout
control = control ?? throw new ArgumentNullException(nameof(control)); control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
if (_disposed)
{
return;
}
if (!control.IsAttachedToVisualTree) if (!control.IsAttachedToVisualTree)
{ {
#if DEBUG #if DEBUG
@ -41,6 +54,11 @@ namespace Avalonia.Layout
#endif #endif
} }
if (control.VisualRoot != _owner)
{
throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
}
_toMeasure.Enqueue(control); _toMeasure.Enqueue(control);
_toArrange.Enqueue(control); _toArrange.Enqueue(control);
QueueLayoutPass(); QueueLayoutPass();
@ -52,6 +70,11 @@ namespace Avalonia.Layout
control = control ?? throw new ArgumentNullException(nameof(control)); control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
if (_disposed)
{
return;
}
if (!control.IsAttachedToVisualTree) if (!control.IsAttachedToVisualTree)
{ {
#if DEBUG #if DEBUG
@ -62,6 +85,11 @@ namespace Avalonia.Layout
#endif #endif
} }
if (control.VisualRoot != _owner)
{
throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
}
_toArrange.Enqueue(control); _toArrange.Enqueue(control);
QueueLayoutPass(); QueueLayoutPass();
} }
@ -69,14 +97,15 @@ namespace Avalonia.Layout
/// <inheritdoc/> /// <inheritdoc/>
public virtual void ExecuteLayoutPass() public virtual void ExecuteLayoutPass()
{ {
const int MaxPasses = 3;
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
if (!_running) if (_disposed)
{ {
_running = true; return;
}
if (!_running)
{
Stopwatch? stopwatch = null; Stopwatch? stopwatch = null;
const LogEventLevel timingLogLevel = LogEventLevel.Information; const LogEventLevel timingLogLevel = LogEventLevel.Information;
@ -99,12 +128,13 @@ namespace Avalonia.Layout
try try
{ {
_running = true;
for (var pass = 0; pass < MaxPasses; ++pass) for (var pass = 0; pass < MaxPasses; ++pass)
{ {
ExecuteMeasurePass(); InnerLayoutPass();
ExecuteArrangePass();
if (_toMeasure.Count == 0) if (!RaiseEffectiveViewportChanged())
{ {
break; break;
} }
@ -131,13 +161,18 @@ namespace Avalonia.Layout
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual void ExecuteInitialLayoutPass(ILayoutRoot root) public virtual void ExecuteInitialLayoutPass()
{ {
if (_disposed)
{
return;
}
try try
{ {
_running = true; _running = true;
Measure(root); Measure(_owner);
Arrange(root); Arrange(_owner);
} }
finally finally
{ {
@ -151,6 +186,60 @@ namespace Avalonia.Layout
ExecuteLayoutPass(); ExecuteLayoutPass();
} }
[Obsolete("Call ExecuteInitialLayoutPass without parameter")]
public void ExecuteInitialLayoutPass(ILayoutRoot root)
{
if (root != _owner)
{
throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root.");
}
ExecuteInitialLayoutPass();
}
public void Dispose()
{
_disposed = true;
_toMeasure.Dispose();
_toArrange.Dispose();
}
void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control)
{
_effectiveViewportChangedListeners ??= new List<EffectiveViewportChangedListener>();
_effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener(
control,
CalculateEffectiveViewport(control)));
}
void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control)
{
if (_effectiveViewportChangedListeners is object)
{
for (var i = _effectiveViewportChangedListeners.Count - 1; i >= 0; --i)
{
if (_effectiveViewportChangedListeners[i].Listener == control)
{
_effectiveViewportChangedListeners.RemoveAt(i);
}
}
}
}
private void InnerLayoutPass()
{
for (var pass = 0; pass < MaxPasses; ++pass)
{
ExecuteMeasurePass();
ExecuteArrangePass();
if (_toMeasure.Count == 0)
{
break;
}
}
}
private void ExecuteMeasurePass() private void ExecuteMeasurePass()
{ {
while (_toMeasure.Count > 0) while (_toMeasure.Count > 0)
@ -234,5 +323,97 @@ namespace Avalonia.Layout
_queued = true; _queued = true;
} }
} }
private bool RaiseEffectiveViewportChanged()
{
var startCount = _toMeasure.Count + _toArrange.Count;
if (_effectiveViewportChangedListeners is object)
{
var count = _effectiveViewportChangedListeners.Count;
var pool = ArrayPool<EffectiveViewportChangedListener>.Shared;
var listeners = pool.Rent(count);
_effectiveViewportChangedListeners.CopyTo(listeners);
try
{
for (var i = 0; i < count; ++i)
{
var l = _effectiveViewportChangedListeners[i];
if (!l.Listener.IsAttachedToVisualTree)
{
continue;
}
var viewport = CalculateEffectiveViewport(l.Listener);
if (viewport != l.Viewport)
{
l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport));
_effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport);
}
}
}
finally
{
pool.Return(listeners, clearArray: true);
}
}
return startCount != _toMeasure.Count + _toArrange.Count;
}
private Rect CalculateEffectiveViewport(IVisual control)
{
var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity);
CalculateEffectiveViewport(control, control, ref viewport);
return viewport;
}
private void CalculateEffectiveViewport(IVisual target, IVisual control, ref Rect viewport)
{
// Recurse until the top level control.
if (control.VisualParent is object)
{
CalculateEffectiveViewport(target, control.VisualParent, ref viewport);
}
else
{
viewport = new Rect(control.Bounds.Size);
}
// Apply the control clip bounds if it's not the target control. We don't apply it to
// the target control because it may itself be clipped to bounds and if so the viewport
// we calculate would be of no use.
if (control != target && control.ClipToBounds)
{
viewport = control.Bounds.Intersect(viewport);
}
// Translate the viewport into this control's coordinate space.
viewport = viewport.Translate(-control.Bounds.Position);
if (control != target && control.RenderTransform is object)
{
var origin = control.RenderTransformOrigin.ToPixels(control.Bounds.Size);
var offset = Matrix.CreateTranslation(origin);
var renderTransform = (-offset) * control.RenderTransform.Value.Invert() * (offset);
viewport = viewport.TransformToAABB(renderTransform);
}
}
private readonly struct EffectiveViewportChangedListener
{
public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport)
{
Listener = listener;
Viewport = viewport;
}
public ILayoutable Listener { get; }
public Rect Viewport { get; }
}
} }
} }

9
src/Avalonia.Layout/LayoutQueue.cs

@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace Avalonia.Layout namespace Avalonia.Layout
{ {
internal class LayoutQueue<T> : IReadOnlyCollection<T> internal class LayoutQueue<T> : IReadOnlyCollection<T>, IDisposable
{ {
private struct Info private struct Info
{ {
@ -84,5 +84,12 @@ namespace Avalonia.Layout
_notFinalizedBuffer.Clear(); _notFinalizedBuffer.Clear();
} }
public void Dispose()
{
_inner.Clear();
_loopQueueInfo.Clear();
_notFinalizedBuffer.Clear();
}
} }
} }

70
src/Avalonia.Layout/Layoutable.cs

@ -132,6 +132,7 @@ namespace Avalonia.Layout
private bool _measuring; private bool _measuring;
private Size? _previousMeasure; private Size? _previousMeasure;
private Rect? _previousArrange; private Rect? _previousArrange;
private EventHandler<EffectiveViewportChangedEventArgs>? _effectiveViewportChanged;
private EventHandler? _layoutUpdated; private EventHandler? _layoutUpdated;
/// <summary> /// <summary>
@ -152,6 +153,32 @@ namespace Avalonia.Layout
VerticalAlignmentProperty); VerticalAlignmentProperty);
} }
/// <summary>
/// Occurs when the element's effective viewport changes.
/// </summary>
public event EventHandler<EffectiveViewportChangedEventArgs>? EffectiveViewportChanged
{
add
{
if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
{
r.LayoutManager.RegisterEffectiveViewportListener(this);
}
_effectiveViewportChanged += value;
}
remove
{
_effectiveViewportChanged -= value;
if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r)
{
r.LayoutManager.UnregisterEffectiveViewportListener(this);
}
}
}
/// <summary> /// <summary>
/// Occurs when a layout pass completes for the control. /// Occurs when a layout pass completes for the control.
/// </summary> /// </summary>
@ -384,13 +411,6 @@ namespace Avalonia.Layout
} }
} }
/// <summary>
/// Called by InvalidateMeasure
/// </summary>
protected virtual void OnMeasureInvalidated()
{
}
/// <summary> /// <summary>
/// Invalidates the measurement of the control and queues a new layout pass. /// Invalidates the measurement of the control and queues a new layout pass.
/// </summary> /// </summary>
@ -436,6 +456,11 @@ namespace Avalonia.Layout
} }
} }
void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e)
{
_effectiveViewportChanged?.Invoke(this, e);
}
/// <summary> /// <summary>
/// Marks a property as affecting the control's measurement. /// Marks a property as affecting the control's measurement.
/// </summary> /// </summary>
@ -717,9 +742,17 @@ namespace Avalonia.Layout
{ {
base.OnAttachedToVisualTreeCore(e); base.OnAttachedToVisualTreeCore(e);
if (_layoutUpdated is object && e.Root is ILayoutRoot r) if (e.Root is ILayoutRoot r)
{ {
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated; if (_layoutUpdated is object)
{
r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated;
}
if (_effectiveViewportChanged is object)
{
r.LayoutManager.RegisterEffectiveViewportListener(this);
}
} }
} }
@ -727,12 +760,27 @@ namespace Avalonia.Layout
{ {
base.OnDetachedFromVisualTreeCore(e); base.OnDetachedFromVisualTreeCore(e);
if (_layoutUpdated is object && e.Root is ILayoutRoot r) if (e.Root is ILayoutRoot r)
{ {
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated; if (_layoutUpdated is object)
{
r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated;
}
if (_effectiveViewportChanged is object)
{
r.LayoutManager.UnregisterEffectiveViewportListener(this);
}
} }
} }
/// <summary>
/// Called by InvalidateMeasure
/// </summary>
protected virtual void OnMeasureInvalidated()
{
}
/// <inheritdoc/> /// <inheritdoc/>
protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent) protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{ {

9
src/Avalonia.Native/NativeControlHostImpl.cs

@ -114,19 +114,18 @@ namespace Avalonia.Native
public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl; public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
public void Hide() public void HideWithSize(Size size)
{ {
_native?.Hide(); _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height));
} }
public void ShowInBounds(TransformedBounds transformedBounds) public void ShowInBounds(Rect bounds)
{ {
if (_attachedTo == null) if (_attachedTo == null)
throw new InvalidOperationException("Native control isn't attached to a toplevel"); throw new InvalidOperationException("Native control isn't attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
Math.Max(1, bounds.Height)); Math.Max(1, bounds.Height));
_native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
} }
public void InitWithChild(IPlatformHandle handle) public void InitWithChild(IPlatformHandle handle)

12
src/Avalonia.Native/PopupImpl.cs

@ -10,6 +10,7 @@ namespace Avalonia.Native
private readonly IAvaloniaNativeFactory _factory; private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts; private readonly AvaloniaNativePlatformOptions _opts;
private readonly GlPlatformFeature _glFeature; private readonly GlPlatformFeature _glFeature;
private readonly IWindowBaseImpl _parent;
public PopupImpl(IAvaloniaNativeFactory factory, public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOptions opts,
@ -19,6 +20,7 @@ namespace Avalonia.Native
_factory = factory; _factory = factory;
_opts = opts; _opts = opts;
_glFeature = glFeature; _glFeature = glFeature;
_parent = parent;
using (var e = new PopupEvents(this)) using (var e = new PopupEvents(this))
{ {
var context = _opts.UseGpu ? glFeature?.DeferredContext : null; var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
@ -58,6 +60,16 @@ namespace Avalonia.Native
} }
} }
public override void Show()
{
var parent = _parent;
while (parent is PopupImpl p)
parent = p._parent;
if (parent is WindowImpl w)
w.Native.TakeFocusFromChildren();
base.Show();
}
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
public void SetWindowManagerAddShadowHint(bool enabled) public void SetWindowManagerAddShadowHint(bool enabled)

2
src/Avalonia.Native/WindowImplBase.cs

@ -319,7 +319,7 @@ namespace Avalonia.Native
} }
public void Show() public virtual void Show()
{ {
_native.Show(); _native.Show();
} }

12
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -1,4 +1,12 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel>
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="ProgressBar"> <Style Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/> <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
@ -69,11 +77,11 @@
Easing="LinearEasing"> Easing="LinearEasing">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding TemplateProperties.IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding TemplateProperties.IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>

43
src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml

@ -2,35 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color> <Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color>
<Color x:Key="SystemAltHighColor">#FF000000</Color> <Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color>
<Color x:Key="SystemAltLowColor">#FF000000</Color>
<Color x:Key="SystemAltMediumColor">#FF000000</Color>
<Color x:Key="SystemAltMediumHighColor">#FF000000</Color>
<Color x:Key="SystemAltMediumLowColor">#FF000000</Color>
<Color x:Key="SystemBaseHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemBaseLowColor">#FF333333</Color>
<Color x:Key="SystemBaseMediumColor">#FF9A9A9A</Color>
<Color x:Key="SystemBaseMediumHighColor">#FFB4B4B4</Color>
<Color x:Key="SystemBaseMediumLowColor">#FF676767</Color>
<Color x:Key="SystemChromeAltLowColor">#FFB4B4B4</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#FFB4B4B4</Color>
<Color x:Key="SystemChromeBlackMediumColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#FF000000</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FF333333</Color>
<Color x:Key="SystemChromeGrayColor">#FF808080</Color>
<Color x:Key="SystemChromeHighColor">#FF808080</Color>
<Color x:Key="SystemChromeLowColor">#FF151515</Color>
<Color x:Key="SystemChromeMediumColor">#FF1D1D1D</Color>
<Color x:Key="SystemChromeMediumLowColor">#FF2C2C2C</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemListLowColor">#FF1D1D1D</Color>
<Color x:Key="SystemListMediumColor">#FF333333</Color>
<Color x:Key="SystemChromeAltMediumHighColor">#CC000000</Color>
<Color x:Key="SystemChromeAltHighColor">#FF333333</Color>
<Color x:Key="SystemRevealListLowColor">#FF1D1D1D</Color>
<Color x:Key="SystemRevealListMediumColor">#FF333333</Color>
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />--> <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />--> <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" /> <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@ -67,13 +40,6 @@
<Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness> <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
<x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double> <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
<!-- Override system generated accent colors -->
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<Color x:Key="SystemAccentColorDark2">#FF004275</Color>
<Color x:Key="SystemAccentColorDark3">#FF002642</Color>
<Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
<Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
<Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
<Color x:Key="RegionColor">#FF000000</Color> <Color x:Key="RegionColor">#FF000000</Color>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" /> <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
@ -218,6 +184,11 @@
<SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99000000" /> <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99000000" />
<SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" /> <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" /> <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<!-- BaseResources for ProgressBar.xaml -->
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
<!-- BaseResources for TextBox.xaml --> <!-- BaseResources for TextBox.xaml -->
<StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />

45
src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml

@ -2,36 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color> <Color x:Key="SystemRevealListLowColor">#17000000</Color>
<Color x:Key="SystemAltHighColor">#FFFFFFFF</Color> <Color x:Key="SystemRevealListMediumColor">#2E000000</Color>
<Color x:Key="SystemAltLowColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltMediumColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltMediumHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltMediumLowColor">#FFFFFFFF</Color>
<Color x:Key="SystemBaseHighColor">#FF000000</Color>
<Color x:Key="SystemBaseLowColor">#FFCCCCCC</Color>
<Color x:Key="SystemBaseMediumColor">#FF898989</Color>
<Color x:Key="SystemBaseMediumHighColor">#FF5D5D5D</Color>
<Color x:Key="SystemBaseMediumLowColor">#FF737373</Color>
<Color x:Key="SystemChromeAltLowColor">#FF5D5D5D</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeBlackMediumColor">#FF5D5D5D</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#FF898989</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeDisabledLowColor">#FF898989</Color>
<Color x:Key="SystemChromeGrayColor">#FF737373</Color>
<Color x:Key="SystemChromeHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeLowColor">#FFECECEC</Color>
<Color x:Key="SystemChromeMediumColor">#FFE6E6E6</Color>
<Color x:Key="SystemChromeMediumLowColor">#FFECECEC</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemListLowColor">#FFE6E6E6</Color>
<Color x:Key="SystemListMediumColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeAltMediumHighColor">#CCFFFFFF</Color>
<Color x:Key="SystemChromeAltHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemRevealListLowColor">#FFE6E6E6</Color>
<Color x:Key="SystemRevealListMediumColor">#FFCCCCCC</Color>
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />--> <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />--> <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" /> <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@ -68,13 +40,6 @@
<Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness> <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
<x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double> <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
<!-- Override system generated accent colors -->
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<Color x:Key="SystemAccentColorDark2">#FF004275</Color>
<Color x:Key="SystemAccentColorDark3">#FF002642</Color>
<Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
<Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
<Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
<!--<RevealBackgroundBrush x:Key="SystemControlHighlightListLowRevealBackgroundBrush" TargetTheme="Light" Color="{ThemeResource SystemRevealListMediumColor}" FallbackColor="{ StaticResource SystemListMediumColor}" />--> <!--<RevealBackgroundBrush x:Key="SystemControlHighlightListLowRevealBackgroundBrush" TargetTheme="Light" Color="{ThemeResource SystemRevealListMediumColor}" FallbackColor="{ StaticResource SystemListMediumColor}" />-->
<Color x:Key="RegionColor">#FFFFFFFF</Color> <Color x:Key="RegionColor">#FFFFFFFF</Color>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" /> <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
@ -222,6 +187,12 @@
<SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" /> <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" /> <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<!-- BaseResources for ProgressBar.xaml -->
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
<!-- BaseResources for TextBox.xaml --> <!-- BaseResources for TextBox.xaml -->
<StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TextControlForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="TextControlForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" />

9
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -336,6 +336,15 @@
<StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />--> <StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />-->
<!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>--> <!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>-->
<!-- Resources for ProgressBar.xaml -->
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
<!-- Resources for TextBox.xaml --> <!-- Resources for TextBox.xaml -->
<SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" /> <SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" />

8
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -248,6 +248,14 @@
<SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" /> <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" />
<SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" /> <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" /> <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<!-- Resources for ProgressBar.xaml -->
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
<!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) --> <!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) -->
<x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double> <x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double>

1
src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj

@ -14,6 +14,7 @@
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" /> <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
<AvaloniaResource Include="FluentTheme.xaml" /> <AvaloniaResource Include="FluentTheme.xaml" />
<AvaloniaResource Include="Accents/*.xaml" /> <AvaloniaResource Include="Accents/*.xaml" />
<AvaloniaResource Include="DensityStyles/*.xaml" />
<!-- Compatibility with old apps, probably need to replace with AvaloniaResource --> <!-- Compatibility with old apps, probably need to replace with AvaloniaResource -->
<EmbeddedResource Include="**/*.xaml" /> <EmbeddedResource Include="**/*.xaml" />
</ItemGroup> </ItemGroup>

23
src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml

@ -0,0 +1,23 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TextBlock" >
<Setter Property="FontSize" Value="14" />
</Style>
<Style>
<Style.Resources>
<x:Double x:Key="ControlContentThemeFontSize">14</x:Double>
<x:Double x:Key="ContentControlFontSize">14</x:Double>
<x:Double x:Key="TextControlThemeMinHeight">24</x:Double>
<Thickness x:Key="TextControlThemePadding">2,2,6,1</Thickness>
<x:Double x:Key="ListViewItemMinHeight">32</x:Double>
<x:Double x:Key="TreeViewItemMinHeight">24</x:Double>
<Thickness x:Key="TimePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostPadding">0,1,0,2</Thickness>
<Thickness x:Key="DatePickerHostMonthPadding">9,0,0,1</Thickness>
<Thickness x:Key="ComboBoxEditableTextPadding">10,0,30,0</Thickness>
<Thickness x:Key="ComboBoxMinHeight">24</Thickness>
<Thickness x:Key="ComboBoxPadding">12,1,0,3</Thickness>
<x:Double x:Key="NavigationViewItemOnLeftMinHeight">32</x:Double>
</Style.Resources>
</Style>
</Styles>

44
src/Avalonia.Themes.Fluent/FocusAdorner.xaml

@ -1,10 +1,34 @@
<Style xmlns="https://github.com/avaloniaui" Selector=":is(Control)"> <Styles xmlns="https://github.com/avaloniaui"
<Setter Property="FocusAdorner"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<FocusAdornerTemplate> <Styles.Resources>
<Rectangle Stroke="Black" <Thickness x:Key="SystemControlFocusVisualMargin">0</Thickness>
StrokeThickness="1" <Thickness x:Key="SystemControlFocusVisualPrimaryThickness">2</Thickness>
StrokeDashArray="1,2" <Thickness x:Key="SystemControlFocusVisualSecondaryThickness">1</Thickness>
Margin="1"/> </Styles.Resources>
</FocusAdornerTemplate>
</Setter> <!-- HighVisibility FocusAdorner -->
</Style> <Style Selector=":is(Control)">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Border BorderThickness="{StaticResource SystemControlFocusVisualPrimaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualPrimaryBrush}"
Margin="{StaticResource SystemControlFocusVisualMargin}">
<Border BorderThickness="{StaticResource SystemControlFocusVisualSecondaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualSecondaryBrush}" />
</Border>
</FocusAdornerTemplate>
</Setter>
</Style>
<!-- DottedLine FocusAdorner -->
<Style Selector="Window.DottedLineFocusAdorner :is(Control)">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Rectangle Stroke="{StaticResource SystemControlFocusVisualPrimaryBrush}"
StrokeThickness="1"
StrokeDashArray="1,2"
Margin="1" />
</FocusAdornerTemplate>
</Setter>
</Style>
</Styles>

191
src/Avalonia.Themes.Fluent/ProgressBar.xaml

@ -1,81 +1,176 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel>
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="ProgressBar"> <Style Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/> <Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ProgressBarBorderThemeThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightTransparentBrush}" />
<Setter Property="Maximum" Value="100" />
<Setter Property="MinHeight" Value="{DynamicResource ProgressBarThemeMinHeight}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid> <Border x:Name="ProgressBarRoot" ClipToBounds="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Border Background="{TemplateBinding Background}" <Panel>
BorderBrush="{TemplateBinding BorderBrush}" <Panel x:Name="DeterminateRoot">
BorderThickness="{TemplateBinding BorderThickness}"> <Border CornerRadius="{DynamicResource ControlCornerRadius}" x:Name="PART_Indicator" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
<Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/> </Panel>
</Border> <Panel x:Name="IndeterminateRoot">
<LayoutTransformControl <Border x:Name="IndeterminateProgressBarIndicator" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
HorizontalAlignment="Center" <Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
VerticalAlignment="Center" </Panel>
IsVisible="{Binding ShowProgressText, RelativeSource={RelativeSource TemplatedParent}}" </Panel>
Name="PART_LayoutTransformControl"> </Border>
<TextBlock
Foreground="{DynamicResource ThemeForegroundBrush}"
Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
</LayoutTransformControl>
</Grid>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ProgressBar:horizontal /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:horizontal /template/ Border#PART_Indicator">
<Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Stretch"/> <Setter Property="VerticalAlignment" Value="Stretch" />
</Style> </Style>
<Style Selector="ProgressBar:vertical /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:vertical /template/ Border#PART_Indicator">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Bottom"/> <Setter Property="VerticalAlignment" Value="Bottom" />
</Style> </Style>
<Style Selector="ProgressBar:horizontal"> <Style Selector="ProgressBar:horizontal">
<Setter Property="MinWidth" Value="200"/> <Setter Property="MinWidth" Value="200" />
<Setter Property="MinHeight" Value="16"/> <Setter Property="MinHeight" Value="4" />
</Style> </Style>
<Style Selector="ProgressBar:vertical"> <Style Selector="ProgressBar:vertical">
<Setter Property="MinWidth" Value="16"/> <Setter Property="MinWidth" Value="4" />
<Setter Property="MinHeight" Value="200"/> <Setter Property="MinHeight" Value="200" />
</Style> </Style>
<Style Selector="ProgressBar:vertical /template/ LayoutTransformControl#PART_LayoutTransformControl"> <Style Selector="ProgressBar:vertical /template/ LayoutTransformControl#PART_LayoutTransformControl">
<Setter Property="LayoutTransform"> <Setter Property="LayoutTransform">
<Setter.Value> <Setter.Value>
<RotateTransform Angle="90"/> <RotateTransform Angle="90" />
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator"> <!-- FadeInAnimation mockup-->
<Style Selector="ProgressBar /template/ Panel#DeterminateRoot">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.197" />
</Transitions>
</Setter>
</Style>
<Style Selector="ProgressBar /template/ Panel#IndeterminateRoot">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.197" />
</Transitions>
</Setter>
</Style>
<Style Selector="ProgressBar /template/ Panel#DeterminateRoot">
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="ProgressBar /template/ Panel#IndeterminateRoot">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="ProgressBar:indeterminate /template/ Panel#DeterminateRoot">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="ProgressBar:indeterminate /template/ Panel#IndeterminateRoot">
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:1.5" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:0.75" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationEndPosition}" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:2" IterationCount="Infinite">
IterationCount="Infinite" <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
Easing="LinearEasing"> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
<KeyFrame Cue="0%"> </KeyFrame>
<Setter Property="TranslateTransform.X" <KeyFrame KeyTime="0:0:1.5" KeySpline="0.4,0,0.6,1">
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:2" IterationCount="Infinite">
IterationCount="Infinite" <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
Easing="LinearEasing"> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame KeyTime="0:0:0.75" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> </KeyFrame>
<KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationEndPosition}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator">
<Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator2">
<Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator">
<Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator2">
<Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
</Styles> </Styles>

16
src/Avalonia.Themes.Fluent/ToolTip.xaml

@ -1,11 +1,14 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Design.PreviewWith> <Design.PreviewWith>
<Grid RowDefinitions="Auto,Auto" <Grid RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Border Grid.Column="0" <Border Grid.Column="0"
Grid.Row="1" Grid.Row="1"
Background="{DynamicResource ThemeAccentBrush}" Background="{DynamicResource SystemControlBackgroundAccentBrush}"
Margin="5" Margin="5"
Padding="50" Padding="50"
ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."> ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.">
@ -19,7 +22,7 @@
<Border Name="Border" <Border Name="Border"
Grid.Column="1" Grid.Column="1"
Grid.Row="1" Grid.Row="1"
Background="{DynamicResource ThemeAccentBrush}" Background="{DynamicResource SystemControlBackgroundAccentBrush}"
Margin="5" Margin="5"
Padding="50" Padding="50"
ToolTip.Placement="Bottom"> ToolTip.Placement="Bottom">
@ -34,6 +37,10 @@
</Grid> </Grid>
</Design.PreviewWith> </Design.PreviewWith>
<Styles.Resources>
<sys:Double x:Key="ToolTipContentMaxWidth">320</sys:Double>
</Styles.Resources>
<Style Selector="ToolTip"> <Style Selector="ToolTip">
<Setter Property="Foreground" Value="{DynamicResource ToolTipForeground}" /> <Setter Property="Foreground" Value="{DynamicResource ToolTipForeground}" />
<Setter Property="Background" Value="{DynamicResource ToolTipBackground}" /> <Setter Property="Background" Value="{DynamicResource ToolTipBackground}" />
@ -42,6 +49,7 @@
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ToolTipContentThemeFontSize}" /> <Setter Property="FontSize" Value="{DynamicResource ToolTipContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource ToolTipBorderThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource ToolTipBorderThemePadding}" />
<Setter Property="MaxWidth" Value="{DynamicResource ToolTipContentMaxWidth}" />
<Setter Property="Transitions"> <Setter Property="Transitions">
<Transitions> <Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.15" /> <DoubleTransition Property="Opacity" Duration="0:0:0.15" />
@ -56,7 +64,7 @@
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
CornerRadius="{DynamicResource OverlayCornerRadius}"> CornerRadius="{DynamicResource OverlayCornerRadius}">
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
MaxWidth="320" MaxWidth="{TemplateBinding MaxWidth}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" /> ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border> </Border>

61
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
@ -11,9 +10,9 @@ namespace Avalonia.Animation.Animators
/// </summary> /// </summary>
public class SolidColorBrushAnimator : Animator<SolidColorBrush> public class SolidColorBrushAnimator : Animator<SolidColorBrush>
{ {
ColorAnimator _colorAnimator; private ColorAnimator _colorAnimator;
void InitializeColorAnimator() private void InitializeColorAnimator()
{ {
_colorAnimator = new ColorAnimator(); _colorAnimator = new ColorAnimator();
@ -27,46 +26,44 @@ namespace Avalonia.Animation.Animators
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete) public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{ {
// Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
foreach (var keyframe in this) foreach (var keyframe in this)
{ {
if (keyframe.Value as ISolidColorBrush == null) if (keyframe.Value is ISolidColorBrush colorBrush)
return Disposable.Empty;
// Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush))
{ {
keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color; keyframe.Value = colorBrush.Color;
}
else
{
return Disposable.Empty;
} }
} }
// Add SCB if the target prop is empty. SolidColorBrush finalTarget;
if (control.GetValue(Property) == null)
control.SetValue(Property, new SolidColorBrush(Colors.Transparent));
var targetVal = control.GetValue(Property); var targetVal = control.GetValue(Property);
if (targetVal is null)
// Continue if target prop is not empty & is a SolidColorBrush derivative. {
if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) finalTarget = new SolidColorBrush(Colors.Transparent);
control.SetValue(Property, finalTarget);
}
else if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush)
{
finalTarget = new SolidColorBrush(immutableSolidColorBrush.Color);
control.SetValue(Property, finalTarget);
}
else if (targetVal is ISolidColorBrush)
{ {
if (_colorAnimator == null)
InitializeColorAnimator();
SolidColorBrush finalTarget;
// If it's ISCB, change it back to SCB.
if (targetVal.GetType() == typeof(ImmutableSolidColorBrush))
{
var col = (ImmutableSolidColorBrush)targetVal;
targetVal = new SolidColorBrush(col.Color);
control.SetValue(Property, targetVal);
}
finalTarget = targetVal as SolidColorBrush; finalTarget = targetVal as SolidColorBrush;
return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
} }
else
{
return Disposable.Empty;
}
if (_colorAnimator == null)
InitializeColorAnimator();
return Disposable.Empty; return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
} }
public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null; public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null;

BIN
src/Avalonia.Visuals/Assets/GraphemeBreak.trie

Binary file not shown.

BIN
src/Avalonia.Visuals/Assets/UnicodeData.trie

Binary file not shown.

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

@ -2,6 +2,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
public enum BiDiClass public enum BiDiClass
{ {
LeftToRight, //L
ArabicLetter, //AL ArabicLetter, //AL
ArabicNumber, //AN ArabicNumber, //AN
ParagraphSeparator, //B ParagraphSeparator, //B
@ -11,7 +12,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
EuropeanSeparator, //ES EuropeanSeparator, //ES
EuropeanTerminator, //ET EuropeanTerminator, //ET
FirstStrongIsolate, //FSI FirstStrongIsolate, //FSI
LeftToRight, //L
LeftToRightEmbedding, //LRE LeftToRightEmbedding, //LRE
LeftToRightIsolate, //LRI LeftToRightIsolate, //LRI
LeftToRightOverride, //LRO LeftToRightOverride, //LRO

65
src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs

@ -4,38 +4,39 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
private static readonly byte[][] s_breakPairTable = private static readonly byte[][] s_breakPairTable =
{ {
new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4}, new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0}, new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,1,0,1,1,0,0,4,2,4,0,0,0,0,0,0,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,0,4,4,4,0,0,0,0,0,0,0,0,0,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
}; };
public static PairBreakType Map(LineBreakClass first, LineBreakClass second) public static PairBreakType Map(LineBreakClass first, LineBreakClass second)

34
src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs

@ -2,24 +2,20 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
public enum GraphemeBreakClass public enum GraphemeBreakClass
{ {
Control, //CN Other,
CR, //CR CR,
EBase, //EB LF,
EBaseGAZ, //EBG Control,
EModifier, //EM Extend,
Extend, //EX ZWJ,
GlueAfterZwj, //GAZ RegionalIndicator,
L, //L Prepend,
LF, //LF SpacingMark,
LV, //LV L,
LVT, //LVT V,
Prepend, //PP T,
RegionalIndicator, //RI LV,
SpacingMark, //SM LVT,
T, //T ExtendedPictographic,
V, //V
Other, //XX
ZWJ, //ZWJ
ExtendedPictographic
} }
} }

4
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs

@ -34,10 +34,11 @@ namespace Avalonia.Media.TextFormatting.Unicode
EBase, //EB EBase, //EB
EModifier, //EM EModifier, //EM
ZWJ, //ZWJ ZWJ, //ZWJ
ContingentBreak, //CB
Unknown, //XX
Ambiguous, //AI Ambiguous, //AI
MandatoryBreak, //BK MandatoryBreak, //BK
ContingentBreak, //CB
ConditionalJapaneseStarter, //CJ ConditionalJapaneseStarter, //CJ
CarriageReturn, //CR CarriageReturn, //CR
LineFeed, //LF LineFeed, //LF
@ -45,6 +46,5 @@ namespace Avalonia.Media.TextFormatting.Unicode
ComplexContext, //SA ComplexContext, //SA
Surrogate, //SG Surrogate, //SG
Space, //SP Space, //SP
Unknown, //XX
} }
} }

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

@ -95,7 +95,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
if (_nextClass.Value == LineBreakClass.MandatoryBreak) if (_nextClass.Value == LineBreakClass.MandatoryBreak)
{ {
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos); _lastPos = _pos;
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
return true; return true;
} }
@ -108,6 +109,7 @@ 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

178
src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs

@ -0,0 +1,178 @@
using System.Collections.Generic;
namespace Avalonia.Media.TextFormatting.Unicode
{
internal static class PropertyValueAliasHelper
{
private static readonly Dictionary<Script, string> s_scriptToTag =
new Dictionary<Script, string>{
{ Script.Unknown, "Zzzz"},
{ Script.Common, "Zyyy"},
{ Script.Inherited, "Zinh"},
{ Script.Adlam, "Adlm"},
{ Script.CaucasianAlbanian, "Aghb"},
{ Script.Ahom, "Ahom"},
{ Script.Arabic, "Arab"},
{ Script.ImperialAramaic, "Armi"},
{ Script.Armenian, "Armn"},
{ Script.Avestan, "Avst"},
{ Script.Balinese, "Bali"},
{ Script.Bamum, "Bamu"},
{ Script.BassaVah, "Bass"},
{ Script.Batak, "Batk"},
{ Script.Bengali, "Beng"},
{ Script.Bhaiksuki, "Bhks"},
{ Script.Bopomofo, "Bopo"},
{ Script.Brahmi, "Brah"},
{ Script.Braille, "Brai"},
{ Script.Buginese, "Bugi"},
{ Script.Buhid, "Buhd"},
{ Script.Chakma, "Cakm"},
{ Script.CanadianAboriginal, "Cans"},
{ Script.Carian, "Cari"},
{ Script.Cham, "Cham"},
{ Script.Cherokee, "Cher"},
{ Script.Chorasmian, "Chrs"},
{ Script.Coptic, "Copt"},
{ Script.Cypriot, "Cprt"},
{ Script.Cyrillic, "Cyrl"},
{ Script.Devanagari, "Deva"},
{ Script.DivesAkuru, "Diak"},
{ Script.Dogra, "Dogr"},
{ Script.Deseret, "Dsrt"},
{ Script.Duployan, "Dupl"},
{ Script.EgyptianHieroglyphs, "Egyp"},
{ Script.Elbasan, "Elba"},
{ Script.Elymaic, "Elym"},
{ Script.Ethiopic, "Ethi"},
{ Script.Georgian, "Geor"},
{ Script.Glagolitic, "Glag"},
{ Script.GunjalaGondi, "Gong"},
{ Script.MasaramGondi, "Gonm"},
{ Script.Gothic, "Goth"},
{ Script.Grantha, "Gran"},
{ Script.Greek, "Grek"},
{ Script.Gujarati, "Gujr"},
{ Script.Gurmukhi, "Guru"},
{ Script.Hangul, "Hang"},
{ Script.Han, "Hani"},
{ Script.Hanunoo, "Hano"},
{ Script.Hatran, "Hatr"},
{ Script.Hebrew, "Hebr"},
{ Script.Hiragana, "Hira"},
{ Script.AnatolianHieroglyphs, "Hluw"},
{ Script.PahawhHmong, "Hmng"},
{ Script.NyiakengPuachueHmong, "Hmnp"},
{ Script.KatakanaOrHiragana, "Hrkt"},
{ Script.OldHungarian, "Hung"},
{ Script.OldItalic, "Ital"},
{ Script.Javanese, "Java"},
{ Script.KayahLi, "Kali"},
{ Script.Katakana, "Kana"},
{ Script.Kharoshthi, "Khar"},
{ Script.Khmer, "Khmr"},
{ Script.Khojki, "Khoj"},
{ Script.KhitanSmallScript, "Kits"},
{ Script.Kannada, "Knda"},
{ Script.Kaithi, "Kthi"},
{ Script.TaiTham, "Lana"},
{ Script.Lao, "Laoo"},
{ Script.Latin, "Latn"},
{ Script.Lepcha, "Lepc"},
{ Script.Limbu, "Limb"},
{ Script.LinearA, "Lina"},
{ Script.LinearB, "Linb"},
{ Script.Lisu, "Lisu"},
{ Script.Lycian, "Lyci"},
{ Script.Lydian, "Lydi"},
{ Script.Mahajani, "Mahj"},
{ Script.Makasar, "Maka"},
{ Script.Mandaic, "Mand"},
{ Script.Manichaean, "Mani"},
{ Script.Marchen, "Marc"},
{ Script.Medefaidrin, "Medf"},
{ Script.MendeKikakui, "Mend"},
{ Script.MeroiticCursive, "Merc"},
{ Script.MeroiticHieroglyphs, "Mero"},
{ Script.Malayalam, "Mlym"},
{ Script.Modi, "Modi"},
{ Script.Mongolian, "Mong"},
{ Script.Mro, "Mroo"},
{ Script.MeeteiMayek, "Mtei"},
{ Script.Multani, "Mult"},
{ Script.Myanmar, "Mymr"},
{ Script.Nandinagari, "Nand"},
{ Script.OldNorthArabian, "Narb"},
{ Script.Nabataean, "Nbat"},
{ Script.Newa, "Newa"},
{ Script.Nko, "Nkoo"},
{ Script.Nushu, "Nshu"},
{ Script.Ogham, "Ogam"},
{ Script.OlChiki, "Olck"},
{ Script.OldTurkic, "Orkh"},
{ Script.Oriya, "Orya"},
{ Script.Osage, "Osge"},
{ Script.Osmanya, "Osma"},
{ Script.Palmyrene, "Palm"},
{ Script.PauCinHau, "Pauc"},
{ Script.OldPermic, "Perm"},
{ Script.PhagsPa, "Phag"},
{ Script.InscriptionalPahlavi, "Phli"},
{ Script.PsalterPahlavi, "Phlp"},
{ Script.Phoenician, "Phnx"},
{ Script.Miao, "Plrd"},
{ Script.InscriptionalParthian, "Prti"},
{ Script.Rejang, "Rjng"},
{ Script.HanifiRohingya, "Rohg"},
{ Script.Runic, "Runr"},
{ Script.Samaritan, "Samr"},
{ Script.OldSouthArabian, "Sarb"},
{ Script.Saurashtra, "Saur"},
{ Script.SignWriting, "Sgnw"},
{ Script.Shavian, "Shaw"},
{ Script.Sharada, "Shrd"},
{ Script.Siddham, "Sidd"},
{ Script.Khudawadi, "Sind"},
{ Script.Sinhala, "Sinh"},
{ Script.Sogdian, "Sogd"},
{ Script.OldSogdian, "Sogo"},
{ Script.SoraSompeng, "Sora"},
{ Script.Soyombo, "Soyo"},
{ Script.Sundanese, "Sund"},
{ Script.SylotiNagri, "Sylo"},
{ Script.Syriac, "Syrc"},
{ Script.Tagbanwa, "Tagb"},
{ Script.Takri, "Takr"},
{ Script.TaiLe, "Tale"},
{ Script.NewTaiLue, "Talu"},
{ Script.Tamil, "Taml"},
{ Script.Tangut, "Tang"},
{ Script.TaiViet, "Tavt"},
{ Script.Telugu, "Telu"},
{ Script.Tifinagh, "Tfng"},
{ Script.Tagalog, "Tglg"},
{ Script.Thaana, "Thaa"},
{ Script.Thai, "Thai"},
{ Script.Tibetan, "Tibt"},
{ Script.Tirhuta, "Tirh"},
{ Script.Ugaritic, "Ugar"},
{ Script.Vai, "Vaii"},
{ Script.WarangCiti, "Wara"},
{ Script.Wancho, "Wcho"},
{ Script.OldPersian, "Xpeo"},
{ Script.Cuneiform, "Xsux"},
{ Script.Yezidi, "Yezi"},
{ Script.Yi, "Yiii"},
{ Script.ZanabazarSquare, "Zanb"},
};
public static string GetTag(Script script)
{
if(!s_scriptToTag.ContainsKey(script))
{
return "Zzzz";
}
return s_scriptToTag[script];
}
}
}

10
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs

@ -2,6 +2,9 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
public enum Script public enum Script
{ {
Unknown, //Zzzz
Common, //Zyyy
Inherited, //Zinh
Adlam, //Adlm Adlam, //Adlm
CaucasianAlbanian, //Aghb CaucasianAlbanian, //Aghb
Ahom, //Ahom Ahom, //Ahom
@ -25,10 +28,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
Carian, //Cari Carian, //Cari
Cham, //Cham Cham, //Cham
Cherokee, //Cher Cherokee, //Cher
Chorasmian, //Chrs
Coptic, //Copt Coptic, //Copt
Cypriot, //Cprt Cypriot, //Cprt
Cyrillic, //Cyrl Cyrillic, //Cyrl
Devanagari, //Deva Devanagari, //Deva
DivesAkuru, //Diak
Dogra, //Dogr Dogra, //Dogr
Deseret, //Dsrt Deseret, //Dsrt
Duployan, //Dupl Duployan, //Dupl
@ -63,6 +68,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
Kharoshthi, //Khar Kharoshthi, //Khar
Khmer, //Khmr Khmer, //Khmr
Khojki, //Khoj Khojki, //Khoj
KhitanSmallScript, //Kits
Kannada, //Knda Kannada, //Knda
Kaithi, //Kthi Kaithi, //Kthi
TaiTham, //Lana TaiTham, //Lana
@ -151,10 +157,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
Wancho, //Wcho Wancho, //Wcho
OldPersian, //Xpeo OldPersian, //Xpeo
Cuneiform, //Xsux Cuneiform, //Xsux
Yezidi, //Yezi
Yi, //Yiii Yi, //Yiii
ZanabazarSquare, //Zanb ZanabazarSquare, //Zanb
Inherited, //Zinh
Common, //Zyyy
Unknown, //Zzzz
} }
} }

15
src/Avalonia.Visuals/Rect.cs

@ -211,6 +211,21 @@ namespace Avalonia
rect.Width * scale.X, rect.Width * scale.X,
rect.Height * scale.Y); rect.Height * scale.Y);
} }
/// <summary>
/// Multiplies a rectangle by a scale.
/// </summary>
/// <param name="rect">The rectangle.</param>
/// <param name="scale">The scale.</param>
/// <returns>The scaled rectangle.</returns>
public static Rect operator *(Rect rect, double scale)
{
return new Rect(
rect.X * scale,
rect.Y * scale,
rect.Width * scale,
rect.Height * scale);
}
/// <summary> /// <summary>
/// Divides a rectangle by a vector. /// Divides a rectangle by a vector.

22
src/Avalonia.X11/X11NativeControlHost.cs

@ -157,21 +157,30 @@ namespace Avalonia.X11
public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost; public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost;
public void Hide() public void HideWithSize(Size size)
{ {
if(_attachedTo == null || _child == null) if(_attachedTo == null || _child == null)
return; return;
_mapped = false; if (_mapped)
XUnmapWindow(_display, _holder.Handle); {
_mapped = false;
XUnmapWindow(_display, _holder.Handle);
}
size *= _attachedTo.Window.Scaling;
XResizeWindow(_display, _child.Handle,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
} }
public void ShowInBounds(TransformedBounds transformedBounds)
public void ShowInBounds(Rect bounds)
{ {
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");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * bounds *= _attachedTo.Window.Scaling;
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
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));
XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height); XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height);
@ -183,7 +192,6 @@ namespace Avalonia.X11
XRaiseWindow(_display, _holder.Handle); XRaiseWindow(_display, _holder.Handle);
_mapped = true; _mapped = true;
} }
Console.WriteLine($"Moved {_child.Handle} to {pixelRect}");
} }
} }
} }

12
src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

@ -168,21 +168,25 @@ namespace Avalonia.Win32
public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost; public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost;
public void Hide() public void HideWithSize(Size size)
{ {
UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero,
-100, -100, 1, 1, -100, -100, 1, 1,
UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW | UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW |
UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
if (_attachedTo == null || _child == null)
return;
size *= _attachedTo.Window.Scaling;
UnmanagedMethods.MoveWindow(_child.Handle, 0, 0,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false);
} }
public unsafe void ShowInBounds(TransformedBounds transformedBounds) public unsafe void ShowInBounds(Rect bounds)
{ {
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");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * bounds *= _attachedTo.Window.Scaling;
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
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));

19
src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs

@ -5,12 +5,13 @@ using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Logging;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
internal class WindowsMountedVolumeInfoListener : IDisposable internal class WindowsMountedVolumeInfoListener : IDisposable
{ {
private readonly CompositeDisposable _disposables; private readonly CompositeDisposable _disposables;
private bool _beenDisposed = false; private bool _beenDisposed = false;
private ObservableCollection<MountedVolumeInfo> mountedDrives; private ObservableCollection<MountedVolumeInfo> mountedDrives;
@ -32,10 +33,22 @@ namespace Avalonia.Win32
var allDrives = DriveInfo.GetDrives(); var allDrives = DriveInfo.GetDrives();
var mountVolInfos = allDrives var mountVolInfos = allDrives
.Where(p => p.IsReady) .Where(p =>
{
try
{
var ret = p.IsReady;
return ret;
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, $"Error in Windows drive enumeration: {e.Message}");
}
return false;
})
.Select(p => new MountedVolumeInfo() .Select(p => new MountedVolumeInfo()
{ {
VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName
: $"{p.VolumeLabel} ({p.Name})", : $"{p.VolumeLabel} ({p.Name})",
VolumePath = p.RootDirectory.FullName, VolumePath = p.RootDirectory.FullName,
VolumeSizeBytes = (ulong)p.TotalSize VolumeSizeBytes = (ulong)p.TotalSize

2
tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs

@ -24,7 +24,7 @@ namespace Avalonia.Benchmarks.Layout
Renderer = new NullRenderer() Renderer = new NullRenderer()
}; };
_root.LayoutManager.ExecuteInitialLayoutPass(_root); _root.LayoutManager.ExecuteInitialLayoutPass();
} }
[Benchmark] [Benchmark]

2
tests/Avalonia.Benchmarks/Layout/Measure.cs

@ -25,7 +25,7 @@ namespace Avalonia.Benchmarks.Layout
_controls.Add(panel); _controls.Add(panel);
_controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 5); _controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 5);
_root.LayoutManager.ExecuteInitialLayoutPass(_root); _root.LayoutManager.ExecuteInitialLayoutPass();
} }
[Benchmark, MethodImpl(MethodImplOptions.NoInlining)] [Benchmark, MethodImpl(MethodImplOptions.NoInlining)]

2
tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs

@ -26,7 +26,7 @@ namespace Avalonia.Benchmarks.Traversal
_shuffledControls = _controls.OrderBy(r => random.Next()).ToList(); _shuffledControls = _controls.OrderBy(r => random.Next()).ToList();
_root.LayoutManager.ExecuteInitialLayoutPass(_root); _root.LayoutManager.ExecuteInitialLayoutPass();
} }
[Benchmark] [Benchmark]

2
tests/Avalonia.Controls.UnitTests/GridTests.cs

@ -1194,7 +1194,7 @@ namespace Avalonia.Controls.UnitTests
Height = 50, Height = 50,
}; };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
PrintColumnDefinitions(grids[0]); PrintColumnDefinitions(grids[0]);
Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth); Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth);

2
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -329,7 +329,7 @@ namespace Avalonia.Controls.UnitTests
return tb; return tb;
}, true); }, true);
lm.ExecuteInitialLayoutPass(wnd); lm.ExecuteInitialLayoutPass();
target.Items = items; target.Items = items;

9
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs

@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroll = (TestScroller)target.Parent; var scroll = (TestScroller)target.Parent;
scroll.Width = scroll.Height = 100; scroll.Width = scroll.Height = 100;
scroll.LayoutManager.ExecuteInitialLayoutPass(scroll); scroll.LayoutManager.ExecuteInitialLayoutPass();
// Ensure than an intermediate measure pass doesn't add more controls than it // Ensure than an intermediate measure pass doesn't add more controls than it
// should. This can happen if target gets measured with Size.Infinity which // should. This can happen if target gets measured with Size.Infinity which
@ -324,6 +324,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot
{ {
public TestScroller()
{
LayoutManager = new LayoutManager(this);
}
public IRenderer Renderer { get; } public IRenderer Renderer { get; }
public Size ClientSize { get; } public Size ClientSize { get; }
public double RenderScaling => 1; public double RenderScaling => 1;
@ -332,7 +337,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public double LayoutScaling => 1; public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; } = new LayoutManager(); public ILayoutManager LayoutManager { get; }
public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
public void Invalidate(Rect rect) => throw new NotImplementedException(); public void Invalidate(Rect rect) => throw new NotImplementedException();

27
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs

@ -723,7 +723,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent; var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100; scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var last = (target.Items as IList)[10]; var last = (target.Items as IList)[10];
@ -740,7 +740,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent; var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100; scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var last = (target.Items as IList)[10]; var last = (target.Items as IList)[10];
@ -838,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent; var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100; scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[5]; var from = target.Panel.Children[5];
var result = ((ILogicalScrollable)target).GetControlInDirection( var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -855,7 +855,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent; var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100; scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[9]; var from = target.Panel.Children[9];
var result = ((ILogicalScrollable)target).GetControlInDirection( var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -874,7 +874,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 100; scroller.Width = 100;
scroller.Height = 95; scroller.Height = 95;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[8]; var from = target.Panel.Children[8];
var result = ((ILogicalScrollable)target).GetControlInDirection( var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -893,7 +893,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 100; scroller.Width = 100;
scroller.Height = 95; scroller.Height = 95;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
((ILogicalScrollable)target).Offset = new Vector(0, 11); ((ILogicalScrollable)target).Offset = new Vector(0, 11);
var from = target.Panel.Children[1]; var from = target.Panel.Children[1];
@ -946,7 +946,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent; var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100; scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[5]; var from = target.Panel.Children[5];
var result = ((ILogicalScrollable)target).GetControlInDirection( var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -963,7 +963,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent; var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100; scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[9]; var from = target.Panel.Children[9];
var result = ((ILogicalScrollable)target).GetControlInDirection( var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -982,7 +982,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 95; scroller.Width = 95;
scroller.Height = 100; scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[8]; var from = target.Panel.Children[8];
var result = ((ILogicalScrollable)target).GetControlInDirection( var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -1001,7 +1001,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 95; scroller.Width = 95;
scroller.Height = 100; scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); scroller.LayoutManager.ExecuteInitialLayoutPass();
((ILogicalScrollable)target).Offset = new Vector(11, 0); ((ILogicalScrollable)target).Offset = new Vector(11, 0);
var from = target.Panel.Children[1]; var from = target.Panel.Children[1];
@ -1062,6 +1062,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, ILogicalRoot private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, ILogicalRoot
{ {
public TestScroller()
{
LayoutManager = new LayoutManager(this);
}
public IRenderer Renderer { get; } public IRenderer Renderer { get; }
public Size ClientSize { get; } public Size ClientSize { get; }
public double RenderScaling => 1; public double RenderScaling => 1;
@ -1070,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public double LayoutScaling => 1; public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; } = new LayoutManager(); public ILayoutManager LayoutManager { get; }
public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
public void Invalidate(Rect rect) => throw new NotImplementedException(); public void Invalidate(Rect rect) => throw new NotImplementedException();

6
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -158,7 +158,7 @@ namespace Avalonia.Controls.UnitTests
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10); target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
target.ScrollChanged += (s, e) => target.ScrollChanged += (s, e) =>
{ {
@ -188,7 +188,7 @@ namespace Avalonia.Controls.UnitTests
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10); target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
target.ScrollChanged += (s, e) => target.ScrollChanged += (s, e) =>
{ {
@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10); target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
target.ScrollChanged += (s, e) => target.ScrollChanged += (s, e) =>
{ {

25
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests
} }
}; };
target.LayoutManager.ExecuteInitialLayoutPass(target); target.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Rect(0, 0, 321, 432), target.Bounds); Assert.Equal(new Rect(0, 0, 321, 432), target.Bounds);
} }
@ -267,6 +267,23 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Close_Should_Dispose_LayoutManager()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
impl.SetupAllProperties();
var layoutManager = new Mock<ILayoutManager>();
var target = new TestTopLevel(impl.Object, layoutManager.Object);
impl.Object.Closed();
layoutManager.Verify(x => x.Dispose());
}
}
[Fact] [Fact]
public void Reacts_To_Changes_In_Global_Styles() public void Reacts_To_Changes_In_Global_Styles()
{ {
@ -282,7 +299,7 @@ namespace Avalonia.Controls.UnitTests
Content = child, Content = child,
}; };
target.LayoutManager.ExecuteInitialLayoutPass(target); target.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Thickness(0), child.BorderThickness); Assert.Equal(new Thickness(0), child.BorderThickness);
@ -295,7 +312,7 @@ namespace Avalonia.Controls.UnitTests
}; };
Application.Current.Styles.Add(style); Application.Current.Styles.Add(style);
target.LayoutManager.ExecuteInitialLayoutPass(target); target.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Thickness(2), child.BorderThickness); Assert.Equal(new Thickness(2), child.BorderThickness);
@ -323,7 +340,7 @@ namespace Avalonia.Controls.UnitTests
public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null) public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
: base(impl) : base(impl)
{ {
_layoutManager = layoutManager ?? new LayoutManager(); _layoutManager = layoutManager ?? new LayoutManager(this);
} }
protected override ILayoutManager CreateLayoutManager() => _layoutManager; protected override ILayoutManager CreateLayoutManager() => _layoutManager;

48
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false; control.Measured = control.Arranged = false;
control.InvalidateMeasure(); control.InvalidateMeasure();
@ -23,13 +23,29 @@ namespace Avalonia.Layout.UnitTests
Assert.True(control.Arranged); Assert.True(control.Arranged);
} }
[Fact]
public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_TopLevel_Is_Not_Visible()
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control, IsVisible = false };
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false;
control.InvalidateMeasure();
root.LayoutManager.ExecuteLayoutPass();
Assert.False(control.Measured);
Assert.False(control.Arranged);
}
[Fact] [Fact]
public void Arranges_InvalidateArranged_Control() public void Arranges_InvalidateArranged_Control()
{ {
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false; control.Measured = control.Arranged = false;
control.InvalidateArrange(); control.InvalidateArrange();
@ -45,7 +61,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot(); var root = new LayoutTestRoot();
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
root.Child = control; root.Child = control;
root.Measured = root.Arranged = false; root.Measured = root.Arranged = false;
@ -80,7 +96,7 @@ namespace Avalonia.Layout.UnitTests
root.DoMeasureOverride = MeasureOverride; root.DoMeasureOverride = MeasureOverride;
control1.DoMeasureOverride = MeasureOverride; control1.DoMeasureOverride = MeasureOverride;
control2.DoMeasureOverride = MeasureOverride; control2.DoMeasureOverride = MeasureOverride;
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control2.InvalidateMeasure(); control2.InvalidateMeasure();
control1.InvalidateMeasure(); control1.InvalidateMeasure();
@ -115,7 +131,7 @@ namespace Avalonia.Layout.UnitTests
root.DoMeasureOverride = MeasureOverride; root.DoMeasureOverride = MeasureOverride;
control1.DoMeasureOverride = MeasureOverride; control1.DoMeasureOverride = MeasureOverride;
control2.DoMeasureOverride = MeasureOverride; control2.DoMeasureOverride = MeasureOverride;
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control2.InvalidateMeasure(); control2.InvalidateMeasure();
root.InvalidateMeasure(); root.InvalidateMeasure();
@ -132,7 +148,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
root.Measured = root.Arranged = false; root.Measured = root.Arranged = false;
control.Measured = control.Arranged = false; control.Measured = control.Arranged = false;
@ -151,7 +167,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false; control.Measured = control.Arranged = false;
control.InvalidateMeasure(); control.InvalidateMeasure();
@ -177,7 +193,7 @@ namespace Avalonia.Layout.UnitTests
return new Size(100, 100); return new Size(100, 100);
}; };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(Size.Infinity, availableSize); Assert.Equal(Size.Infinity, availableSize);
} }
@ -199,7 +215,7 @@ namespace Avalonia.Layout.UnitTests
return s; return s;
}; };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Size(100, 100), arrangeSize); Assert.Equal(new Size(100, 100), arrangeSize);
root.Width = 120; root.Width = 120;
@ -225,7 +241,7 @@ namespace Avalonia.Layout.UnitTests
} }
}; };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Size(0, 0), root.DesiredSize); Assert.Equal(new Size(0, 0), root.DesiredSize);
border.Width = 100; border.Width = 100;
@ -241,7 +257,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = false; control.Measured = false;
int cnt = 0; int cnt = 0;
@ -272,7 +288,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control.Arranged = false; control.Arranged = false;
int cnt = 0; int cnt = 0;
@ -313,7 +329,7 @@ namespace Avalonia.Layout.UnitTests
panel.Children.AddRange(nonArrageableTargets); panel.Children.AddRange(nonArrageableTargets);
panel.Children.AddRange(targets); panel.Children.AddRange(targets);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
foreach (var c in panel.Children.OfType<LayoutTestControl>()) foreach (var c in panel.Children.OfType<LayoutTestControl>())
{ {
@ -347,7 +363,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl(); var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = false; control.Measured = false;
control.DoMeasureOverride = (l, s) => control.DoMeasureOverride = (l, s) =>
@ -380,7 +396,7 @@ namespace Avalonia.Layout.UnitTests
var root = new LayoutTestRoot { Child = control }; var root = new LayoutTestRoot { Child = control };
var count = 0; var count = 0;
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = false; control.Measured = false;
control.DoMeasureOverride = (l, s) => control.DoMeasureOverride = (l, s) =>
@ -399,7 +415,7 @@ namespace Avalonia.Layout.UnitTests
root.InvalidateMeasure(); root.InvalidateMeasure();
control.InvalidateMeasure(); control.InvalidateMeasure();
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Size(200, 200), control.Bounds.Size); Assert.Equal(new Size(200, 200), control.Bounds.Size);
Assert.Equal(new Size(200, 200), control.DesiredSize); Assert.Equal(new Size(200, 200), control.DesiredSize);

4
tests/Avalonia.Layout.UnitTests/LayoutableTests.cs

@ -208,14 +208,12 @@ namespace Avalonia.Layout.UnitTests
{ {
Border border1; Border border1;
Border border2; Border border2;
var layoutManager = new LayoutManager();
var root = new TestRoot var root = new TestRoot
{ {
Child = border1 = new Border Child = border1 = new Border
{ {
Child = border2 = new Border(), Child = border2 = new Border(),
}, },
LayoutManager = layoutManager,
}; };
var raised = 0; var raised = 0;
@ -233,7 +231,7 @@ namespace Avalonia.Layout.UnitTests
root.Measure(new Size(100, 100)); root.Measure(new Size(100, 100));
root.Arrange(new Rect(0, 0, 100, 100)); root.Arrange(new Rect(0, 0, 100, 100));
layoutManager.ExecuteLayoutPass(); root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(3, raised); Assert.Equal(3, raised);
Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds); Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds);

424
tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs

@ -0,0 +1,424 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Layout.UnitTests
{
public class LayoutableTests_EffectiveViewportChanged
{
[Fact]
public async Task EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas();
var raised = 0;
target.EffectiveViewportChanged += (s, e) =>
{
++raised;
};
root.Child = target;
Assert.Equal(0, raised);
});
}
[Fact]
public async Task EffectiveViewportChanged_Raised_Before_LayoutUpdated()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas();
var raised = 0;
target.EffectiveViewportChanged += (s, e) =>
{
++raised;
};
root.Child = target;
await ExecuteInitialLayoutPass(root);
Assert.Equal(1, raised);
});
}
[Fact]
public async Task Parent_Affects_EffectiveViewport()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 100, Height = 100 };
var parent = new Border { Width = 200, Height = 200, Child = target };
var raised = 0;
root.Child = parent;
target.EffectiveViewportChanged += (s, e) =>
{
Assert.Equal(new Rect(-550, -400, 1200, 900), e.EffectiveViewport);
++raised;
};
await ExecuteInitialLayoutPass(root);
});
}
[Fact]
public async Task Invalidating_In_Handler_Causes_Layout_To_Be_Rerun_Before_LayoutUpdated_Raised()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new TestCanvas();
var raised = 0;
var layoutUpdatedRaised = 0;
root.LayoutUpdated += (s, e) =>
{
Assert.Equal(2, target.MeasureCount);
Assert.Equal(2, target.ArrangeCount);
++layoutUpdatedRaised;
};
target.EffectiveViewportChanged += (s, e) =>
{
target.InvalidateMeasure();
++raised;
};
root.Child = target;
await ExecuteInitialLayoutPass(root);
Assert.Equal(1, raised);
Assert.Equal(1, layoutUpdatedRaised);
});
}
[Fact]
public async Task Viewport_Extends_Beyond_Centered_Control()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 52, Height = 52, };
var raised = 0;
target.EffectiveViewportChanged += (s, e) =>
{
Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport);
++raised;
};
root.Child = target;
await ExecuteInitialLayoutPass(root);
Assert.Equal(1, raised);
});
}
[Fact]
public async Task Viewport_Extends_Beyond_Nested_Centered_Control()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 52, Height = 52 };
var parent = new Border { Width = 100, Height = 100, Child = target };
var raised = 0;
target.EffectiveViewportChanged += (s, e) =>
{
Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport);
++raised;
};
root.Child = parent;
await ExecuteInitialLayoutPass(root);
Assert.Equal(1, raised);
});
}
[Fact]
public async Task ScrollViewer_Determines_EffectiveViewport()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 200, Height = 200 };
var scroller = new ScrollViewer { Width = 100, Height = 100, Content = target, Template = ScrollViewerTemplate() };
var raised = 0;
target.EffectiveViewportChanged += (s, e) =>
{
Assert.Equal(new Rect(0, 0, 100, 100), e.EffectiveViewport);
++raised;
};
root.Child = scroller;
await ExecuteInitialLayoutPass(root);
Assert.Equal(1, raised);
});
}
[Fact]
public async Task Scrolled_ScrollViewer_Determines_EffectiveViewport()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 200, Height = 200 };
var scroller = new ScrollViewer { Width = 100, Height = 100, Content = target, Template = ScrollViewerTemplate() };
var raised = 0;
root.Child = scroller;
await ExecuteInitialLayoutPass(root);
scroller.Offset = new Vector(0, 10);
await ExecuteScrollerLayoutPass(root, scroller, target, (s, e) =>
{
Assert.Equal(new Rect(0, 10, 100, 100), e.EffectiveViewport);
++raised;
});
Assert.Equal(1, raised);
});
}
[Fact]
public async Task Moving_Parent_Updates_EffectiveViewport()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 100, Height = 100 };
var parent = new Border { Width = 200, Height = 200, Child = target };
var raised = 0;
root.Child = parent;
await ExecuteInitialLayoutPass(root);
target.EffectiveViewportChanged += (s, e) =>
{
Assert.Equal(new Rect(-554, -400, 1200, 900), e.EffectiveViewport);
++raised;
};
parent.Margin = new Thickness(8, 0, 0, 0);
await ExecuteLayoutPass(root);
Assert.Equal(1, raised);
});
}
[Fact]
public async Task Translate_Transform_Doesnt_Affect_EffectiveViewport()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 100, Height = 100 };
var parent = new Border { Width = 200, Height = 200, Child = target };
var raised = 0;
root.Child = parent;
await ExecuteInitialLayoutPass(root);
target.EffectiveViewportChanged += (s, e) => ++raised;
target.RenderTransform = new TranslateTransform { X = 8 };
target.InvalidateMeasure();
await ExecuteLayoutPass(root);
Assert.Equal(0, raised);
});
}
[Fact]
public async Task Translate_Transform_On_Parent_Affects_EffectiveViewport()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 100, Height = 100 };
var parent = new Border { Width = 200, Height = 200, Child = target };
var raised = 0;
root.Child = parent;
await ExecuteInitialLayoutPass(root);
target.EffectiveViewportChanged += (s, e) =>
{
Assert.Equal(new Rect(-558, -400, 1200, 900), e.EffectiveViewport);
++raised;
};
// Change the parent render transform to move it. A layout is then needed before
// EffectiveViewportChanged is raised.
parent.RenderTransform = new TranslateTransform { X = 8 };
parent.InvalidateMeasure();
await ExecuteLayoutPass(root);
Assert.Equal(1, raised);
});
}
[Fact]
public async Task Rotate_Transform_On_Parent_Affects_EffectiveViewport()
{
await RunOnUIThread.Execute(async () =>
{
var root = CreateRoot();
var target = new Canvas { Width = 100, Height = 100 };
var parent = new Border { Width = 200, Height = 200, Child = target };
var raised = 0;
root.Child = parent;
await ExecuteInitialLayoutPass(root);
target.EffectiveViewportChanged += (s, e) =>
{
AssertArePixelEqual(new Rect(-651, -792, 1484, 1484), e.EffectiveViewport);
++raised;
};
parent.RenderTransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
parent.RenderTransform = new RotateTransform { Angle = 45 };
parent.InvalidateMeasure();
await ExecuteLayoutPass(root);
Assert.Equal(1, raised);
});
}
private TestRoot CreateRoot() => new TestRoot { Width = 1200, Height = 900 };
private Task ExecuteInitialLayoutPass(TestRoot root)
{
root.LayoutManager.ExecuteInitialLayoutPass();
return Task.CompletedTask;
}
private Task ExecuteLayoutPass(TestRoot root)
{
root.LayoutManager.ExecuteLayoutPass();
return Task.CompletedTask;
}
private Task ExecuteScrollerLayoutPass(
TestRoot root,
ScrollViewer scroller,
Control target,
Action<object, EffectiveViewportChangedEventArgs> handler)
{
void ViewportChanged(object sender, EffectiveViewportChangedEventArgs e)
{
handler(sender, e);
}
target.EffectiveViewportChanged += ViewportChanged;
root.LayoutManager.ExecuteLayoutPass();
return Task.CompletedTask;
}
private IControlTemplate ScrollViewerTemplate()
{
return new FuncControlTemplate<ScrollViewer>((control, scope) => new Grid
{
ColumnDefinitions = new ColumnDefinitions
{
new ColumnDefinition(1, GridUnitType.Star),
new ColumnDefinition(GridLength.Auto),
},
RowDefinitions = new RowDefinitions
{
new RowDefinition(1, GridUnitType.Star),
new RowDefinition(GridLength.Auto),
},
Children =
{
new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
[~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
[~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
[~ScrollContentPresenter.CanVerticallyScrollProperty] = control[~ScrollViewer.CanVerticallyScrollProperty],
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "horizontalScrollBar",
Orientation = Orientation.Horizontal,
[~RangeBase.MaximumProperty] = control[~ScrollViewer.HorizontalScrollBarMaximumProperty],
[~~RangeBase.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty],
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
[Grid.RowProperty] = 1,
}.RegisterInNameScope(scope),
new ScrollBar
{
Name = "verticalScrollBar",
Orientation = Orientation.Vertical,
[~RangeBase.MaximumProperty] = control[~ScrollViewer.VerticalScrollBarMaximumProperty],
[~~RangeBase.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty],
[~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty],
[~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
[Grid.ColumnProperty] = 1,
}.RegisterInNameScope(scope),
},
});
}
private void AssertArePixelEqual(Rect expected, Rect actual)
{
var expectedRounded = new Rect((int)expected.X, (int)expected.Y, (int)expected.Width, (int)expected.Height);
var actualRounded = new Rect((int)actual.X, (int)actual.Y, (int)actual.Width, (int)actual.Height);
Assert.Equal(expectedRounded, actualRounded);
}
private class TestCanvas : Canvas
{
public int MeasureCount { get; private set; }
public int ArrangeCount { get; private set; }
protected override Size MeasureOverride(Size availableSize)
{
++MeasureCount;
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
++ArrangeCount;
return base.ArrangeOverride(finalSize);
}
}
private static class RunOnUIThread
{
public static async Task Execute(Func<Task> func)
{
await func();
}
}
}
}

20
tests/Avalonia.LeakTests/ControlTests.cs

@ -44,7 +44,7 @@ namespace Avalonia.LeakTests
window.Show(); window.Show();
// Do a layout and make sure that Canvas gets added to visual tree. // Do a layout and make sure that Canvas gets added to visual tree.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<Canvas>(window.Presenter.Child); Assert.IsType<Canvas>(window.Presenter.Child);
// Clear the content and ensure the Canvas is removed. // Clear the content and ensure the Canvas is removed.
@ -82,7 +82,7 @@ namespace Avalonia.LeakTests
window.Show(); window.Show();
// Do a layout and make sure that Canvas gets added to visual tree. // Do a layout and make sure that Canvas gets added to visual tree.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<Canvas>(window.Find<Canvas>("foo")); Assert.IsType<Canvas>(window.Find<Canvas>("foo"));
Assert.IsType<Canvas>(window.Presenter.Child); Assert.IsType<Canvas>(window.Presenter.Child);
@ -122,7 +122,7 @@ namespace Avalonia.LeakTests
// Do a layout and make sure that ScrollViewer gets added to visual tree and its // Do a layout and make sure that ScrollViewer gets added to visual tree and its
// template applied. // template applied.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<ScrollViewer>(window.Presenter.Child); Assert.IsType<ScrollViewer>(window.Presenter.Child);
Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child); Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
@ -159,7 +159,7 @@ namespace Avalonia.LeakTests
// Do a layout and make sure that TextBox gets added to visual tree and its // Do a layout and make sure that TextBox gets added to visual tree and its
// template applied. // template applied.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<TextBox>(window.Presenter.Child); Assert.IsType<TextBox>(window.Presenter.Child);
Assert.NotEmpty(window.Presenter.Child.GetVisualChildren()); Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
@ -203,7 +203,7 @@ namespace Avalonia.LeakTests
// Do a layout and make sure that TextBox gets added to visual tree and its // Do a layout and make sure that TextBox gets added to visual tree and its
// Text property set. // Text property set.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<TextBox>(window.Presenter.Child); Assert.IsType<TextBox>(window.Presenter.Child);
Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text); Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
@ -241,7 +241,7 @@ namespace Avalonia.LeakTests
// Do a layout and make sure that TextBox gets added to visual tree and its // Do a layout and make sure that TextBox gets added to visual tree and its
// template applied. // template applied.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.Same(textBox, window.Presenter.Child); Assert.Same(textBox, window.Presenter.Child);
// Get the border from the TextBox template. // Get the border from the TextBox template.
@ -295,7 +295,7 @@ namespace Avalonia.LeakTests
window.Show(); window.Show();
// Do a layout and make sure that TreeViewItems get realized. // Do a layout and make sure that TreeViewItems get realized.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.Single(target.ItemContainerGenerator.Containers); Assert.Single(target.ItemContainerGenerator.Containers);
// Clear the content and ensure the TreeView is removed. // Clear the content and ensure the TreeView is removed.
@ -329,7 +329,7 @@ namespace Avalonia.LeakTests
window.Show(); window.Show();
// Do a layout and make sure that Slider gets added to visual tree. // Do a layout and make sure that Slider gets added to visual tree.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<Slider>(window.Presenter.Child); Assert.IsType<Slider>(window.Presenter.Child);
// Clear the content and ensure the Slider is removed. // Clear the content and ensure the Slider is removed.
@ -403,7 +403,7 @@ namespace Avalonia.LeakTests
// Do a layout and make sure that Canvas gets added to visual tree with // Do a layout and make sure that Canvas gets added to visual tree with
// its render transform. // its render transform.
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
var canvas = Assert.IsType<Canvas>(window.Presenter.Child); var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
Assert.IsType<RotateTransform>(canvas.RenderTransform); Assert.IsType<RotateTransform>(canvas.RenderTransform);
@ -514,7 +514,7 @@ namespace Avalonia.LeakTests
window.Show(); window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window); window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<Path>(window.Presenter.Child); Assert.IsType<Path>(window.Presenter.Child);
window.Content = null; window.Content = null;

4
tests/Avalonia.UnitTests/TestRoot.cs

@ -19,6 +19,8 @@ namespace Avalonia.UnitTests
public TestRoot() public TestRoot()
{ {
Renderer = Mock.Of<IRenderer>(); Renderer = Mock.Of<IRenderer>();
LayoutManager = new LayoutManager(this);
IsVisible = true;
} }
public TestRoot(IControl child) public TestRoot(IControl child)
@ -44,7 +46,7 @@ namespace Avalonia.UnitTests
public double LayoutScaling { get; set; } = 1; public double LayoutScaling { get; set; } = 1;
public ILayoutManager LayoutManager { get; set; } = new LayoutManager(); public ILayoutManager LayoutManager { get; set; }
public double RenderScaling => 1; public double RenderScaling => 1;

3
tests/Avalonia.UnitTests/TestTemplatedRoot.cs

@ -16,6 +16,7 @@ namespace Avalonia.UnitTests
public TestTemplatedRoot() public TestTemplatedRoot()
{ {
LayoutManager = new LayoutManager(this);
Template = new FuncControlTemplate<TestTemplatedRoot>((x, scope) => new ContentPresenter Template = new FuncControlTemplate<TestTemplatedRoot>((x, scope) => new ContentPresenter
{ {
Name = "PART_ContentPresenter", Name = "PART_ContentPresenter",
@ -28,7 +29,7 @@ namespace Avalonia.UnitTests
public double LayoutScaling => 1; public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; set; } = new LayoutManager(); public ILayoutManager LayoutManager { get; set; }
public double RenderScaling => 1; public double RenderScaling => 1;

67
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt

@ -1,33 +1,34 @@
OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ CB
OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % % PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % % _
PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % _
B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ % B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ % _
ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _ ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _ _
CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _
H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _
JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ % JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ % _
JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _
JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _
RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ % RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ % _
EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % % EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % % _
EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
ZWJ _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ % _ % % _ _ ^ # ^ _ _ _ _ _ _ % % % ZWJ % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
CB _ ^ ^ % % _ ^ ^ ^ _ _ _ _ _ _ _ _ _ _ _ ^ # ^ _ _ _ _ _ _ _ _ % _

61
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
@ -12,6 +11,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
public static void Execute() public static void Execute()
{ {
if (!Directory.Exists("Generated"))
{
Directory.CreateDirectory("Generated");
}
using (var stream = File.Create("Generated\\GraphemeBreak.trie")) using (var stream = File.Create("Generated\\GraphemeBreak.trie"))
{ {
var trie = GenerateBreakTypeTrie(); var trie = GenerateBreakTypeTrie();
@ -22,48 +26,29 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
private static UnicodeTrie GenerateBreakTypeTrie() private static UnicodeTrie GenerateBreakTypeTrie()
{ {
var graphemeBreakClassValues = UnicodeEnumsGenerator.GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)");
var graphemeBreakClassMapping = graphemeBreakClassValues.Select(x => x.name).ToList();
var trieBuilder = new UnicodeTrieBuilder(); var trieBuilder = new UnicodeTrieBuilder();
var graphemeBreakData = ReadBreakData( var graphemeBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakProperty.txt"));
"https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt");
foreach (var (start, end, graphemeBreakType) in graphemeBreakData)
{
if (!graphemeBreakClassMapping.Contains(graphemeBreakType))
{
continue;
}
if (start == end)
{
trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
}
else
{
trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
}
}
var emojiBreakData = ReadBreakData("https://unicode.org/Public/emoji/12.0/emoji-data.txt"); var emojiBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "emoji/emoji-data.txt"));
foreach (var (start, end, graphemeBreakType) in emojiBreakData) foreach (var breakData in new [] { graphemeBreakData, emojiBreakData })
{ {
if (!graphemeBreakClassMapping.Contains(graphemeBreakType)) foreach (var (start, end, graphemeBreakType) in breakData)
{ {
continue; if (!Enum.TryParse<GraphemeBreakClass>(graphemeBreakType, out var value))
} {
continue;
}
if (start == end) if (start == end)
{ {
trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); trieBuilder.Set(start, (uint)value);
} }
else else
{ {
trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); trieBuilder.SetRange(start, end, (uint)value);
}
} }
} }
@ -113,7 +98,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
end = Convert.ToInt32(match.Groups[2].Value, 16); end = Convert.ToInt32(match.Groups[2].Value, 16);
} }
data.Add((start, end, match.Groups[3].Value)); var breakType = match.Groups[3].Value;
data.Add((start, end, breakType));
} }
} }
} }

83
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs

@ -1,9 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Xunit; using Xunit;
@ -16,10 +11,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public class GraphemeBreakClassTrieGeneratorTests public class GraphemeBreakClassTrieGeneratorTests
{ {
[Theory(Skip = "Only run when we update the trie.")] [Theory(Skip = "Only run when we update the trie.")]
[ClassData(typeof(GraphemeEnumeratorTestDataGenerator))] [ClassData(typeof(GraphemeBreakTestDataGenerator))]
public void Should_Enumerate(string text, int expectedLength) public void Should_Enumerate(string text, int expectedLength)
{ {
var enumerator = new GraphemeEnumerator(text.AsMemory()); var textMemory = text.AsMemory();
var enumerator = new GraphemeEnumerator(textMemory);
Assert.True(enumerator.MoveNext()); Assert.True(enumerator.MoveNext());
@ -31,7 +28,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
const string text = "ABCDEFGHIJ"; const string text = "ABCDEFGHIJ";
var enumerator = new GraphemeEnumerator(text.AsMemory()); var textMemory = text.AsMemory();
var enumerator = new GraphemeEnumerator(textMemory);
var count = 0; var count = 0;
@ -51,73 +50,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
GraphemeBreakClassTrieGenerator.Execute(); GraphemeBreakClassTrieGenerator.Execute();
} }
public class GraphemeEnumeratorTestDataGenerator : IEnumerable<object[]> private class GraphemeBreakTestDataGenerator : TestDataGenerator
{ {
private readonly List<object[]> _testData; public GraphemeBreakTestDataGenerator()
: base("auxiliary/GraphemeBreakTest.txt")
public GraphemeEnumeratorTestDataGenerator()
{
_testData = ReadTestData();
}
public IEnumerator<object[]> GetEnumerator()
{
return _testData.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private static List<object[]> ReadTestData()
{ {
var testData = new List<object[]>();
using (var client = new HttpClient())
{
using (var result = client.GetAsync("https://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.txt").GetAwaiter().GetResult())
{
if (!result.IsSuccessStatusCode)
return testData;
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line == null)
{
break;
}
if (line.StartsWith("#") || string.IsNullOrEmpty(line))
{
continue;
}
var elements = line.Split('#')[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
var chars = elements[0].Replace(" × ", " ").Split(' ');
var codepoints = chars.Where(x => x != "" && x != "×")
.Select(x => Convert.ToInt32(x, 16)).ToArray();
var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
var data = new object[] { text, length };
testData.Add(data);
}
}
}
}
return testData;
} }
} }
} }

2
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs

@ -3,7 +3,7 @@ using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utility; using Avalonia.Utility;
using Xunit; using Xunit;
namespace Avalonia.Visuals.UnitTests.Media.Text namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
public class LineBreakerTests public class LineBreakerTests
{ {

85
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs

@ -0,0 +1,85 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
public abstract class TestDataGenerator : IEnumerable<object[]>
{
private readonly string _fileName;
private readonly List<object[]> _testData;
protected TestDataGenerator(string fileName)
{
_fileName = fileName;
_testData = ReadTestData();
}
public IEnumerator<object[]> GetEnumerator()
{
return _testData.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private List<object[]> ReadTestData()
{
var testData = new List<object[]>();
using (var client = new HttpClient())
{
var url = Path.Combine(UnicodeDataGenerator.Ucd, _fileName);
using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{
if (!result.IsSuccessStatusCode)
return testData;
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line == null)
{
break;
}
if (line.StartsWith("#") || string.IsNullOrEmpty(line))
{
continue;
}
var elements = line.Split('#');
elements = elements[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
var chars = elements[0].Replace(" × ", " ").Split(' ');
var codepoints = chars.Where(x => x != "" && x != "×")
.Select(x => Convert.ToInt32(x, 16)).ToArray();
var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
var data = new object[] { text, length };
testData.Add(data);
}
}
}
}
return testData;
}
}
}

61
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs

@ -9,13 +9,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
internal static class UnicodeDataGenerator internal static class UnicodeDataGenerator
{ {
public const string Ucd = "https://www.unicode.org/Public/13.0.0/ucd/";
public static void Execute() public static void Execute()
{ {
var codepoints = new Dictionary<int, UnicodeDataItem>(); var codepoints = new Dictionary<int, UnicodeDataItem>();
var generalCategoryValues = UnicodeEnumsGenerator.CreateGeneralCategoryEnum(); var generalCategoryEntries =
UnicodeEnumsGenerator.CreateGeneralCategoryEnum();
var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryValues); var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryEntries);
var generalCategoryData = ReadGeneralCategoryData(); var generalCategoryData = ReadGeneralCategoryData();
@ -26,23 +29,23 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddGeneralCategoryRange(codepoints, range, generalCategory); AddGeneralCategoryRange(codepoints, range, generalCategory);
} }
var scriptValues = UnicodeEnumsGenerator.CreateScriptEnum(); var scriptEntries = UnicodeEnumsGenerator.CreateScriptEnum();
var scriptMappings = CreateNameToIndexMappings(scriptValues); var scriptMappings = CreateNameToIndexMappings(scriptEntries);
var scriptData = ReadScriptData(); var scriptData = ReadScriptData();
foreach (var (range, name) in scriptData) foreach (var (range, name) in scriptData)
{ {
var script = scriptMappings[name.Replace("_", "")]; var script = scriptMappings[name];
AddScriptRange(codepoints, range, script); AddScriptRange(codepoints, range, script);
} }
var biDiClassValues = UnicodeEnumsGenerator.CreateBiDiClassEnum(); var biDiClassEntries =
UnicodeEnumsGenerator.CreateBiDiClassEnum();
var biDiClassMappings = CreateTagToIndexMappings(biDiClassValues); var biDiClassMappings = CreateTagToIndexMappings(biDiClassEntries);
var biDiData = ReadBiDiData(); var biDiData = ReadBiDiData();
@ -53,9 +56,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddBiDiClassRange(codepoints, range, biDiClass); AddBiDiClassRange(codepoints, range, biDiClass);
} }
var lineBreakClassValues = UnicodeEnumsGenerator.CreateLineBreakClassEnum(); var lineBreakClassEntries =
UnicodeEnumsGenerator.CreateLineBreakClassEnum();
var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassValues); var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassEntries);
var lineBreakClassData = ReadLineBreakClassData(); var lineBreakClassData = ReadLineBreakClassData();
@ -66,11 +70,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddLineBreakClassRange(codepoints, range, lineBreakClass); AddLineBreakClassRange(codepoints, range, lineBreakClass);
} }
const int initialValue = ((int)LineBreakClass.Unknown << UnicodeData.LINEBREAK_SHIFT) | //const int initialValue = (0 << UnicodeData.LINEBREAK_SHIFT) |
((int)BiDiClass.LeftToRight << UnicodeData.BIDI_SHIFT) | // (0 << UnicodeData.BIDI_SHIFT) |
((int)Script.Unknown << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other; // (0 << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
var builder = new UnicodeTrieBuilder(initialValue); var builder = new UnicodeTrieBuilder(/*initialValue*/);
foreach (var properties in codepoints.Values) foreach (var properties in codepoints.Values)
{ {
@ -88,27 +92,30 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
trie.Save(stream); trie.Save(stream);
} }
UnicodeEnumsGenerator.CreatePropertyValueAliasHelper(scriptEntries, generalCategoryEntries,
biDiClassEntries, lineBreakClassEntries);
} }
private static Dictionary<string, int> CreateTagToIndexMappings(List<(string name, string tag, string comment)> values) private static Dictionary<string, int> CreateTagToIndexMappings(List<DataEntry> entries)
{ {
var mappings = new Dictionary<string, int>(); var mappings = new Dictionary<string, int>();
for (var i = 0; i < values.Count; i++) for (var i = 0; i < entries.Count; i++)
{ {
mappings.Add(values[i].tag, i); mappings.Add(entries[i].Tag, i);
} }
return mappings; return mappings;
} }
private static Dictionary<string, int> CreateNameToIndexMappings(List<(string name, string tag, string comment)> values) private static Dictionary<string, int> CreateNameToIndexMappings(List<DataEntry> entries)
{ {
var mappings = new Dictionary<string, int>(); var mappings = new Dictionary<string, int>();
for (var i = 0; i < values.Count; i++) for (var i = 0; i < entries.Count; i++)
{ {
mappings.Add(values[i].name, i); mappings.Add(entries[i].Name, i);
} }
return mappings; return mappings;
@ -180,24 +187,22 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public static List<(CodepointRange, string)> ReadGeneralCategoryData() public static List<(CodepointRange, string)> ReadGeneralCategoryData()
{ {
return ReadUnicodeData( return ReadUnicodeData("extracted/DerivedGeneralCategory.txt");
"https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt");
} }
public static List<(CodepointRange, string)> ReadScriptData() public static List<(CodepointRange, string)> ReadScriptData()
{ {
return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt"); return ReadUnicodeData("Scripts.txt");
} }
public static List<(CodepointRange, string)> ReadBiDiData() public static List<(CodepointRange, string)> ReadBiDiData()
{ {
return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt"); return ReadUnicodeData("extracted/DerivedBidiClass.txt");
} }
public static List<(CodepointRange, string)> ReadLineBreakClassData() public static List<(CodepointRange, string)> ReadLineBreakClassData()
{ {
return ReadUnicodeData( return ReadUnicodeData("extracted/DerivedLineBreak.txt");
"https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedLineBreak.txt");
} }
private static List<(CodepointRange, string)> ReadUnicodeData(string file) private static List<(CodepointRange, string)> ReadUnicodeData(string file)
@ -208,7 +213,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
using (var result = client.GetAsync(file).GetAwaiter().GetResult()) var url = Path.Combine(Ucd, file);
using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{ {
if (!result.IsSuccessStatusCode) if (!result.IsSuccessStatusCode)
{ {

25
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs

@ -1,4 +1,6 @@
using Xunit; using System;
using Avalonia.Media.TextFormatting.Unicode;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media.TextFormatting namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
@ -13,5 +15,26 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
UnicodeDataGenerator.Execute(); UnicodeDataGenerator.Execute();
} }
[Theory(Skip = "Only run when we update the trie.")]
[ClassData(typeof(LineBreakTestDataGenerator))]
public void Should_Enumerate_LineBreaks(string text, int expectedLength)
{
var textMemory = text.AsMemory();
var enumerator = new LineBreakEnumerator(textMemory);
Assert.True(enumerator.MoveNext());
Assert.Equal(expectedLength, enumerator.Current.PositionWrap);
}
private class LineBreakTestDataGenerator : TestDataGenerator
{
public LineBreakTestDataGenerator()
: base("auxiliary/LineBreakTest.txt")
{
}
}
} }
} }

181
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs

@ -8,9 +8,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
internal static class UnicodeEnumsGenerator internal static class UnicodeEnumsGenerator
{ {
public static List<(string name, string tag, string comment)> CreateScriptEnum() public static List<DataEntry> CreateScriptEnum()
{ {
var scriptValues = GetPropertyValueAliases("# Script (sc)"); var entries = new List<DataEntry>
{
new DataEntry("Unknown", "Zzzz", string.Empty),
new DataEntry("Common", "Zyyy", string.Empty),
new DataEntry("Inherited", "Zinh", string.Empty)
};
ParseDataEntries("# Script (sc)", entries);
using (var stream = File.Create("Generated\\Script.cs")) using (var stream = File.Create("Generated\\Script.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -20,22 +27,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum Script"); writer.WriteLine(" public enum Script");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in scriptValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
return scriptValues; return entries;
} }
public static List<(string name, string tag, string comment)> CreateGeneralCategoryEnum() public static List<DataEntry> CreateGeneralCategoryEnum()
{ {
var generalCategoryValues = GetPropertyValueAliases("# General_Category (gc)"); var entries = new List<DataEntry> { new DataEntry("Other", "C", " Cc | Cf | Cn | Co | Cs") };
ParseDataEntries("# General_Category (gc)", entries);
using (var stream = File.Create("Generated\\GeneralCategory.cs")) using (var stream = File.Create("Generated\\GeneralCategory.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -45,22 +54,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GeneralCategory"); writer.WriteLine(" public enum GeneralCategory");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in generalCategoryValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
return generalCategoryValues; return entries;
} }
public static List<(string name, string tag, string comment)> CreateGraphemeBreakTypeEnum() public static List<DataEntry> CreateGraphemeBreakTypeEnum()
{ {
var graphemeClusterBreakValues = GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)"); var entries = new List<DataEntry> { new DataEntry("Other", "XX", string.Empty) };
ParseDataEntries("# Grapheme_Cluster_Break (GCB)", entries);
using (var stream = File.Create("Generated\\GraphemeBreakClass.cs")) using (var stream = File.Create("Generated\\GraphemeBreakClass.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -70,10 +81,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GraphemeBreakClass"); writer.WriteLine(" public enum GraphemeBreakClass");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in graphemeClusterBreakValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" ExtendedPictographic"); writer.WriteLine(" ExtendedPictographic");
@ -82,7 +93,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine("}"); writer.WriteLine("}");
} }
return graphemeClusterBreakValues; return entries;
} }
private static List<string> GenerateBreakPairTable() private static List<string> GenerateBreakPairTable()
@ -185,20 +196,32 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
} }
} }
public static List<(string name, string tag, string comment)> CreateLineBreakClassEnum() public static List<DataEntry> CreateLineBreakClassEnum()
{ {
var usedLineBreakClasses = GenerateBreakPairTable(); var usedLineBreakClasses = GenerateBreakPairTable();
var lineBreakValues = GetPropertyValueAliases("# Line_Break (lb)"); var entries = new List<DataEntry> { new DataEntry("Unknown", "XX", string.Empty) };
ParseDataEntries("# Line_Break (lb)", entries);
var lineBreakClassMappings = lineBreakValues.ToDictionary(x => x.tag, x => (x.name, x.tag, x.comment)); var orderedLineBreakEntries = new Dictionary<string, DataEntry>();
var orderedLineBreakValues = usedLineBreakClasses.Select(x => foreach (var tag in usedLineBreakClasses)
{ {
var value = lineBreakClassMappings[x]; var entry = entries.Single(x => x.Tag == tag);
lineBreakClassMappings.Remove(x);
return value; orderedLineBreakEntries.Add(tag, entry);
}).ToList(); }
foreach (var entry in entries)
{
if (orderedLineBreakEntries.ContainsKey(entry.Tag))
{
continue;
}
orderedLineBreakEntries.Add(entry.Tag, entry);
}
using (var stream = File.Create("Generated\\LineBreakClass.cs")) using (var stream = File.Create("Generated\\LineBreakClass.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -208,32 +231,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum LineBreakClass"); writer.WriteLine(" public enum LineBreakClass");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in orderedLineBreakValues) foreach (var entry in orderedLineBreakEntries.Values)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
}
writer.WriteLine();
foreach (var (name, tag, comment) in lineBreakClassMappings.Values)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
orderedLineBreakValues.AddRange(lineBreakClassMappings.Values); return orderedLineBreakEntries.Values.ToList();
return orderedLineBreakValues;
} }
public static List<(string name, string tag, string comment)> CreateBiDiClassEnum() public static List<DataEntry> CreateBiDiClassEnum()
{ {
var biDiClassValues = GetPropertyValueAliases("# Bidi_Class (bc)"); var entries = new List<DataEntry> { new DataEntry("Left_To_Right", "L", string.Empty) };
ParseDataEntries("# Bidi_Class (bc)", entries);
using (var stream = File.Create("Generated\\BiDiClass.cs")) using (var stream = File.Create("Generated\\BiDiClass.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -243,23 +258,21 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum BiDiClass"); writer.WriteLine(" public enum BiDiClass");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in biDiClassValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
return biDiClassValues; return entries;
} }
public static void CreatePropertyValueAliasHelper(List<(string name, string tag, string comment)> scriptValues, public static void CreatePropertyValueAliasHelper(List<DataEntry> scriptEntries, IEnumerable<DataEntry> generalCategoryEntries,
List<(string name, string tag, string comment)> generalCategoryValues, IEnumerable<DataEntry> biDiClassEntries, IEnumerable<DataEntry> lineBreakClassEntries)
List<(string name, string tag, string comment)> biDiClassValues,
List<(string name, string tag, string comment)> lineBreakValues)
{ {
using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs")) using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -269,35 +282,35 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode");
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" public static class PropertyValueAliasHelper"); writer.WriteLine(" internal static class PropertyValueAliasHelper");
writer.WriteLine(" {"); writer.WriteLine(" {");
WritePropertyValueAliasGetTag(writer, scriptValues, "Script", "Zzzz"); WritePropertyValueAliasGetTag(writer, scriptEntries, "Script", "Zzzz");
WritePropertyValueAlias(writer, scriptValues, "Script", "Unknown"); WritePropertyValueAlias(writer, scriptEntries, "Script", "Unknown");
WritePropertyValueAlias(writer, generalCategoryValues, "GeneralCategory", "Other"); WritePropertyValueAlias(writer, generalCategoryEntries, "GeneralCategory", "Other");
WritePropertyValueAlias(writer, biDiClassValues, "BiDiClass", "LeftToRight"); WritePropertyValueAlias(writer, biDiClassEntries, "BiDiClass", "LeftToRight");
WritePropertyValueAlias(writer, lineBreakValues, "LineBreakClass", "Unknown"); WritePropertyValueAlias(writer, lineBreakClassEntries, "LineBreakClass", "Unknown");
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
} }
public static List<(string name, string tag, string comment)> GetPropertyValueAliases(string property) public static void ParseDataEntries(string property, List<DataEntry> entries)
{ {
var data = new List<(string name, string tag, string comment)>();
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
using (var result = client.GetAsync("https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt").GetAwaiter().GetResult()) var url = Path.Combine(UnicodeDataGenerator.Ucd, "PropertyValueAliases.txt");
using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{ {
if (!result.IsSuccessStatusCode) if (!result.IsSuccessStatusCode)
{ {
return data; return;
} }
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
@ -337,7 +350,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
elements = elements[2].Split('#'); elements = elements[2].Split('#');
var name = elements[0].Trim().Replace("_", string.Empty); var name = elements[0].Trim();
if (entries.Any(x => x.Name == name))
{
continue;
}
var comment = string.Empty; var comment = string.Empty;
@ -346,24 +364,25 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
comment = elements[1]; comment = elements[1];
} }
data.Add((name, tag, comment)); var entry = new DataEntry(name, tag, comment);
entries.Add(entry);
} }
} }
} }
} }
return data;
} }
private static void WritePropertyValueAliasGetTag(TextWriter writer, private static void WritePropertyValueAliasGetTag(TextWriter writer, IEnumerable<DataEntry> entries,
IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue) string typeName, string defaultValue)
{ {
writer.WriteLine($" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = "); writer.WriteLine(
$" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = ");
writer.WriteLine($" new Dictionary<{typeName}, string>{{"); writer.WriteLine($" new Dictionary<{typeName}, string>{{");
foreach (var (name, tag, comment) in values) foreach (var entry in entries)
{ {
writer.WriteLine($" {{ {typeName}.{name}, \"{tag}\"}},"); writer.WriteLine($" {{ {typeName}.{entry.Name.Replace("_", "")}, \"{entry.Tag}\"}},");
} }
writer.WriteLine(" };"); writer.WriteLine(" };");
@ -382,15 +401,15 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(); writer.WriteLine();
} }
private static void WritePropertyValueAlias(TextWriter writer, private static void WritePropertyValueAlias(TextWriter writer, IEnumerable<DataEntry> entries, string typeName,
IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue) string defaultValue)
{ {
writer.WriteLine($" private static readonly Dictionary<string, {typeName}> s_tagTo{typeName} = "); writer.WriteLine($" private static readonly Dictionary<string, {typeName}> s_tagTo{typeName} = ");
writer.WriteLine($" new Dictionary<string,{typeName}>{{"); writer.WriteLine($" new Dictionary<string,{typeName}>{{");
foreach (var (name, tag, comment) in values) foreach (var entry in entries)
{ {
writer.WriteLine($" {{ \"{tag}\", {typeName}.{name}}},"); writer.WriteLine($" {{ \"{entry.Tag}\", {typeName}.{entry.Name.Replace("_", "")}}},");
} }
writer.WriteLine(" };"); writer.WriteLine(" };");
@ -409,4 +428,18 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(); writer.WriteLine();
} }
} }
public readonly struct DataEntry
{
public DataEntry(string name, string tag, string comment)
{
Name = name;
Tag = tag;
Comment = comment;
}
public string Name { get; }
public string Tag { get; }
public string Comment { get; }
}
} }

8
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

@ -134,7 +134,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var root = new TestRoot(child); var root = new TestRoot(child);
root.Renderer = new ImmediateRenderer(root); root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
root.Measure(new Size(50, 100)); root.Measure(new Size(50, 100));
root.Arrange(new Rect(new Size(50, 100))); root.Arrange(new Rect(new Size(50, 100)));
@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var root = new TestRoot(child); var root = new TestRoot(child);
root.Renderer = new ImmediateRenderer(root); root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
root.Measure(new Size(300, 100)); root.Measure(new Size(300, 100));
root.Arrange(new Rect(new Size(300, 100))); root.Arrange(new Rect(new Size(300, 100)));
@ -222,7 +222,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var root = new TestRoot(rootGrid); var root = new TestRoot(rootGrid);
root.Renderer = new ImmediateRenderer(root); root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
var rootSize = new Size(RootWidth, RootHeight); var rootSize = new Size(RootWidth, RootHeight);
root.Measure(rootSize); root.Measure(rootSize);
@ -277,7 +277,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var root = new TestRoot(rootGrid); var root = new TestRoot(rootGrid);
root.Renderer = new ImmediateRenderer(root); root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root); root.LayoutManager.ExecuteInitialLayoutPass();
var rootSize = new Size(RootWidth, RootHeight); var rootSize = new Size(RootWidth, RootHeight);
root.Measure(rootSize); root.Measure(rootSize);

6
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@ -653,7 +653,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var scene = new Scene(tree); var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder(); var sceneBuilder = new SceneBuilder();
@ -696,7 +696,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var scene = new Scene(tree); var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder(); var sceneBuilder = new SceneBuilder();
@ -744,7 +744,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var scene = new Scene(tree); var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder(); var sceneBuilder = new SceneBuilder();

10
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

@ -40,7 +40,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5); var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -105,7 +105,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5); var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -147,7 +147,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5); var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -197,7 +197,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5); var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -241,7 +241,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}; };
var layout = tree.LayoutManager; var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree); layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5); var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);

Loading…
Cancel
Save