Browse Source

Merge branch 'master' into feature/extend-client-area-to-decorations

# Conflicts:
#	native/Avalonia.Native/inc/avalonia-native.h
#	native/Avalonia.Native/src/OSX/window.mm
pull/3967/head
Dan Walmsley 6 years ago
parent
commit
092474d9b7
  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. 34
      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. 9
      samples/interop/NativeEmbedSample/MainWindow.xaml
  13. 6
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  14. 4
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  15. 3
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  16. 2
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  17. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  18. 89
      src/Avalonia.Controls/NativeControlHost.cs
  19. 4
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  20. 67
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  21. 2
      src/Avalonia.Controls/SelectionModel.cs
  22. 9
      src/Avalonia.Controls/TopLevel.cs
  23. 4
      src/Avalonia.Controls/Window.cs
  24. 2
      src/Avalonia.Controls/WindowBase.cs
  25. 24
      src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs
  26. 24
      src/Avalonia.Layout/ILayoutManager.cs
  27. 7
      src/Avalonia.Layout/ILayoutable.cs
  28. 205
      src/Avalonia.Layout/LayoutManager.cs
  29. 9
      src/Avalonia.Layout/LayoutQueue.cs
  30. 70
      src/Avalonia.Layout/Layoutable.cs
  31. 9
      src/Avalonia.Native/NativeControlHostImpl.cs
  32. 12
      src/Avalonia.Native/PopupImpl.cs
  33. 2
      src/Avalonia.Native/WindowImplBase.cs
  34. 38
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  35. 39
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  36. 1
      src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj
  37. 23
      src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml
  38. 61
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  39. BIN
      src/Avalonia.Visuals/Assets/GraphemeBreak.trie
  40. BIN
      src/Avalonia.Visuals/Assets/UnicodeData.trie
  41. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
  42. 65
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
  43. 34
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
  44. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
  45. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  46. 178
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs
  47. 10
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs
  48. 15
      src/Avalonia.Visuals/Rect.cs
  49. 22
      src/Avalonia.X11/X11NativeControlHost.cs
  50. 12
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  51. 19
      src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs
  52. 2
      tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs
  53. 2
      tests/Avalonia.Benchmarks/Layout/Measure.cs
  54. 2
      tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs
  55. 2
      tests/Avalonia.Controls.UnitTests/GridTests.cs
  56. 2
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  57. 9
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs
  58. 27
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
  59. 6
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  60. 25
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  61. 48
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  62. 4
      tests/Avalonia.Layout.UnitTests/LayoutableTests.cs
  63. 424
      tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs
  64. 20
      tests/Avalonia.LeakTests/ControlTests.cs
  65. 4
      tests/Avalonia.UnitTests/TestRoot.cs
  66. 3
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs
  67. 67
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
  68. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
  69. 83
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
  70. 2
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
  71. 85
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs
  72. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
  73. 25
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
  74. 181
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs
  75. 8
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs
  76. 6
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  77. 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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
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
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
@ -211,8 +211,8 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
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\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*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 = 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*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13

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

@ -287,6 +287,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
virtual HRESULT TakeFocusFromChildren() = 0;
virtual HRESULT SetExtendClientArea (bool enable) = 0;
virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) = 0;
virtual HRESULT GetExtendTitleBarHeight (double*ret) = 0;
@ -506,8 +507,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
virtual void* GetParentHandle() = 0;
virtual HRESULT InitializeWithChildHandle(void* child) = 0;
virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
virtual void MoveTo(float x, float y, float width, float height) = 0;
virtual void Hide() = 0;
virtual void ShowInBounds(float x, float y, float width, float height) = 0;
virtual void HideWithSize(float width, float height) = 0;
virtual void ReleaseChild() = 0;
};

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

@ -97,7 +97,7 @@ public:
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)
return;
@ -106,7 +106,7 @@ public:
IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{
slf->MoveTo(x, y, width, height);
slf->ShowInBounds(x, y, width, height);
slf->Release();
});
return;
@ -122,9 +122,24 @@ public:
[[_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];
[_child setFrame: frame];
}
virtual void ReleaseChild() override

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

@ -111,10 +111,15 @@ public:
{
SetPosition(lastPositionSet);
UpdateStyle();
[Window makeKeyAndOrderFront:Window];
[NSApp activateIgnoringOtherApps:YES];
if(ShouldTakeFocusOnShow())
{
[Window makeKeyAndOrderFront:Window];
[NSApp activateIgnoringOtherApps:YES];
}
else
{
[Window orderFront: Window];
}
[Window setTitle:_lastTitle];
_shown = true;
@ -123,6 +128,11 @@ public:
}
}
virtual bool ShouldTakeFocusOnShow()
{
return true;
}
virtual HRESULT Hide () override
{
@autoreleasepool
@ -807,6 +817,16 @@ private:
}
}
virtual HRESULT TakeFocusFromChildren () override
{
if(Window == nil)
return S_OK;
if([Window isKeyWindow])
[Window makeFirstResponder: View];
return S_OK;
}
virtual HRESULT SetExtendClientArea (bool enable) override
{
_isClientAreaExtended = enable;
@ -2096,7 +2116,6 @@ private:
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
virtual NSWindowStyleMask GetStyle() override
{
@ -2114,6 +2133,11 @@ protected:
return S_OK;
}
}
public:
virtual bool ShouldTakeFocusOnShow() override
{
return false;
}
};
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>
<PackagePath>build\</PackagePath>
</Content>
<Content Include="AvaloniaItemSchema.xaml">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
</Content>
</ItemGroup>
<Import Project="..\..\build\SharedVersion.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">
<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>

22
packages/Avalonia/AvaloniaBuildTasks.targets

@ -4,6 +4,20 @@
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
<AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
</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"
AssemblyFile="$(AvaloniaBuildTasksLocation)"
@ -31,9 +45,12 @@
<Target Name="GenerateAvaloniaResources"
BeforeTargets="CoreCompile;CoreResGen"
Inputs="@(AvaloniaResource);$(MSBuildAllProjects)"
Inputs="@(AvaloniaResource);@(AvaloniaXaml);$(MSBuildAllProjects)"
Outputs="$(AvaloniaResourcesTemporaryFilePath)"
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)">
<ItemGroup>
<AvaloniaResource Include="@(AvaloniaXaml)"/>
</ItemGroup>
<GenerateAvaloniaResourcesTask
Condition="'$(_AvaloniaUseExternalMSBuild)' != 'true'"
Output="$(AvaloniaResourcesTemporaryFilePath)"
@ -79,5 +96,6 @@
<ItemGroup>
<UpToDateCheckInput Include="@(AvaloniaResource)" />
<UpToDateCheckInput Include="@(AvaloniaXaml)" />
</ItemGroup>
</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
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>
### Backers

9
samples/interop/NativeEmbedSample/MainWindow.xaml

@ -20,7 +20,16 @@
<DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</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"/>
</DockPanel>
<Grid ColumnDefinitions="*,5,*">
<DockPanel>

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

@ -14,7 +14,7 @@ namespace Avalonia.Threading
private readonly DispatcherPriority _priority;
private TimeSpan _interval;
/// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary>
@ -154,6 +154,8 @@ namespace Avalonia.Threading
TimeSpan interval,
DispatcherPriority priority = DispatcherPriority.Normal)
{
interval = (interval != TimeSpan.Zero) ? interval : TimeSpan.FromTicks(1);
var timer = new DispatcherTimer(priority) { Interval = interval };
timer.Tick += (s, e) =>
@ -197,7 +199,7 @@ namespace Avalonia.Threading
}
}
/// <summary>
/// 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())
{
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;
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);
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,
"XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
var resources = BuildResourceSources();

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

@ -25,7 +25,8 @@ namespace Avalonia.Build.Tasks
public static partial class XamlCompilerTaskExecutor
{
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
{

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

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

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

@ -18,7 +18,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
EnsureInitialized();
ApplyTemplate();
LayoutManager.ExecuteInitialLayoutPass(this);
LayoutManager.ExecuteInitialLayoutPass();
}
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.LogicalTree;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -12,14 +14,18 @@ namespace Avalonia.Controls
private INativeControlHostControlTopLevelAttachment _attachment;
private IPlatformHandle _nativeControlHandle;
private bool _queuedForDestruction;
private bool _queuedForMoveResize;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
static NativeControlHost()
{
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
}
private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost();
public NativeControlHost()
{
_propertyChangedHandler = PropertyChangedHandler;
}
private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost();
@ -27,21 +33,46 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_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();
}
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.IsEffectiveValueChange && e.Property == BoundsProperty)
EnqueueForMoveResize();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = null;
if (_propertyChangedSubscriptions != null)
{
foreach (var v in _propertyChangedSubscriptions)
v.PropertyChanged -= _propertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
UpdateHost();
}
void UpdateHost()
private void UpdateHost()
{
_queuedForMoveResize = false;
_currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
var needsAttachment = _currentHost != null;
var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
if (needsAttachment)
{
@ -93,22 +124,46 @@ namespace Avalonia.Controls
}
}
if (needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value);
else if (needsAttachment)
_attachment?.Hide();
if (_attachment?.AttachedTo != _currentHost)
return;
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()
{
var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue;
if (_currentHost == null)
return false;
var bounds = GetAbsoluteBounds();
var needsShow = IsEffectivelyVisible && bounds.HasValue;
if(needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value);
return needsShow;
if (needsShow)
_attachment?.ShowInBounds(bounds.Value);
else
_attachment?.HideWithSize(Bounds.Size);
return false;
}
void CheckDestruction()
private void CheckDestruction()
{
_queuedForDestruction = false;
if (_currentRoot == null)
@ -117,10 +172,12 @@ namespace Avalonia.Controls
protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
if (_currentHost == null)
throw new InvalidOperationException();
return _currentHost.CreateDefaultChild(parent);
}
void DestroyNativeControl()
private void DestroyNativeControl()
{
if (_nativeControlHandle != null)
{

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

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

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

@ -49,8 +49,8 @@ namespace Avalonia.Controls
// For non-virtualizing layouts, we do not need to keep
// updating viewports and invalidating measure often. So when
// a non virtualizing layout is used, we stop doing all that work.
bool _managingViewportDisabled;
private IDisposable _effectiveViewportChangedRevoker;
private bool _managingViewportDisabled;
private bool _effectiveViewportChangedSubscribed;
private bool _layoutUpdatedSubscribed;
public ViewportManager(ItemsRepeater owner)
@ -228,11 +228,15 @@ namespace Avalonia.Controls
_pendingViewportShift = default;
_unshiftableShift = default;
_effectiveViewportChangedRevoker?.Dispose();
if (!_managingViewportDisabled)
if (_managingViewportDisabled && _effectiveViewportChangedSubscribed)
{
_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.
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.
foreach (var child in _owner.Children)
{
@ -373,7 +382,7 @@ namespace Avalonia.Controls
if (parent == null)
{
throw new InvalidOperationException("OnBringIntoViewRequested called with args.target element not under the ItemsRepeater that recieved the call");
return null;
}
return targetChild;
@ -415,15 +424,15 @@ namespace Avalonia.Controls
_scroller = null;
}
_effectiveViewportChangedRevoker?.Dispose();
_effectiveViewportChangedRevoker = null;
_owner.EffectiveViewportChanged -= OnEffectiveViewportChanged;
_effectiveViewportChangedSubscribed = 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);
UpdateViewport(effectiveViewport);
UpdateViewport(e.EffectiveViewport);
_pendingViewportShift = default;
_unshiftableShift = default;
@ -468,8 +477,8 @@ namespace Avalonia.Controls
}
else if (!_managingViewportDisabled)
{
_effectiveViewportChangedRevoker?.Dispose();
_effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner);
_owner.EffectiveViewportChanged += OnEffectiveViewportChanged;
_effectiveViewportChangedSubscribed = 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
{
public ScrollerInfo(ScrollViewer scroller)

2
src/Avalonia.Controls/SelectionModel.cs

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

9
src/Avalonia.Controls/TopLevel.cs

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

4
src/Avalonia.Controls/Window.cs

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

2
src/Avalonia.Controls/WindowBase.cs

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

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>
/// Manages measuring and arranging of controls.
/// </summary>
public interface ILayoutManager
public interface ILayoutManager : IDisposable
{
/// <summary>
/// Raised when the layout manager completes a layout pass.
@ -35,6 +35,15 @@ namespace Avalonia.Layout
/// </remarks>
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>
/// Executes the initial layout pass on a layout root.
/// </summary>
@ -43,6 +52,19 @@ namespace Avalonia.Layout
/// 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>
[Obsolete("Call ExecuteInitialLayoutPass without parameter")]
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>
/// <param name="control">The child control.</param>
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.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.VisualTree;
#nullable enable
@ -10,16 +13,21 @@ namespace Avalonia.Layout
/// <summary>
/// Manages measuring and arranging of controls.
/// </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> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
private readonly Action _executeLayoutPass;
private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners;
private bool _disposed;
private bool _queued;
private bool _running;
public LayoutManager()
public LayoutManager(ILayoutRoot owner)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
_executeLayoutPass = ExecuteLayoutPass;
}
@ -31,6 +39,11 @@ namespace Avalonia.Layout
control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess();
if (_disposed)
{
return;
}
if (!control.IsAttachedToVisualTree)
{
#if DEBUG
@ -41,6 +54,11 @@ namespace Avalonia.Layout
#endif
}
if (control.VisualRoot != _owner)
{
throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager.");
}
_toMeasure.Enqueue(control);
_toArrange.Enqueue(control);
QueueLayoutPass();
@ -52,6 +70,11 @@ namespace Avalonia.Layout
control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess();
if (_disposed)
{
return;
}
if (!control.IsAttachedToVisualTree)
{
#if DEBUG
@ -62,6 +85,11 @@ namespace Avalonia.Layout
#endif
}
if (control.VisualRoot != _owner)
{
throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager.");
}
_toArrange.Enqueue(control);
QueueLayoutPass();
}
@ -69,14 +97,15 @@ namespace Avalonia.Layout
/// <inheritdoc/>
public virtual void ExecuteLayoutPass()
{
const int MaxPasses = 3;
Dispatcher.UIThread.VerifyAccess();
if (!_running)
if (_disposed)
{
_running = true;
return;
}
if (!_running)
{
Stopwatch? stopwatch = null;
const LogEventLevel timingLogLevel = LogEventLevel.Information;
@ -99,12 +128,13 @@ namespace Avalonia.Layout
try
{
_running = true;
for (var pass = 0; pass < MaxPasses; ++pass)
{
ExecuteMeasurePass();
ExecuteArrangePass();
InnerLayoutPass();
if (_toMeasure.Count == 0)
if (!RaiseEffectiveViewportChanged())
{
break;
}
@ -131,13 +161,18 @@ namespace Avalonia.Layout
}
/// <inheritdoc/>
public virtual void ExecuteInitialLayoutPass(ILayoutRoot root)
public virtual void ExecuteInitialLayoutPass()
{
if (_disposed)
{
return;
}
try
{
_running = true;
Measure(root);
Arrange(root);
Measure(_owner);
Arrange(_owner);
}
finally
{
@ -151,6 +186,60 @@ namespace Avalonia.Layout
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()
{
while (_toMeasure.Count > 0)
@ -234,5 +323,97 @@ namespace Avalonia.Layout
_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
{
internal class LayoutQueue<T> : IReadOnlyCollection<T>
internal class LayoutQueue<T> : IReadOnlyCollection<T>, IDisposable
{
private struct Info
{
@ -84,5 +84,12 @@ namespace Avalonia.Layout
_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 Size? _previousMeasure;
private Rect? _previousArrange;
private EventHandler<EffectiveViewportChangedEventArgs>? _effectiveViewportChanged;
private EventHandler? _layoutUpdated;
/// <summary>
@ -152,6 +153,32 @@ namespace Avalonia.Layout
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>
/// Occurs when a layout pass completes for the control.
/// </summary>
@ -384,13 +411,6 @@ namespace Avalonia.Layout
}
}
/// <summary>
/// Called by InvalidateMeasure
/// </summary>
protected virtual void OnMeasureInvalidated()
{
}
/// <summary>
/// Invalidates the measurement of the control and queues a new layout pass.
/// </summary>
@ -436,6 +456,11 @@ namespace Avalonia.Layout
}
}
void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e)
{
_effectiveViewportChanged?.Invoke(this, e);
}
/// <summary>
/// Marks a property as affecting the control's measurement.
/// </summary>
@ -717,9 +742,17 @@ namespace Avalonia.Layout
{
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);
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/>
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 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)
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),
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)

12
src/Avalonia.Native/PopupImpl.cs

@ -10,6 +10,7 @@ namespace Avalonia.Native
private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts;
private readonly GlPlatformFeature _glFeature;
private readonly IWindowBaseImpl _parent;
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
@ -19,6 +20,7 @@ namespace Avalonia.Native
_factory = factory;
_opts = opts;
_glFeature = glFeature;
_parent = parent;
using (var e = new PopupEvents(this))
{
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 void SetWindowManagerAddShadowHint(bool enabled)

2
src/Avalonia.Native/WindowImplBase.cs

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

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

@ -2,35 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAltHighColor">#FF000000</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>
<Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color>
<Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color>
<!--<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" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@ -67,13 +40,6 @@
<Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
<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>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />

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

@ -2,36 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAltHighColor">#FFFFFFFF</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>
<Color x:Key="SystemRevealListLowColor">#17000000</Color>
<Color x:Key="SystemRevealListMediumColor">#2E000000</Color>
<!--<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" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@ -68,13 +40,6 @@
<Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
<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}" />-->
<Color x:Key="RegionColor">#FFFFFFFF</Color>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />

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

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

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

@ -1,6 +1,5 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Media.Immutable;
@ -11,9 +10,9 @@ namespace Avalonia.Animation.Animators
/// </summary>
public class SolidColorBrushAnimator : Animator<SolidColorBrush>
{
ColorAnimator _colorAnimator;
private ColorAnimator _colorAnimator;
void InitializeColorAnimator()
private void InitializeColorAnimator()
{
_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)
{
// Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
foreach (var keyframe in this)
{
if (keyframe.Value as ISolidColorBrush == null)
return Disposable.Empty;
// Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush))
if (keyframe.Value is ISolidColorBrush colorBrush)
{
keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color;
keyframe.Value = colorBrush.Color;
}
else
{
return Disposable.Empty;
}
}
// Add SCB if the target prop is empty.
if (control.GetValue(Property) == null)
control.SetValue(Property, new SolidColorBrush(Colors.Transparent));
SolidColorBrush finalTarget;
var targetVal = control.GetValue(Property);
// Continue if target prop is not empty & is a SolidColorBrush derivative.
if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType()))
if (targetVal is null)
{
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;
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;

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
{
LeftToRight, //L
ArabicLetter, //AL
ArabicNumber, //AN
ParagraphSeparator, //B
@ -11,7 +12,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
EuropeanSeparator, //ES
EuropeanTerminator, //ET
FirstStrongIsolate, //FSI
LeftToRight, //L
LeftToRightEmbedding, //LRE
LeftToRightIsolate, //LRI
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 =
{
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[] {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,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,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},
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[] {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,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,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,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,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,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,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},
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[] {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,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,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,0,0,0,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,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[] {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,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[] {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,1,1,1,1,1,1,4,2,4,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,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,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,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,0,1,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,1,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,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,0,1},
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[] {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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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)

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

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

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

@ -34,10 +34,11 @@ namespace Avalonia.Media.TextFormatting.Unicode
EBase, //EB
EModifier, //EM
ZWJ, //ZWJ
ContingentBreak, //CB
Unknown, //XX
Ambiguous, //AI
MandatoryBreak, //BK
ContingentBreak, //CB
ConditionalJapaneseStarter, //CJ
CarriageReturn, //CR
LineFeed, //LF
@ -45,6 +46,5 @@ namespace Avalonia.Media.TextFormatting.Unicode
ComplexContext, //SA
Surrogate, //SG
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)
{
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos);
_lastPos = _pos;
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
return true;
}
@ -108,6 +109,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
case PairBreakType.DI: // Direct break
shouldBreak = true;
_lastPos = _pos;
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
{
Unknown, //Zzzz
Common, //Zyyy
Inherited, //Zinh
Adlam, //Adlm
CaucasianAlbanian, //Aghb
Ahom, //Ahom
@ -25,10 +28,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
Carian, //Cari
Cham, //Cham
Cherokee, //Cher
Chorasmian, //Chrs
Coptic, //Copt
Cypriot, //Cprt
Cyrillic, //Cyrl
Devanagari, //Deva
DivesAkuru, //Diak
Dogra, //Dogr
Deseret, //Dsrt
Duployan, //Dupl
@ -63,6 +68,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
Kharoshthi, //Khar
Khmer, //Khmr
Khojki, //Khoj
KhitanSmallScript, //Kits
Kannada, //Knda
Kaithi, //Kthi
TaiTham, //Lana
@ -151,10 +157,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
Wancho, //Wcho
OldPersian, //Xpeo
Cuneiform, //Xsux
Yezidi, //Yezi
Yi, //Yiii
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.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>
/// 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 void Hide()
public void HideWithSize(Size size)
{
if(_attachedTo == null || _child == null)
return;
_mapped = false;
XUnmapWindow(_display, _holder.Handle);
if (_mapped)
{
_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();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
bounds *= _attachedTo.Window.Scaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));
XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height);
@ -183,7 +192,6 @@ namespace Avalonia.X11
XRaiseWindow(_display, _holder.Handle);
_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 void Hide()
public void HideWithSize(Size size)
{
UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero,
-100, -100, 1, 1,
UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW |
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();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
bounds *= _attachedTo.Window.Scaling;
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
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.Linq;
using Avalonia.Controls.Platform;
using Avalonia.Logging;
namespace Avalonia.Win32
{
internal class WindowsMountedVolumeInfoListener : IDisposable
{
private readonly CompositeDisposable _disposables;
private readonly CompositeDisposable _disposables;
private bool _beenDisposed = false;
private ObservableCollection<MountedVolumeInfo> mountedDrives;
@ -32,10 +33,22 @@ namespace Avalonia.Win32
var allDrives = DriveInfo.GetDrives();
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()
{
VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName
VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName
: $"{p.VolumeLabel} ({p.Name})",
VolumePath = p.RootDirectory.FullName,
VolumeSizeBytes = (ulong)p.TotalSize

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

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

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

@ -25,7 +25,7 @@ namespace Avalonia.Benchmarks.Layout
_controls.Add(panel);
_controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 5);
_root.LayoutManager.ExecuteInitialLayoutPass(_root);
_root.LayoutManager.ExecuteInitialLayoutPass();
}
[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();
_root.LayoutManager.ExecuteInitialLayoutPass(_root);
_root.LayoutManager.ExecuteInitialLayoutPass();
}
[Benchmark]

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

@ -1194,7 +1194,7 @@ namespace Avalonia.Controls.UnitTests
Height = 50,
};
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
PrintColumnDefinitions(grids[0]);
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;
}, true);
lm.ExecuteInitialLayoutPass(wnd);
lm.ExecuteInitialLayoutPass();
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;
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
// 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
{
public TestScroller()
{
LayoutManager = new LayoutManager(this);
}
public IRenderer Renderer { get; }
public Size ClientSize { get; }
public double RenderScaling => 1;
@ -332,7 +337,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; } = new LayoutManager();
public ILayoutManager LayoutManager { get; }
public IRenderTarget CreateRenderTarget() => 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;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var last = (target.Items as IList)[10];
@ -740,7 +740,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var last = (target.Items as IList)[10];
@ -838,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[5];
var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -855,7 +855,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[9];
var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -874,7 +874,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 100;
scroller.Height = 95;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[8];
var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -893,7 +893,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 100;
scroller.Height = 95;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
((ILogicalScrollable)target).Offset = new Vector(0, 11);
var from = target.Panel.Children[1];
@ -946,7 +946,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[5];
var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -963,7 +963,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var scroller = (TestScroller)target.Parent;
scroller.Width = scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[9];
var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -982,7 +982,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 95;
scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
var from = target.Panel.Children[8];
var result = ((ILogicalScrollable)target).GetControlInDirection(
@ -1001,7 +1001,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
scroller.Width = 95;
scroller.Height = 100;
scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
scroller.LayoutManager.ExecuteInitialLayoutPass();
((ILogicalScrollable)target).Offset = new Vector(11, 0);
var from = target.Panel.Children[1];
@ -1062,6 +1062,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, ILogicalRoot
{
public TestScroller()
{
LayoutManager = new LayoutManager(this);
}
public IRenderer Renderer { get; }
public Size ClientSize { get; }
public double RenderScaling => 1;
@ -1070,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public double LayoutScaling => 1;
public ILayoutManager LayoutManager { get; } = new LayoutManager();
public ILayoutManager LayoutManager { get; }
public IRenderTarget CreateRenderTarget() => 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.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
target.ScrollChanged += (s, e) =>
{
@ -188,7 +188,7 @@ namespace Avalonia.Controls.UnitTests
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
target.ScrollChanged += (s, e) =>
{
@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
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);
}
@ -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]
public void Reacts_To_Changes_In_Global_Styles()
{
@ -282,7 +299,7 @@ namespace Avalonia.Controls.UnitTests
Content = child,
};
target.LayoutManager.ExecuteInitialLayoutPass(target);
target.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Thickness(0), child.BorderThickness);
@ -295,7 +312,7 @@ namespace Avalonia.Controls.UnitTests
};
Application.Current.Styles.Add(style);
target.LayoutManager.ExecuteInitialLayoutPass(target);
target.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Thickness(2), child.BorderThickness);
@ -323,7 +340,7 @@ namespace Avalonia.Controls.UnitTests
public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
: base(impl)
{
_layoutManager = layoutManager ?? new LayoutManager();
_layoutManager = layoutManager ?? new LayoutManager(this);
}
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 root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false;
control.InvalidateMeasure();
@ -23,13 +23,29 @@ namespace Avalonia.Layout.UnitTests
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]
public void Arranges_InvalidateArranged_Control()
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false;
control.InvalidateArrange();
@ -45,7 +61,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl();
var root = new LayoutTestRoot();
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
root.Child = control;
root.Measured = root.Arranged = false;
@ -80,7 +96,7 @@ namespace Avalonia.Layout.UnitTests
root.DoMeasureOverride = MeasureOverride;
control1.DoMeasureOverride = MeasureOverride;
control2.DoMeasureOverride = MeasureOverride;
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control2.InvalidateMeasure();
control1.InvalidateMeasure();
@ -115,7 +131,7 @@ namespace Avalonia.Layout.UnitTests
root.DoMeasureOverride = MeasureOverride;
control1.DoMeasureOverride = MeasureOverride;
control2.DoMeasureOverride = MeasureOverride;
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control2.InvalidateMeasure();
root.InvalidateMeasure();
@ -132,7 +148,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
root.Measured = root.Arranged = false;
control.Measured = control.Arranged = false;
@ -151,7 +167,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = control.Arranged = false;
control.InvalidateMeasure();
@ -177,7 +193,7 @@ namespace Avalonia.Layout.UnitTests
return new Size(100, 100);
};
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(Size.Infinity, availableSize);
}
@ -199,7 +215,7 @@ namespace Avalonia.Layout.UnitTests
return s;
};
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
Assert.Equal(new Size(100, 100), arrangeSize);
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);
border.Width = 100;
@ -241,7 +257,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = false;
int cnt = 0;
@ -272,7 +288,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control.Arranged = false;
int cnt = 0;
@ -313,7 +329,7 @@ namespace Avalonia.Layout.UnitTests
panel.Children.AddRange(nonArrageableTargets);
panel.Children.AddRange(targets);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
foreach (var c in panel.Children.OfType<LayoutTestControl>())
{
@ -347,7 +363,7 @@ namespace Avalonia.Layout.UnitTests
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = false;
control.DoMeasureOverride = (l, s) =>
@ -380,7 +396,7 @@ namespace Avalonia.Layout.UnitTests
var root = new LayoutTestRoot { Child = control };
var count = 0;
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
control.Measured = false;
control.DoMeasureOverride = (l, s) =>
@ -399,7 +415,7 @@ namespace Avalonia.Layout.UnitTests
root.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.DesiredSize);

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

@ -208,14 +208,12 @@ namespace Avalonia.Layout.UnitTests
{
Border border1;
Border border2;
var layoutManager = new LayoutManager();
var root = new TestRoot
{
Child = border1 = new Border
{
Child = border2 = new Border(),
},
LayoutManager = layoutManager,
};
var raised = 0;
@ -233,7 +231,7 @@ namespace Avalonia.Layout.UnitTests
root.Measure(new Size(100, 100));
root.Arrange(new Rect(0, 0, 100, 100));
layoutManager.ExecuteLayoutPass();
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(3, raised);
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();
// 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);
// Clear the content and ensure the Canvas is removed.
@ -82,7 +82,7 @@ namespace Avalonia.LeakTests
window.Show();
// 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.Presenter.Child);
@ -122,7 +122,7 @@ namespace Avalonia.LeakTests
// Do a layout and make sure that ScrollViewer gets added to visual tree and its
// template applied.
window.LayoutManager.ExecuteInitialLayoutPass(window);
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<ScrollViewer>(window.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
// template applied.
window.LayoutManager.ExecuteInitialLayoutPass(window);
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<TextBox>(window.Presenter.Child);
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
// Text property set.
window.LayoutManager.ExecuteInitialLayoutPass(window);
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<TextBox>(window.Presenter.Child);
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
// template applied.
window.LayoutManager.ExecuteInitialLayoutPass(window);
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.Same(textBox, window.Presenter.Child);
// Get the border from the TextBox template.
@ -295,7 +295,7 @@ namespace Avalonia.LeakTests
window.Show();
// Do a layout and make sure that TreeViewItems get realized.
window.LayoutManager.ExecuteInitialLayoutPass(window);
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.Single(target.ItemContainerGenerator.Containers);
// Clear the content and ensure the TreeView is removed.
@ -329,7 +329,7 @@ namespace Avalonia.LeakTests
window.Show();
// 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);
// 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
// its render transform.
window.LayoutManager.ExecuteInitialLayoutPass(window);
window.LayoutManager.ExecuteInitialLayoutPass();
var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
Assert.IsType<RotateTransform>(canvas.RenderTransform);
@ -514,7 +514,7 @@ namespace Avalonia.LeakTests
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window);
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<Path>(window.Presenter.Child);
window.Content = null;

4
tests/Avalonia.UnitTests/TestRoot.cs

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

3
tests/Avalonia.UnitTests/TestTemplatedRoot.cs

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

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

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using Avalonia.Media.TextFormatting.Unicode;
@ -12,6 +11,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
public static void Execute()
{
if (!Directory.Exists("Generated"))
{
Directory.CreateDirectory("Generated");
}
using (var stream = File.Create("Generated\\GraphemeBreak.trie"))
{
var trie = GenerateBreakTypeTrie();
@ -22,48 +26,29 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
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 graphemeBreakData = ReadBreakData(
"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 graphemeBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakProperty.txt"));
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)
{
trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
}
else
{
trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
if (start == end)
{
trieBuilder.Set(start, (uint)value);
}
else
{
trieBuilder.SetRange(start, end, (uint)value);
}
}
}
@ -113,7 +98,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
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.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using Avalonia.Media.TextFormatting.Unicode;
using Xunit;
@ -16,10 +11,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public class GraphemeBreakClassTrieGeneratorTests
{
[Theory(Skip = "Only run when we update the trie.")]
[ClassData(typeof(GraphemeEnumeratorTestDataGenerator))]
[ClassData(typeof(GraphemeBreakTestDataGenerator))]
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());
@ -31,7 +28,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
const string text = "ABCDEFGHIJ";
var enumerator = new GraphemeEnumerator(text.AsMemory());
var textMemory = text.AsMemory();
var enumerator = new GraphemeEnumerator(textMemory);
var count = 0;
@ -51,73 +50,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
GraphemeBreakClassTrieGenerator.Execute();
}
public class GraphemeEnumeratorTestDataGenerator : IEnumerable<object[]>
private class GraphemeBreakTestDataGenerator : TestDataGenerator
{
private readonly List<object[]> _testData;
public GraphemeEnumeratorTestDataGenerator()
{
_testData = ReadTestData();
}
public IEnumerator<object[]> GetEnumerator()
{
return _testData.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private static List<object[]> ReadTestData()
public GraphemeBreakTestDataGenerator()
: base("auxiliary/GraphemeBreakTest.txt")
{
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 Xunit;
namespace Avalonia.Visuals.UnitTests.Media.Text
namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
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
{
public const string Ucd = "https://www.unicode.org/Public/13.0.0/ucd/";
public static void Execute()
{
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();
@ -26,23 +29,23 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddGeneralCategoryRange(codepoints, range, generalCategory);
}
var scriptValues = UnicodeEnumsGenerator.CreateScriptEnum();
var scriptEntries = UnicodeEnumsGenerator.CreateScriptEnum();
var scriptMappings = CreateNameToIndexMappings(scriptValues);
var scriptMappings = CreateNameToIndexMappings(scriptEntries);
var scriptData = ReadScriptData();
foreach (var (range, name) in scriptData)
{
var script = scriptMappings[name.Replace("_", "")];
var script = scriptMappings[name];
AddScriptRange(codepoints, range, script);
}
var biDiClassValues = UnicodeEnumsGenerator.CreateBiDiClassEnum();
var biDiClassEntries =
UnicodeEnumsGenerator.CreateBiDiClassEnum();
var biDiClassMappings = CreateTagToIndexMappings(biDiClassValues);
var biDiClassMappings = CreateTagToIndexMappings(biDiClassEntries);
var biDiData = ReadBiDiData();
@ -53,9 +56,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddBiDiClassRange(codepoints, range, biDiClass);
}
var lineBreakClassValues = UnicodeEnumsGenerator.CreateLineBreakClassEnum();
var lineBreakClassEntries =
UnicodeEnumsGenerator.CreateLineBreakClassEnum();
var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassValues);
var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassEntries);
var lineBreakClassData = ReadLineBreakClassData();
@ -66,11 +70,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddLineBreakClassRange(codepoints, range, lineBreakClass);
}
const int initialValue = ((int)LineBreakClass.Unknown << UnicodeData.LINEBREAK_SHIFT) |
((int)BiDiClass.LeftToRight << UnicodeData.BIDI_SHIFT) |
((int)Script.Unknown << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
//const int initialValue = (0 << UnicodeData.LINEBREAK_SHIFT) |
// (0 << UnicodeData.BIDI_SHIFT) |
// (0 << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
var builder = new UnicodeTrieBuilder(initialValue);
var builder = new UnicodeTrieBuilder(/*initialValue*/);
foreach (var properties in codepoints.Values)
{
@ -88,27 +92,30 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
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>();
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;
}
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>();
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;
@ -180,24 +187,22 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public static List<(CodepointRange, string)> ReadGeneralCategoryData()
{
return ReadUnicodeData(
"https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt");
return ReadUnicodeData("extracted/DerivedGeneralCategory.txt");
}
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()
{
return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt");
return ReadUnicodeData("extracted/DerivedBidiClass.txt");
}
public static List<(CodepointRange, string)> ReadLineBreakClassData()
{
return ReadUnicodeData(
"https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedLineBreak.txt");
return ReadUnicodeData("extracted/DerivedLineBreak.txt");
}
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 result = client.GetAsync(file).GetAwaiter().GetResult())
var url = Path.Combine(Ucd, file);
using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{
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
{
@ -13,5 +15,26 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
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
{
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 writer = new StreamWriter(stream))
@ -20,22 +27,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum Script");
writer.WriteLine(" {");
foreach (var (name, tag, comment) in scriptValues)
foreach (var entry in entries)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
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 writer = new StreamWriter(stream))
@ -45,22 +54,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GeneralCategory");
writer.WriteLine(" {");
foreach (var (name, tag, comment) in generalCategoryValues)
foreach (var entry in entries)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
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 writer = new StreamWriter(stream))
@ -70,10 +81,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GraphemeBreakClass");
writer.WriteLine(" {");
foreach (var (name, tag, comment) in graphemeClusterBreakValues)
foreach (var entry in entries)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" ExtendedPictographic");
@ -82,7 +93,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine("}");
}
return graphemeClusterBreakValues;
return entries;
}
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 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];
lineBreakClassMappings.Remove(x);
return value;
}).ToList();
var entry = entries.Single(x => x.Tag == tag);
orderedLineBreakEntries.Add(tag, entry);
}
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 writer = new StreamWriter(stream))
@ -208,32 +231,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum LineBreakClass");
writer.WriteLine(" {");
foreach (var (name, tag, comment) in orderedLineBreakValues)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
}
writer.WriteLine();
foreach (var (name, tag, comment) in lineBreakClassMappings.Values)
foreach (var entry in orderedLineBreakEntries.Values)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" }");
writer.WriteLine("}");
}
orderedLineBreakValues.AddRange(lineBreakClassMappings.Values);
return orderedLineBreakValues;
return orderedLineBreakEntries.Values.ToList();
}
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 writer = new StreamWriter(stream))
@ -243,23 +258,21 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum BiDiClass");
writer.WriteLine(" {");
foreach (var (name, tag, comment) in biDiClassValues)
foreach (var entry in entries)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
}
writer.WriteLine(" }");
writer.WriteLine("}");
}
return biDiClassValues;
return entries;
}
public static void CreatePropertyValueAliasHelper(List<(string name, string tag, string comment)> scriptValues,
List<(string name, string tag, string comment)> generalCategoryValues,
List<(string name, string tag, string comment)> biDiClassValues,
List<(string name, string tag, string comment)> lineBreakValues)
public static void CreatePropertyValueAliasHelper(List<DataEntry> scriptEntries, IEnumerable<DataEntry> generalCategoryEntries,
IEnumerable<DataEntry> biDiClassEntries, IEnumerable<DataEntry> lineBreakClassEntries)
{
using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs"))
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("{");
writer.WriteLine(" public static class PropertyValueAliasHelper");
writer.WriteLine(" internal static class PropertyValueAliasHelper");
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("}");
}
}
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 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)
{
return data;
return;
}
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
@ -337,7 +350,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
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;
@ -346,24 +364,25 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
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,
IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue)
private static void WritePropertyValueAliasGetTag(TextWriter writer, IEnumerable<DataEntry> entries,
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>{{");
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(" };");
@ -382,15 +401,15 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine();
}
private static void WritePropertyValueAlias(TextWriter writer,
IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue)
private static void WritePropertyValueAlias(TextWriter writer, IEnumerable<DataEntry> entries, string typeName,
string defaultValue)
{
writer.WriteLine($" private static readonly Dictionary<string, {typeName}> s_tagTo{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(" };");
@ -409,4 +428,18 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
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);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
root.Measure(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);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
root.Measure(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);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
var rootSize = new Size(RootWidth, RootHeight);
root.Measure(rootSize);
@ -277,7 +277,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var root = new TestRoot(rootGrid);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
root.LayoutManager.ExecuteInitialLayoutPass();
var rootSize = new Size(RootWidth, RootHeight);
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;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
@ -696,7 +696,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
@ -744,7 +744,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var scene = new Scene(tree);
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;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -105,7 +105,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -147,7 +147,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -197,7 +197,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
@ -241,7 +241,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
var layout = tree.LayoutManager;
layout.ExecuteInitialLayoutPass(tree);
layout.ExecuteInitialLayoutPass();
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);

Loading…
Cancel
Save