Browse Source

Merge branch 'master' into InputTimeStampMacOSFix

pull/11096/head
Sergey Mikolaytis 3 years ago
committed by GitHub
parent
commit
62cc3994cf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      .editorconfig
  2. 6
      Avalonia.Desktop.slnf
  3. 24
      Avalonia.sln
  4. 2
      build/DevAnalyzers.props
  5. 1
      nukebuild/Build.cs
  6. 24
      nukebuild/RefAssemblyGenerator.cs
  7. 5
      nukebuild/numerge.config
  8. 21
      packages/Avalonia/Avalonia.csproj
  9. 2
      packages/Avalonia/Avalonia.props
  10. 2
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  11. 6
      samples/ControlCatalog.NetCore/Properties/launchSettings.json
  12. 2
      samples/ControlCatalog/App.xaml
  13. 48
      samples/ControlCatalog/MainView.xaml.cs
  14. 11
      samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
  15. 1
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  16. 2
      samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj
  17. 4
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  18. 2
      samples/RenderDemo/Pages/WriteableBitmapPage.cs
  19. 10
      samples/Sandbox/MainWindow.axaml.cs
  20. 2
      samples/Sandbox/Sandbox.csproj
  21. 12
      src/Avalonia.Base/Animation/Animation.cs
  22. 2
      src/Avalonia.Base/ClassBindingManager.cs
  23. 2
      src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs
  24. 2
      src/Avalonia.Base/Controls/IResourceDictionary.cs
  25. 21
      src/Avalonia.Base/Controls/IThemeVariantProvider.cs
  26. 12
      src/Avalonia.Base/Controls/ResourceDictionary.cs
  27. 46
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  28. 13
      src/Avalonia.Base/Input/Cursor.cs
  29. 3
      src/Avalonia.Base/Input/DragEventArgs.cs
  30. 3
      src/Avalonia.Base/Input/PointerDeltaEventArgs.cs
  31. 12
      src/Avalonia.Base/Input/PointerEventArgs.cs
  32. 3
      src/Avalonia.Base/Input/PointerWheelEventArgs.cs
  33. 10
      src/Avalonia.Base/Layout/LayoutManager.cs
  34. 18
      src/Avalonia.Base/Layout/LayoutQueue.cs
  35. 40
      src/Avalonia.Base/Layout/Layoutable.cs
  36. 11
      src/Avalonia.Base/Media/Color.cs
  37. 2
      src/Avalonia.Base/Media/DashStyle.cs
  38. 2
      src/Avalonia.Base/Media/GeometryDrawing.cs
  39. 2
      src/Avalonia.Base/Media/GradientBrush.cs
  40. 36
      src/Avalonia.Base/Media/HslColor.cs
  41. 1
      src/Avalonia.Base/Media/ImmediateDrawingContext.cs
  42. 16
      src/Avalonia.Base/Media/PlatformDrawingContext.cs
  43. 2
      src/Avalonia.Base/Media/PolyLineSegment.cs
  44. 2
      src/Avalonia.Base/Media/TransformGroup.cs
  45. 23
      src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs
  46. 22
      src/Avalonia.Base/Metadata/UnstableAttribute.cs
  47. 295
      src/Avalonia.Base/Platform/AssetLoader.cs
  48. 2
      src/Avalonia.Base/Platform/IAssetLoader.cs
  49. 7
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  50. 255
      src/Avalonia.Base/Platform/StandardAssetLoader.cs
  51. 2
      src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs
  52. 27
      src/Avalonia.Base/Platform/Storage/NoopStorageProvider.cs
  53. 5
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  54. 14
      src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs
  55. 1
      src/Avalonia.Base/StyledElement.cs
  56. 1
      src/Avalonia.Base/Styling/IThemeVariantHost.cs
  57. 4
      src/Avalonia.Base/Styling/ThemeVariant.cs
  58. 47
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  59. 1
      src/Avalonia.Base/Visual.cs
  60. 39
      src/Avalonia.Controls/AppBuilder.cs
  61. 2
      src/Avalonia.Controls/Application.cs
  62. 2
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  63. 2
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs
  64. 6
      src/Avalonia.Controls/Avalonia.Controls.csproj
  65. 2
      src/Avalonia.Controls/ColumnDefinition.cs
  66. 2
      src/Avalonia.Controls/ContextMenu.cs
  67. 12
      src/Avalonia.Controls/DefinitionBase.cs
  68. 2
      src/Avalonia.Controls/DefinitionList.cs
  69. 2
      src/Avalonia.Controls/Documents/Span.cs
  70. 2
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  71. 43
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  72. 4
      src/Avalonia.Controls/LayoutTransformControl.cs
  73. 8
      src/Avalonia.Controls/ListBox.cs
  74. 6
      src/Avalonia.Controls/MenuItem.cs
  75. 3
      src/Avalonia.Controls/Platform/ScreenHelper.cs
  76. 9
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  77. 5
      src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs
  78. 2
      src/Avalonia.Controls/Primitives/UniformGrid.cs
  79. 3
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  80. 95
      src/Avalonia.Controls/ProgressBar.cs
  81. 1
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  82. 4
      src/Avalonia.Controls/RowDefinition.cs
  83. 40
      src/Avalonia.Controls/Screens.cs
  84. 6
      src/Avalonia.Controls/SplitView/SplitView.cs
  85. 6
      src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs
  86. 2
      src/Avalonia.Controls/TabControl.cs
  87. 4
      src/Avalonia.Controls/TabItem.cs
  88. 2
      src/Avalonia.Controls/ToggleSwitch.cs
  89. 17
      src/Avalonia.Controls/TopLevel.cs
  90. 6
      src/Avalonia.Controls/TreeViewItem.cs
  91. 28
      src/Avalonia.Controls/Utils/RealizedStackElements.cs
  92. 9
      src/Avalonia.Controls/VirtualizingCarouselPanel.cs
  93. 5
      src/Avalonia.Controls/VirtualizingPanel.cs
  94. 48
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  95. 6
      src/Avalonia.Controls/Window.cs
  96. 28
      src/Avalonia.Controls/WindowBase.cs
  97. 12
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  98. 4
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  99. 107
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  100. 14
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

17
.editorconfig

@ -186,6 +186,23 @@ csharp_wrap_before_ternary_opsigns = false
# Avalonia DevAnalyzer preferences
dotnet_diagnostic.AVADEV2001.severity = error
# Avalonia PublicAnalyzer preferences
dotnet_diagnostic.AVP1000.severity = error
dotnet_diagnostic.AVP1001.severity = error
dotnet_diagnostic.AVP1002.severity = error
dotnet_diagnostic.AVP1010.severity = error
dotnet_diagnostic.AVP1011.severity = error
dotnet_diagnostic.AVP1012.severity = warning
dotnet_diagnostic.AVP1013.severity = error
dotnet_diagnostic.AVP1020.severity = error
dotnet_diagnostic.AVP1021.severity = error
dotnet_diagnostic.AVP1022.severity = error
dotnet_diagnostic.AVP1030.severity = error
dotnet_diagnostic.AVP1031.severity = error
dotnet_diagnostic.AVP1032.severity = error
dotnet_diagnostic.AVP1040.severity = error
dotnet_diagnostic.AVA2001.severity = error
# Xaml files
[*.{xaml,axaml}]
indent_size = 2

6
Avalonia.Desktop.slnf

@ -23,8 +23,6 @@
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.Fonts.Inter\\Avalonia.Fonts.Inter.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
"src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
@ -33,6 +31,8 @@
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj",
"src\\Headless\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Headless\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
"src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
@ -65,4 +65,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
}
}

24
Avalonia.sln

@ -181,9 +181,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Headless\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.Vnc", "src\Headless\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}"
EndProject
@ -233,7 +233,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\R
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\PublicAnalyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\Avalonia.Analyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}"
ProjectSection(SolutionItems) = preProject
@ -260,6 +260,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.Desktop", "sam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.iOS", "samples\SafeAreaDemo.iOS\SafeAreaDemo.iOS.csproj", "{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -599,6 +605,14 @@ Global
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -690,6 +704,10 @@ Global
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}

2
build/DevAnalyzers.props

@ -5,7 +5,7 @@
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0"/>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\src\tools\PublicAnalyzers\Avalonia.Analyzers.csproj"
<ProjectReference Include="$(MSBuildThisFileDirectory)..\src\tools\Avalonia.Analyzers\Avalonia.Analyzers.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"

1
nukebuild/Build.cs

@ -212,6 +212,7 @@ partial class Build : NukeBuild
RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests");
RunCoreTest("Avalonia.Headless.UnitTests");
});
Target RunRenderTests => _ => _

24
nukebuild/RefAssemblyGenerator.cs

@ -96,7 +96,7 @@ public class RefAssemblyGenerator
| MethodAttributes.HideBySig, type.Module.TypeSystem.Void));
}
var forceUnstable = type.CustomAttributes.Any(a =>
var forceUnstable = type.CustomAttributes.FirstOrDefault(a =>
a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute");
foreach (var m in type.Methods)
@ -109,22 +109,28 @@ public class RefAssemblyGenerator
}
}
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, bool force)
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute unstableAttribute)
{
if (!force && (
def.HasCustomAttributes == false
|| def.CustomAttributes.All(a => a.AttributeType.FullName != "Avalonia.Metadata.UnstableAttribute")))
if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute"))
return;
if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute"))
unstableAttribute = def.CustomAttributes.FirstOrDefault(a =>
a.AttributeType.FullName == "Avalonia.Metadata.UnstableAttribute") ?? unstableAttribute;
if (unstableAttribute is null)
return;
var message = unstableAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString();
if (string.IsNullOrEmpty(message))
{
message = "This is a part of unstable API and can be changed in minor releases. Consider replacing it with alternatives or reach out developers on GitHub.";
}
def.CustomAttributes.Add(new CustomAttribute(obsoleteCtor)
{
ConstructorArguments =
{
new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String,
"This is a part of unstable API and can be changed in minor releases. You have been warned")
new CustomAttributeArgument(obsoleteCtor.Module.TypeSystem.String, message)
}
});
}
@ -168,4 +174,4 @@ public class RefAssemblyGenerator
}
}
}
}
}

5
nukebuild/numerge.config

@ -16,6 +16,11 @@
"Id": "Avalonia.Generators",
"IgnoreMissingFrameworkBinaries": true,
"DoNotMergeDependencies": true
},
{
"Id": "Avalonia.Analyzers",
"IgnoreMissingFrameworkBinaries": true,
"DoNotMergeDependencies": true
}
]
}

21
packages/Avalonia/Avalonia.csproj

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="0.0.12" />
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
<PrivateAssets>all</PrivateAssets>
@ -15,6 +16,10 @@
ReferenceOutputAssembly="false"
PrivateAssets="all"
OutputItemType="Analyzer" />
<ProjectReference Include="..\..\src\tools\Avalonia.Analyzers\Avalonia.Analyzers.csproj"
ReferenceOutputAssembly="false"
PrivateAssets="all"
OutputItemType="Analyzer" />
</ItemGroup>
<PropertyGroup>
@ -60,4 +65,20 @@
<Import Project="..\..\build\NetFX.props" />
<Import Project="..\..\build\CoreLibraries.props" />
<Import Project="..\..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
<Target Name="WriteCurrentPackageVersionProps" BeforeTargets="_GetPackageFiles">
<PropertyGroup>
<PackageVersionPropsPath>$(IntermediateOutputPath)/AvaloniaVersion.props</PackageVersionPropsPath>
</PropertyGroup>
<WriteLinesToFile
File="$(PackageVersionPropsPath)"
Overwrite="true"
Lines="&lt;Project&gt;&lt;PropertyGroup&gt;&lt;AvaloniaMainPackageVersion&gt;$(PackageVersion)&lt;/AvaloniaMainPackageVersion&gt;&lt;/PropertyGroup&gt;&lt;/Project&gt;" />
<ItemGroup>
<Content Include="$(PackageVersionPropsPath)">
<Pack>true</Pack>
<PackagePath>build</PackagePath>
</Content>
</ItemGroup>
</Target>
</Project>

2
packages/Avalonia/Avalonia.props

@ -4,9 +4,11 @@
<AvaloniaPreviewerNetFullToolPath>$(MSBuildThisFileDirectory)\..\tools\net461\designer\Avalonia.Designer.HostApp.exe</AvaloniaPreviewerNetFullToolPath>
<AvaloniaBuildTasksLocation>$(MSBuildThisFileDirectory)\..\tools\netstandard2.0\Avalonia.Build.Tasks.dll</AvaloniaBuildTasksLocation>
<AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
<UsedAvaloniaProducts>$(UsedAvaloniaProducts);AvaloniaUI</UsedAvaloniaProducts>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
<Import Project="$(MSBuildThisFileDirectory)\Avalonia.Generators.props"/>
<Import Project="$(MSBuildThisFileDirectory)..\build\AvaloniaVersion.props" />
<!-- Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package -->
<ItemGroup>

2
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -26,7 +26,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Headless\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />

6
samples/ControlCatalog.NetCore/Properties/launchSettings.json

@ -6,6 +6,10 @@
"Dxgi": {
"commandName": "Project",
"commandLineArgs": "--dxgi"
},
"VNC": {
"commandName": "Project",
"commandLineArgs": "--vnc"
}
}
}
}

2
samples/ControlCatalog/App.xaml

@ -26,8 +26,6 @@
<Color x:Key="CatalogBaseHighColor">#FFFFFFFF</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
<Color x:Key="SystemAccentColor">#FF0078D7</Color>
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<!-- Styles attached dynamically depending on current theme (simple or fluent) -->
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />

48
samples/ControlCatalog/MainView.xaml.cs

@ -19,13 +19,9 @@ namespace ControlCatalog
{
public class MainView : UserControl
{
private readonly IPlatformSettings _platformSettings;
public MainView()
{
AvaloniaXamlLoader.Load(this);
_platformSettings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
var sideBar = this.Get<TabControl>("Sidebar");
@ -141,50 +137,6 @@ namespace ControlCatalog
ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
};
}
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues());
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
}
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, 0.3);
Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, 0.5);
Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, 0.7);
static Color ChangeColorLuminosity(Color color, double luminosityFactor)
{
var red = (double)color.R;
var green = (double)color.G;
var blue = (double)color.B;
if (luminosityFactor < 0)
{
luminosityFactor = 1 + luminosityFactor;
red *= luminosityFactor;
green *= luminosityFactor;
blue *= luminosityFactor;
}
else if (luminosityFactor >= 0)
{
red = (255 - red) * luminosityFactor + red;
green = (255 - green) * luminosityFactor + green;
blue = (255 - blue) * luminosityFactor + blue;
}
return new Color(color.A, (byte)red, (byte)green, (byte)blue);
}
}
}
}

11
samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs

@ -19,18 +19,16 @@ namespace ControlCatalog.Pages
public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Scale), 1.0d);
public double Scale { get => GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); }
public static readonly StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation));
public static readonly StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation),
coerce: (_, val) => val % (Math.PI * 2));
/// <summary>
/// Rotation, measured in Radians!
/// </summary>
public double Rotation
{
get => GetValue(RotationProperty);
set
{
double valueToUse = value % (Math.PI * 2);
SetValue(RotationProperty, valueToUse);
}
set => SetValue(RotationProperty, value);
}
public static readonly StyledProperty<double> ViewportCenterYProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterY), 0.0d);
@ -213,5 +211,6 @@ namespace ControlCatalog.Pages
return workingPoint;
}
}
}

1
samples/IntegrationTestApp/IntegrationTestApp.csproj

@ -3,6 +3,7 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);AVP1012</NoWarn>
</PropertyGroup>
<PropertyGroup>

2
samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj

@ -19,7 +19,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Headless\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MobileSandbox\MobileSandbox.csproj" />

4
samples/RenderDemo/Pages/CustomSkiaPage.cs

@ -44,9 +44,9 @@ namespace RenderDemo.Pages
public bool HitTest(Point p) => false;
public bool Equals(ICustomDrawOperation other) => false;
static Stopwatch St = Stopwatch.StartNew();
public void Render(IDrawingContextImpl context)
public void Render(ImmediateDrawingContext context)
{
var leaseFeature = context.GetFeature<ISkiaSharpApiLeaseFeature>();
var leaseFeature = context.TryGetFeature<ISkiaSharpApiLeaseFeature>();
if (leaseFeature == null)
context.DrawGlyphRun(Brushes.Black, _noSkia.PlatformImpl);
else

2
samples/RenderDemo/Pages/WriteableBitmapPage.cs

@ -59,7 +59,7 @@ namespace RenderDemo.Pages
color = new Color(fillAlpha, r, g, b);
}
data[y * fb.Size.Width + x] = (int) color.ToUint32();
data[y * fb.Size.Width + x] = (int) color.ToUInt32();
}
}

10
samples/Sandbox/MainWindow.axaml.cs

@ -6,17 +6,11 @@ using Avalonia.Win32.WinRT.Composition;
namespace Sandbox
{
public class MainWindow : Window
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.AttachDevTools();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
InitializeComponent();
}
}
}

2
samples/Sandbox/Sandbox.csproj

@ -4,6 +4,7 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
</PropertyGroup>
<ItemGroup>
@ -17,4 +18,5 @@
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\SourceGenerators.props" />
</Project>

12
src/Avalonia.Base/Animation/Animation.cs

@ -200,7 +200,7 @@ namespace Avalonia.Animation
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter,
public static void SetAnimator(IAnimationSetter setter,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)]
Type value)
{
@ -319,7 +319,7 @@ namespace Avalonia.Animation
if (animators.Count == 1)
{
var subscription = animators[0].Apply(this, control, clock, match, onComplete);
if (subscription is not null)
{
subscriptions.Add(subscription);
@ -348,9 +348,11 @@ namespace Avalonia.Animation
if (onComplete != null)
{
Task.WhenAll(completionTasks!).ContinueWith(
(_, state) => ((Action)state!).Invoke(),
onComplete);
Task.WhenAll(completionTasks!)
.ContinueWith((_, state) => ((Action)state!).Invoke()
, onComplete
, TaskScheduler.FromCurrentSynchronizationContext()
);
}
}
return new CompositeDisposable(subscriptions);

2
src/Avalonia.Base/ClassBindingManager.cs

@ -17,6 +17,8 @@ namespace Avalonia
return target.Bind(prop, source, anchor);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1001:The same AvaloniaProperty should not be registered twice",
Justification = "Classes.attr binding feature is implemented using intermediate avalonia properties for each class")]
private static AvaloniaProperty RegisterClassProxyProperty(string className)
{
var prop = AvaloniaProperty.Register<StyledElement, bool>("__AvaloniaReserved::Classes::" + className);

2
src/Avalonia.Base/Collections/AvaloniaDictionaryExtensions.cs

@ -35,7 +35,7 @@ namespace Avalonia.Collections
/// Indicates if a weak subscription should be used to track changes to the collection.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
internal static IDisposable ForEachItem<TKey, TValue>(
public static IDisposable ForEachItem<TKey, TValue>(
this IAvaloniaReadOnlyDictionary<TKey, TValue> collection,
Action<TKey, TValue> added,
Action<TKey, TValue> removed,

2
src/Avalonia.Base/Controls/IResourceDictionary.cs

@ -18,6 +18,6 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a collection of merged resource dictionaries that are specifically keyed and composed to address theme scenarios.
/// </summary>
IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries { get; }
IDictionary<ThemeVariant, IThemeVariantProvider> ThemeDictionaries { get; }
}
}

21
src/Avalonia.Base/Controls/IThemeVariantProvider.cs

@ -0,0 +1,21 @@
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Controls;
/// <summary>
/// Resource provider with theme variant awareness.
/// Can be used with <see cref="IResourceDictionary.ThemeDictionaries"/>.
/// </summary>
/// <remarks>
/// This is a helper interface for the XAML compiler to make Key property accessibly by the markup extensions.
/// Which means, it can only be used with ResourceDictionaries and markup extensions in the XAML code.
/// </remarks>
[Unstable("This XAML-only API might be removed in the future minor updates.")]
public interface IThemeVariantProvider : IResourceProvider
{
/// <summary>
/// Key property set by the compiler.
/// </summary>
ThemeVariant? Key { get; set; }
}

12
src/Avalonia.Base/Controls/ResourceDictionary.cs

@ -13,13 +13,13 @@ namespace Avalonia.Controls
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public class ResourceDictionary : IResourceDictionary
public class ResourceDictionary : IResourceDictionary, IThemeVariantProvider
{
private object? lastDeferredItemKey;
private Dictionary<object, object?>? _inner;
private IResourceHost? _owner;
private AvaloniaList<IResourceProvider>? _mergedDictionaries;
private AvaloniaDictionary<ThemeVariant, IResourceProvider>? _themeDictionary;
private AvaloniaDictionary<ThemeVariant, IThemeVariantProvider>? _themeDictionary;
/// <summary>
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
@ -93,13 +93,13 @@ namespace Avalonia.Controls
}
}
public IDictionary<ThemeVariant, IResourceProvider> ThemeDictionaries
public IDictionary<ThemeVariant, IThemeVariantProvider> ThemeDictionaries
{
get
{
if (_themeDictionary == null)
{
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IResourceProvider>(2);
_themeDictionary = new AvaloniaDictionary<ThemeVariant, IThemeVariantProvider>(2);
_themeDictionary.ForEachItem(
(_, x) =>
{
@ -120,6 +120,8 @@ namespace Avalonia.Controls
return _themeDictionary;
}
}
ThemeVariant? IThemeVariantProvider.Key { get; set; }
bool IResourceNode.HasResources
{
@ -192,7 +194,7 @@ namespace Avalonia.Controls
if (_themeDictionary is not null)
{
IResourceProvider? themeResourceProvider;
IThemeVariantProvider? themeResourceProvider;
if (theme is not null && theme != ThemeVariant.Default)
{
if (_themeDictionary.TryGetValue(theme, out themeResourceProvider)

46
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -119,7 +119,19 @@ namespace Avalonia.Controls
resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider));
key = key ?? throw new ArgumentNullException(nameof(key));
return new FloatingResourceObservable(resourceProvider, key, converter);
return new FloatingResourceObservable(resourceProvider, key, null, converter);
}
public static IObservable<object?> GetResourceObservable(
this IResourceProvider resourceProvider,
object key,
ThemeVariant? defaultThemeVariant,
Func<object?, object?>? converter = null)
{
resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider));
key = key ?? throw new ArgumentNullException(nameof(key));
return new FloatingResourceObservable(resourceProvider, key, defaultThemeVariant, converter);
}
private class ResourceObservable : LightweightObservableBase<object?>
@ -128,7 +140,10 @@ namespace Avalonia.Controls
private readonly object _key;
private readonly Func<object?, object?>? _converter;
public ResourceObservable(IResourceHost target, object key, Func<object?, object?>? converter)
public ResourceObservable(
IResourceHost target,
object key,
Func<object?, object?>? converter)
{
_target = target;
_key = key;
@ -170,11 +185,8 @@ namespace Avalonia.Controls
private object? GetValue()
{
if (_target is not IThemeVariantHost themeVariantHost
|| !_target.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
var theme = (_target as IThemeVariantHost)?.ActualThemeVariant;
var value = _target.FindResource(theme, _key) ?? AvaloniaProperty.UnsetValue;
return _converter?.Invoke(value) ?? value;
}
@ -183,14 +195,20 @@ namespace Avalonia.Controls
private class FloatingResourceObservable : LightweightObservableBase<object?>
{
private readonly IResourceProvider _target;
private readonly ThemeVariant? _overrideThemeVariant;
private readonly object _key;
private readonly Func<object?, object?>? _converter;
private IResourceHost? _owner;
public FloatingResourceObservable(IResourceProvider target, object key, Func<object?, object?>? converter)
public FloatingResourceObservable(
IResourceProvider target,
object key,
ThemeVariant? overrideThemeVariant,
Func<object?, object?>? converter)
{
_target = target;
_key = key;
_overrideThemeVariant = overrideThemeVariant;
_converter = converter;
}
@ -233,7 +251,7 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged -= ResourcesChanged;
}
if (_owner is IThemeVariantHost themeVariantHost)
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost)
{
themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
}
@ -244,12 +262,11 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged += ResourcesChanged;
}
if (_owner is IThemeVariantHost themeVariantHost2)
if (_overrideThemeVariant is null && _owner is IThemeVariantHost themeVariantHost2)
{
themeVariantHost2.ActualThemeVariantChanged -= ActualThemeVariantChanged;
}
PublishNext();
}
@ -265,11 +282,8 @@ namespace Avalonia.Controls
private object? GetValue()
{
if (!(_target.Owner is IThemeVariantHost themeVariantHost)
|| !_target.Owner.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
var theme = _overrideThemeVariant ?? (_target.Owner as IThemeVariantHost)?.ActualThemeVariant;
var value = _target.Owner?.FindResource(theme, _key) ?? AvaloniaProperty.UnsetValue;
return _converter?.Invoke(value) ?? value;
}

13
src/Avalonia.Base/Input/Cursor.cs

@ -42,19 +42,21 @@ namespace Avalonia.Input
public class Cursor : IDisposable
{
public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow);
private string _name;
internal Cursor(ICursorImpl platformImpl)
private Cursor(ICursorImpl platformImpl, string name)
{
PlatformImpl = platformImpl;
_name = name;
}
public Cursor(StandardCursorType cursorType)
: this(GetCursorFactory().GetCursor(cursorType))
: this(GetCursorFactory().GetCursor(cursorType), cursorType.ToString())
{
}
public Cursor(IBitmap cursor, PixelPoint hotSpot)
: this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot))
: this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot), "BitmapCursor")
{
}
@ -73,5 +75,10 @@ namespace Avalonia.Input
{
return AvaloniaLocator.Current.GetRequiredService<ICursorFactory>();
}
public override string ToString()
{
return _name;
}
}
}

3
src/Avalonia.Base/Input/DragEventArgs.cs

@ -25,8 +25,7 @@ namespace Avalonia.Input
return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0);
}
[Unstable]
[Obsolete("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")]
[Unstable("This constructor might be removed in 12.0. For unit testing, consider using DragDrop.DoDragDrop or IHeadlessWindow.DragDrop.")]
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
: base(routedEvent)
{

3
src/Avalonia.Base/Input/PointerDeltaEventArgs.cs

@ -9,8 +9,7 @@ namespace Avalonia.Input
{
public Vector Delta { get; }
[Unstable]
[Obsolete("This constructor might be removed in 12.0.")]
[Unstable("This constructor might be removed in 12.0.")]
public PointerDeltaEventArgs(RoutedEvent routedEvent, object? source,
IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta)

12
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -14,8 +14,7 @@ namespace Avalonia.Input
private readonly PointerPointProperties _properties;
private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
[Unstable]
[Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
[Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
public PointerEventArgs(RoutedEvent routedEvent,
object? source,
IPointer pointer,
@ -129,8 +128,7 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
[Unstable]
[Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
[Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
public PointerPressedEventArgs(
object source,
IPointer pointer,
@ -150,8 +148,7 @@ namespace Avalonia.Input
public class PointerReleasedEventArgs : PointerEventArgs
{
[Unstable]
[Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
[Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
public PointerReleasedEventArgs(
object source, IPointer pointer,
Visual rootVisual, Point rootVisualPosition, ulong timestamp,
@ -173,8 +170,7 @@ namespace Avalonia.Input
{
public IPointer Pointer { get; }
[Unstable]
[Obsolete("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")]
[Unstable("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")]
public PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
{
Pointer = pointer;

3
src/Avalonia.Base/Input/PointerWheelEventArgs.cs

@ -9,8 +9,7 @@ namespace Avalonia.Input
{
public Vector Delta { get; }
[Unstable]
[Obsolete("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow.MouseWheel.")]
[Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow.MouseWheel.")]
public PointerWheelEventArgs(object source, IPointer pointer, Visual rootVisual,
Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta)

10
src/Avalonia.Base/Layout/LayoutManager.cs

@ -17,7 +17,7 @@ namespace Avalonia.Layout
/// </summary>
public class LayoutManager : ILayoutManager, IDisposable
{
private const int MaxPasses = 3;
private const int MaxPasses = 10;
private readonly Layoutable _owner;
private readonly LayoutQueue<Layoutable> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid);
@ -249,10 +249,12 @@ namespace Avalonia.Layout
{
var control = _toMeasure.Dequeue();
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
if (!control.IsMeasureValid)
{
Measure(control);
}
_toArrange.Enqueue(control);
}
}
@ -262,7 +264,7 @@ namespace Avalonia.Layout
{
var control = _toArrange.Dequeue();
if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
if (!control.IsArrangeValid)
{
Arrange(control);
}
@ -297,8 +299,6 @@ namespace Avalonia.Layout
{
control.Measure(control.PreviousMeasure.Value);
}
_toArrange.Enqueue(control);
}
return true;

18
src/Avalonia.Base/Layout/LayoutQueue.cs

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Logging;
namespace Avalonia.Layout
{
@ -48,10 +49,21 @@ namespace Avalonia.Layout
{
_loopQueueInfo.TryGetValue(item, out var info);
if (!info.Active && info.Count < _maxEnqueueCountPerLoop)
if (!info.Active)
{
_inner.Enqueue(item);
_loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
if (info.Count < _maxEnqueueCountPerLoop)
{
_inner.Enqueue(item);
_loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
}
else
{
Logger.TryGet(LogEventLevel.Warning, LogArea.Layout)?.Log(
this,
"Layout cycle detected. Item {Item} was enqueued {Count} times.",
item,
info.Count);
}
}
}

40
src/Avalonia.Base/Layout/Layoutable.cs

@ -776,10 +776,24 @@ namespace Avalonia.Layout
// All changes to visibility cause the parent element to be notified.
this.GetVisualParent<Layoutable>()?.ChildDesiredSizeChanged(this);
// We only invalidate outselves when visibility is changed to true.
if (change.GetNewValue<bool>())
{
// We only invalidate ourselves when visibility is changed to true.
InvalidateMeasure();
// If any descendant had its measure/arrange invalidated while we were hidden,
// they will need to to be registered with the layout manager now that they
// are again effectively visible. If IsEffectivelyVisible becomes an observable
// property then we can piggy-pack on that; for the moment we do this manually.
if (VisualRoot is ILayoutRoot layoutRoot)
{
var count = VisualChildren.Count;
for (var i = 0; i < count; ++i)
{
(VisualChildren[i] as Layoutable)?.AncestorBecameVisible(layoutRoot.LayoutManager);
}
}
}
}
}
@ -804,6 +818,30 @@ namespace Avalonia.Layout
InvalidateMeasure();
}
private void AncestorBecameVisible(ILayoutManager layoutManager)
{
if (!IsVisible)
return;
if (!IsMeasureValid)
{
layoutManager.InvalidateMeasure(this);
InvalidateVisual();
}
else if (!IsArrangeValid)
{
layoutManager.InvalidateArrange(this);
InvalidateVisual();
}
var count = VisualChildren.Count;
for (var i = 0; i < count; ++i)
{
(VisualChildren[i] as Layoutable)?.AncestorBecameVisible(layoutManager);
}
}
/// <summary>
/// Called when the layout manager raises a LayoutUpdated event.
/// </summary>

11
src/Avalonia.Base/Media/Color.cs

@ -449,7 +449,7 @@ namespace Avalonia.Media
/// </returns>
public override string ToString()
{
uint rgb = ToUint32();
uint rgb = ToUInt32();
return KnownColors.GetKnownColorName(rgb) ?? $"#{rgb.ToString("x8", CultureInfo.InvariantCulture)}";
}
@ -459,11 +459,18 @@ namespace Avalonia.Media
/// <returns>
/// The integer representation of the color.
/// </returns>
public uint ToUint32()
public uint ToUInt32()
{
return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B;
}
/// <inheritdoc cref="Color.ToUInt32"/>
[Obsolete("Use Color.ToUInt32() instead.")]
public uint ToUint32()
{
return ToUInt32();
}
/// <summary>
/// Returns the HSL color model equivalent of this RGB color.
/// </summary>

2
src/Avalonia.Base/Media/DashStyle.cs

@ -44,6 +44,8 @@ namespace Avalonia.Media
/// </summary>
/// <param name="dashes">The dashes collection.</param>
/// <param name="offset">The dash sequence offset.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public DashStyle(IEnumerable<double>? dashes, double offset)
{
Dashes = (dashes as AvaloniaList<double>) ?? new AvaloniaList<double>(dashes ?? Array.Empty<double>());

2
src/Avalonia.Base/Media/GeometryDrawing.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media
public class GeometryDrawing : Drawing
{
// Adding the Pen's stroke thickness here could yield wrong results due to transforms.
private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUint32(), 0);
private static readonly IPen s_boundsPen = new ImmutablePen(Colors.Black.ToUInt32(), 0);
/// <summary>
/// Defines the <see cref="Geometry"/> property.

2
src/Avalonia.Base/Media/GradientBrush.cs

@ -38,6 +38,8 @@ namespace Avalonia.Media
/// <summary>
/// Initializes a new instance of the <see cref="GradientBrush"/> class.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public GradientBrush()
{
this.GradientStops = new GradientStops();

36
src/Avalonia.Base/Media/HslColor.cs

@ -98,43 +98,13 @@ namespace Avalonia.Media
L = hsl.L;
}
/// <summary>
/// Gets the Alpha (transparency) component in the range from 0..1 (percentage).
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>0 is fully transparent.</item>
/// <item>1 is fully opaque.</item>
/// </list>
/// </remarks>
/// <inheritdoc cref="HsvColor.A"/>
public double A { get; }
/// <summary>
/// Gets the Hue component in the range from 0..360 (degrees).
/// This is the color's location, in degrees, on a color wheel/circle from 0 to 360.
/// Note that 360 is equivalent to 0 and will be adjusted automatically.
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>0/360 degrees is Red.</item>
/// <item>60 degrees is Yellow.</item>
/// <item>120 degrees is Green.</item>
/// <item>180 degrees is Cyan.</item>
/// <item>240 degrees is Blue.</item>
/// <item>300 degrees is Magenta.</item>
/// </list>
/// </remarks>
/// <inheritdoc cref="HsvColor.H"/>
public double H { get; }
/// <summary>
/// Gets the Saturation component in the range from 0..1 (percentage).
/// </summary>
/// <remarks>
/// <list type="bullet">
/// <item>0 is a shade of gray (no color).</item>
/// <item>1 is the full color.</item>
/// </list>
/// </remarks>
/// <inheritdoc cref="HsvColor.S"/>
public double S { get; }
/// <summary>

1
src/Avalonia.Base/Media/ImmediateDrawingContext.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;

16
src/Avalonia.Base/Media/PlatformDrawingContext.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Avalonia.Logging;
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
@ -41,8 +42,19 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) =>
_impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode);
public override void Custom(ICustomDrawOperation custom) =>
custom.Render(_impl);
public override void Custom(ICustomDrawOperation custom)
{
using var immediateDrawingContext = new ImmediateDrawingContext(_impl, false);
try
{
custom.Render(immediateDrawingContext);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, LogArea.Visual)
?.Log(custom, $"Exception in {custom.GetType().Name}.{nameof(ICustomDrawOperation.Render)} {{0}}", e);
}
}
public override void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun)
{

2
src/Avalonia.Base/Media/PolyLineSegment.cs

@ -28,6 +28,8 @@ namespace Avalonia.Media
/// <summary>
/// Initializes a new instance of the <see cref="PolyLineSegment"/> class.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public PolyLineSegment()
{
Points = new Points();

2
src/Avalonia.Base/Media/TransformGroup.cs

@ -11,6 +11,8 @@ namespace Avalonia.Media
public static readonly StyledProperty<Transforms> ChildrenProperty =
AvaloniaProperty.Register<TransformGroup, Transforms>(nameof(Children));
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public TransformGroup()
{
Children = new Transforms();

23
src/Avalonia.Base/Metadata/AvaloniaListAttribute.cs

@ -0,0 +1,23 @@
using System;
namespace Avalonia.Metadata;
/// <summary>
/// Defines how compiler should split avalonia list string value before parsing individual items.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class AvaloniaListAttribute : Attribute
{
/// <summary>
/// Separator used to split input string.
/// Default value is ','.
/// </summary>
public string[]? Separators { get; init; }
/// <summary>
/// Split options used to split input string.
/// Default value is RemoveEmptyEntries with TrimEntries.
/// </summary>
// StringSplitOptions.TrimEntries = 2, but only on net6 target.
public StringSplitOptions SplitOptions { get; init; } = StringSplitOptions.RemoveEmptyEntries | (StringSplitOptions)2;
}

22
src/Avalonia.Base/Metadata/UnstableAttribute.cs

@ -1,4 +1,4 @@
using System;
using System;
namespace Avalonia.Metadata
{
@ -9,5 +9,25 @@ namespace Avalonia.Metadata
[AttributeUsage(AttributeTargets.All)]
public sealed class UnstableAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="UnstableAttribute"/> class.
/// </summary>
public UnstableAttribute()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UnstableAttribute"/> class.
/// </summary>
/// <param name="message">The text string that describes alternative workarounds.</param>
public UnstableAttribute(string? message)
{
Message = message;
}
/// <summary>
/// Gets a value that indicates whether the compiler will treat usage of the obsolete program element as an error.
/// </summary>
public string? Message { get; }
}
}

295
src/Avalonia.Base/Platform/AssetLoader.cs

@ -1,281 +1,48 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
#if !BUILDTASK
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
#endif
namespace Avalonia.Platform
{
/// <summary>
/// Loads assets compiled into the application binary.
/// </summary>
public class AssetLoader
namespace Avalonia.Platform;
#if !BUILDTASK
: IAssetLoader
/// <inheritdoc cref="IAssetLoader"/>
#endif
{
public static class AssetLoader
{
#if !BUILDTASK
private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver();
private AssemblyDescriptor? _defaultResmAssembly;
/// <remarks>
/// Introduced for tests.
/// </remarks>
internal static void SetAssemblyDescriptorResolver(IAssemblyDescriptorResolver resolver) =>
s_assemblyDescriptorResolver = resolver;
/// <summary>
/// Initializes a new instance of the <see cref="AssetLoader"/> class.
/// </summary>
/// <param name="assembly">
/// The default assembly from which to load resm: assets for which no assembly is specified.
/// </param>
public AssetLoader(Assembly? assembly = null)
{
if (assembly == null)
assembly = Assembly.GetEntryAssembly();
if (assembly != null)
_defaultResmAssembly = new AssemblyDescriptor(assembly);
}
/// <summary>
/// Sets the default assembly from which to load assets for which no assembly is specified.
/// </summary>
/// <param name="assembly">The default assembly.</param>
public void SetDefaultAssembly(Assembly assembly)
{
_defaultResmAssembly = new AssemblyDescriptor(assembly);
}
/// <summary>
/// Checks if an asset with the specified URI exists.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri, Uri? baseUri = null)
{
return TryGetAsset(uri, baseUri, out _);
}
/// <summary>
/// Opens the asset with the requested URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>A stream containing the asset contents.</returns>
/// <exception cref="FileNotFoundException">
/// The asset could not be found.
/// </exception>
public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
/// <summary>
/// Opens the asset with the requested URI and returns the asset stream and the
/// assembly containing the asset.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>
/// The stream containing the resource contents together with the assembly.
/// </returns>
/// <exception cref="FileNotFoundException">
/// The asset could not be found.
/// </exception>
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
{
if (TryGetAsset(uri, baseUri, out var assetDescriptor))
{
return (assetDescriptor.GetStream(), assetDescriptor.Assembly);
}
throw new FileNotFoundException($"The resource {uri} could not be found.");
}
public Assembly? GetAssembly(Uri uri, Uri? baseUri)
{
if (!uri.IsAbsoluteUri && baseUri != null)
{
uri = new Uri(baseUri, uri);
}
if (TryGetAssembly(uri, out var assemblyDescriptor))
{
return assemblyDescriptor.Assembly;
}
return null;
}
/// <summary>
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
public IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri)
{
if (uri.IsAbsoluteResm())
{
if (!TryGetAssembly(uri, out var assembly))
{
assembly = _defaultResmAssembly;
}
return assembly?.Resources?
.Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
.Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
Enumerable.Empty<Uri>();
}
uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares())
{
if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
{
return Enumerable.Empty<Uri>();
}
private static IAssetLoader GetAssetLoader() => AvaloniaLocator.Current.GetRequiredService<IAssetLoader>();
if (assembly?.AvaloniaResources == null)
{
return Enumerable.Empty<Uri>();
}
/// <inheritdoc cref="IAssetLoader.SetDefaultAssembly"/>
public static void SetDefaultAssembly(Assembly assembly) => GetAssetLoader().SetDefaultAssembly(assembly);
if (path.Length > 0 && path[path.Length - 1] != '/')
{
path += '/';
}
/// <inheritdoc cref="IAssetLoader.Exists"/>
public static bool Exists(Uri uri, Uri? baseUri = null) => GetAssetLoader().Exists(uri, baseUri);
return assembly.AvaloniaResources
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
.Select(x => new Uri($"avares://{assembly.Name}{x.Key}"));
}
/// <inheritdoc cref="IAssetLoader.Open"/>
public static Stream Open(Uri uri, Uri? baseUri = null) => GetAssetLoader().Open(uri, baseUri);
return Enumerable.Empty<Uri>();
}
/// <inheritdoc cref="IAssetLoader.OpenAndGetAssembly"/>
public static (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
=> GetAssetLoader().OpenAndGetAssembly(uri, baseUri);
private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
{
assetDescriptor = null;
/// <inheritdoc cref="IAssetLoader.GetAssembly"/>
public static Assembly? GetAssembly(Uri uri, Uri? baseUri = null)
=> GetAssetLoader().GetAssembly(uri, baseUri);
if (uri.IsAbsoluteResm())
{
if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
{
assembly = _defaultResmAssembly;
}
if (assembly?.Resources != null)
{
var resourceKey = uri.AbsolutePath;
if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
{
return true;
}
}
}
uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares())
{
if (TryGetResAsmAndPath(uri, out var assembly, out var path))
{
if (assembly.AvaloniaResources == null)
{
return false;
}
if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor))
{
return true;
}
}
}
return false;
}
private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path)
{
path = uri.GetUnescapeAbsolutePath();
if (TryLoadAssembly(uri.Authority, out assembly))
{
return true;
}
return false;
}
private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{
assembly = null;
if (uri != null)
{
if (!uri.IsAbsoluteUri)
{
return false;
}
if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
{
return true;
}
if (uri.IsResm())
{
var assemblyName = uri.GetAssemblyNameFromQuery();
if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly))
{
return true;
}
}
}
return false;
}
private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{
assembly = null;
try
{
assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName);
return true;
}
catch (Exception) { }
return false;
}
/// <inheritdoc cref="IAssetLoader.GetAssets"/>
public static IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri)
=> GetAssetLoader().GetAssets(uri, baseUri);
#endif
public static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))
UriParser.Register(new GenericUriParser(
GenericUriParserOptions.GenericAuthority |
GenericUriParserOptions.NoUserInfo |
GenericUriParserOptions.NoPort |
GenericUriParserOptions.NoQuery |
GenericUriParserOptions.NoFragment), "avares", -1);
}
internal static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))
UriParser.Register(new GenericUriParser(
GenericUriParserOptions.GenericAuthority |
GenericUriParserOptions.NoUserInfo |
GenericUriParserOptions.NoPort |
GenericUriParserOptions.NoQuery |
GenericUriParserOptions.NoFragment), "avares", -1);
}
}

2
src/Avalonia.Base/Platform/IAssetLoader.cs

@ -9,7 +9,7 @@ namespace Avalonia.Platform
/// <summary>
/// Loads assets compiled into the application binary.
/// </summary>
[Unstable]
[Unstable("IAssetLoader interface and AvaloniaLocator usage is considered unstable. Please use AssetLoader static class instead.")]
public interface IAssetLoader
{
/// <summary>

7
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -1,6 +1,5 @@
using System;
using Avalonia.Media;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using Avalonia.Metadata;
@ -168,12 +167,6 @@ namespace Avalonia.Platform
/// </summary>
void PopBitmapBlendMode();
/// <summary>
/// Adds a custom draw operation
/// </summary>
/// <param name="custom">Custom draw operation</param>
void Custom(ICustomDrawOperation custom);
/// <summary>
/// Attempts to get an optional feature from the drawing context implementation
/// </summary>

255
src/Avalonia.Base/Platform/StandardAssetLoader.cs

@ -0,0 +1,255 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
namespace Avalonia.Platform;
/// <summary>
/// Loads assets compiled into the application binary.
/// </summary>
internal class StandardAssetLoader : IAssetLoader
{
private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver;
private AssemblyDescriptor? _defaultResmAssembly;
public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null)
{
if (assembly == null)
assembly = Assembly.GetEntryAssembly();
if (assembly != null)
_defaultResmAssembly = new AssemblyDescriptor(assembly);
_assemblyDescriptorResolver = resolver;
}
public StandardAssetLoader(Assembly? assembly = null) : this(new AssemblyDescriptorResolver(), assembly)
{
}
/// <summary>
/// Sets the default assembly from which to load assets for which no assembly is specified.
/// </summary>
/// <param name="assembly">The default assembly.</param>
public void SetDefaultAssembly(Assembly assembly)
{
_defaultResmAssembly = new AssemblyDescriptor(assembly);
}
/// <summary>
/// Checks if an asset with the specified URI exists.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri, Uri? baseUri = null)
{
return TryGetAsset(uri, baseUri, out _);
}
/// <summary>
/// Opens the asset with the requested URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>A stream containing the asset contents.</returns>
/// <exception cref="FileNotFoundException">
/// The asset could not be found.
/// </exception>
public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1;
/// <summary>
/// Opens the asset with the requested URI and returns the asset stream and the
/// assembly containing the asset.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>
/// The stream containing the resource contents together with the assembly.
/// </returns>
/// <exception cref="FileNotFoundException">
/// The asset could not be found.
/// </exception>
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
{
if (TryGetAsset(uri, baseUri, out var assetDescriptor))
{
return (assetDescriptor.GetStream(), assetDescriptor.Assembly);
}
throw new FileNotFoundException($"The resource {uri} could not be found.");
}
public Assembly? GetAssembly(Uri uri, Uri? baseUri)
{
if (!uri.IsAbsoluteUri && baseUri != null)
{
uri = new Uri(baseUri, uri);
}
if (TryGetAssembly(uri, out var assemblyDescriptor))
{
return assemblyDescriptor.Assembly;
}
return null;
}
/// <summary>
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
public IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri)
{
if (uri.IsAbsoluteResm())
{
if (!TryGetAssembly(uri, out var assembly))
{
assembly = _defaultResmAssembly;
}
return assembly?.Resources?
.Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
.Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
Enumerable.Empty<Uri>();
}
uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares())
{
if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
{
return Enumerable.Empty<Uri>();
}
if (assembly?.AvaloniaResources == null)
{
return Enumerable.Empty<Uri>();
}
if (path.Length > 0 && path[path.Length - 1] != '/')
{
path += '/';
}
return assembly.AvaloniaResources
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
.Select(x => new Uri($"avares://{assembly.Name}{x.Key}"));
}
return Enumerable.Empty<Uri>();
}
private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
{
assetDescriptor = null;
if (uri.IsAbsoluteResm())
{
if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
{
assembly = _defaultResmAssembly;
}
if (assembly?.Resources != null)
{
var resourceKey = uri.AbsolutePath;
if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
{
return true;
}
}
}
uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares())
{
if (TryGetResAsmAndPath(uri, out var assembly, out var path))
{
if (assembly.AvaloniaResources == null)
{
return false;
}
if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor))
{
return true;
}
}
}
return false;
}
private bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path)
{
path = uri.GetUnescapeAbsolutePath();
if (TryLoadAssembly(uri.Authority, out assembly))
{
return true;
}
return false;
}
private bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{
assembly = null;
if (uri != null)
{
if (!uri.IsAbsoluteUri)
{
return false;
}
if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
{
return true;
}
if (uri.IsResm())
{
var assemblyName = uri.GetAssemblyNameFromQuery();
if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly))
{
return true;
}
}
}
return false;
}
private bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{
assembly = null;
try
{
assembly = _assemblyDescriptorResolver.GetAssembly(assemblyName);
return true;
}
catch (Exception) { }
return false;
}
}

2
src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs

@ -14,7 +14,7 @@ namespace Avalonia.Platform
AssetLoader.RegisterResUriParsers();
AvaloniaLocator.CurrentMutable
.Bind<IRuntimePlatform>().ToConstant(standardPlatform)
.Bind<IAssetLoader>().ToConstant(new AssetLoader(assembly))
.Bind<IAssetLoader>().ToConstant(new StandardAssetLoader(assembly))
.Bind<IDynamicLibraryLoader>().ToConstant(
#if NET6_0_OR_GREATER
new Net6Loader()

27
src/Avalonia.Base/Platform/Storage/NoopStorageProvider.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform.Storage.FileIO;
namespace Avalonia.Platform.Storage;
internal class NoopStorageProvider : BclStorageProvider
{
public override bool CanOpen => false;
public override Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
{
return Task.FromResult<IReadOnlyList<IStorageFile>>(Array.Empty<IStorageFile>());
}
public override bool CanSave => false;
public override Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
{
return Task.FromResult<IStorageFile?>(null);
}
public override bool CanPickFolder => false;
public override Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
{
return Task.FromResult<IReadOnlyList<IStorageFolder>>(Array.Empty<IStorageFolder>());
}
}

5
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -143,11 +143,6 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
_impl.PopBitmapBlendMode();
}
public void Custom(ICustomDrawOperation custom)
{
_impl.Custom(custom);
}
public object? GetFeature(Type t) => _impl.GetFeature(t);

14
src/Avalonia.Base/Rendering/SceneGraph/CustomDrawOperation.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Platform;
@ -17,7 +18,16 @@ namespace Avalonia.Rendering.SceneGraph
public override void Render(IDrawingContextImpl context)
{
Custom.Render(context);
using var immediateDrawingContext = new ImmediateDrawingContext(context, false);
try
{
Custom.Render(immediateDrawingContext);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, LogArea.Visual)
?.Log(Custom, $"Exception in {Custom.GetType().Name}.{nameof(ICustomDrawOperation.Render)} {{0}}", e);
}
}
public override void Dispose() => Custom.Dispose();
@ -48,6 +58,6 @@ namespace Avalonia.Rendering.SceneGraph
/// Renders the node to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
void Render(IDrawingContextImpl context);
void Render(ImmediateDrawingContext context);
}
}

1
src/Avalonia.Base/StyledElement.cs

@ -289,6 +289,7 @@ namespace Avalonia
public StyledElement? Parent { get; private set; }
/// <inheritdoc />
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030:StyledProperty accessors should not have side effects", Justification = "False positive?")]
public ThemeVariant ActualThemeVariant => GetValue(ThemeVariant.ActualThemeVariantProperty);
/// <summary>

1
src/Avalonia.Base/Styling/IThemeVariantHost.cs

@ -7,7 +7,6 @@ namespace Avalonia.Styling;
/// <summary>
/// Interface for the host element with a theme variant.
/// </summary>
[Unstable]
public interface IThemeVariantHost : IResourceHost
{
/// <summary>

4
src/Avalonia.Base/Styling/ThemeVariant.cs

@ -9,6 +9,10 @@ namespace Avalonia.Styling;
/// Specifies a UI theme variant that should be used for the Control and Application types.
/// </summary>
[TypeConverter(typeof(ThemeVariantTypeConverter))]
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010:AvaloniaProperty objects should be owned by the type in which they are stored",
Justification = "ActualThemeVariant and RequestedThemeVariant properties are shared Avalonia.Base and Avalonia.Controls projects," +
"but shouldn't be visible on the StyledElement class." +
"Ideally we woould introduce readonly styled properties.")]
public sealed record ThemeVariant
{
/// <summary>

47
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -1,5 +1,4 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
@ -118,11 +117,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<TResult> synchronously on the
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <param name="callback">
/// A Func<TResult> delegate to invoke through the dispatcher.
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
/// <returns>
/// The return value from the delegate being invoked.
@ -136,11 +135,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<TResult> synchronously on the
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <param name="callback">
/// A Func<TResult> delegate to invoke through the dispatcher.
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
@ -156,11 +155,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<TResult> synchronously on the
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <param name="callback">
/// A Func<TResult> delegate to invoke through the dispatcher.
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
@ -183,11 +182,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<TResult> synchronously on the
/// Executes the specified Func&lt;TResult&gt; synchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <param name="callback">
/// A Func<TResult> delegate to invoke through the dispatcher.
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
@ -317,11 +316,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<TResult> asynchronously on the
/// Executes the specified Func&lt;TResult&gt; asynchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <param name="callback">
/// A Func<TResult> delegate to invoke through the dispatcher.
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
/// <returns>
/// An operation representing the queued delegate to be invoked.
@ -335,11 +334,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<TResult> asynchronously on the
/// Executes the specified Func&lt;TResult&gt; asynchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <param name="callback">
/// A Func<TResult> delegate to invoke through the dispatcher.
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
@ -355,11 +354,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<TResult> asynchronously on the
/// Executes the specified Func&lt;TResult&gt; asynchronously on the
/// thread that the Dispatcher was created on.
/// </summary>
/// <param name="callback">
/// A Func<TResult> delegate to invoke through the dispatcher.
/// A Func&lt;TResult&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
@ -479,7 +478,7 @@ public partial class Dispatcher
// operation has already started when the timeout expires,
// we still wait for it to complete. This is different
// than simply waiting on the operation with a timeout
// because we are the ones queueing the dispatcher
// because we are the ones queuing the dispatcher
// operation, not the caller. We can't leave the operation
// in a state that it might execute if we return that it did not
// invoke.
@ -492,12 +491,12 @@ public partial class Dispatcher
// Old async semantics return from Wait without
// throwing an exception if the operation was aborted.
// There is no need to test the timout condition, since
// There is no need to test the timeout condition, since
// the old async semantics would just return the result,
// which would be null.
// This should not block because either the operation
// is using the old async sematics, or the operation
// is using the old async semantics, or the operation
// completed successfully.
result = operation.GetResult();
}
@ -543,11 +542,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<Task> asynchronously on the
/// Executes the specified Func&lt;Task&gt; asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="callback">
/// A Func<Task> delegate to invoke through the dispatcher.
/// A Func&lt;Task&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
@ -564,11 +563,11 @@ public partial class Dispatcher
}
/// <summary>
/// Executes the specified Func<Task<TResult>> asynchronously on the
/// Executes the specified Func&lt;Task&lt;TResult&gt;&gt; asynchronously on the
/// thread that the Dispatcher was created on
/// </summary>
/// <param name="callback">
/// A Func<Task<TResult>> delegate to invoke through the dispatcher.
/// <param name="action">
/// A Func&lt;Task&lt;TResult&gt;&gt; delegate to invoke through the dispatcher.
/// </param>
/// <param name="priority">
/// The priority that determines in what order the specified
@ -595,4 +594,4 @@ public partial class Dispatcher
_ = action ?? throw new ArgumentNullException(nameof(action));
InvokeAsyncImpl(new SendOrPostCallbackDispatcherOperation(this, priority, action, arg, true), CancellationToken.None);
}
}
}

1
src/Avalonia.Base/Visual.cs

@ -329,6 +329,7 @@ namespace Avalonia
/// <summary>
/// Gets the control's parent visual.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "GetVisualParent extension method is supposed to be used instead.")]
internal Visual? VisualParent => _visualParent;
/// <summary>

39
src/Avalonia.Controls/AppBuilder.cs

@ -118,6 +118,43 @@ namespace Avalonia
};
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// Should only be used for testing and design purposes, as it relies on dynamic code.
/// </summary>
/// <param name="entryPointType">
/// Parameter from which <see cref="AppBuilder"/> should be created.
/// It either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application.
/// </param>
/// <returns>An <see cref="AppBuilder"/> instance. If can't be created, thrown an exception.</returns>
internal static AppBuilder Configure(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type entryPointType)
{
var appBuilderObj = entryPointType
.GetMethod(
"BuildAvaloniaApp",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
null,
Array.Empty<Type>(),
null)?
.Invoke(null, Array.Empty<object?>());
if (appBuilderObj is AppBuilder appBuilder)
{
return appBuilder;
}
if (typeof(Application).IsAssignableFrom(entryPointType))
{
return Configure(() => (Application)Activator.CreateInstance(entryPointType)!);
}
throw new InvalidOperationException(
$"Unable to create AppBuilder from type {entryPointType.Name}." +
$"Input type either needs to have BuildAvaloniaApp -> AppBuilder method or inherit Application type.");
}
protected AppBuilder Self => this;
public AppBuilder AfterSetup(Action<AppBuilder> callback)
@ -206,7 +243,7 @@ namespace Avalonia
_optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind<T>().ToFunc(options); };
return Self;
}
/// <summary>
/// Registers an action that is executed with the current font manager.
/// </summary>

2
src/Avalonia.Controls/Application.cs

@ -94,6 +94,8 @@ namespace Avalonia
}
/// <inheritdoc />
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031", Justification = "This property is supposed to be a styled readonly property.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030", Justification = "False positive.")]
public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty);
/// <summary>

2
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -2042,6 +2042,8 @@ namespace Avalonia.Controls
/// <summary>
/// Identifies the Value dependency property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1002:AvaloniaProperty objects should not be owned by a generic type",
Justification = "This property is not supposed to be used from XAML.")]
public static readonly StyledProperty<T> ValueProperty =
AvaloniaProperty.Register<BindingEvaluator<T>, T>(nameof(Value));

2
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs

@ -9,7 +9,7 @@ namespace Avalonia.Controls
{
/// <summary>
/// Specifies how text in the text box portion of the <see cref="AutoCompleteBox" />
/// control is used to filter items specified by the <see cref="AutoCompleteBox.Items" />
/// control is used to filter items specified by the <see cref="AutoCompleteBox.ItemsSource" />
/// property for display in the drop-down.
/// </summary>
public enum AutoCompleteFilterMode

6
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -16,5 +16,11 @@
<InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.LeakTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Headless, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Headless.XUnit, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Native, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.DesignerSupport.Remote, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Browser, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
</Project>

2
src/Avalonia.Controls/ColumnDefinition.cs

@ -46,8 +46,8 @@ namespace Avalonia.Controls
/// <param name="value">The width of the column.</param>
/// <param name="type">The width unit of the column.</param>
public ColumnDefinition(double value, GridUnitType type)
: this(new GridLength(value, type))
{
Width = new GridLength(value, type);
}
/// <summary>

2
src/Avalonia.Controls/ContextMenu.cs

@ -56,6 +56,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Placement"/> property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1013",
Justification = "We keep PlacementModeProperty for backward compatibility.")]
public static readonly StyledProperty<PlacementMode> PlacementProperty =
Popup.PlacementProperty.AddOwner<ContextMenu>();

12
src/Avalonia.Controls/DefinitionBase.cs

@ -37,7 +37,7 @@ namespace Avalonia.Controls
{
// start with getting SharedSizeGroup value.
// this property is NOT inherited which should result in better overall perf.
if (SharedSizeGroup is { } sharedSizeGroupId && PrivateSharedSizeScope is { } privateSharedSizeScope)
if (SharedSizeGroup is { } sharedSizeGroupId && GetValue(PrivateSharedSizeScopeProperty) is { } privateSharedSizeScope)
{
_sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
_sharedState.AddMember(this);
@ -333,7 +333,7 @@ namespace Avalonia.Controls
if (definition._sharedState == null
&& sharedSizeGroupId != null
&& definition.PrivateSharedSizeScope is { } privateSharedSizeScope)
&& definition.GetValue(PrivateSharedSizeScopeProperty) is { } privateSharedSizeScope)
{
// if definition is not registered and both: shared size group id AND private shared scope
// are available, then register definition.
@ -412,14 +412,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Private getter of shared state collection dynamic property.
/// </summary>
private SharedSizeScope? PrivateSharedSizeScope
{
get { return GetValue(PrivateSharedSizeScopeProperty); }
}
/// <summary>
/// Convenience accessor to UseSharedMinimum flag
/// </summary>

2
src/Avalonia.Controls/DefinitionList.cs

@ -1,9 +1,11 @@
using System.Collections;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
[AvaloniaList(Separators = new [] { ",", " " })]
public abstract class DefinitionList<T> : AvaloniaList<T> where T : DefinitionBase
{
public DefinitionList()

2
src/Avalonia.Controls/Documents/Span.cs

@ -18,6 +18,8 @@ namespace Avalonia.Controls.Documents
AvaloniaProperty.Register<Span, InlineCollection>(
nameof(Inlines));
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "Collection properties shouldn't be set with SetCurrentValue.")]
public Span()
{
Inlines = new InlineCollection

2
src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs

@ -44,7 +44,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="OverlayInputPassThroughElement"/> property
/// </summary>
public static readonly StyledProperty<IInputElement?> OverlayInputPassThroughElementProperty =
Popup.OverlayInputPassThroughElementProperty.AddOwner<FlyoutBase>();
Popup.OverlayInputPassThroughElementProperty.AddOwner<PopupFlyoutBase>();
private readonly Lazy<Popup> _popupLazy;
private Rect? _enlargedPopupRect;

43
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -7,8 +7,8 @@ namespace Avalonia.Controls.Generators
/// </summary>
/// <remarks>
/// When creating a container for an item from a <see cref="VirtualizingPanel"/>, the following
/// method order should be followed:
///
/// process should be followed:
///
/// - <see cref="IsItemItsOwnContainer(Control)"/> should first be called if the item is
/// derived from the <see cref="Control"/> class. If this method returns true then the
/// item itself should be used as the container.
@ -19,9 +19,29 @@ namespace Avalonia.Controls.Generators
/// - The container should then be added to the panel using
/// <see cref="VirtualizingPanel.AddInternalChild(Control)"/>
/// - Finally, <see cref="ItemContainerPrepared(Control, object?, int)"/> should be called.
/// - When the item is ready to be recycled, <see cref="ClearItemContainer(Control)"/> should
/// be called if <see cref="IsItemItsOwnContainer(Control)"/> returned false.
///
/// NOTE: If <see cref="IsItemItsOwnContainer(Control)"/> in the first step above returns true
/// then the above steps should be carried out a single time; the first time the item is
/// displayed. Otherwise the steps should be carried out each time a new container is realized
/// for an item.
///
/// When unrealizing a container, the following process should be followed:
///
/// - If <see cref="IsItemItsOwnContainer(Control)"/> for the item returned true then the item
/// cannot be unrealized or recycled.
/// - Otherwise, <see cref="ClearItemContainer(Control)"/> should be called for the container
/// - If recycling is supported then the container should be added to a recycle pool.
/// - It is assumed that recyclable containers will not be removed from the panel but instead
/// hidden from view using e.g. `container.IsVisible = false`.
///
/// When recycling an unrealized container, the following process should be followed:
///
/// - An element should be taken from the recycle pool.
/// - The container should be made visible.
/// - <see cref="PrepareItemContainer(Control, object?, int)"/> method should be called for the
/// container.
/// - <see cref="ItemContainerPrepared(Control, object?, int)"/> should be called.
///
/// NOTE: Although this class is similar to that found in WPF/UWP, in Avalonia this class only
/// concerns itself with generating and clearing item containers; it does not maintain a
/// record of the currently realized containers, that responsibility is delegated to the
@ -65,7 +85,7 @@ namespace Avalonia.Controls.Generators
/// <param name="index">The index of the item to display.</param>
/// <remarks>
/// If <see cref="IsItemItsOwnContainer(Control)"/> is true for an item, then this method
/// only needs to be called a single time, otherwise this method should be called after the
/// must only be called a single time, otherwise this method must be called after the
/// container is created, and each subsequent time the container is recycled to display a
/// new item.
/// </remarks>
@ -80,10 +100,11 @@ namespace Avalonia.Controls.Generators
/// <param name="item">The item being displayed.</param>
/// <param name="index">The index of the item being displayed.</param>
/// <remarks>
/// This method should be called when a container has been fully prepared and added
/// This method must be called when a container has been fully prepared and added
/// to the logical and visual trees, but may be called before a layout pass has completed.
/// It should be called regardless of the result of
/// <see cref="IsItemItsOwnContainer(Control)"/>.
/// It must be called regardless of the result of
/// <see cref="IsItemItsOwnContainer(Control)"/> but if that method returned true then
/// must be called only a single time.
/// </remarks>
public void ItemContainerPrepared(Control container, object? item, int index) =>
_owner.ItemContainerPrepared(container, item, index);
@ -102,6 +123,12 @@ namespace Avalonia.Controls.Generators
/// Undoes the effects of the <see cref="PrepareItemContainer(Control, object, int)"/> method.
/// </summary>
/// <param name="container">The container control.</param>
/// <remarks>
/// This method must be called when a container is unrealized. The container must have
/// already have been removed from the virtualizing panel's list of realized containers before
/// this method is called. This method must not be called if
/// <see cref="IsItemItsOwnContainer"/> returned true for the item.
/// </remarks>
public void ClearItemContainer(Control container) => _owner.ClearItemContainer(container);
[Obsolete("Use ItemsControl.ContainerFromIndex")]

4
src/Avalonia.Controls/LayoutTransformControl.cs

@ -63,7 +63,7 @@ namespace Avalonia.Controls
{
if (TransformRoot == null || LayoutTransform == null)
{
LayoutTransform = RenderTransform;
SetCurrentValue(LayoutTransformProperty, RenderTransform);
return base.ArrangeOverride(finalSize);
}
@ -176,7 +176,7 @@ namespace Avalonia.Controls
else
{
_renderTransformChangedEvent?.Dispose();
LayoutTransform = null;
ClearValue(LayoutTransformProperty);
}
}
}

8
src/Avalonia.Controls/ListBox.cs

@ -29,18 +29,24 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="SelectedItems"/> property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010",
Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")]
public static readonly new DirectProperty<SelectingItemsControl, IList?> SelectedItemsProperty =
SelectingItemsControl.SelectedItemsProperty;
/// <summary>
/// Defines the <see cref="Selection"/> property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010",
Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")]
public static readonly new DirectProperty<SelectingItemsControl, ISelectionModel> SelectionProperty =
SelectingItemsControl.SelectionProperty;
/// <summary>
/// Defines the <see cref="SelectionMode"/> property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1010",
Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")]
public static readonly new StyledProperty<SelectionMode> SelectionModeProperty =
SelectingItemsControl.SelectionModeProperty;
@ -84,6 +90,8 @@ namespace Avalonia.Controls
/// Note that the selection mode only applies to selections made via user interaction.
/// Multiple selections can be made programmatically regardless of the value of this property.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1012",
Justification = "This property is owned by SelectingItemsControl, but protected there. ListBox changes its visibility.")]
public new SelectionMode SelectionMode
{
get { return base.SelectionMode; }

6
src/Avalonia.Controls/MenuItem.cs

@ -53,12 +53,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<KeyGesture?> InputGestureProperty =
AvaloniaProperty.Register<MenuItem, KeyGesture?>(nameof(InputGesture));
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsSelectedProperty =
SelectingItemsControl.IsSelectedProperty.AddOwner<MenuItem>();
/// <summary>
/// Defines the <see cref="IsSubMenuOpen"/> property.
/// </summary>

3
src/Avalonia.Controls/Platform/ScreenHelper.cs

@ -1,11 +1,12 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Platform
{
public static class ScreenHelper
internal static class ScreenHelper
{
public static Screen? ScreenFromPoint(PixelPoint point, IReadOnlyList<Screen> screens)
{

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

@ -1,5 +1,5 @@
using System;
using Avalonia.Collections;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
@ -442,7 +442,7 @@ namespace Avalonia.Controls.Presenters
var contentTemplate = ContentTemplate;
var oldChild = Child;
var newChild = CreateChild(content, oldChild, contentTemplate);
var logicalChildren = Host?.LogicalChildren ?? LogicalChildren;
var logicalChildren = GetEffectiveLogicalChildren();
// Remove the old child if we're not recycling it.
if (newChild != oldChild)
@ -488,6 +488,9 @@ namespace Avalonia.Controls.Presenters
}
private IAvaloniaList<ILogical> GetEffectiveLogicalChildren()
=> Host?.LogicalChildren ?? LogicalChildren;
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
@ -692,7 +695,7 @@ namespace Avalonia.Controls.Presenters
else if (Child != null)
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
GetEffectiveLogicalChildren().Remove(Child);
((ISetInheritanceParent)Child).SetParent(Child.Parent);
Child = null;
_recyclingDataTemplate = null;

5
src/Avalonia.Controls/Presenters/PanelContainerGenerator.cs

@ -67,9 +67,12 @@ namespace Avalonia.Controls.Presenters
for (var i = 0; i < count; ++i)
{
var c = children[index + i];
if (!c.IsSet(ItemIsOwnContainerProperty))
{
itemsControl.RemoveLogicalChild(children[i + index]);
generator.ClearItemContainer(c);
generator.ClearItemContainer(c);
}
}
children.RemoveRange(index, count);

2
src/Avalonia.Controls/Primitives/UniformGrid.cs

@ -123,7 +123,7 @@ namespace Avalonia.Controls.Primitives
if (FirstColumn >= Columns)
{
FirstColumn = 0;
SetCurrentValue(FirstColumnProperty, 0);
}
var itemCount = FirstColumn;

3
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -29,6 +29,9 @@ namespace Avalonia.Controls.Primitives
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1030")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031",
Justification = "A hack to make ChromeOverlayLayer lazily creatable. It is expected that GetValue(ChromeOverlayLayerProperty) alone won't work.")]
public ChromeOverlayLayer ChromeOverlayLayer
{
get

95
src/Avalonia.Controls/ProgressBar.cs

@ -17,7 +17,13 @@ namespace Avalonia.Controls
[PseudoClasses(":vertical", ":horizontal", ":indeterminate")]
public class ProgressBar : RangeBase
{
public class ProgressBarTemplateProperties : AvaloniaObject
/// <summary>
/// Provides calculated values for use with the <see cref="ProgressBar"/>'s control theme or template.
/// </summary>
/// <remarks>
/// This class is NOT intended for general use outside of control templates.
/// </remarks>
public class ProgressBarTemplateSettings : AvaloniaObject
{
private double _container2Width;
private double _containerWidth;
@ -26,38 +32,38 @@ namespace Avalonia.Controls
private double _container2AnimationStartPosition;
private double _container2AnimationEndPosition;
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
public static readonly DirectProperty<ProgressBarTemplateSettings, double> ContainerAnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(ContainerAnimationStartPosition),
p => p.ContainerAnimationStartPosition,
(p, o) => p.ContainerAnimationStartPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
public static readonly DirectProperty<ProgressBarTemplateSettings, double> ContainerAnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(ContainerAnimationEndPosition),
p => p.ContainerAnimationEndPosition,
(p, o) => p.ContainerAnimationEndPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
public static readonly DirectProperty<ProgressBarTemplateSettings, double> Container2AnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(Container2AnimationStartPosition),
p => p.Container2AnimationStartPosition,
(p, o) => p.Container2AnimationStartPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
public static readonly DirectProperty<ProgressBarTemplateSettings, double> Container2AnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(Container2AnimationEndPosition),
p => p.Container2AnimationEndPosition,
(p, o) => p.Container2AnimationEndPosition = o);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2WidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
public static readonly DirectProperty<ProgressBarTemplateSettings, double> Container2WidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(Container2Width),
p => p.Container2Width,
(p, o) => p.Container2Width = o);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerWidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
public static readonly DirectProperty<ProgressBarTemplateSettings, double> ContainerWidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(ContainerWidth),
p => p.ContainerWidth,
(p, o) => p.ContainerWidth = o);
@ -103,29 +109,57 @@ namespace Avalonia.Controls
private Border? _indicator;
private IDisposable? _trackSizeChangedListener;
/// <summary>
/// Defines the <see cref="IsIndeterminate"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
/// <summary>
/// Defines the <see cref="ShowProgressText"/> property.
/// </summary>
public static readonly StyledProperty<bool> ShowProgressTextProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(ShowProgressText));
/// <summary>
/// Defines the <see cref="ProgressTextFormat"/> property.
/// </summary>
public static readonly StyledProperty<string> ProgressTextFormatProperty =
AvaloniaProperty.Register<ProgressBar, string>(nameof(ProgressTextFormat), "{1:0}%");
/// <summary>
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
/// <summary>
/// Defines the <see cref="Percentage"/> property.
/// </summary>
public static readonly DirectProperty<ProgressBar, double> PercentageProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(Percentage),
o => o.Percentage);
/// <summary>
/// Defines the <see cref="IndeterminateStartingOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.Register<ProgressBar, double>(nameof(IndeterminateStartingOffset));
/// <summary>
/// Defines the <see cref="IndeterminateEndingOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> IndeterminateEndingOffsetProperty =
AvaloniaProperty.Register<ProgressBar, double>(nameof(IndeterminateEndingOffset));
/// <summary>
/// Gets the overall percentage complete of the progress
/// </summary>
/// <remarks>
/// This read-only property is automatically calculated using the current <see cref="RangeBase.Value"/> and
/// the effective range (<see cref="RangeBase.Maximum"/> - <see cref="RangeBase.Minimum"/>).
/// </remarks>
public double Percentage
{
get { return _percentage; }
@ -154,31 +188,50 @@ namespace Avalonia.Controls
OrientationProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
}
/// <summary>
/// Initializes a new instance of the <see cref="ProgressBar"/> class.
/// </summary>
public ProgressBar()
{
UpdatePseudoClasses(IsIndeterminate, Orientation);
}
public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties();
/// <summary>
/// Gets or sets the TemplateSettings for the <see cref="ProgressBar"/>.
/// </summary>
public ProgressBarTemplateSettings TemplateSettings { get; } = new ProgressBarTemplateSettings();
/// <summary>
/// Gets or sets a value indicating whether the progress bar shows the actual value or a generic,
/// continues progress indicator (indeterminate state).
/// </summary>
public bool IsIndeterminate
{
get => GetValue(IsIndeterminateProperty);
set => SetValue(IsIndeterminateProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether progress text will be shown.
/// </summary>
public bool ShowProgressText
{
get => GetValue(ShowProgressTextProperty);
set => SetValue(ShowProgressTextProperty, value);
}
/// <summary>
/// Gets or sets the format string applied to the internally calculated progress text before it is shown.
/// </summary>
public string ProgressTextFormat
{
get => GetValue(ProgressTextFormatProperty);
set => SetValue(ProgressTextFormatProperty, value);
}
/// <summary>
/// Gets or sets the orientation of the <see cref="ProgressBar"/>.
/// </summary>
public Orientation Orientation
{
get => GetValue(OrientationProperty);
@ -193,6 +246,7 @@ namespace Avalonia.Controls
return result;
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@ -242,15 +296,14 @@ namespace Avalonia.Controls
var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
TemplateProperties.ContainerWidth = barIndicatorWidth;
TemplateProperties.Container2Width = barIndicatorWidth2;
TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
TemplateSettings.ContainerWidth = barIndicatorWidth;
TemplateSettings.Container2Width = barIndicatorWidth2;
TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
TemplateSettings.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
TemplateSettings.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
TemplateSettings.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
TemplateSettings.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
// Remove these properties when we switch to fluent as default and removed the old one.
SetCurrentValue(IndeterminateStartingOffsetProperty,-dim);

1
src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs

@ -71,6 +71,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets a value that indicates the refresh state of the visualizer.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "False positive")]
protected RefreshVisualizerState RefreshVisualizerState
{
get

4
src/Avalonia.Controls/RowDefinition.cs

@ -46,8 +46,8 @@ namespace Avalonia.Controls
/// <param name="value">The height of the row.</param>
/// <param name="type">The height unit of the column.</param>
public RowDefinition(double value, GridUnitType type)
: this(new GridLength(value, type))
{
Height = new GridLength(value, type);
}
/// <summary>
@ -56,7 +56,7 @@ namespace Avalonia.Controls
/// <param name="height">The height of the column.</param>
public RowDefinition(GridLength height)
{
Height = height;
SetCurrentValue(HeightProperty, height);
}
/// <summary>

40
src/Avalonia.Controls/Screens.cs

@ -1,8 +1,7 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform;
using Avalonia.VisualTree;
#nullable enable
@ -38,21 +37,58 @@ namespace Avalonia.Controls
_iScreenImpl = iScreenImpl;
}
/// <summary>
/// Retrieves a Screen for the display that contains the rectangle.
/// </summary>
/// <param name="bounds">Bounds that specifies the area for which to retrieve the display.</param>
/// <returns>The <see cref="Screen"/>.</returns>
public Screen? ScreenFromBounds(PixelRect bounds)
{
return _iScreenImpl.ScreenFromRect(bounds);
}
/// <summary>
/// Retrieves a Screen for the display that contains the specified <see cref="WindowBase"/>.
/// </summary>
/// <param name="window">The window for which to retrieve the Screen.</param>
/// <exception cref="ObjectDisposedException">Window platform implementation was already disposed.</exception>
/// <returns>The <see cref="Screen"/>.</returns>
public Screen? ScreenFromWindow(WindowBase window)
{
if (window.PlatformImpl is null)
{
throw new ObjectDisposedException("Window platform implementation was already disposed.");
}
return _iScreenImpl.ScreenFromWindow(window.PlatformImpl);
}
/// <summary>
/// Retrieves a Screen for the display that contains the specified <see cref="IWindowBaseImpl"/>.
/// </summary>
/// <param name="window">The window impl for which to retrieve the Screen.</param>
/// <returns>The <see cref="Screen"/>.</returns>
[Obsolete("Use ScreenFromWindow(WindowBase) overload.")]
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return _iScreenImpl.ScreenFromWindow(window);
}
/// <summary>
/// Retrieves a Screen for the display that contains the specified point.
/// </summary>
/// <param name="point">A Point that specifies the location for which to retrieve a Screen.</param>
/// <returns>The <see cref="Screen"/>.</returns>
public Screen? ScreenFromPoint(PixelPoint point)
{
return _iScreenImpl.ScreenFromPoint(point);
}
/// <summary>
/// Retrieves a Screen for the display that contains the specified <see cref="Visual"/>.
/// </summary>
/// <param name="visual">A Visual for which to retrieve a Screen.</param>
/// <returns>The <see cref="Screen"/>.</returns>
public Screen? ScreenFromVisual(Visual visual)
{
var tl = visual.PointToScreen(visual.Bounds.TopLeft);

6
src/Avalonia.Controls/SplitView/SplitView.cs

@ -217,8 +217,8 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled
/// <para>When enabled, and the pane is open in Overlay or CompactOverlay mode,
/// the contents of the splitview are darkened to visually separate the open pane
/// and the rest of the SplitView</para>
/// the contents of the <see cref="SplitView"/> are darkened to visually separate the open pane
/// and the rest of the <see cref="SplitView"/>.</para>
/// </summary>
public bool UseLightDismissOverlayMode
{
@ -227,7 +227,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the TemplateSettings for the SplitView
/// Gets or sets the TemplateSettings for the <see cref="SplitView"/>.
/// </summary>
public SplitViewTemplateSettings TemplateSettings
{

6
src/Avalonia.Controls/SplitView/SplitViewTemplateSettings.cs

@ -2,8 +2,10 @@
{
/// <summary>
/// Provides calculated values for use with the <see cref="SplitView"/>'s control theme or template.
/// This class is NOT intended for general use.
/// </summary>
/// <remarks>
/// This class is NOT intended for general use outside of control templates.
/// </remarks>
public class SplitViewTemplateSettings : AvaloniaObject
{
internal SplitViewTemplateSettings() { }
@ -17,12 +19,14 @@
AvaloniaProperty.Register<SplitViewTemplateSettings, GridLength>(
nameof(PaneColumnGridLength));
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")]
public double ClosedPaneWidth
{
get => GetValue(ClosedPaneWidthProperty);
internal set => SetValue(ClosedPaneWidthProperty, value);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")]
public GridLength PaneColumnGridLength
{
get => GetValue(PaneColumnGridLengthProperty);

2
src/Avalonia.Controls/TabControl.cs

@ -115,6 +115,7 @@ namespace Avalonia.Controls
/// <value>
/// The content of the selected tab.
/// </value>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")]
public object? SelectedContent
{
get { return GetValue(SelectedContentProperty); }
@ -127,6 +128,7 @@ namespace Avalonia.Controls
/// <value>
/// The content template of the selected tab.
/// </value>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1032", Justification = "This property is supposed to be a styled readonly property.")]
public IDataTemplate? SelectedContentTemplate
{
get { return GetValue(SelectedContentTemplateProperty); }

4
src/Avalonia.Controls/TabItem.cs

@ -42,6 +42,8 @@ namespace Avalonia.Controls
/// <value>
/// The tab strip placement.
/// </value>
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1031",
Justification = "This property is supposed to be inherited only and settable on parent TabControl.")]
public Dock TabStripPlacement
{
get { return GetValue(TabStripPlacementProperty); }
@ -83,7 +85,7 @@ namespace Avalonia.Controls
{
Header = obj.NewValue;
}
}
}
}
}
}

2
src/Avalonia.Controls/ToggleSwitch.cs

@ -201,7 +201,7 @@ namespace Avalonia.Controls
}
else
{
IsChecked = shouldBecomeChecked;
SetCurrentValue(IsCheckedProperty, shouldBecomeChecked);
}
}
else

17
src/Avalonia.Controls/TopLevel.cs

@ -83,11 +83,11 @@ namespace Avalonia.Controls
/// <inheritdoc cref="ThemeVariantScope.ActualThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
ThemeVariantScope.ActualThemeVariantProperty.AddOwner<Application>();
ThemeVariantScope.ActualThemeVariantProperty.AddOwner<TopLevel>();
/// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<Application>();
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<TopLevel>();
/// <summary>
/// Defines the SystemBarColor attached property.
@ -285,6 +285,11 @@ namespace Avalonia.Controls
/// </summary>
public event EventHandler? Closed;
/// <summary>
/// Gets or sets a method called when the TopLevel's scaling changes.
/// </summary>
public event EventHandler? ScalingChanged;
/// <summary>
/// Gets or sets the client size of the window.
/// </summary>
@ -428,14 +433,17 @@ namespace Avalonia.Controls
double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1;
/// <inheritdoc/>
double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1;
public double RenderScaling => PlatformImpl?.RenderScaling ?? 1;
IStyleHost IStyleHost.StylingParent => _globalStyles!;
/// <summary>
/// File System storage service used for file pickers and bookmarks.
/// </summary>
public IStorageProvider StorageProvider => _storageProvider
??= AvaloniaLocator.Current.GetService<IStorageProviderFactory>()?.CreateProvider(this)
?? PlatformImpl?.TryGetFeature<IStorageProvider>()
?? throw new InvalidOperationException("StorageProvider platform implementation is not available.");
?? new NoopStorageProvider();
public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature<IInsetsManager>();
@ -587,6 +595,7 @@ namespace Avalonia.Controls
protected virtual void HandleScalingChanged(double scaling)
{
LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
ScalingChanged?.Invoke(this, EventArgs.Empty);
}
private static bool TransparencyLevelsMatch (WindowTransparencyLevel requested, WindowTransparencyLevel received)

6
src/Avalonia.Controls/TreeViewItem.cs

@ -45,6 +45,7 @@ namespace Avalonia.Controls
private TreeView? _treeView;
private Control? _header;
private Control? _headerPresenter;
private int _level;
private bool _templateApplied;
private bool _deferredBringIntoViewFlag;
@ -255,15 +256,16 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_header is InputElement previousInputMethod)
if (_headerPresenter is InputElement previousInputMethod)
{
previousInputMethod.DoubleTapped -= HeaderDoubleTapped;
}
_header = e.NameScope.Find<Control>("PART_Header");
_headerPresenter = e.NameScope.Find<Control>("PART_HeaderPresenter");
_templateApplied = true;
if (_header is InputElement im)
if (_headerPresenter is InputElement im)
{
im.DoubleTapped += HeaderDoubleTapped;
}

28
src/Avalonia.Controls/Utils/RealizedStackElements.cs

@ -353,7 +353,10 @@ namespace Avalonia.Controls.Utils
for (var i = start; i < end; ++i)
{
if (_elements[i] is Control element)
{
_elements[i] = null;
recycleElement(element);
}
}
_elements.RemoveRange(start, end - start);
@ -389,10 +392,13 @@ namespace Avalonia.Controls.Utils
if (_elements is null || _elements.Count == 0)
return;
foreach (var e in _elements)
for (var i = 0; i < _elements.Count; i++)
{
if (e is not null)
if (_elements[i] is Control e)
{
_elements[i] = null;
recycleElement(e);
}
}
_startU = _firstIndex = 0;
@ -422,7 +428,10 @@ namespace Avalonia.Controls.Utils
for (var i = 0; i < endIndex; ++i)
{
if (_elements[i] is Control e)
{
_elements[i] = null;
recycleElement(e, i + FirstIndex);
}
}
_elements.RemoveRange(0, endIndex);
@ -453,7 +462,10 @@ namespace Avalonia.Controls.Utils
for (var i = startIndex; i < count; ++i)
{
if (_elements[i] is Control e)
{
_elements[i] = null;
recycleElement(e, i + FirstIndex);
}
}
_elements.RemoveRange(startIndex, _elements.Count - startIndex);
@ -470,13 +482,13 @@ namespace Avalonia.Controls.Utils
if (_elements is null || _elements.Count == 0)
return;
var i = FirstIndex;
foreach (var e in _elements)
for (var i = 0; i < _elements.Count; i++)
{
if (e is not null)
recycleElement(e, i);
++i;
if (_elements[i] is Control e)
{
_elements[i] = null;
recycleElement(e, i + FirstIndex);
}
}
_startU = _firstIndex = 0;

9
src/Avalonia.Controls/VirtualizingCarouselPanel.cs

@ -168,7 +168,13 @@ namespace Avalonia.Controls
protected internal override Control? ContainerFromIndex(int index)
{
return index == _realizedIndex ? _realized : null;
if (index < 0 || index >= Items.Count)
return null;
if (index == _realizedIndex)
return _realized;
if (Items[index] is Control c && c.GetValue(ItemIsOwnContainerProperty))
return c;
return null;
}
protected internal override IEnumerable<Control>? GetRealizedContainers()
@ -264,7 +270,6 @@ namespace Avalonia.Controls
if (controlItem.IsSet(ItemIsOwnContainerProperty))
{
controlItem.IsVisible = true;
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem;
}
else if (generator.IsItemItsOwnContainer(controlItem))

5
src/Avalonia.Controls/VirtualizingPanel.cs

@ -76,6 +76,11 @@ namespace Avalonia.Controls
/// The container for the item at the specified index within the item collection, if the
/// item is realized; otherwise, null.
/// </returns>
/// <remarks>
/// Note for implementors: if the item at the the specified index is an ItemIsOwnContainer
/// item that has previously been realized, then the item should be returned even if it
/// currently falls outside the realized viewport.
/// </remarks>
protected internal abstract Control? ContainerFromIndex(int index);
/// <summary>

48
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
@ -326,7 +325,17 @@ namespace Avalonia.Controls
return _realizedElements?.Elements.Where(x => x is not null)!;
}
protected internal override Control? ContainerFromIndex(int index) => _realizedElements?.GetElement(index);
protected internal override Control? ContainerFromIndex(int index)
{
if (index < 0 || index >= Items.Count)
return null;
if (_realizedElements?.GetElement(index) is { } realized)
return realized;
if (Items[index] is Control c && c.GetValue(ItemIsOwnContainerProperty))
return c;
return null;
}
protected internal override int IndexFromContainer(Control container) => _realizedElements?.GetIndex(container) ?? -1;
protected internal override Control? ScrollIntoView(int index)
@ -556,7 +565,6 @@ namespace Avalonia.Controls
GetItemIsOwnContainer(items, index) ??
GetRecycledElement(items, index) ??
CreateElement(items, index);
InvalidateHack(e);
return e;
}
@ -578,7 +586,6 @@ namespace Avalonia.Controls
if (controlItem.IsSet(ItemIsOwnContainerProperty))
{
controlItem.IsVisible = true;
generator.ItemContainerPrepared(controlItem, item, index);
return controlItem;
}
else if (generator.IsItemItsOwnContainer(controlItem))
@ -705,39 +712,6 @@ namespace Avalonia.Controls
}
}
private static void InvalidateHack(Control c)
{
bool HasInvalidations(Control c)
{
if (!c.IsMeasureValid)
return true;
for (var i = 0; i < c.VisualChildren.Count; ++i)
{
if (c.VisualChildren[i] is Control child)
{
if (!child.IsMeasureValid || HasInvalidations(child))
return true;
}
}
return false;
}
void Invalidate(Control c)
{
c.InvalidateMeasure();
for (var i = 0; i < c.VisualChildren.Count; ++i)
{
if (c.VisualChildren[i] is Control child)
Invalidate(child);
}
}
if (HasInvalidations(c))
Invalidate(c);
}
private void OnUnrealizedFocusedElementLostFocus(object? sender, RoutedEventArgs e)
{
if (_unrealizedFocusedElement is null || sender != _unrealizedFocusedElement)

6
src/Avalonia.Controls/Window.cs

@ -713,7 +713,7 @@ namespace Avalonia.Controls
Owner = owner;
owner?.AddChild(this, false);
SetWindowStartupLocation(owner?.PlatformImpl);
SetWindowStartupLocation(owner);
PlatformImpl?.Show(ShowActivated, false);
Renderer.Start();
@ -789,7 +789,7 @@ namespace Avalonia.Controls
Owner = owner;
owner.AddChild(this, true);
SetWindowStartupLocation(owner.PlatformImpl);
SetWindowStartupLocation(owner);
PlatformImpl?.Show(ShowActivated, true);
@ -870,7 +870,7 @@ namespace Avalonia.Controls
}
}
private void SetWindowStartupLocation(IWindowBaseImpl? owner = null)
private void SetWindowStartupLocation(Window? owner = null)
{
var startupLocation = WindowStartupLocation;

28
src/Avalonia.Controls/WindowBase.cs

@ -83,6 +83,19 @@ namespace Avalonia.Controls
/// <summary>
/// Occurs when the window is resized.
/// </summary>
/// <remarks>
/// Although this event is similar to the <see cref="Control.SizeChanged"/> event, they are
/// conceptually different:
///
/// - <see cref="Resized"/> is a window-level event, fired when a resize notification arrives
/// from the platform windowing subsystem. The event args contain details of the source of
/// the resize event in the <see cref="WindowResizedEventArgs.Reason"/> property. This
/// event is raised before layout has been run on the window's content.
/// - <see cref="Control.SizeChanged"/> is a layout-level event, fired when a layout pass
/// completes on a control. <see cref="Control.SizeChanged"/> is present on all controls
/// and is fired when the control's size changes for any reason, including a
/// <see cref="Resized"/> event in the case of a Window.
/// </remarks>
public event EventHandler<WindowResizedEventArgs>? Resized;
public new IWindowBaseImpl? PlatformImpl => (IWindowBaseImpl?) base.PlatformImpl;
@ -116,6 +129,11 @@ namespace Avalonia.Controls
set { SetValue(TopmostProperty, value); }
}
/// <summary>
/// Gets the scaling factor for Window positioning and sizing.
/// </summary>
public double DesktopScaling => PlatformImpl?.DesktopScaling ?? 1;
/// <summary>
/// Activates the window.
/// </summary>
@ -232,14 +250,16 @@ namespace Avalonia.Controls
{
FrameSize = PlatformImpl?.FrameSize;
if (ClientSize != clientSize)
var clientSizeChanged = ClientSize != clientSize;
ClientSize = clientSize;
OnResized(new WindowResizedEventArgs(clientSize, reason));
if (clientSizeChanged)
{
ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass();
Renderer.Resized(clientSize);
}
OnResized(new WindowResizedEventArgs(clientSize, reason));
}
/// <summary>

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

@ -179,17 +179,9 @@ namespace Avalonia.DesignerSupport.Remote
var entryPoint = asm.EntryPoint;
if (entryPoint == null)
throw Die($"Assembly {args.AppPath} doesn't have an entry point");
var builderMethod = entryPoint.DeclaringType.GetMethod(
BuilderMethodName,
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
null,
Array.Empty<Type>(),
null);
if (builderMethod == null)
throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
Log($"Obtaining AppBuilder instance from {entryPoint.DeclaringType!.FullName}");
var appBuilder = AppBuilder.Configure(entryPoint.DeclaringType);
Design.IsDesignMode = true;
Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}");
var appBuilder = builderMethod.Invoke(null, null);
Log($"Initializing application in design mode");
var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer));
transport = initializer.ConfigureApp(transport, args, appBuilder);

4
src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs

@ -37,7 +37,7 @@ namespace Avalonia.Diagnostics.Controls
_ => null
};
RequestedThemeVariant = application.RequestedThemeVariant;
SetCurrentValue(RequestedThemeVariantProperty, application.RequestedThemeVariant);
_application.PropertyChanged += ApplicationOnPropertyChanged;
}
@ -131,7 +131,7 @@ namespace Avalonia.Diagnostics.Controls
{
if (e.Property == Avalonia.Application.RequestedThemeVariantProperty)
{
RequestedThemeVariant = e.GetNewValue<ThemeVariant>();
SetCurrentValue(RequestedThemeVariantProperty, e.GetNewValue<ThemeVariant>());
}
}

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

@ -1,5 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
@ -21,6 +27,8 @@ namespace Avalonia.Diagnostics.ViewModels
SettersFilter.RefreshFilter += (s, e) => Details?.UpdateStyleFilters();
}
public event EventHandler<string>? ClipboardCopyRequested;
public MainViewModel MainView { get; }
public FilterViewModel PropertiesFilter { get; }
@ -106,6 +114,105 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public void CopySelector()
{
var currentVisual = SelectedNode?.Visual as Visual;
if (currentVisual is not null)
{
var selector = GetVisualSelector(currentVisual);
ClipboardCopyRequested?.Invoke(this, selector);
}
}
public void CopySelectorFromTemplateParent()
{
var parts = new List<string>();
var currentVisual = SelectedNode?.Visual as Visual;
while (currentVisual is not null)
{
parts.Add(GetVisualSelector(currentVisual));
currentVisual = currentVisual.TemplatedParent as Visual;
}
if (parts.Any())
{
parts.Reverse();
var selector = string.Join(" /template/ ", parts);
ClipboardCopyRequested?.Invoke(this, selector);
}
}
public void ExpandRecursively()
{
if (SelectedNode is { } selectedNode)
{
ExpandNode(selectedNode);
var stack = new Stack<TreeNode>();
stack.Push(selectedNode);
while (stack.Count > 0)
{
var item = stack.Pop();
item.IsExpanded = true;
foreach (var child in item.Children)
{
stack.Push(child);
}
}
}
}
public void CollapseChildren()
{
if (SelectedNode is { } selectedNode)
{
var stack = new Stack<TreeNode>();
stack.Push(selectedNode);
while (stack.Count > 0)
{
var item = stack.Pop();
item.IsExpanded = false;
foreach (var child in item.Children)
{
stack.Push(child);
}
}
}
}
public void CaptureNodeScreenshot()
{
MainView.Shot(null);
}
public void BringIntoView()
{
(SelectedNode?.Visual as Control)?.BringIntoView();
}
public void Focus()
{
(SelectedNode?.Visual as Control)?.Focus();
}
private static string GetVisualSelector(Visual visual)
{
var name = string.IsNullOrEmpty(visual.Name) ? "" : $"#{visual.Name}";
var classes = string.Concat(visual.Classes
.Where(c => !c.StartsWith(":"))
.Select(c => '.' + c));
var typeName = ((IStyleable)visual).StyleKey.Name;
return $"{typeName}{name}{classes}";
}
private void ExpandNode(TreeNode? node)
{
if (node != null)

14
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

@ -26,6 +26,20 @@
<Setter Property="Background" Value="Transparent" />
</Style>
</TreeView.Styles>
<TreeView.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Copy">
<MenuItem Header="Copy individual node selector" Command="{Binding CopySelector}" />
<MenuItem Header="Copy selector from template parent" Command="{Binding CopySelectorFromTemplateParent}" />
</MenuItem>
<MenuItem Header="-" />
<MenuItem Header="Expand recursively" Command="{Binding ExpandRecursively}" />
<MenuItem Header="Collapse children" Command="{Binding CollapseChildren}" />
<MenuItem Header="Capture node screenshot" Command="{Binding CaptureNodeScreenshot}" />
<MenuItem Header="Bring into view" Command="{Binding BringIntoView}" />
<MenuItem Header="Focus" Command="{Binding Focus}" />
</MenuFlyout>
</TreeView.ContextFlyout>
</TreeView>
<GridSplitter Background="{DynamicResource ThemeControlMidBrush}" Width="1" Grid.Column="1"/>

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

Loading…
Cancel
Save