Browse Source

Merge remote-tracking branch 'origin/refactor_pointerover_backportable' into Stylus

# Conflicts:
#	src/Avalonia.Input/MouseDevice.cs
Stylus
Max Katz 5 years ago
parent
commit
c0a1a6d0c7
  1. 30
      Avalonia.sln
  2. 1
      NuGet.Config
  3. 26
      azure-pipelines.yml
  4. 9
      build/DevAnalyzers.props
  5. 6
      build/HarfBuzzSharp.props
  6. 6
      build/SkiaSharp.props
  7. 3
      global.json
  8. 10
      native/Avalonia.Native/src/OSX/window.mm
  9. 42
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  10. 28
      samples/ControlCatalog.Web/LinkerConfig.xml
  11. 8
      samples/ControlCatalog/MainView.xaml
  12. 9
      samples/ControlCatalog/MainView.xaml.cs
  13. 32
      samples/ControlCatalog/Pages/ButtonsPage.xaml
  14. 5
      samples/ControlCatalog/Pages/CanvasPage.xaml
  15. 3
      samples/ControlCatalog/Pages/PointersPage.cs
  16. 2
      samples/ControlCatalog/Pages/ScreenPage.cs
  17. 8
      samples/IntegrationTestApp/MainWindow.axaml
  18. 2
      samples/IntegrationTestApp/MainWindow.axaml.cs
  19. 3
      samples/RenderDemo/MainWindow.xaml
  20. 7
      samples/RenderDemo/Pages/TextFormatterPage.axaml
  21. 118
      samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
  22. 4
      samples/RenderDemo/RenderDemo.csproj
  23. 18
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  24. 6
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  25. 1
      src/Avalonia.Animation/Avalonia.Animation.csproj
  26. 2
      src/Avalonia.Animation/Easing/Easing.cs
  27. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  28. 2
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  29. 2
      src/Avalonia.Base/Collections/AvaloniaList.cs
  30. 2
      src/Avalonia.Base/Data/Core/PropertyPath.cs
  31. 2
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  32. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  33. 2
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  34. 1
      src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
  35. 2
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  36. 19
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  37. 26
      src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs
  38. 2
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  39. 2
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  40. 24
      src/Avalonia.Controls/ApiCompatBaseline.txt
  41. 2
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  42. 8
      src/Avalonia.Controls/AutoCompleteBox.cs
  43. 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  44. 123
      src/Avalonia.Controls/Button.cs
  45. 2
      src/Avalonia.Controls/ButtonSpinner.cs
  46. 21
      src/Avalonia.Controls/Calendar/Calendar.cs
  47. 5
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  48. 5
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  49. 4
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  50. 1
      src/Avalonia.Controls/Chrome/TitleBar.cs
  51. 21
      src/Avalonia.Controls/ComboBox.cs
  52. 2
      src/Avalonia.Controls/ContentControl.cs
  53. 92
      src/Avalonia.Controls/Control.cs
  54. 9
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  55. 20
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  56. 31
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  57. 12
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  58. 17
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  59. 167
      src/Avalonia.Controls/Documents/TextElement.cs
  60. 28
      src/Avalonia.Controls/DropDown.cs
  61. 15
      src/Avalonia.Controls/DropDownButton.cs
  62. 12
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  63. 67
      src/Avalonia.Controls/HotkeyManager.cs
  64. 2
      src/Avalonia.Controls/Image.cs
  65. 2
      src/Avalonia.Controls/ListBox.cs
  66. 15
      src/Avalonia.Controls/MenuItem.cs
  67. 1
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  68. 5
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  69. 14
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  70. 168
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  71. 4
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  72. 71
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  73. 2
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  74. 30
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  75. 5
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  76. 9
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  77. 4
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  78. 10
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  79. 31
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  80. 1
      src/Avalonia.Controls/ProgressBar.cs
  81. 3
      src/Avalonia.Controls/ScrollViewer.cs
  82. 4
      src/Avalonia.Controls/Selection/InternalSelectionModel.cs
  83. 126
      src/Avalonia.Controls/Shapes/Rectangle.cs
  84. 3
      src/Avalonia.Controls/Slider.cs
  85. 34
      src/Avalonia.Controls/SplitButton/SplitButton.cs
  86. 1
      src/Avalonia.Controls/SplitView.cs
  87. 2
      src/Avalonia.Controls/TabControl.cs
  88. 297
      src/Avalonia.Controls/TextBlock.cs
  89. 112
      src/Avalonia.Controls/TextBox.cs
  90. 2
      src/Avalonia.Controls/ToggleSwitch.cs
  91. 33
      src/Avalonia.Controls/TopLevel.cs
  92. 2
      src/Avalonia.Controls/TreeView.cs
  93. 1
      src/Avalonia.Controls/TreeViewItem.cs
  94. 4
      src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs
  95. 7
      src/Avalonia.Controls/Window.cs
  96. 24
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json
  97. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  98. 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  99. 5
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  100. 4
      src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml

30
Avalonia.sln

@ -117,6 +117,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Base.props = build\Base.props build\Base.props = build\Base.props
build\Binding.props = build\Binding.props build\Binding.props = build\Binding.props
build\CoreLibraries.props = build\CoreLibraries.props build\CoreLibraries.props = build\CoreLibraries.props
build\DevAnalyzers.props = build\DevAnalyzers.props
build\EmbedXaml.props = build\EmbedXaml.props build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
@ -234,7 +235,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PlatformSupport",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.PlatformSupport.UnitTests", "tests\Avalonia.PlatformSupport.UnitTests\Avalonia.PlatformSupport.UnitTests.csproj", "{CE910927-CE5A-456F-BC92-E4C757354A5C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PlatformSupport.UnitTests", "tests\Avalonia.PlatformSupport.UnitTests\Avalonia.PlatformSupport.UnitTests.csproj", "{CE910927-CE5A-456F-BC92-E4C757354A5C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevAnalyzers", "src\tools\DevAnalyzers\DevAnalyzers.csproj", "{2B390431-288C-435C-BB6B-A374033BD8D1}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -2238,6 +2241,30 @@ Global
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhone.Build.0 = Release|Any CPU {CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhone.Build.0 = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {CE910927-CE5A-456F-BC92-E4C757354A5C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.AppStore|iPhone.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Debug|iPhone.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|Any CPU.Build.0 = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhone.ActiveCfg = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhone.Build.0 = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -2303,6 +2330,7 @@ Global
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{CE910927-CE5A-456F-BC92-E4C757354A5C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {CE910927-CE5A-456F-BC92-E4C757354A5C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
NuGet.Config

@ -5,5 +5,6 @@
<clear /> <clear />
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" /> <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="dotnet-eng" value="https://nuget.avaloniaui.net/repository/avalonia-devdeps/index.json" protocolVersion="3" /> <add key="dotnet-eng" value="https://nuget.avaloniaui.net/repository/avalonia-devdeps/index.json" protocolVersion="3" />
<add key="skiasharp" value="https://aka.ms/skiasharp-eap/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

26
azure-pipelines.yml

@ -31,14 +31,14 @@ jobs:
vmImage: 'ubuntu-20.04' vmImage: 'ubuntu-20.04'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414' displayName: 'Use .NET Core SDK 3.1.418'
inputs: inputs:
version: 3.1.414 version: 3.1.418
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100' displayName: 'Use .NET Core SDK 6.0.202'
inputs: inputs:
version: 6.0.200 version: 6.0.202
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Run Build' displayName: 'Run Build'
@ -62,14 +62,14 @@ jobs:
vmImage: 'macOS-10.15' vmImage: 'macOS-10.15'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414' displayName: 'Use .NET Core SDK 3.1.418'
inputs: inputs:
version: 3.1.414 version: 3.1.418
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100' displayName: 'Use .NET Core SDK 6.0.202'
inputs: inputs:
version: 6.0.200 version: 6.0.202
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Mono 5.18' displayName: 'Install Mono 5.18'
@ -134,20 +134,20 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)' SolutionDir: '$(Build.SourcesDirectory)'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414' displayName: 'Use .NET Core SDK 3.1.418'
inputs: inputs:
version: 3.1.414 version: 3.1.418
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100' displayName: 'Use .NET Core SDK 6.0.202'
inputs: inputs:
version: 6.0.200 version: 6.0.202
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Workloads' displayName: 'Install Workloads'
inputs: inputs:
script: | script: |
dotnet workload install --no-cache --disable-parallel android ios --skip-manifest-update --source "https://api.nuget.org/v3/index.json" dotnet workload install android ios
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Nuke' displayName: 'Install Nuke'

9
build/DevAnalyzers.props

@ -0,0 +1,9 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\src\Tools\DevAnalyzers\DevAnalyzers.csproj"
PrivateAssets="all"
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0"/>
</ItemGroup>
</Project>

6
build/HarfBuzzSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.8.2-preview.209" /> <PackageReference Include="HarfBuzzSharp" Version="2.8.2-preview.254" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2-preview.209" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2-preview.254" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2-preview.209"/> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2-preview.254"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

6
build/SkiaSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.209" /> <PackageReference Include="SkiaSharp" Version="2.88.0-preview.254" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0-preview.209" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0-preview.254" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0-preview.209"/> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0-preview.254"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

3
global.json

@ -1,11 +1,10 @@
{ {
"sdk": { "sdk": {
"version": "6.0.200", "version": "6.0.202",
"rollForward": "latestFeature" "rollForward": "latestFeature"
}, },
"msbuild-sdks": { "msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43", "Microsoft.Build.Traversal": "1.0.43",
"Xamarin.Legacy.Sdk": "0.1.2-alpha6",
"MSBuild.Sdk.Extras": "3.0.22", "MSBuild.Sdk.Extras": "3.0.22",
"AggregatePackage.NuGet.Sdk" : "0.1.12" "AggregatePackage.NuGet.Sdk" : "0.1.12"
} }

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

@ -2461,6 +2461,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
if(_parent != nullptr) if(_parent != nullptr)
{ {
auto cparent = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(cparent != nullptr)
{
if(cparent->WindowState() == Maximized)
{
cparent->SetWindowState(Normal);
}
}
_parent->GetPosition(&position); _parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position); _parent->BaseEvents->PositionChanged(position);
} }

42
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<WasmBuildNative>True</WasmBuildNative> <!--Temporal hack that fixes compilation in VS-->
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<EmccTotalMemory>16777216</EmccTotalMemory>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<BlazorLinkerDescriptor Include="LinkerConfig.xml" />
</ItemGroup>
<!-- In debug, make builds faster by reducing optimizations --> <!-- In debug, make builds faster by reducing optimizations -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<WasmNativeStrip>false</WasmNativeStrip> <WasmNativeStrip>false</WasmNativeStrip>
@ -23,19 +22,36 @@
<EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag> <EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag> <EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
<RunAOTCompilation>false</RunAOTCompilation> <RunAOTCompilation>false</RunAOTCompilation>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<InvariantGlobalization>true</InvariantGlobalization>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<UseNativeHttpHandler>true</UseNativeHttpHandler>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0"/> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all"/> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.targets" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.CompilationTuning.props" />
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj"/> <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj"/> <ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.targets" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.CompilationTuning.props" />
</Project> </Project>

28
samples/ControlCatalog.Web/LinkerConfig.xml

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
This file specifies which parts of the BCL or Blazor packages must not be
stripped by the IL Linker even if they aren't referenced by user code.
-->
<linker>
<assembly fullname="mscorlib">
<!--
Preserve the methods in WasmRuntime because its methods are called by
JavaScript client-side code to implement timers.
Fixes: https://github.com/dotnet/blazor/issues/239
-->
<type fullname="System.Threading.WasmRuntime"/>
</assembly>
<assembly fullname="System.Core">
<!--
System.Linq.Expressions* is required by Json.NET and any
expression.Compile caller. The assembly isn't stripped.
-->
<type fullname="System.Linq.Expressions*"/>
</assembly>
<!--
In this example, the app's entry point assembly is listed. The assembly
isn't stripped by the IL Linker.
-->
<assembly fullname="ControlCatalog" preserve="All" />
</linker>

8
samples/ControlCatalog/MainView.xaml

@ -190,6 +190,14 @@
<WindowTransparencyLevel>Mica</WindowTransparencyLevel> <WindowTransparencyLevel>Mica</WindowTransparencyLevel>
</ComboBox.Items> </ComboBox.Items>
</ComboBox> </ComboBox>
<ComboBox x:Name="FlowDirection"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBox.Items>
<FlowDirection>LeftToRight</FlowDirection>
<FlowDirection>RightToLeft</FlowDirection>
</ComboBox.Items>
</ComboBox>
<ComboBox HorizontalAlignment="Stretch" <ComboBox HorizontalAlignment="Stretch"
Items="{Binding WindowStates}" Items="{Binding WindowStates}"
SelectedItem="{Binding WindowState}" /> SelectedItem="{Binding WindowState}" />

9
samples/ControlCatalog/MainView.xaml.cs

@ -76,6 +76,15 @@ namespace ControlCatalog
} }
}; };
var flowDirections = this.Find<ComboBox>("FlowDirection");
flowDirections.SelectionChanged += (sender, e) =>
{
if (flowDirections.SelectedItem is FlowDirection flowDirection)
{
this.FlowDirection = flowDirection;
}
};
var decorations = this.Find<ComboBox>("Decorations"); var decorations = this.Find<ComboBox>("Decorations");
decorations.SelectionChanged += (sender, e) => decorations.SelectionChanged += (sender, e) =>
{ {

32
samples/ControlCatalog/Pages/ButtonsPage.xaml

@ -147,6 +147,35 @@
</StackPanel> </StackPanel>
</Border> </Border>
<!-- DropDownButton -->
<Border Classes="header-border">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Text="DropDownButton"
Classes="header" />
<TextBlock TextWrapping="Wrap">A button with an added drop-down chevron to visually indicate it has a flyout with additional actions.</TextBlock>
</StackPanel>
</Border>
<Border Classes="thin"
Padding="15">
<StackPanel Orientation="Vertical"
Spacing="8">
<DropDownButton Flyout="{StaticResource SharedMenuFlyout}">
<TextBlock Text="Drop Down Button" />
</DropDownButton>
<DropDownButton Padding="0,0,8,0">
<Border Background="Teal"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="32"
Width="32" />
</DropDownButton>
<DropDownButton IsEnabled="False">Disabled</DropDownButton>
<DropDownButton />
</StackPanel>
</Border>
<!-- SplitButton --> <!-- SplitButton -->
<Border Classes="header-border"> <Border Classes="header-border">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
@ -169,7 +198,8 @@
<TextBlock Text="Disabled" /> <TextBlock Text="Disabled" />
</SplitButton> </SplitButton>
<SplitButton Flyout="{StaticResource SharedMenuFlyout}" <SplitButton Flyout="{StaticResource SharedMenuFlyout}"
Content="Re-themed"> Content="Re-themed"
Foreground="White">
<SplitButton.Styles> <SplitButton.Styles>
<Style> <Style>
<Style.Resources> <Style.Resources>

5
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -4,7 +4,7 @@
<StackPanel Orientation="Vertical" Spacing="4"> <StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock> <TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>
<Canvas Background="Yellow" Width="300" Height="400"> <Canvas Background="Yellow" Width="300" Height="400">
<Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31"> <Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31" RadiusX="10" RadiusY="10">
<Rectangle.OpacityMask> <Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops> <LinearGradientBrush.GradientStops>
@ -12,8 +12,9 @@
<GradientStop Offset="1" Color="Transparent"/> <GradientStop Offset="1" Color="Transparent"/>
</LinearGradientBrush.GradientStops> </LinearGradientBrush.GradientStops>
</LinearGradientBrush> </LinearGradientBrush>
</Rectangle.OpacityMask> </Rectangle.OpacityMask>
</Rectangle> </Rectangle>
<Rectangle Fill="hsva(240, 83%, 73%, 90%)" Stroke="hsl(5, 85%, 85%)" StrokeThickness="2" Width="40" Height="20" Canvas.Left="150" Canvas.Top="10" RadiusX="10" RadiusY="5" />
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/> <Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/>
<Path Fill="Orange" Data="M 0,0 c 0,0 50,0 50,-50 c 0,0 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/> <Path Fill="Orange" Data="M 0,0 c 0,0 50,0 50,-50 c 0,0 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250"> <Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250">

3
samples/ControlCatalog/Pages/PointersPage.cs

@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
@ -133,7 +134,7 @@ public class PointersPage : Decorator
{ {
public PointerIntermediatePointsTab() public PointerIntermediatePointsTab()
{ {
this[TextBlock.ForegroundProperty] = Brushes.Black; this[TextElement.ForegroundProperty] = Brushes.Black;
var slider = new Slider var slider = new Slider
{ {
Margin = new Thickness(5), Margin = new Thickness(5),

2
samples/ControlCatalog/Pages/ScreenPage.cs

@ -13,6 +13,8 @@ namespace ControlCatalog.Pages
{ {
private double _leftMost; private double _leftMost;
protected override bool BypassFlowDirectionPolicies => true;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);

8
samples/IntegrationTestApp/MainWindow.axaml

@ -82,12 +82,16 @@
<Menu DockPanel.Dock="Top"> <Menu DockPanel.Dock="Top">
<MenuItem Name="RootMenuItem" Header="_Root"> <MenuItem Name="RootMenuItem" Header="_Root">
<MenuItem Name="Child1MenuItem" Header="_Child 1" InputGesture="Ctrl+O" Click="MenuClicked"/> <MenuItem Name="Child1MenuItem" Header="_Child 1" InputGesture="Ctrl+O" Click="MenuClicked"/>
<MenuItem Name="Child2MenuItem" Header="_Child 1"> <MenuItem Name="Child2MenuItem" Header="C_hild 2">
<MenuItem Name="GrandchildMenuItem" Header="_Grandchild" Click="MenuClicked"/> <MenuItem Name="GrandchildMenuItem" Header="_Grandchild" Click="MenuClicked"/>
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
</Menu> </Menu>
<TextBlock Name="ClickedMenuItem">None</TextBlock> <StackPanel>
<TextBlock Name="ClickedMenuItem">None</TextBlock>
<Button Name="MenuClickedMenuItemReset">Reset</Button>
<TextBox Name="MenuFocusTest"/>
</StackPanel>
</DockPanel> </DockPanel>
</TabItem> </TabItem>
</TabControl> </TabControl>

2
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -62,6 +62,8 @@ namespace IntegrationTestApp
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = 0; this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = 0;
if (source?.Name == "ListBoxSelectionClear") if (source?.Name == "ListBoxSelectionClear")
this.FindControl<ListBox>("BasicListBox").SelectedIndex = -1; this.FindControl<ListBox>("BasicListBox").SelectedIndex = -1;
if (source?.Name == "MenuClickedMenuItemReset")
this.FindControl<TextBlock>("ClickedMenuItem").Text = "None";
} }
} }
} }

3
samples/RenderDemo/MainWindow.xaml

@ -60,6 +60,9 @@
<TabItem Header="FormattedText"> <TabItem Header="FormattedText">
<pages:FormattedTextPage /> <pages:FormattedTextPage />
</TabItem> </TabItem>
<TabItem Header="TextFormatter">
<pages:TextFormatterPage />
</TabItem>
<TabItem Header="LineBounds"> <TabItem Header="LineBounds">
<pages:LineBoundsPage /> <pages:LineBoundsPage />
</TabItem> </TabItem>

7
samples/RenderDemo/Pages/TextFormatterPage.axaml

@ -0,0 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.TextFormatterPage">
</UserControl>

118
samples/RenderDemo/Pages/TextFormatterPage.axaml.cs

@ -0,0 +1,118 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
namespace RenderDemo.Pages
{
public class TextFormatterPage : UserControl
{
private TextLine _textLine;
public TextFormatterPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public override void Render(DrawingContext context)
{
_textLine?.Draw(context, new Point());
}
protected override Size MeasureOverride(Size availableSize)
{
var defaultRunProperties = new GenericTextRunProperties(Typeface.Default, foregroundBrush: Brushes.Black,
baselineAlignment: BaselineAlignment.Center);
var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties);
var control = new Button { Content = new TextBlock { Text = "ClickMe" } };
Content = control;
var textSource = new CustomTextSource(control, defaultRunProperties);
control.Measure(Size.Infinity);
_textLine =
TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties);
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
var currentX = 0d;
foreach (var textRun in _textLine.TextRuns)
{
if (textRun is ControlRun controlRun)
{
controlRun.Control.Arrange(new Rect(new Point(currentX, 0), controlRun.Size));
}
if (textRun is DrawableTextRun drawableTextRun)
{
currentX += drawableTextRun.Size.Width;
}
}
return finalSize;
}
private class CustomTextSource : ITextSource
{
private readonly Control _control;
private readonly TextRunProperties _defaultProperties;
private readonly string _text = "<-Hello World->";
public CustomTextSource(Control control, TextRunProperties defaultProperties)
{
_control = control;
_defaultProperties = defaultProperties;
}
public TextRun? GetTextRun(int textSourceIndex)
{
if (textSourceIndex >= _text.Length * 2 + TextRun.DefaultTextSourceLength)
{
return null;
}
if (textSourceIndex == _text.Length)
{
return new ControlRun(_control, _defaultProperties);
}
return new TextCharacters(_text.AsMemory(), _defaultProperties);
}
}
private class ControlRun : DrawableTextRun
{
private readonly Control _control;
public ControlRun(Control control, TextRunProperties properties)
{
_control = control;
Properties = properties;
}
public Control Control => _control;
public override Size Size => _control.DesiredSize;
public override double Baseline => 0;
public override TextRunProperties? Properties { get; }
public override void Draw(DrawingContext drawingContext, Point origin)
{
// noop
}
}
}
}

4
samples/RenderDemo/RenderDemo.csproj

@ -5,6 +5,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\..\src\Avalonia.Visuals\Rendering\SceneGraph\LineBoundsHelper.cs" Link="LineBoundsHelper.cs" /> <Compile Include="..\..\src\Avalonia.Visuals\Rendering\SceneGraph\LineBoundsHelper.cs" Link="LineBoundsHelper.cs" />
<Compile Update="Pages\TextFormatterPage.axaml.cs">
<DependentUpon>TextFormatterPage.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />

18
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -195,9 +195,9 @@
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}" Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplate="{TemplateBinding HeaderTemplate}"
TextBlock.FontFamily="{TemplateBinding FontFamily}" TextElement.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}" TextElement.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" /> TextElement.FontWeight="{TemplateBinding FontWeight}" />
</Panel> </Panel>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
@ -216,25 +216,25 @@
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}" CornerRadius="{TemplateBinding CornerRadius}"
TextBlock.FontFamily="{TemplateBinding FontFamily}" TextElement.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}" TextElement.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" /> TextElement.FontWeight="{TemplateBinding FontWeight}" />
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="catalog|HamburgerMenu > TabItem /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton /template/ ContentPresenter"> <Style Selector="catalog|HamburgerMenu > TabItem /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton /template/ ContentPresenter">
<Setter Property="Border.Background" Value="Transparent" /> <Setter Property="Border.Background" Value="Transparent" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemBaseHighColor}" /> <Setter Property="TextElement.Foreground" Value="{DynamicResource SystemBaseHighColor}" />
</Style> </Style>
<Style Selector="catalog|HamburgerMenu > TabItem:pointerover /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pointerover /template/ ContentPresenter"> <Style Selector="catalog|HamburgerMenu > TabItem:pointerover /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" /> <Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" /> <Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" /> <Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style> </Style>
<Style Selector="catalog|HamburgerMenu > TabItem:pressed /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pressed /template/ ContentPresenter"> <Style Selector="catalog|HamburgerMenu > TabItem:pressed /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" /> <Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" /> <Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" /> <Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style> </Style>
<Style Selector=":is(Button).NavigationButton:pressed"> <Style Selector=":is(Button).NavigationButton:pressed">
<Setter Property="RenderTransform" Value="none" /> <Setter Property="RenderTransform" Value="none" />

6
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -1,7 +1,6 @@
<Project Sdk="Xamarin.Legacy.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0-android</TargetFrameworks> <TargetFramework>net6.0-android</TargetFramework>
<TargetFrameworks Condition="'$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);monoandroid11.0</TargetFrameworks>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> <SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver> <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
@ -16,4 +15,5 @@
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\..\build\DevAnalyzers.props" />
</Project> </Project>

1
src/Avalonia.Animation/Avalonia.Animation.csproj

@ -11,4 +11,5 @@
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project> </Project>

2
src/Avalonia.Animation/Easing/Easing.cs

@ -39,7 +39,7 @@ namespace Avalonia.Animation.Easings
var derivedTypes = typeof(Easing).Assembly.GetTypes() var derivedTypes = typeof(Easing).Assembly.GetTypes()
.Where(p => p.Namespace == s_thisType.Namespace) .Where(p => p.Namespace == s_thisType.Namespace)
.Where(p => p.IsSubclassOf(s_thisType)) .Where(p => p.IsSubclassOf(s_thisType))
.Select(p => p).ToList(); .Select(p => p);
foreach (var easingType in derivedTypes) foreach (var easingType in derivedTypes)
_easingTypes.Add(easingType.Name, easingType); _easingTypes.Add(easingType.Name, easingType);

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -12,4 +12,5 @@
<Import Project="..\..\build\System.Memory.props" /> <Import Project="..\..\build\System.Memory.props" />
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project> </Project>

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

@ -123,7 +123,7 @@ namespace Avalonia.Collections
{ {
var e = new NotifyCollectionChangedEventArgs( var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, NotifyCollectionChangedAction.Remove,
old.ToList(), old.ToArray(),
-1); -1);
CollectionChanged(this, e); CollectionChanged(this, e);
} }

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

@ -222,7 +222,7 @@ namespace Avalonia.Collections
{ {
var e = ResetBehavior == ResetBehavior.Reset ? var e = ResetBehavior == ResetBehavior.Reset ?
EventArgsCache.ResetCollectionChanged : EventArgsCache.ResetCollectionChanged :
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToList(), 0); new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToArray(), 0);
_inner.Clear(); _inner.Clear();

2
src/Avalonia.Base/Data/Core/PropertyPath.cs

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core
public PropertyPath(IEnumerable<IPropertyPathElement> elements) public PropertyPath(IEnumerable<IPropertyPathElement> elements)
{ {
Elements = elements.ToList(); Elements = elements.ToArray();
} }
} }

2
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@ -23,7 +23,7 @@ namespace Avalonia.Utilities
var assetDoc = XDocument.Load(stream); var assetDoc = XDocument.Load(stream);
XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value; XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value;
List<AvaloniaResourcesIndexEntry> entries= List<AvaloniaResourcesIndexEntry> entries =
(from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry") (from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry")
select new AvaloniaResourcesIndexEntry select new AvaloniaResourcesIndexEntry
{ {

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -83,6 +83,9 @@
<Compile Include="../Avalonia.Visuals/Media/Color.cs"> <Compile Include="../Avalonia.Visuals/Media/Color.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="../Avalonia.Visuals/Media/HslColor.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Visuals/Media/HsvColor.cs"> <Compile Include="../Avalonia.Visuals/Media/HsvColor.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>

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

@ -105,7 +105,7 @@ namespace Avalonia.Build.Tasks
{ {
var typeToXamlIndex = new Dictionary<string, string>(); var typeToXamlIndex = new Dictionary<string, string>();
foreach (var s in sources.ToList()) foreach (var s in sources.ToArray())
{ {
if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml")) if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml"))
{ {

1
src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj

@ -23,4 +23,5 @@
<Import Project="..\..\build\JetBrains.Annotations.props" /> <Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project> </Project>

2
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -3946,7 +3946,7 @@ namespace Avalonia.Collections
{ {
sort.Initialize(itemType); sort.Initialize(itemType);
if(seq is IOrderedEnumerable<object> orderedEnum) if (seq is IOrderedEnumerable<object> orderedEnum)
{ {
seq = sort.ThenBy(orderedEnum); seq = sort.ThenBy(orderedEnum);
} }

19
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -2060,6 +2060,25 @@ namespace Avalonia.Controls
forceHorizontalScroll: true); forceHorizontalScroll: true);
} }
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (DataConnection.DataSource != null && !DataConnection.EventsWired)
{
DataConnection.WireEvents(DataConnection.DataSource);
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
// When wired to INotifyCollectionChanged, the DataGrid will be cleaned up by GC
if (DataConnection.DataSource != null && DataConnection.EventsWired)
{
DataConnection.UnWireEvents(DataConnection.DataSource);
}
}
/// <summary> /// <summary>
/// Arranges the content of the <see cref="T:Avalonia.Controls.DataGridRow" />. /// Arranges the content of the <see cref="T:Avalonia.Controls.DataGridRow" />.

26
src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs

@ -10,6 +10,7 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Controls.Documents;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -32,7 +33,7 @@ namespace Avalonia.Controls
/// Identifies the FontFamily dependency property. /// Identifies the FontFamily dependency property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty = public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<DataGridTextColumn>(); TextElement.FontFamilyProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font name. /// Gets or sets the font name.
@ -47,7 +48,7 @@ namespace Avalonia.Controls
/// Identifies the FontSize dependency property. /// Identifies the FontSize dependency property.
/// </summary> /// </summary>
public static readonly AttachedProperty<double> FontSizeProperty = public static readonly AttachedProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<DataGridTextColumn>(); TextElement.FontSizeProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font size. /// Gets or sets the font size.
@ -64,7 +65,7 @@ namespace Avalonia.Controls
/// Identifies the FontStyle dependency property. /// Identifies the FontStyle dependency property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty = public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<DataGridTextColumn>(); TextElement.FontStyleProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font style. /// Gets or sets the font style.
@ -79,7 +80,7 @@ namespace Avalonia.Controls
/// Identifies the FontWeight dependency property. /// Identifies the FontWeight dependency property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty = public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<DataGridTextColumn>(); TextElement.FontWeightProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font weight or thickness. /// Gets or sets the font weight or thickness.
@ -90,11 +91,26 @@ namespace Avalonia.Controls
set => SetValue(FontWeightProperty, value); set => SetValue(FontWeightProperty, value);
} }
/// <summary>
/// Identifies the FontStretch dependency property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font weight or thickness.
/// </summary>
public FontStretch FontStretch
{
get => GetValue(FontStretchProperty);
set => SetValue(FontStretchProperty, value);
}
/// <summary> /// <summary>
/// Identifies the Foreground dependency property. /// Identifies the Foreground dependency property.
/// </summary> /// </summary>
public static readonly AttachedProperty<IBrush> ForegroundProperty = public static readonly AttachedProperty<IBrush> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<DataGridTextColumn>(); TextElement.ForegroundProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets a brush that describes the foreground of the column cells. /// Gets or sets a brush that describes the foreground of the column cells.

2
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@ -17,7 +17,7 @@
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}" TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>

2
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -124,7 +124,7 @@
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}" <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}" TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />

24
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -1,4 +1,6 @@
Compat issues with assembly Avalonia.Controls: Compat issues with assembly Avalonia.Controls:
TypesMustExist : Type 'Avalonia.Controls.DropDown' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Controls.DropDownItem' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
@ -29,6 +31,26 @@ MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChang
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontFamily> Avalonia.AttachedProperty<Avalonia.Media.FontFamily> Avalonia.Controls.TextBlock.FontFamilyProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontStyle> Avalonia.AttachedProperty<Avalonia.Media.FontStyle> Avalonia.Controls.TextBlock.FontStyleProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.AttachedProperty<Avalonia.Media.FontWeight> Avalonia.Controls.TextBlock.FontWeightProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<Avalonia.Media.IBrush> Avalonia.AttachedProperty<Avalonia.Media.IBrush> Avalonia.Controls.TextBlock.ForegroundProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AttachedProperty<System.Double> Avalonia.AttachedProperty<System.Double> Avalonia.Controls.TextBlock.FontSizeProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<Avalonia.Media.TextAlignment> Avalonia.StyledProperty<Avalonia.Media.TextAlignment> Avalonia.Controls.TextBlock.TextAlignmentProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<Avalonia.Media.TextTrimming> Avalonia.StyledProperty<Avalonia.Media.TextTrimming> Avalonia.Controls.TextBlock.TextTrimmingProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<Avalonia.Media.TextWrapping> Avalonia.StyledProperty<Avalonia.Media.TextWrapping> Avalonia.Controls.TextBlock.TextWrappingProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.TextBlock.LineHeightProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Int32> Avalonia.StyledProperty<System.Int32> Avalonia.Controls.TextBlock.MaxLinesProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontFamily Avalonia.Controls.TextBlock.GetFontFamily(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.TextBlock.GetFontSize(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontStyle Avalonia.Controls.TextBlock.GetFontStyle(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.FontWeight Avalonia.Controls.TextBlock.GetFontWeight(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.IBrush Avalonia.Controls.TextBlock.GetForeground(Avalonia.Controls.Control)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontFamily(Avalonia.Controls.Control, Avalonia.Media.FontFamily)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontSize(Avalonia.Controls.Control, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontStyle(Avalonia.Controls.Control, Avalonia.Media.FontStyle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetFontWeight(Avalonia.Controls.Control, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.TextBlock.SetForeground(Avalonia.Controls.Control, Avalonia.Media.IBrush)' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.TopLevel' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract. CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.TopLevel' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Viewbox' does not inherit from base type 'Avalonia.Controls.Decorator' in the implementation but it does in the contract. CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Viewbox' does not inherit from base type 'Avalonia.Controls.Decorator' in the implementation but it does in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
@ -72,4 +94,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 73 Total Issues: 95

2
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -65,7 +65,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// <inheritdoc/> /// <inheritdoc/>
public Window? MainWindow { get; set; } public Window? MainWindow { get; set; }
public IReadOnlyList<Window> Windows => _windows.ToList(); public IReadOnlyList<Window> Windows => _windows.ToArray();
private void HandleWindowClosed(Window window) private void HandleWindowClosed(Window window)
{ {

8
src/Avalonia.Controls/AutoCompleteBox.cs

@ -252,6 +252,10 @@ namespace Avalonia.Controls
/// drop-down that contains possible matches based on the input in the text /// drop-down that contains possible matches based on the input in the text
/// box. /// box.
/// </summary> /// </summary>
[TemplatePart(ElementPopup, typeof(Popup))]
[TemplatePart(ElementSelector, typeof(SelectingItemsControl))]
[TemplatePart(ElementSelectionAdapter, typeof(ISelectionAdapter))]
[TemplatePart(ElementTextBox, typeof(TextBox))]
[PseudoClasses(":dropdownopen")] [PseudoClasses(":dropdownopen")]
public class AutoCompleteBox : TemplatedControl public class AutoCompleteBox : TemplatedControl
{ {
@ -2180,7 +2184,7 @@ namespace Avalonia.Controls
} }
// Store a local cached copy of the data // Store a local cached copy of the data
_items = newValue == null ? null : new List<object>(newValue.Cast<object>().ToList()); _items = newValue == null ? null : new List<object>(newValue.Cast<object>());
// Clear and set the view on the selection adapter // Clear and set the view on the selection adapter
ClearView(); ClearView();
@ -2239,7 +2243,7 @@ namespace Avalonia.Controls
ClearView(); ClearView();
if (Items != null) if (Items != null)
{ {
_items = new List<object>(Items.Cast<object>().ToList()); _items = new List<object>(Items.Cast<object>());
} }
} }

1
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -19,4 +19,5 @@
<Import Project="..\..\build\JetBrains.Annotations.props" /> <Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project> </Project>

123
src/Avalonia.Controls/Button.cs

@ -29,11 +29,14 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// A button control. /// A standard button control.
/// </summary> /// </summary>
[PseudoClasses(":pressed")] [PseudoClasses(pcFlyoutOpen, pcPressed)]
public class Button : ContentControl, ICommandSource public class Button : ContentControl, ICommandSource, IClickableControl
{ {
protected const string pcPressed = ":pressed";
protected const string pcFlyoutOpen = ":flyout-open";
/// <summary> /// <summary>
/// Defines the <see cref="ClickMode"/> property. /// Defines the <see cref="ClickMode"/> property.
/// </summary> /// </summary>
@ -92,6 +95,7 @@ namespace Avalonia.Controls
private ICommand? _command; private ICommand? _command;
private bool _commandCanExecute = true; private bool _commandCanExecute = true;
private KeyGesture? _hotkey; private KeyGesture? _hotkey;
private bool _isFlyoutOpen = false;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Button"/> class. /// Initializes static members of the <see cref="Button"/> class.
@ -107,7 +111,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public Button() public Button()
{ {
UpdatePseudoClasses(IsPressed);
} }
/// <summary> /// <summary>
@ -328,11 +331,30 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Opens the button's flyout.
/// </summary>
protected virtual void OpenFlyout() protected virtual void OpenFlyout()
{ {
Flyout?.ShowAt(this); Flyout?.ShowAt(this);
} }
/// <summary>
/// Invoked when the button's flyout is opened.
/// </summary>
protected virtual void OnFlyoutOpened()
{
// Available for derived types
}
/// <summary>
/// Invoked when the button's flyout is closed.
/// </summary>
protected virtual void OnFlyoutClosed()
{
// Available for derived types
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
@ -382,6 +404,14 @@ namespace Avalonia.Controls
IsPressed = false; IsPressed = false;
} }
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
UnregisterFlyoutEvents(Flyout);
RegisterFlyoutEvents(Flyout);
UpdatePseudoClasses();
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change) protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{ {
@ -442,17 +472,26 @@ namespace Avalonia.Controls
} }
else if (change.Property == IsPressedProperty) else if (change.Property == IsPressedProperty)
{ {
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>()); UpdatePseudoClasses();
} }
else if (change.Property == FlyoutProperty) else if (change.Property == FlyoutProperty)
{ {
var oldFlyout = change.OldValue.GetValueOrDefault() as FlyoutBase;
var newFlyout = change.NewValue.GetValueOrDefault() as FlyoutBase;
// If flyout is changed while one is already open, make sure we // If flyout is changed while one is already open, make sure we
// close the old one first // close the old one first
if (change.OldValue.GetValueOrDefault() is FlyoutBase oldFlyout && if (oldFlyout != null &&
oldFlyout.IsOpen) oldFlyout.IsOpen)
{ {
oldFlyout.Hide(); oldFlyout.Hide();
} }
// Must unregister events here while a reference to the old flyout still exists
UnregisterFlyoutEvents(oldFlyout);
RegisterFlyoutEvents(newFlyout);
UpdatePseudoClasses();
} }
} }
@ -493,6 +532,32 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Registers all flyout events.
/// </summary>
/// <param name="flyout">The flyout to connect events to.</param>
private void RegisterFlyoutEvents(FlyoutBase? flyout)
{
if (flyout != null)
{
flyout.Opened += Flyout_Opened;
flyout.Closed += Flyout_Closed;
}
}
/// <summary>
/// Explicitly unregisters all flyout events.
/// </summary>
/// <param name="flyout">The flyout to disconnect events from.</param>
private void UnregisterFlyoutEvents(FlyoutBase? flyout)
{
if (flyout != null)
{
flyout.Opened -= Flyout_Opened;
flyout.Closed -= Flyout_Closed;
}
}
/// <summary> /// <summary>
/// Starts listening for the Enter key when the button <see cref="IsDefault"/>. /// Starts listening for the Enter key when the button <see cref="IsDefault"/>.
/// </summary> /// </summary>
@ -560,11 +625,53 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Updates the visual state of the control by applying latest PseudoClasses. /// Updates the visual state of the control by applying latest PseudoClasses.
/// </summary> /// </summary>
private void UpdatePseudoClasses(bool isPressed) private void UpdatePseudoClasses()
{ {
PseudoClasses.Set(":pressed", isPressed); PseudoClasses.Set(pcFlyoutOpen, _isFlyoutOpen);
PseudoClasses.Set(pcPressed, IsPressed);
} }
void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e); void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
void IClickableControl.RaiseClick() => OnClick();
/// <summary>
/// Event handler for when the button's flyout is opened.
/// </summary>
private void Flyout_Opened(object? sender, EventArgs e)
{
var flyout = sender as FlyoutBase;
// It is possible to share flyouts among multiple controls including Button.
// This can cause a problem here since all controls that share a flyout receive
// the same Opened/Closed events at the same time.
// For Button that means they all would be updating their pseudoclasses accordingly.
// In other words, all Buttons with a shared Flyout would have the backgrounds changed together.
// To fix this, only continue here if the Flyout target matches this Button instance.
if (object.ReferenceEquals(flyout?.Target, this))
{
_isFlyoutOpen = true;
UpdatePseudoClasses();
OnFlyoutOpened();
}
}
/// <summary>
/// Event handler for when the button's flyout is closed.
/// </summary>
private void Flyout_Closed(object? sender, EventArgs e)
{
var flyout = sender as FlyoutBase;
// See comments in Flyout_Opened
if (object.ReferenceEquals(flyout?.Target, this))
{
_isFlyoutOpen = false;
UpdatePseudoClasses();
OnFlyoutClosed();
}
}
} }
} }

2
src/Avalonia.Controls/ButtonSpinner.cs

@ -16,6 +16,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Represents a spinner control that includes two Buttons. /// Represents a spinner control that includes two Buttons.
/// </summary> /// </summary>
[TemplatePart("PART_DecreaseButton", typeof(Button))]
[TemplatePart("PART_IncreaseButton", typeof(Button))]
[PseudoClasses(":left", ":right")] [PseudoClasses(":left", ":right")]
public class ButtonSpinner : Spinner public class ButtonSpinner : Spinner
{ {

21
src/Avalonia.Controls/Calendar/Calendar.cs

@ -6,6 +6,7 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
@ -222,6 +223,8 @@ namespace Avalonia.Controls
/// element in XAML. /// element in XAML.
/// </para> /// </para>
/// </remarks> /// </remarks>
[TemplatePart(PART_ElementMonth, typeof(CalendarItem))]
[TemplatePart(PART_ElementRoot, typeof(Panel))]
public class Calendar : TemplatedControl public class Calendar : TemplatedControl
{ {
internal const int RowsPerMonth = 7; internal const int RowsPerMonth = 7;
@ -261,6 +264,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<Calendar, DayOfWeek>( AvaloniaProperty.Register<Calendar, DayOfWeek>(
nameof(FirstDayOfWeek), nameof(FirstDayOfWeek),
defaultValue: DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek); defaultValue: DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek);
/// <summary> /// <summary>
/// Gets or sets the day that is considered the beginning of the week. /// Gets or sets the day that is considered the beginning of the week.
/// </summary> /// </summary>
@ -273,6 +277,7 @@ namespace Avalonia.Controls
get { return GetValue(FirstDayOfWeekProperty); } get { return GetValue(FirstDayOfWeekProperty); }
set { SetValue(FirstDayOfWeekProperty, value); } set { SetValue(FirstDayOfWeekProperty, value); }
} }
/// <summary> /// <summary>
/// FirstDayOfWeekProperty property changed handler. /// FirstDayOfWeekProperty property changed handler.
/// </summary> /// </summary>
@ -289,6 +294,7 @@ namespace Avalonia.Controls
throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek"); throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek");
} }
} }
/// <summary> /// <summary>
/// Inherited code: Requires comment. /// Inherited code: Requires comment.
/// </summary> /// </summary>
@ -311,6 +317,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<Calendar, bool>( AvaloniaProperty.Register<Calendar, bool>(
nameof(IsTodayHighlighted), nameof(IsTodayHighlighted),
defaultValue: true); defaultValue: true);
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the current date is /// Gets or sets a value indicating whether the current date is
/// highlighted. /// highlighted.
@ -324,6 +331,7 @@ namespace Avalonia.Controls
get { return GetValue(IsTodayHighlightedProperty); } get { return GetValue(IsTodayHighlightedProperty); }
set { SetValue(IsTodayHighlightedProperty, value); } set { SetValue(IsTodayHighlightedProperty, value); }
} }
/// <summary> /// <summary>
/// IsTodayHighlightedProperty property changed handler. /// IsTodayHighlightedProperty property changed handler.
/// </summary> /// </summary>
@ -343,6 +351,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> HeaderBackgroundProperty = public static readonly StyledProperty<IBrush> HeaderBackgroundProperty =
AvaloniaProperty.Register<Calendar, IBrush>(nameof(HeaderBackground)); AvaloniaProperty.Register<Calendar, IBrush>(nameof(HeaderBackground));
public IBrush HeaderBackground public IBrush HeaderBackground
{ {
get { return GetValue(HeaderBackgroundProperty); } get { return GetValue(HeaderBackgroundProperty); }
@ -367,6 +376,7 @@ namespace Avalonia.Controls
get { return GetValue(DisplayModeProperty); } get { return GetValue(DisplayModeProperty); }
set { SetValue(DisplayModeProperty, value); } set { SetValue(DisplayModeProperty, value); }
} }
/// <summary> /// <summary>
/// DisplayModeProperty property changed handler. /// DisplayModeProperty property changed handler.
/// </summary> /// </summary>
@ -424,6 +434,7 @@ namespace Avalonia.Controls
|| mode == CalendarMode.Year || mode == CalendarMode.Year
|| mode == CalendarMode.Decade; || mode == CalendarMode.Decade;
} }
private void OnDisplayModeChanged(CalendarModeChangedEventArgs args) private void OnDisplayModeChanged(CalendarModeChangedEventArgs args)
{ {
DisplayModeChanged?.Invoke(this, args); DisplayModeChanged?.Invoke(this, args);
@ -433,6 +444,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<Calendar, CalendarSelectionMode>( AvaloniaProperty.Register<Calendar, CalendarSelectionMode>(
nameof(SelectionMode), nameof(SelectionMode),
defaultValue: CalendarSelectionMode.SingleDate); defaultValue: CalendarSelectionMode.SingleDate);
/// <summary> /// <summary>
/// Gets or sets a value that indicates what kind of selections are /// Gets or sets a value that indicates what kind of selections are
/// allowed. /// allowed.
@ -457,6 +469,7 @@ namespace Avalonia.Controls
get { return GetValue(SelectionModeProperty); } get { return GetValue(SelectionModeProperty); }
set { SetValue(SelectionModeProperty, value); } set { SetValue(SelectionModeProperty, value); }
} }
private void OnSelectionModeChanged(AvaloniaPropertyChangedEventArgs e) private void OnSelectionModeChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if (IsValidSelectionMode(e.NewValue!)) if (IsValidSelectionMode(e.NewValue!))
@ -471,6 +484,7 @@ namespace Avalonia.Controls
throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode"); throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode");
} }
} }
/// <summary> /// <summary>
/// Inherited code: Requires comment. /// Inherited code: Requires comment.
/// </summary> /// </summary>
@ -492,6 +506,7 @@ namespace Avalonia.Controls
o => o.SelectedDate, o => o.SelectedDate,
(o, v) => o.SelectedDate = v, (o, v) => o.SelectedDate = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Gets or sets the currently selected date. /// Gets or sets the currently selected date.
/// </summary> /// </summary>
@ -720,6 +735,7 @@ namespace Avalonia.Controls
o => o.DisplayDate, o => o.DisplayDate,
(o, v) => o.DisplayDate = v, (o, v) => o.DisplayDate = v,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Gets or sets the date to display. /// Gets or sets the date to display.
/// </summary> /// </summary>
@ -1973,6 +1989,7 @@ namespace Avalonia.Controls
} }
} }
} }
private void Calendar_KeyUp(KeyEventArgs e) private void Calendar_KeyUp(KeyEventArgs e)
{ {
if (!e.Handled && (e.Key == Key.LeftShift || e.Key == Key.RightShift)) if (!e.Handled && (e.Key == Key.LeftShift || e.Key == Key.RightShift))
@ -1980,6 +1997,7 @@ namespace Avalonia.Controls
ProcessShiftKeyUp(); ProcessShiftKeyUp();
} }
} }
internal void ProcessShiftKeyUp() internal void ProcessShiftKeyUp()
{ {
if (_isShiftPressed && (SelectionMode == CalendarSelectionMode.SingleRange || SelectionMode == CalendarSelectionMode.MultipleRange)) if (_isShiftPressed && (SelectionMode == CalendarSelectionMode.SingleRange || SelectionMode == CalendarSelectionMode.MultipleRange))
@ -2028,6 +2046,7 @@ namespace Avalonia.Controls
} }
} }
} }
protected override void OnLostFocus(RoutedEventArgs e) protected override void OnLostFocus(RoutedEventArgs e)
{ {
base.OnLostFocus(e); base.OnLostFocus(e);
@ -2054,6 +2073,7 @@ namespace Avalonia.Controls
} }
} }
} }
/// <summary> /// <summary>
/// Called when the IsEnabled property changes. /// Called when the IsEnabled property changes.
/// </summary> /// </summary>
@ -2098,6 +2118,7 @@ namespace Avalonia.Controls
private const string PART_ElementRoot = "Root"; private const string PART_ElementRoot = "Root";
private const string PART_ElementMonth = "CalendarItem"; private const string PART_ElementMonth = "CalendarItem";
/// <summary> /// <summary>
/// Builds the visual tree for the /// Builds the visual tree for the
/// <see cref="T:System.Windows.Controls.Calendar" /> when a new /// <see cref="T:System.Windows.Controls.Calendar" /> when a new

5
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@ -7,6 +7,7 @@ using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
@ -116,6 +117,10 @@ namespace Avalonia.Controls
Custom = 2 Custom = 2
} }
[TemplatePart(ElementButton, typeof(Button))]
[TemplatePart(ElementCalendar, typeof(Calendar))]
[TemplatePart(ElementPopup, typeof(Popup))]
[TemplatePart(ElementTextBox, typeof(TextBox))]
public class CalendarDatePicker : TemplatedControl public class CalendarDatePicker : TemplatedControl
{ {
private const string ElementTextBox = "PART_TextBox"; private const string ElementTextBox = "PART_TextBox";

5
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -19,6 +19,11 @@ namespace Avalonia.Controls.Primitives
/// Represents the currently displayed month or year on a /// Represents the currently displayed month or year on a
/// <see cref="T:Avalonia.Controls.Calendar" />. /// <see cref="T:Avalonia.Controls.Calendar" />.
/// </summary> /// </summary>
[TemplatePart(PART_ElementHeaderButton, typeof(Button))]
[TemplatePart(PART_ElementMonthView, typeof(Grid))]
[TemplatePart(PART_ElementNextButton, typeof(Button))]
[TemplatePart(PART_ElementPreviousButton, typeof(Button))]
[TemplatePart(PART_ElementYearView, typeof(Grid))]
[PseudoClasses(":calendardisabled")] [PseudoClasses(":calendardisabled")]
public sealed class CalendarItem : TemplatedControl public sealed class CalendarItem : TemplatedControl
{ {

4
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@ -8,6 +8,10 @@ namespace Avalonia.Controls.Chrome
/// <summary> /// <summary>
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled. /// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
/// </summary> /// </summary>
[TemplatePart("PART_CloseButton", typeof(Panel))]
[TemplatePart("PART_RestoreButton", typeof(Panel))]
[TemplatePart("PART_MinimiseButton", typeof(Panel))]
[TemplatePart("PART_FullScreenButton", typeof(Panel))]
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")]
public class CaptionButtons : TemplatedControl public class CaptionButtons : TemplatedControl
{ {

1
src/Avalonia.Controls/Chrome/TitleBar.cs

@ -8,6 +8,7 @@ namespace Avalonia.Controls.Chrome
/// <summary> /// <summary>
/// Draws a titlebar when managed client decorations are enabled. /// Draws a titlebar when managed client decorations are enabled.
/// </summary> /// </summary>
[TemplatePart("PART_CaptionButtons", typeof(CaptionButtons))]
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")]
public class TitleBar : TemplatedControl public class TitleBar : TemplatedControl
{ {

21
src/Avalonia.Controls/ComboBox.cs

@ -13,12 +13,14 @@ using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Controls.Metadata;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// A drop-down list control. /// A drop-down list control.
/// </summary> /// </summary>
[TemplatePart("PART_Popup", typeof(Popup))]
public class ComboBox : SelectingItemsControl public class ComboBox : SelectingItemsControl
{ {
/// <summary> /// <summary>
@ -182,6 +184,25 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(SelectedItem); this.UpdateSelectionBoxItem(SelectedItem);
} }
// Because the SelectedItem isn't connected to the visual tree
public override void InvalidateMirrorTransform()
{
base.InvalidateMirrorTransform();
if (SelectedItem is Control selectedControl)
{
selectedControl.InvalidateMirrorTransform();
foreach (var visual in selectedControl.GetVisualDescendants())
{
if (visual is Control childControl)
{
childControl.InvalidateMirrorTransform();
}
}
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {

2
src/Avalonia.Controls/ContentControl.cs

@ -1,4 +1,5 @@
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -12,6 +13,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Displays <see cref="Content"/> according to a <see cref="FuncDataTemplate"/>. /// Displays <see cref="Content"/> according to a <see cref="FuncDataTemplate"/>.
/// </summary> /// </summary>
[TemplatePart("PART_ContentPresenter", typeof(IContentPresenter))]
public class ContentControl : TemplatedControl, IContentControl, IContentPresenterHost public class ContentControl : TemplatedControl, IContentControl, IContentPresenterHost
{ {
/// <summary> /// <summary>

92
src/Avalonia.Controls/Control.cs

@ -1,7 +1,7 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
@ -67,7 +67,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty = public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(nameof(FlowDirection), inherits: true); AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(nameof(FlowDirection), inherits: true);
private DataTemplates? _dataTemplates; private DataTemplates? _dataTemplates;
private IControl? _focusAdorner; private IControl? _focusAdorner;
private AutomationPeer? _automationPeer; private AutomationPeer? _automationPeer;
@ -137,9 +137,39 @@ namespace Avalonia.Controls
public new IControl? Parent => (IControl?)base.Parent; public new IControl? Parent => (IControl?)base.Parent;
/// <summary>
/// Gets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The flow direction.</returns>
public static FlowDirection GetFlowDirection(Control control)
{
return control.GetValue(FlowDirectionProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FlowDirectionProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFlowDirection(Control control, FlowDirection value)
{
control.SetValue(FlowDirectionProperty, value);
}
/// <inheritdoc/> /// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary>
/// Gets a value indicating whether control bypass FlowDirecton policies.
/// </summary>
/// <remarks>
/// Related to FlowDirection system and returns false as default, so if
/// <see cref="FlowDirection"/> is RTL then control will get a mirror presentation.
/// For controls that want to avoid this behavior, override this property and return true.
/// </remarks>
protected virtual bool BypassFlowDirectionPolicies => false;
/// <inheritdoc/> /// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter) void ISetterValue.Initialize(ISetter setter)
{ {
@ -199,6 +229,14 @@ namespace Avalonia.Controls
base.OnDetachedFromVisualTreeCore(e); base.OnDetachedFromVisualTreeCore(e);
} }
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
InvalidateMirrorTransform();
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e) protected override void OnGotFocus(GotFocusEventArgs e)
{ {
@ -309,5 +347,55 @@ namespace Avalonia.Controls
} }
} }
} }
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == FlowDirectionProperty)
{
InvalidateMirrorTransform();
foreach (var visual in VisualChildren)
{
if (visual is Control child)
{
child.InvalidateMirrorTransform();
}
}
}
}
/// <summary>
/// Computes the <see cref="IVisual.HasMirrorTransform"/> value according to the
/// <see cref="FlowDirection"/> and <see cref="BypassFlowDirectionPolicies"/>
/// </summary>
public virtual void InvalidateMirrorTransform()
{
var flowDirection = this.FlowDirection;
var parentFlowDirection = FlowDirection.LeftToRight;
bool bypassFlowDirectionPolicies = BypassFlowDirectionPolicies;
bool parentBypassFlowDirectionPolicies = false;
var parent = this.FindAncestorOfType<Control>();
if (parent != null)
{
parentFlowDirection = parent.FlowDirection;
parentBypassFlowDirectionPolicies = parent.BypassFlowDirectionPolicies;
}
else if (Parent is Control logicalParent)
{
parentFlowDirection = logicalParent.FlowDirection;
parentBypassFlowDirectionPolicies = logicalParent.BypassFlowDirectionPolicies;
}
bool thisShouldBeMirrored = flowDirection == FlowDirection.RightToLeft && !bypassFlowDirectionPolicies;
bool parentShouldBeMirrored = parentFlowDirection == FlowDirection.RightToLeft && !parentBypassFlowDirectionPolicies;
bool shouldApplyMirrorTransform = thisShouldBeMirrored != parentShouldBeMirrored;
HasMirrorTransform = shouldApplyMirrorTransform;
}
} }
} }

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

@ -14,6 +14,15 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control to allow the user to select a date /// A control to allow the user to select a date
/// </summary> /// </summary>
[TemplatePart("ButtonContentGrid", typeof(Grid))]
[TemplatePart("DayText", typeof(TextBlock))]
[TemplatePart("FirstSpacer", typeof(Rectangle))]
[TemplatePart("FlyoutButton", typeof(Button))]
[TemplatePart("MonthText", typeof(TextBlock))]
[TemplatePart("PickerPresenter", typeof(DatePickerPresenter))]
[TemplatePart("Popup", typeof(Popup))]
[TemplatePart("SecondSpacer", typeof(Rectangle))]
[TemplatePart("YearText", typeof(TextBlock))]
[PseudoClasses(":hasnodate")] [PseudoClasses(":hasnodate")]
public class DatePicker : TemplatedControl public class DatePicker : TemplatedControl
{ {

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

@ -1,4 +1,5 @@
using Avalonia.Controls.Primitives; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -12,6 +13,23 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a date for a /// Defines the presenter used for selecting a date for a
/// <see cref="DatePicker"/> /// <see cref="DatePicker"/>
/// </summary> /// </summary>
[TemplatePart("AcceptButton", typeof(Button))]
[TemplatePart("DayDownButton", typeof(RepeatButton))]
[TemplatePart("DayHost", typeof(Panel))]
[TemplatePart("DaySelector", typeof(DateTimePickerPanel))]
[TemplatePart("DayUpButton", typeof(RepeatButton))]
[TemplatePart("DismissButton", typeof(Button))]
[TemplatePart("FirstSpacer", typeof(Rectangle))]
[TemplatePart("MonthDownButton", typeof(RepeatButton))]
[TemplatePart("MonthHost", typeof(Panel))]
[TemplatePart("MonthSelector", typeof(DateTimePickerPanel))]
[TemplatePart("MonthUpButton", typeof(RepeatButton))]
[TemplatePart("PickerContainer", typeof(Grid))]
[TemplatePart("SecondSpacer", typeof(Rectangle))]
[TemplatePart("YearDownButton", typeof(RepeatButton))]
[TemplatePart("YearHost", typeof(Panel))]
[TemplatePart("YearSelector", typeof(DateTimePickerPanel))]
[TemplatePart("YearUpButton", typeof(RepeatButton))]
public class DatePickerPresenter : PickerPresenterBase public class DatePickerPresenter : PickerPresenterBase
{ {
/// <summary> /// <summary>

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

@ -1,7 +1,9 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -60,6 +62,7 @@ namespace Avalonia.Controls.Primitives
private Vector _offset; private Vector _offset;
private bool _hasInit; private bool _hasInit;
private bool _suppressUpdateOffset; private bool _suppressUpdateOffset;
private ScrollContentPresenter? _parentScroller;
public DateTimePickerPanel() public DateTimePickerPanel()
{ {
@ -255,6 +258,8 @@ namespace Avalonia.Controls.Primitives
_suppressUpdateOffset = true; _suppressUpdateOffset = true;
SelectedValue = (int)newSel * Increment + MinimumValue; SelectedValue = (int)newSel * Increment + MinimumValue;
_suppressUpdateOffset = false; _suppressUpdateOffset = false;
System.Diagnostics.Debug.WriteLine($"Offset: {_offset} ItemHeight: {ItemHeight}");
} }
} }
@ -270,7 +275,7 @@ namespace Avalonia.Controls.Primitives
public Size Extent => _extent; public Size Extent => _extent;
public Size Viewport => new Size(0, ItemHeight); public Size Viewport => Bounds.Size;
public event EventHandler? ScrollInvalidated; public event EventHandler? ScrollInvalidated;
@ -341,6 +346,20 @@ namespace Avalonia.Controls.Primitives
return finalSize; return finalSize;
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_parentScroller = this.GetVisualParent() as ScrollContentPresenter;
_parentScroller?.AddHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_parentScroller?.RemoveHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded);
_parentScroller = null;
}
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
switch (e.Key) switch (e.Key)
@ -554,5 +573,15 @@ namespace Avalonia.Controls.Primitives
{ {
ScrollInvalidated?.Invoke(this, e); ScrollInvalidated?.Invoke(this, e);
} }
private void OnScrollGestureEnded(object? sender, ScrollGestureEndedEventArgs e)
{
var snapY = Math.Round(Offset.Y / ItemHeight) * ItemHeight;
if (snapY != Offset.Y)
{
Offset = Offset.WithY(snapY);
}
}
} }
} }

12
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -12,6 +12,18 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control to allow the user to select a time. /// A control to allow the user to select a time.
/// </summary> /// </summary>
[TemplatePart("FirstColumnDivider", typeof(Rectangle))]
[TemplatePart("FirstPickerHost", typeof(Border))]
[TemplatePart("FlyoutButton", typeof(Button))]
[TemplatePart("FlyoutButtonContentGrid", typeof(Grid))]
[TemplatePart("HourTextBlock", typeof(TextBlock))]
[TemplatePart("MinuteTextBlock", typeof(TextBlock))]
[TemplatePart("PeriodTextBlock", typeof(TextBlock))]
[TemplatePart("PickerPresenter", typeof(TimePickerPresenter))]
[TemplatePart("Popup", typeof(Popup))]
[TemplatePart("SecondColumnDivider", typeof(Rectangle))]
[TemplatePart("SecondPickerHost", typeof(Border))]
[TemplatePart("ThirdPickerHost", typeof(Border))]
[PseudoClasses(":hasnotime")] [PseudoClasses(":hasnotime")]
public class TimePicker : TemplatedControl public class TimePicker : TemplatedControl
{ {

17
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Primitives; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -10,6 +11,20 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a time. Intended for use with /// Defines the presenter used for selecting a time. Intended for use with
/// <see cref="TimePicker"/> but can be used independently /// <see cref="TimePicker"/> but can be used independently
/// </summary> /// </summary>
[TemplatePart("AcceptButton", typeof(Button))]
[TemplatePart("DismissButton", typeof(Button))]
[TemplatePart("HourDownButton", typeof(RepeatButton))]
[TemplatePart("HourSelector", typeof(DateTimePickerPanel))]
[TemplatePart("HourUpButton", typeof(RepeatButton))]
[TemplatePart("MinuteDownButton", typeof(RepeatButton))]
[TemplatePart("MinuteSelector", typeof(DateTimePickerPanel))]
[TemplatePart("MinuteUpButton", typeof(RepeatButton))]
[TemplatePart("PeriodDownButton", typeof(RepeatButton))]
[TemplatePart("PeriodHost", typeof(Panel))]
[TemplatePart("PeriodSelector", typeof(DateTimePickerPanel))]
[TemplatePart("PeriodUpButton", typeof(RepeatButton))]
[TemplatePart("PickerContainer", typeof(Grid))]
[TemplatePart("SecondSpacer", typeof(Rectangle))]
public class TimePickerPresenter : PickerPresenterBase public class TimePickerPresenter : PickerPresenterBase
{ {
/// <summary> /// <summary>

167
src/Avalonia.Controls/Documents/TextElement.cs

@ -13,37 +13,60 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="Background"/> property. /// Defines the <see cref="Background"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IBrush?> BackgroundProperty = public static readonly StyledProperty<IBrush?> BackgroundProperty =
AvaloniaProperty.Register<TextElement, IBrush?>(nameof(Background), inherits: true); Border.BackgroundProperty.AddOwner<TextElement>();
/// <summary> /// <summary>
/// Defines the <see cref="FontFamily"/> property. /// Defines the <see cref="FontFamily"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty = public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<TextElement>(); AvaloniaProperty.RegisterAttached<TextElement, Control, FontFamily>(
nameof(FontFamily),
defaultValue: FontFamily.Default,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="FontSize"/> property. /// Defines the <see cref="FontSize"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<double> FontSizeProperty = public static readonly AttachedProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<TextElement>(); AvaloniaProperty.RegisterAttached<TextElement, Control, double>(
nameof(FontSize),
defaultValue: 12,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="FontStyle"/> property. /// Defines the <see cref="FontStyle"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty = public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<TextElement>(); AvaloniaProperty.RegisterAttached<TextElement, Control, FontStyle>(
nameof(FontStyle),
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="FontWeight"/> property. /// Defines the <see cref="FontWeight"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty = public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<TextElement>(); AvaloniaProperty.RegisterAttached<TextElement, Control, FontWeight>(
nameof(FontWeight),
inherits: true,
defaultValue: FontWeight.Normal);
/// <summary>
/// Defines the <see cref="FontStretch"/> property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
AvaloniaProperty.RegisterAttached<TextElement, Control, FontStretch>(
nameof(FontStretch),
inherits: true,
defaultValue: FontStretch.Normal);
/// <summary> /// <summary>
/// Defines the <see cref="Foreground"/> property. /// Defines the <see cref="Foreground"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty = public static readonly AttachedProperty<IBrush?> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TextElement>(); AvaloniaProperty.RegisterAttached<TextElement, Control, IBrush?>(
nameof(Foreground),
Brushes.Black,
inherits: true);
/// <summary> /// <summary>
/// Gets or sets a brush used to paint the control's background. /// Gets or sets a brush used to paint the control's background.
@ -90,6 +113,15 @@ namespace Avalonia.Controls.Documents
set { SetValue(FontWeightProperty, value); } set { SetValue(FontWeightProperty, value); }
} }
/// <summary>
/// Gets or sets the font stretch.
/// </summary>
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary> /// <summary>
/// Gets or sets a brush used to paint the text. /// Gets or sets a brush used to paint the text.
/// </summary> /// </summary>
@ -98,7 +130,127 @@ namespace Avalonia.Controls.Documents
get { return GetValue(ForegroundProperty); } get { return GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); } set { SetValue(ForegroundProperty, value); }
} }
/// <summary>
/// Gets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font family.</returns>
public static FontFamily GetFontFamily(Control control)
{
return control.GetValue(FontFamilyProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontFamilyProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontFamily(Control control, FontFamily value)
{
control.SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font size.</returns>
public static double GetFontSize(Control control)
{
return control.GetValue(FontSizeProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontSizeProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontSize(Control control, double value)
{
control.SetValue(FontSizeProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font style.</returns>
public static FontStyle GetFontStyle(Control control)
{
return control.GetValue(FontStyleProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStyleProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontStyle(Control control, FontStyle value)
{
control.SetValue(FontStyleProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font weight.</returns>
public static FontWeight GetFontWeight(Control control)
{
return control.GetValue(FontWeightProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontWeightProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontWeight(Control control, FontWeight value)
{
control.SetValue(FontWeightProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The font stretch.</returns>
public static FontStretch GetFontStretch(Control control)
{
return control.GetValue(FontStretchProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="FontStretchProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetFontStretch(Control control, FontStretch value)
{
control.SetValue(FontStretchProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="ForegroundProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The foreground.</returns>
public static IBrush? GetForeground(Control control)
{
return control.GetValue(ForegroundProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="ForegroundProperty"/> on a control.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetForeground(Control control, IBrush? value)
{
control.SetValue(ForegroundProperty, value);
}
/// <summary> /// <summary>
/// Raised when the visual representation of the text element changes. /// Raised when the visual representation of the text element changes.
/// </summary> /// </summary>
@ -115,6 +267,7 @@ namespace Avalonia.Controls.Documents
case nameof(FontSize): case nameof(FontSize):
case nameof(FontStyle): case nameof(FontStyle):
case nameof(FontWeight): case nameof(FontWeight):
case nameof(FontStretch):
case nameof(Foreground): case nameof(Foreground):
Invalidate(); Invalidate();
break; break;

28
src/Avalonia.Controls/DropDown.cs

@ -1,28 +0,0 @@
using System;
using Avalonia.Logging;
using Avalonia.Styling;
namespace Avalonia.Controls
{
[Obsolete("Use ComboBox")]
public class DropDown : ComboBox, IStyleable
{
public DropDown()
{
Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, "DropDown is deprecated: Use ComboBox");
}
Type IStyleable.StyleKey => typeof(ComboBox);
}
[Obsolete("Use ComboBoxItem")]
public class DropDownItem : ComboBoxItem, IStyleable
{
public DropDownItem()
{
Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, "DropDownItem is deprecated: Use ComboBoxItem");
}
Type IStyleable.StyleKey => typeof(ComboBoxItem);
}
}

15
src/Avalonia.Controls/DropDownButton.cs

@ -0,0 +1,15 @@
namespace Avalonia.Controls
{
/// <summary>
/// A button with an added drop-down chevron to visually indicate it has a flyout with additional actions.
/// </summary>
public class DropDownButton : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="DropDownButton"/> class.
/// </summary>
public DropDownButton()
{
}
}
}

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

@ -82,7 +82,7 @@ namespace Avalonia.Controls.Generators
{ {
var toMove = _containers.Where(x => x.Key >= index) var toMove = _containers.Where(x => x.Key >= index)
.OrderByDescending(x => x.Key) .OrderByDescending(x => x.Key)
.ToList(); .ToArray();
foreach (var i in toMove) foreach (var i in toMove)
{ {
@ -111,7 +111,7 @@ namespace Avalonia.Controls.Generators
} }
var toMove = _containers.Where(x => x.Key >= startingIndex) var toMove = _containers.Where(x => x.Key >= startingIndex)
.OrderBy(x => x.Key).ToList(); .OrderBy(x => x.Key).ToArray();
foreach (var i in toMove) foreach (var i in toMove)
{ {
@ -122,9 +122,9 @@ namespace Avalonia.Controls.Generators
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result)); Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
if (toMove.Count > 0) if (toMove.Length > 0)
{ {
var containers = toMove.Select(x => x.Value).ToList(); var containers = toMove.Select(x => x.Value).ToArray();
Recycled?.Invoke(this, new ItemContainerEventArgs(containers[0].Index, containers)); Recycled?.Invoke(this, new ItemContainerEventArgs(containers[0].Index, containers));
} }
} }
@ -138,10 +138,10 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/> /// <inheritdoc/>
public virtual IEnumerable<ItemContainerInfo> Clear() public virtual IEnumerable<ItemContainerInfo> Clear()
{ {
var result = Containers.ToList(); var result = Containers.ToArray();
_containers.Clear(); _containers.Clear();
if (result.Count > 0) if (result.Length > 0)
{ {
Dematerialized?.Invoke(this, new ItemContainerEventArgs(0, result)); Dematerialized?.Invoke(this, new ItemContainerEventArgs(0, result));
} }

67
src/Avalonia.Controls/HotkeyManager.cs

@ -12,21 +12,61 @@ namespace Avalonia.Controls
class HotkeyCommandWrapper : ICommand class HotkeyCommandWrapper : ICommand
{ {
public HotkeyCommandWrapper(ICommandSource? control) readonly WeakReference reference;
public HotkeyCommandWrapper(IControl control)
{ {
CommandSource = control; reference = new WeakReference(control);
} }
public readonly ICommandSource? CommandSource; public ICommand? GetCommand()
{
if (reference.Target is { } target)
{
if (target is ICommandSource commandSource && commandSource.Command is { } command)
{
return command;
}
else if (target is IClickableControl { })
{
return this;
}
}
return null;
}
private ICommand? GetCommand() => CommandSource?.Command; public bool CanExecute(object? parameter)
{
if (reference.Target is { } target)
{
if (target is ICommandSource commandSource && commandSource.Command is { } command)
{
return commandSource.IsEffectivelyEnabled
&& command.CanExecute(commandSource.CommandParameter) == true;
}
else if (target is IClickableControl clickable)
{
return clickable.IsEffectivelyEnabled;
}
}
return false;
}
public bool CanExecute(object? parameter) => public void Execute(object? parameter)
CommandSource?.Command?.CanExecute(CommandSource.CommandParameter) == true {
&& CommandSource.IsEffectivelyEnabled; if (reference.Target is { } target)
{
if (target is ICommandSource commandSource && commandSource.Command is { } command)
{
command.Execute(commandSource.CommandParameter);
}
else if (target is IClickableControl { IsEffectivelyEnabled: true } clickable)
{
clickable.RaiseClick();
}
}
}
public void Execute(object? parameter) =>
GetCommand()?.Execute(CommandSource?.CommandParameter);
#pragma warning disable 67 // Event not used #pragma warning disable 67 // Event not used
public event EventHandler? CanExecuteChanged; public event EventHandler? CanExecuteChanged;
@ -47,7 +87,7 @@ namespace Avalonia.Controls
public Manager(IControl control) public Manager(IControl control)
{ {
_control = control; _control = control;
_wrapper = new HotkeyCommandWrapper(_control as ICommandSource); _wrapper = new HotkeyCommandWrapper(_control);
} }
public void Init() public void Init()
@ -104,13 +144,14 @@ namespace Avalonia.Controls
{ {
HotKeyProperty.Changed.Subscribe(args => HotKeyProperty.Changed.Subscribe(args =>
{ {
if (args.NewValue.Value is null) return; if (args.NewValue.Value is null)
return;
var control = args.Sender as IControl; var control = args.Sender as IControl;
if (control is not ICommandSource) if (control is not IClickableControl)
{ {
Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?.Log(control, Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?.Log(control,
$"The element {args.Sender.GetType().Name} does not implement ICommandSource and does not support binding a HotKey ({args.NewValue})."); $"The element {args.Sender.GetType().Name} does not implement IClickableControl and does not support binding a HotKey ({args.NewValue}).");
return; return;
} }

2
src/Avalonia.Controls/Image.cs

@ -66,6 +66,8 @@ namespace Avalonia.Controls
set { SetValue(StretchDirectionProperty, value); } set { SetValue(StretchDirectionProperty, value); }
} }
protected override bool BypassFlowDirectionPolicies => true;
/// <summary> /// <summary>
/// Renders the control. /// Renders the control.
/// </summary> /// </summary>

2
src/Avalonia.Controls/ListBox.cs

@ -1,5 +1,6 @@
using System.Collections; using System.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection; using Avalonia.Controls.Selection;
@ -12,6 +13,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// An <see cref="ItemsControl"/> in which individual items can be selected. /// An <see cref="ItemsControl"/> in which individual items can be selected.
/// </summary> /// </summary>
[TemplatePart("PART_ScrollViewer", typeof(IScrollable))]
public class ListBox : SelectingItemsControl public class ListBox : SelectingItemsControl
{ {
/// <summary> /// <summary>

15
src/Avalonia.Controls/MenuItem.cs

@ -20,8 +20,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A menu item control. /// A menu item control.
/// </summary> /// </summary>
[TemplatePart("PART_Popup", typeof(Popup))]
[PseudoClasses(":separator", ":icon", ":open", ":pressed", ":selected")] [PseudoClasses(":separator", ":icon", ":open", ":pressed", ":selected")]
public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable, ICommandSource public class MenuItem : HeaderedSelectingItemsControl, IMenuItem, ISelectable, ICommandSource, IClickableControl
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Command"/> property. /// Defines the <see cref="Command"/> property.
@ -643,7 +644,9 @@ namespace Avalonia.Controls
/// <param name="e">The property change event.</param> /// <param name="e">The property change event.</param>
private void IsSelectedChanged(AvaloniaPropertyChangedEventArgs e) private void IsSelectedChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if ((bool)e.NewValue!) var parentMenu = Parent as Menu;
if ((bool)e.NewValue! && (parentMenu is null || parentMenu.IsOpen))
{ {
Focus(); Focus();
} }
@ -704,6 +707,14 @@ namespace Avalonia.Controls
void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e); void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e);
void IClickableControl.RaiseClick()
{
if (IsEffectivelyEnabled)
{
RaiseEvent(new RoutedEventArgs(ClickEvent));
}
}
/// <summary> /// <summary>
/// A dependency resolver which returns a <see cref="MenuItemAccessKeyHandler"/>. /// A dependency resolver which returns a <see cref="MenuItemAccessKeyHandler"/>.
/// </summary> /// </summary>

1
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -14,6 +14,7 @@ namespace Avalonia.Controls.Notifications
/// <summary> /// <summary>
/// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>. /// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>.
/// </summary> /// </summary>
[TemplatePart("PART_Items", typeof(Panel))]
[PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")] [PseudoClasses(":topleft", ":topright", ":bottomleft", ":bottomright")]
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
{ {

5
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -2,6 +2,7 @@
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
@ -15,6 +16,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values. /// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
/// </summary> /// </summary>
[TemplatePart("PART_Spinner", typeof(Spinner))]
[TemplatePart("PART_TextBox", typeof(TextBox))]
public class NumericUpDown : TemplatedControl public class NumericUpDown : TemplatedControl
{ {
/// <summary> /// <summary>
@ -1051,7 +1054,7 @@ namespace Avalonia.Controls
var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c)); var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c));
var textSpecialCharacters = text.Where(c => !char.IsDigit(c)); var textSpecialCharacters = text.Where(c => !char.IsDigit(c));
// same non-digit characters on currentValueText and new text => remove them on new Text to parse it again. // same non-digit characters on currentValueText and new text => remove them on new Text to parse it again.
if (currentValueTextSpecialCharacters.Except(textSpecialCharacters).ToList().Count == 0) if (!currentValueTextSpecialCharacters.Except(textSpecialCharacters).Any())
{ {
foreach (var character in textSpecialCharacters) foreach (var character in textSpecialCharacters)
{ {

14
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -149,13 +149,18 @@ namespace Avalonia.Controls.Platform
case Key.Up: case Key.Up:
case Key.Down: case Key.Down:
{ {
if (item?.IsTopLevel == true) if (item?.IsTopLevel == true && item.HasSubMenu)
{ {
if (item.HasSubMenu && !item.IsSubMenuOpen) if (!item.IsSubMenuOpen)
{ {
Open(item, true); Open(item, true);
e.Handled = true;
} }
else
{
item.MoveSelection(NavigationDirection.First, true);
}
e.Handled = true;
} }
else else
{ {
@ -247,7 +252,8 @@ namespace Avalonia.Controls.Platform
// new menu. // new menu.
if (item.IsSubMenuOpen && if (item.IsSubMenuOpen &&
item.Parent is IMenu && item.Parent is IMenu &&
item.Parent.SelectedItem is object) item.Parent.SelectedItem is object &&
item.Parent.SelectedItem != item)
{ {
item.Close(); item.Close();
Open(item.Parent.SelectedItem, true); Open(item.Parent.SelectedItem, true);

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

@ -1,5 +1,6 @@
using System; using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -46,7 +47,73 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty = public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
Border.BoxShadowProperty.AddOwner<ContentPresenter>(); Border.BoxShadowProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty =
TextElement.ForegroundProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextElement.FontFamilyProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
TextElement.FontSizeProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextElement.FontStyleProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextElement.FontWeightProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="FontStretch"/> property.
/// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="TextAlignment"/> property
/// </summary>
public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty =
TextBlock.TextAlignmentProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="TextWrapping"/> property
/// </summary>
public static readonly AttachedProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="TextTrimming"/> property
/// </summary>
public static readonly AttachedProperty<TextTrimming> TextTrimmingProperty =
TextBlock.TextTrimmingProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="LineHeight"/> property
/// </summary>
public static readonly AttachedProperty<double> LineHeightProperty =
TextBlock.LineHeightProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="MaxLines"/> property
/// </summary>
public static readonly AttachedProperty<int> MaxLinesProperty =
TextBlock.MaxLinesProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>
/// Defines the <see cref="Child"/> property. /// Defines the <see cref="Child"/> property.
/// </summary> /// </summary>
@ -159,6 +226,105 @@ namespace Avalonia.Controls.Presenters
set => SetValue(BoxShadowProperty, value); set => SetValue(BoxShadowProperty, value);
} }
/// <summary>
/// Gets or sets a brush used to paint the text.
/// </summary>
public IBrush? Foreground
{
get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}
/// <summary>
/// Gets or sets the font family.
/// </summary>
public FontFamily FontFamily
{
get => GetValue(FontFamilyProperty);
set => SetValue(FontFamilyProperty, value);
}
/// <summary>
/// Gets or sets the font size.
/// </summary>
public double FontSize
{
get => GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
/// <summary>
/// Gets or sets the font style.
/// </summary>
public FontStyle FontStyle
{
get => GetValue(FontStyleProperty);
set => SetValue(FontStyleProperty, value);
}
/// <summary>
/// Gets or sets the font weight.
/// </summary>
public FontWeight FontWeight
{
get => GetValue(FontWeightProperty);
set => SetValue(FontWeightProperty, value);
}
/// <summary>
/// Gets or sets the font stretch.
/// </summary>
public FontStretch FontStretch
{
get => GetValue(FontStretchProperty);
set => SetValue(FontStretchProperty, value);
}
/// <summary>
/// Gets or sets the text alignment
/// </summary>
public TextAlignment TextAlignment
{
get => GetValue(TextAlignmentProperty);
set => SetValue(TextAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the text wrapping
/// </summary>
public TextWrapping TextWrapping
{
get => GetValue(TextWrappingProperty);
set => SetValue(TextWrappingProperty, value);
}
/// <summary>
/// Gets or sets the text trimming
/// </summary>
public TextTrimming TextTrimming
{
get => GetValue(TextTrimmingProperty);
set => SetValue(TextTrimmingProperty, value);
}
/// <summary>
/// Gets or sets the line height
/// </summary>
public double LineHeight
{
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
/// <summary>
/// Gets or sets the max lines
/// </summary>
public int MaxLines
{
get => GetValue(MaxLinesProperty);
set => SetValue(MaxLinesProperty, value);
}
/// <summary> /// <summary>
/// Gets the control displayed by the presenter. /// Gets the control displayed by the presenter.
/// </summary> /// </summary>

4
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -389,7 +389,7 @@ namespace Avalonia.Controls.Presenters
{ {
var logicalUnits = delta.Y / logicalScrollItemSize.Y; var logicalUnits = delta.Y / logicalScrollItemSize.Y;
delta = delta.WithY(delta.Y - logicalUnits * logicalScrollItemSize.Y); delta = delta.WithY(delta.Y - logicalUnits * logicalScrollItemSize.Y);
dy = logicalUnits * scrollable!.ScrollSize.Height; dy = logicalUnits;
} }
else else
dy = delta.Y; dy = delta.Y;
@ -407,7 +407,7 @@ namespace Avalonia.Controls.Presenters
{ {
var logicalUnits = delta.X / logicalScrollItemSize.X; var logicalUnits = delta.X / logicalScrollItemSize.X;
delta = delta.WithX(delta.X - logicalUnits * logicalScrollItemSize.X); delta = delta.WithX(delta.X - logicalUnits * logicalScrollItemSize.X);
dx = logicalUnits * scrollable!.ScrollSize.Width; dx = logicalUnits;
} }
else else
dx = delta.X; dx = delta.X;

71
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -8,6 +8,7 @@ using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.Controls.Documents;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
{ {
@ -121,8 +122,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public FontFamily FontFamily public FontFamily FontFamily
{ {
get => TextBlock.GetFontFamily(this); get => TextElement.GetFontFamily(this);
set => TextBlock.SetFontFamily(this, value); set => TextElement.SetFontFamily(this, value);
} }
/// <summary> /// <summary>
@ -130,8 +131,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public double FontSize public double FontSize
{ {
get => TextBlock.GetFontSize(this); get => TextElement.GetFontSize(this);
set => TextBlock.SetFontSize(this, value); set => TextElement.SetFontSize(this, value);
} }
/// <summary> /// <summary>
@ -139,8 +140,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public FontStyle FontStyle public FontStyle FontStyle
{ {
get => TextBlock.GetFontStyle(this); get => TextElement.GetFontStyle(this);
set => TextBlock.SetFontStyle(this, value); set => TextElement.SetFontStyle(this, value);
} }
/// <summary> /// <summary>
@ -148,8 +149,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public FontWeight FontWeight public FontWeight FontWeight
{ {
get => TextBlock.GetFontWeight(this); get => TextElement.GetFontWeight(this);
set => TextBlock.SetFontWeight(this, value); set => TextElement.SetFontWeight(this, value);
} }
/// <summary> /// <summary>
@ -157,8 +158,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public FontStretch FontStretch public FontStretch FontStretch
{ {
get => TextBlock.GetFontStretch(this); get => TextElement.GetFontStretch(this);
set => TextBlock.SetFontStretch(this, value); set => TextElement.SetFontStretch(this, value);
} }
/// <summary> /// <summary>
@ -166,8 +167,8 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
public IBrush? Foreground public IBrush? Foreground
{ {
get => TextBlock.GetForeground(this); get => TextElement.GetForeground(this);
set => TextBlock.SetForeground(this, value); set => TextElement.SetForeground(this, value);
} }
/// <summary> /// <summary>
@ -281,6 +282,8 @@ namespace Avalonia.Controls.Presenters
} }
} }
protected override bool BypassFlowDirectionPolicies => true;
/// <summary> /// <summary>
/// Creates the <see cref="TextLayout"/> used to render the text. /// Creates the <see cref="TextLayout"/> used to render the text.
/// </summary> /// </summary>
@ -353,7 +356,7 @@ namespace Avalonia.Controls.Presenters
foreach (var rect in rects) foreach (var rect in rects)
{ {
context.FillRectangle(selectionBrush, rect); context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1));
} }
} }
@ -528,9 +531,9 @@ namespace Avalonia.Controls.Presenters
{ {
return finalSize; return finalSize;
} }
_constraint = finalSize; _constraint = new Size(finalSize.Width, Math.Ceiling(finalSize.Height));
_textLayout = null; _textLayout = null;
return finalSize; return finalSize;
@ -637,14 +640,7 @@ namespace Avalonia.Controls.Presenters
if (Text is null) if (Text is null)
{ {
return default; return default;
} }
if (FlowDirection == FlowDirection.RightToLeft)
{
direction = direction == LogicalDirection.Forward ?
LogicalDirection.Backward :
LogicalDirection.Forward;
}
var characterHit = _lastCharacterHit; var characterHit = _lastCharacterHit;
var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
@ -666,7 +662,7 @@ namespace Avalonia.Controls.Presenters
caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (textLine.NewLineLength > 0 && caretIndex == textLine.TextRange.Start + textLine.TextRange.Length) if (textLine.NewLineLength > 0 && caretIndex == textLine.FirstTextSourceIndex + textLine.Length)
{ {
characterHit = new CharacterHit(caretIndex); characterHit = new CharacterHit(caretIndex);
} }
@ -678,7 +674,7 @@ namespace Avalonia.Controls.Presenters
break; break;
} }
if (caretIndex - textLine.NewLineLength == textLine.TextRange.Start + textLine.TextRange.Length) if (caretIndex - textLine.NewLineLength == textLine.FirstTextSourceIndex + textLine.Length)
{ {
break; break;
} }
@ -719,6 +715,13 @@ namespace Avalonia.Controls.Presenters
public void MoveCaretHorizontal(LogicalDirection direction = LogicalDirection.Forward) public void MoveCaretHorizontal(LogicalDirection direction = LogicalDirection.Forward)
{ {
if (FlowDirection == FlowDirection.RightToLeft)
{
direction = direction == LogicalDirection.Forward ?
LogicalDirection.Backward :
LogicalDirection.Forward;
}
var characterHit = GetNextCharacterHit(direction); var characterHit = GetNextCharacterHit(direction);
UpdateCaret(characterHit); UpdateCaret(characterHit);
@ -779,19 +782,25 @@ namespace Avalonia.Controls.Presenters
switch (change.Property.Name) switch (change.Property.Name)
{ {
case nameof (TextBlock.Foreground): case nameof (Foreground):
case nameof (TextBlock.FontSize): case nameof (FontSize):
case nameof (TextBlock.FontStyle): case nameof (FontStyle):
case nameof (TextBlock.FontWeight): case nameof (FontWeight):
case nameof (TextBlock.FontFamily): case nameof (FontFamily):
case nameof (FontStretch):
case nameof (Text): case nameof (Text):
case nameof (TextAlignment): case nameof (TextAlignment):
case nameof (TextWrapping): case nameof (TextWrapping):
case nameof (SelectionStart): case nameof (SelectionStart):
case nameof (SelectionEnd): case nameof (SelectionEnd):
case nameof (SelectionForegroundBrush): case nameof (SelectionForegroundBrush):
case nameof (PasswordChar): case nameof (PasswordChar):
case nameof (RevealPassword): case nameof (RevealPassword):
case nameof(FlowDirection):
{ {
InvalidateTextLayout(); InvalidateTextLayout();
break; break;

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

@ -76,7 +76,7 @@ namespace Avalonia.Controls.Primitives
Rect? rect = null) Rect? rect = null)
{ {
_positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot()!, target, placement, offset, anchor, _positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot()!, target, placement, offset, anchor,
gravity, constraintAdjustment, rect); gravity, constraintAdjustment, rect, FlowDirection);
UpdatePosition(); UpdatePosition();
} }

30
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -46,6 +46,7 @@ Copyright © 2019 Nikita Tsukanov
using System; using System;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives.PopupPositioning namespace Avalonia.Controls.Primitives.PopupPositioning
{ {
@ -444,7 +445,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
TopLevel topLevel, TopLevel topLevel,
IVisual target, PlacementMode placement, Point offset, IVisual target, PlacementMode placement, Point offset,
PopupAnchor anchor, PopupGravity gravity, PopupAnchor anchor, PopupGravity gravity,
PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect) PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect,
FlowDirection flowDirection)
{ {
// We need a better way for tracking the last pointer position // We need a better way for tracking the last pointer position
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
@ -503,6 +505,32 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
else else
throw new InvalidOperationException("Invalid value for Popup.PlacementMode"); throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
} }
// Invert coordinate system if FlowDirection is RTL
if (flowDirection == FlowDirection.RightToLeft)
{
if ((positionerParameters.Anchor & PopupAnchor.Right) == PopupAnchor.Right)
{
positionerParameters.Anchor ^= PopupAnchor.Right;
positionerParameters.Anchor |= PopupAnchor.Left;
}
else if ((positionerParameters.Anchor & PopupAnchor.Left) == PopupAnchor.Left)
{
positionerParameters.Anchor ^= PopupAnchor.Left;
positionerParameters.Anchor |= PopupAnchor.Right;
}
if ((positionerParameters.Gravity & PopupGravity.Right) == PopupGravity.Right)
{
positionerParameters.Gravity ^= PopupGravity.Right;
positionerParameters.Gravity |= PopupGravity.Left;
}
else if ((positionerParameters.Gravity & PopupGravity.Left) == PopupGravity.Left)
{
positionerParameters.Gravity ^= PopupGravity.Left;
positionerParameters.Gravity |= PopupGravity.Right;
}
}
} }
} }

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

@ -23,8 +23,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens => public IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens =>
_parent.Screen.AllScreens.Select(s => new ManagedPopupPositionerScreenInfo( _parent.Screen.AllScreens
s.Bounds.ToRect(1), s.WorkingArea.ToRect(1))).ToList(); .Select(s => new ManagedPopupPositionerScreenInfo(s.Bounds.ToRect(1), s.WorkingArea.ToRect(1)))
.ToArray();
public Rect ParentClientAreaScreenGeometry public Rect ParentClientAreaScreenGeometry
{ {

9
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -17,7 +17,6 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
{ {
private readonly TopLevel _parent;
private PopupPositionerParameters _positionerParameters; private PopupPositionerParameters _positionerParameters;
/// <summary> /// <summary>
@ -47,7 +46,7 @@ namespace Avalonia.Controls.Primitives
public PopupRoot(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver? dependencyResolver) public PopupRoot(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver? dependencyResolver)
: base(impl, dependencyResolver) : base(impl, dependencyResolver)
{ {
_parent = parent; ParentTopLevel = parent;
} }
/// <summary> /// <summary>
@ -73,6 +72,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
IStyleHost? IStyleHost.StylingParent => Parent; IStyleHost? IStyleHost.StylingParent => Parent;
public TopLevel ParentTopLevel { get; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
@ -91,8 +92,8 @@ namespace Avalonia.Controls.Primitives
PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All, PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
Rect? rect = null) Rect? rect = null)
{ {
_positionerParameters.ConfigurePosition(_parent, target, _positionerParameters.ConfigurePosition(ParentTopLevel, target,
placement, offset, anchor, gravity, constraintAdjustment, rect); placement, offset, anchor, gravity, constraintAdjustment, rect, FlowDirection);
if (_positionerParameters.Size != default) if (_positionerParameters.Size != default)
UpdatePosition(); UpdatePosition();

4
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -22,6 +22,10 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// A scrollbar control. /// A scrollbar control.
/// </summary> /// </summary>
[TemplatePart("PART_LineDownButton", typeof(Button))]
[TemplatePart("PART_LineUpButton", typeof(Button))]
[TemplatePart("PART_PageDownButton", typeof(Button))]
[TemplatePart("PART_PageUpButton", typeof(Button))]
[PseudoClasses(":vertical", ":horizontal")] [PseudoClasses(":vertical", ":horizontal")]
public class ScrollBar : RangeBase public class ScrollBar : RangeBase
{ {

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

@ -292,11 +292,11 @@ namespace Avalonia.Controls.Primitives
"collection is different to the Items on the control."); "collection is different to the Items on the control.");
} }
var oldSelection = _selection?.SelectedItems.ToList(); var oldSelection = _selection?.SelectedItems.ToArray();
DeinitializeSelectionModel(_selection); DeinitializeSelectionModel(_selection);
_selection = value; _selection = value;
if (oldSelection?.Count > 0) if (oldSelection?.Length > 0)
{ {
RaiseEvent(new SelectionChangedEventArgs( RaiseEvent(new SelectionChangedEventArgs(
SelectionChangedEvent, SelectionChangedEvent,
@ -845,8 +845,8 @@ namespace Avalonia.Controls.Primitives
{ {
var ev = new SelectionChangedEventArgs( var ev = new SelectionChangedEventArgs(
SelectionChangedEvent, SelectionChangedEvent,
e.DeselectedItems.ToList(), e.DeselectedItems.ToArray(),
e.SelectedItems.ToList()); e.SelectedItems.ToArray());
RaiseEvent(ev); RaiseEvent(ev);
} }
} }
@ -988,7 +988,7 @@ namespace Avalonia.Controls.Primitives
RaiseEvent(new SelectionChangedEventArgs( RaiseEvent(new SelectionChangedEventArgs(
SelectionChangedEvent, SelectionChangedEvent,
Array.Empty<object>(), Array.Empty<object>(),
Selection.SelectedItems.ToList())); Selection.SelectedItems.ToArray()));
} }
} }

31
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Logging; using Avalonia.Logging;
@ -41,31 +42,37 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="FontFamily"/> property. /// Defines the <see cref="FontFamily"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<FontFamily> FontFamilyProperty = public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<TemplatedControl>(); TextElement.FontFamilyProperty.AddOwner<TemplatedControl>();
/// <summary> /// <summary>
/// Defines the <see cref="FontSize"/> property. /// Defines the <see cref="FontSize"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> FontSizeProperty = public static readonly StyledProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<TemplatedControl>(); TextElement.FontSizeProperty.AddOwner<TemplatedControl>();
/// <summary> /// <summary>
/// Defines the <see cref="FontStyle"/> property. /// Defines the <see cref="FontStyle"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<FontStyle> FontStyleProperty = public static readonly StyledProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<TemplatedControl>(); TextElement.FontStyleProperty.AddOwner<TemplatedControl>();
/// <summary> /// <summary>
/// Defines the <see cref="FontWeight"/> property. /// Defines the <see cref="FontWeight"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<FontWeight> FontWeightProperty = public static readonly StyledProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<TemplatedControl>(); TextElement.FontWeightProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
public static readonly StyledProperty<FontStretch> FontStretchProperty =
TextElement.FontStretchProperty.AddOwner<TemplatedControl>();
/// <summary> /// <summary>
/// Defines the <see cref="Foreground"/> property. /// Defines the <see cref="Foreground"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IBrush?> ForegroundProperty = public static readonly StyledProperty<IBrush?> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TemplatedControl>(); TextElement.ForegroundProperty.AddOwner<TemplatedControl>();
/// <summary> /// <summary>
/// Defines the <see cref="Padding"/> property. /// Defines the <see cref="Padding"/> property.
@ -185,6 +192,15 @@ namespace Avalonia.Controls.Primitives
set { SetValue(FontWeightProperty, value); } set { SetValue(FontWeightProperty, value); }
} }
/// <summary>
/// Gets or sets the font stretch used to draw the control's text.
/// </summary>
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary> /// <summary>
/// Gets or sets the brush used to draw the control's text and other foreground elements. /// Gets or sets the brush used to draw the control's text and other foreground elements.
/// </summary> /// </summary>
@ -340,6 +356,11 @@ namespace Avalonia.Controls.Primitives
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
} }
/// <summary>
/// Called when the control's template is applied.
/// In simple terms, this means the method is called just before the control is displayed.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnApplyTemplate(TemplateAppliedEventArgs e) protected virtual void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
} }

1
src/Avalonia.Controls/ProgressBar.cs

@ -10,6 +10,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control used to indicate the progress of an operation. /// A control used to indicate the progress of an operation.
/// </summary> /// </summary>
[TemplatePart("PART_Indicator", typeof(Border))]
[PseudoClasses(":vertical", ":horizontal", ":indeterminate")] [PseudoClasses(":vertical", ":horizontal", ":indeterminate")]
public class ProgressBar : RangeBase public class ProgressBar : RangeBase
{ {

3
src/Avalonia.Controls/ScrollViewer.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
@ -11,6 +12,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control which scrolls its content if the content is bigger than the space available. /// A control which scrolls its content if the content is bigger than the space available.
/// </summary> /// </summary>
[TemplatePart("PART_HorizontalScrollBar", typeof(ScrollBar))]
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider
{ {
/// <summary> /// <summary>

4
src/Avalonia.Controls/Selection/InternalSelectionModel.cs

@ -182,8 +182,8 @@ namespace Avalonia.Controls.Selection
try try
{ {
var items = WritableSelectedItems; var items = WritableSelectedItems;
var deselected = e.DeselectedItems.ToList(); var deselected = e.DeselectedItems.ToArray();
var selected = e.SelectedItems.ToList(); var selected = e.SelectedItems.ToArray();
_ignoreSelectedItemsChanges = true; _ignoreSelectedItemsChanges = true;

126
src/Avalonia.Controls/Shapes/Rectangle.cs

@ -2,19 +2,139 @@ using Avalonia.Media;
namespace Avalonia.Controls.Shapes namespace Avalonia.Controls.Shapes
{ {
/// <summary>
/// Represents a rectangle with optional rounded corners.
/// </summary>
public class Rectangle : Shape public class Rectangle : Shape
{ {
private const double PiOver2 = 1.57079633; // 90 deg to rad
/// <summary>
/// Defines the <see cref="RadiusX"/> property.
/// </summary>
public static readonly StyledProperty<double> RadiusXProperty =
AvaloniaProperty.Register<Rectangle, double>(nameof(RadiusX));
/// <summary>
/// Defines the <see cref="RadiusY"/> property.
/// </summary>
public static readonly StyledProperty<double> RadiusYProperty =
AvaloniaProperty.Register<Rectangle, double>(nameof(RadiusY));
static Rectangle() static Rectangle()
{ {
AffectsGeometry<Rectangle>(BoundsProperty, StrokeThicknessProperty); AffectsGeometry<Rectangle>(
BoundsProperty,
RadiusXProperty,
RadiusYProperty,
StrokeThicknessProperty);
}
/// <summary>
/// Gets or sets the radius on the X-axis used to round the corners of the rectangle.
/// Corner radii are represented by an ellipse so this is the X-axis width of the ellipse.
/// </summary>
public double RadiusX
{
get => GetValue(RadiusXProperty);
set => SetValue(RadiusXProperty, value);
}
/// <summary>
/// Gets or sets the radius on the Y-axis used to round the corners of the rectangle.
/// Corner radii are represented by an ellipse so this is the Y-axis height of the ellipse.
/// </summary>
public double RadiusY
{
get => GetValue(RadiusYProperty);
set => SetValue(RadiusYProperty, value);
} }
/// <inheritdoc/>
protected override Geometry CreateDefiningGeometry() protected override Geometry CreateDefiningGeometry()
{ {
var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2); // TODO: If RectangleGeometry ever supports RadiusX/Y like in WPF,
return new RectangleGeometry(rect); // this code can be removed/combined with that implementation
double x = RadiusX;
double y = RadiusY;
if (x == 0 && y == 0)
{
// Optimization when there are no corner radii
var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
return new RectangleGeometry(rect);
}
else
{
var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
var geometry = new StreamGeometry();
var arcSize = new Size(x, y);
using (StreamGeometryContext context = geometry.Open())
{
// The rectangle is constructed as follows:
//
// (origin)
// Corner 4 Corner 1
// Top/Left Line 1 Top/Right
// \_ __________ _/
// | |
// Line 4 | | Line 2
// _ |__________| _
// / Line 3 \
// Corner 3 Corner 2
// Bottom/Left Bottom/Right
//
// - Lines 1,3 follow the deflated rectangle bounds minus RadiusX
// - Lines 2,4 follow the deflated rectangle bounds minus RadiusY
// - All corners are constructed using elliptical arcs
// Line 1 + Corner 1
context.BeginFigure(new Point(rect.Left + x, rect.Top), true);
context.LineTo(new Point(rect.Right - x, rect.Top));
context.ArcTo(
new Point(rect.Right, rect.Top + y),
arcSize,
rotationAngle: PiOver2,
isLargeArc: false,
SweepDirection.Clockwise);
// Line 2 + Corner 2
context.LineTo(new Point(rect.Right, rect.Bottom - y));
context.ArcTo(
new Point(rect.Right - x, rect.Bottom),
arcSize,
rotationAngle: PiOver2,
isLargeArc: false,
SweepDirection.Clockwise);
// Line 3 + Corner 3
context.LineTo(new Point(rect.Left + x, rect.Bottom));
context.ArcTo(
new Point(rect.Left, rect.Bottom - y),
arcSize,
rotationAngle: PiOver2,
isLargeArc: false,
SweepDirection.Clockwise);
// Line 4 + Corner 4
context.LineTo(new Point(rect.Left, rect.Top + y));
context.ArcTo(
new Point(rect.Left + x, rect.Top),
arcSize,
rotationAngle: PiOver2,
isLargeArc: false,
SweepDirection.Clockwise);
context.EndFigure(true);
}
return geometry;
}
} }
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
return new Size(StrokeThickness, StrokeThickness); return new Size(StrokeThickness, StrokeThickness);

3
src/Avalonia.Controls/Slider.cs

@ -42,6 +42,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control that lets the user select from a range of values by moving a Thumb control along a Track. /// A control that lets the user select from a range of values by moving a Thumb control along a Track.
/// </summary> /// </summary>
[TemplatePart("PART_DecreaseButton", typeof(Button))]
[TemplatePart("PART_IncreaseButton", typeof(Button))]
[TemplatePart("PART_Track", typeof(Track))]
[PseudoClasses(":vertical", ":horizontal", ":pressed")] [PseudoClasses(":vertical", ":horizontal", ":pressed")]
public class Slider : RangeBase public class Slider : RangeBase
{ {

34
src/Avalonia.Controls/SplitButton/SplitButton.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -13,6 +12,8 @@ namespace Avalonia.Controls
/// A button with primary and secondary parts that can each be pressed separately. /// A button with primary and secondary parts that can each be pressed separately.
/// The primary part behaves like a <see cref="Button"/> and the secondary part opens a flyout. /// The primary part behaves like a <see cref="Button"/> and the secondary part opens a flyout.
/// </summary> /// </summary>
[TemplatePart("PART_PrimaryButton", typeof(Button))]
[TemplatePart("PART_SecondaryButton", typeof(Button))]
[PseudoClasses(pcFlyoutOpen, pcPressed)] [PseudoClasses(pcFlyoutOpen, pcPressed)]
public class SplitButton : ContentControl, ICommandSource public class SplitButton : ContentControl, ICommandSource
{ {
@ -314,16 +315,9 @@ namespace Avalonia.Controls
} }
// Must unregister events here while a reference to the old flyout still exists // Must unregister events here while a reference to the old flyout still exists
if (oldFlyout != null) UnregisterFlyoutEvents(oldFlyout);
{
UnregisterFlyoutEvents(oldFlyout);
}
if (newFlyout != null)
{
RegisterFlyoutEvents(newFlyout);
}
RegisterFlyoutEvents(newFlyout);
UpdatePseudoClasses(); UpdatePseudoClasses();
} }
@ -415,6 +409,22 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Invoked when the split button's flyout is opened.
/// </summary>
protected virtual void OnFlyoutOpened()
{
// Available for derived types
}
/// <summary>
/// Invoked when the split button's flyout is closed.
/// </summary>
protected virtual void OnFlyoutClosed()
{
// Available for derived types
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// Event Handling // Event Handling
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -464,6 +474,8 @@ namespace Avalonia.Controls
{ {
_isFlyoutOpen = true; _isFlyoutOpen = true;
UpdatePseudoClasses(); UpdatePseudoClasses();
OnFlyoutOpened();
} }
} }
@ -479,6 +491,8 @@ namespace Avalonia.Controls
{ {
_isFlyoutOpen = false; _isFlyoutOpen = false;
UpdatePseudoClasses(); UpdatePseudoClasses();
OnFlyoutClosed();
} }
} }
} }

1
src/Avalonia.Controls/SplitView.cs

@ -77,6 +77,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A control with two views: A collapsible pane and an area for content /// A control with two views: A collapsible pane and an area for content
/// </summary> /// </summary>
[TemplatePart("PART_PaneRoot", typeof(Panel))]
[PseudoClasses(":open", ":closed")] [PseudoClasses(":open", ":closed")]
[PseudoClasses(":compactoverlay", ":compactinline", ":overlay", ":inline")] [PseudoClasses(":compactoverlay", ":compactinline", ":overlay", ":inline")]
[PseudoClasses(":left", ":right")] [PseudoClasses(":left", ":right")]

2
src/Avalonia.Controls/TabControl.cs

@ -11,12 +11,14 @@ using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Automation; using Avalonia.Automation;
using Avalonia.Controls.Metadata;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// A tab control that displays a tab strip along with the content of the selected tab. /// A tab control that displays a tab strip along with the content of the selected tab.
/// </summary> /// </summary>
[TemplatePart("PART_ItemsPresenter", typeof(ItemsPresenter))]
public class TabControl : SelectingItemsControl, IContentPresenterHost public class TabControl : SelectingItemsControl, IContentPresenterHost
{ {
/// <summary> /// <summary>

297
src/Avalonia.Controls/TextBlock.cs

@ -28,78 +28,69 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Thickness> PaddingProperty = public static readonly StyledProperty<Thickness> PaddingProperty =
Decorator.PaddingProperty.AddOwner<TextBlock>(); Decorator.PaddingProperty.AddOwner<TextBlock>();
// TODO: Define these attached properties elsewhere (e.g. on a Text class) and AddOwner
// them into TextBlock.
/// <summary> /// <summary>
/// Defines the <see cref="FontFamily"/> property. /// Defines the <see cref="FontFamily"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty = public static readonly StyledProperty<FontFamily> FontFamilyProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontFamily>( TextElement.FontFamilyProperty.AddOwner<TextBlock>();
nameof(FontFamily),
defaultValue: FontFamily.Default,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="FontSize"/> property. /// Defines the <see cref="FontSize"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<double> FontSizeProperty = public static readonly StyledProperty<double> FontSizeProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, double>( TextElement.FontSizeProperty.AddOwner<TextBlock>();
nameof(FontSize),
defaultValue: 12,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="FontStyle"/> property. /// Defines the <see cref="FontStyle"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty = public static readonly StyledProperty<FontStyle> FontStyleProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontStyle>( TextElement.FontStyleProperty.AddOwner<TextBlock>();
nameof(FontStyle),
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="FontWeight"/> property. /// Defines the <see cref="FontWeight"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty = public static readonly StyledProperty<FontWeight> FontWeightProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontWeight>( TextElement.FontWeightProperty.AddOwner<TextBlock>();
nameof(FontWeight),
inherits: true,
defaultValue: FontWeight.Normal);
/// <summary> /// <summary>
/// Defines the <see cref="FontStretch"/> property. /// Defines the <see cref="FontWeight"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<FontStretch> FontStretchProperty = public static readonly StyledProperty<FontStretch> FontStretchProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontStretch>( TextElement.FontStretchProperty.AddOwner<TextBlock>();
nameof(FontStretch),
inherits: true,
defaultValue: FontStretch.Normal);
/// <summary> /// <summary>
/// Defines the <see cref="Foreground"/> property. /// Defines the <see cref="Foreground"/> property.
/// </summary> /// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty = public static readonly StyledProperty<IBrush?> ForegroundProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush?>( TextElement.ForegroundProperty.AddOwner<TextBlock>();
nameof(Foreground),
Brushes.Black, /// <summary>
inherits: true); /// DependencyProperty for <see cref="BaselineOffset" /> property.
/// </summary>
public static readonly AttachedProperty<double> BaselineOffsetProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, double>(
nameof(BaselineOffset),
0,
true);
/// <summary> /// <summary>
/// Defines the <see cref="LineHeight"/> property. /// Defines the <see cref="LineHeight"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> LineHeightProperty = public static readonly AttachedProperty<double> LineHeightProperty =
AvaloniaProperty.Register<TextBlock, double>( AvaloniaProperty.RegisterAttached<TextBlock, Control, double>(
nameof(LineHeight), nameof(LineHeight),
double.NaN, double.NaN,
validate: IsValidLineHeight); validate: IsValidLineHeight,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="MaxLines"/> property. /// Defines the <see cref="MaxLines"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<int> MaxLinesProperty = public static readonly AttachedProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBlock, int>( AvaloniaProperty.RegisterAttached<TextBlock, Control, int>(
nameof(MaxLines), nameof(MaxLines),
validate: IsValidMaxLines); validate: IsValidMaxLines,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="Text"/> property. /// Defines the <see cref="Text"/> property.
@ -121,20 +112,24 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="TextAlignment"/> property. /// Defines the <see cref="TextAlignment"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TextAlignment> TextAlignmentProperty = public static readonly AttachedProperty<TextAlignment> TextAlignmentProperty =
AvaloniaProperty.Register<TextBlock, TextAlignment>(nameof(TextAlignment)); AvaloniaProperty.RegisterAttached<TextBlock, Control, TextAlignment>(nameof(TextAlignment),
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="TextWrapping"/> property. /// Defines the <see cref="TextWrapping"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TextWrapping> TextWrappingProperty = public static readonly AttachedProperty<TextWrapping> TextWrappingProperty =
AvaloniaProperty.Register<TextBlock, TextWrapping>(nameof(TextWrapping)); AvaloniaProperty.RegisterAttached<TextBlock, Control, TextWrapping>(nameof(TextWrapping),
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="TextTrimming"/> property. /// Defines the <see cref="TextTrimming"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<TextTrimming> TextTrimmingProperty = public static readonly AttachedProperty<TextTrimming> TextTrimmingProperty =
AvaloniaProperty.Register<TextBlock, TextTrimming>(nameof(TextTrimming), defaultValue: TextTrimming.None); AvaloniaProperty.RegisterAttached<TextBlock, Control, TextTrimming>(nameof(TextTrimming),
defaultValue: TextTrimming.None,
inherits: true);
/// <summary> /// <summary>
/// Defines the <see cref="TextDecorations"/> property. /// Defines the <see cref="TextDecorations"/> property.
@ -222,7 +217,7 @@ namespace Avalonia.Controls
public InlineCollection Inlines { get; } public InlineCollection Inlines { get; }
/// <summary> /// <summary>
/// Gets or sets the font family. /// Gets or sets the font family used to draw the control's text.
/// </summary> /// </summary>
public FontFamily FontFamily public FontFamily FontFamily
{ {
@ -231,7 +226,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Gets or sets the font size. /// Gets or sets the size of the control's text in points.
/// </summary> /// </summary>
public double FontSize public double FontSize
{ {
@ -240,7 +235,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Gets or sets the font style. /// Gets or sets the font style used to draw the control's text.
/// </summary> /// </summary>
public FontStyle FontStyle public FontStyle FontStyle
{ {
@ -249,16 +244,16 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Gets or sets the font weight. /// Gets or sets the font weight used to draw the control's text.
/// </summary> /// </summary>
public FontWeight FontWeight public FontWeight FontWeight
{ {
get { return GetValue(FontWeightProperty); } get { return GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); } set { SetValue(FontWeightProperty, value); }
} }
/// <summary> /// <summary>
/// Gets or sets the font stretch. /// Gets or sets the font stretch used to draw the control's text.
/// </summary> /// </summary>
public FontStretch FontStretch public FontStretch FontStretch
{ {
@ -267,7 +262,7 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Gets or sets a brush used to paint the text. /// Gets or sets the brush used to draw the control's text and other foreground elements.
/// </summary> /// </summary>
public IBrush? Foreground public IBrush? Foreground
{ {
@ -328,127 +323,193 @@ namespace Avalonia.Controls
get => GetValue(TextDecorationsProperty); get => GetValue(TextDecorationsProperty);
set => SetValue(TextDecorationsProperty, value); set => SetValue(TextDecorationsProperty, value);
} }
protected override bool BypassFlowDirectionPolicies => true;
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="FontFamilyProperty"/> on a control. /// The BaselineOffset property provides an adjustment to baseline offset
/// </summary> /// </summary>
/// <param name="control">The control.</param> public double BaselineOffset
/// <returns>The font family.</returns>
public static FontFamily GetFontFamily(Control control)
{ {
return control.GetValue(FontFamilyProperty); get { return (double)GetValue(BaselineOffsetProperty); }
set { SetValue(BaselineOffsetProperty, value); }
} }
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="FontSizeProperty"/> on a control. /// Reads the attached property from the given element
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to read the attached property.</param>
/// <returns>The font size.</returns> public static double GetBaselineOffset(Control control)
public static double GetFontSize(Control control)
{ {
return control.GetValue(FontSizeProperty); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(BaselineOffsetProperty);
} }
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="FontStyleProperty"/> on a control. /// Writes the attached property BaselineOffset to the given element.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to write the attached property.</param>
/// <returns>The font style.</returns> /// <param name="value">The property value to set</param>
public static FontStyle GetFontStyle(Control control) public static void SetBaselineOffset(Control control, double value)
{ {
return control.GetValue(FontStyleProperty); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(BaselineOffsetProperty, value);
} }
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="FontWeightProperty"/> on a control. /// Reads the attached property from the given element
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to read the attached property.</param>
/// <returns>The font weight.</returns> public static TextAlignment GetTextAlignment(Control control)
public static FontWeight GetFontWeight(Control control)
{ {
return control.GetValue(FontWeightProperty); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextAlignmentProperty);
} }
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="FontStretchProperty"/> on a control. /// Writes the attached property BaselineOffset to the given element.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to write the attached property.</param>
/// <returns>The font stretch.</returns> /// <param name="alignment">The property value to set</param>
public static FontStretch GetFontStretch(Control control) public static void SetTextAlignment(Control control, TextAlignment alignment)
{ {
return control.GetValue(FontStretchProperty); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextAlignmentProperty, alignment);
} }
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="ForegroundProperty"/> on a control. /// Reads the attached property from the given element
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to read the attached property.</param>
/// <returns>The foreground.</returns> public static TextWrapping GetTextWrapping(Control control)
public static IBrush? GetForeground(Control control)
{ {
return control.GetValue(ForegroundProperty); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextWrappingProperty);
} }
/// <summary> /// <summary>
/// Sets the value of the attached <see cref="FontFamilyProperty"/> on a control. /// Writes the attached property BaselineOffset to the given element.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to write the attached property.</param>
/// <param name="value">The property value to set.</param> /// <param name="wrapping">The property value to set</param>
public static void SetFontFamily(Control control, FontFamily value) public static void SetTextWrapping(Control control, TextWrapping wrapping)
{ {
control.SetValue(FontFamilyProperty, value); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextWrappingProperty, wrapping);
} }
/// <summary> /// <summary>
/// Sets the value of the attached <see cref="FontSizeProperty"/> on a control. /// Reads the attached property from the given element
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to read the attached property.</param>
/// <param name="value">The property value to set.</param> public static TextTrimming GetTextTrimming(Control control)
public static void SetFontSize(Control control, double value)
{ {
control.SetValue(FontSizeProperty, value); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(TextTrimmingProperty);
} }
/// <summary> /// <summary>
/// Sets the value of the attached <see cref="FontStyleProperty"/> on a control. /// Writes the attached property BaselineOffset to the given element.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to write the attached property.</param>
/// <param name="value">The property value to set.</param> /// <param name="trimming">The property value to set</param>
public static void SetFontStyle(Control control, FontStyle value) public static void SetTextTrimming(Control control, TextTrimming trimming)
{ {
control.SetValue(FontStyleProperty, value); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(TextTrimmingProperty, trimming);
} }
/// <summary> /// <summary>
/// Sets the value of the attached <see cref="FontWeightProperty"/> on a control. /// Reads the attached property from the given element
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to read the attached property.</param>
/// <param name="value">The property value to set.</param> public static double GetLineHeight(Control control)
public static void SetFontWeight(Control control, FontWeight value)
{ {
control.SetValue(FontWeightProperty, value); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(LineHeightProperty);
} }
/// <summary> /// <summary>
/// Sets the value of the attached <see cref="FontStretchProperty"/> on a control. /// Writes the attached property BaselineOffset to the given element.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to write the attached property.</param>
/// <param name="value">The property value to set.</param> /// <param name="height">The property value to set</param>
public static void SetFontStretch(Control control, FontStretch value) public static void SetLineHeight(Control control, double height)
{ {
control.SetValue(FontStretchProperty, value); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(LineHeightProperty, height);
} }
/// <summary> /// <summary>
/// Sets the value of the attached <see cref="ForegroundProperty"/> on a control. /// Reads the attached property from the given element
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The element to which to read the attached property.</param>
/// <param name="value">The property value to set.</param> public static int GetMaxLines(Control control)
public static void SetForeground(Control control, IBrush? value)
{ {
control.SetValue(ForegroundProperty, value); if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
return control.GetValue(MaxLinesProperty);
} }
/// <summary>
/// Writes the attached property BaselineOffset to the given element.
/// </summary>
/// <param name="control">The element to which to write the attached property.</param>
/// <param name="maxLines">The property value to set</param>
public static void SetMaxLines(Control control, int maxLines)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control));
}
control.SetValue(MaxLinesProperty, maxLines);
}
/// <summary> /// <summary>
/// Renders the <see cref="TextBlock"/> to a drawing context. /// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary> /// </summary>
@ -562,7 +623,7 @@ namespace Avalonia.Controls
return finalSize; return finalSize;
} }
_constraint = finalSize; _constraint = new Size(finalSize.Width, Math.Ceiling(finalSize.Height));
_textLayout = null; _textLayout = null;
@ -588,15 +649,19 @@ namespace Avalonia.Controls
case nameof (FontWeight): case nameof (FontWeight):
case nameof (FontStyle): case nameof (FontStyle):
case nameof (FontFamily): case nameof (FontFamily):
case nameof (FontStretch):
case nameof (TextWrapping): case nameof (TextWrapping):
case nameof (TextTrimming): case nameof (TextTrimming):
case nameof (TextAlignment): case nameof (TextAlignment):
case nameof (FlowDirection): case nameof (FlowDirection):
case nameof (Padding): case nameof (Padding):
case nameof (LineHeight): case nameof (LineHeight):
case nameof (MaxLines): case nameof (MaxLines):
case nameof (InlinesProperty):
case nameof (Text): case nameof (Text):
case nameof (TextDecorations): case nameof (TextDecorations):

112
src/Avalonia.Controls/TextBox.cs

@ -23,6 +23,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Represents a control that can be used to display or edit unformatted text. /// Represents a control that can be used to display or edit unformatted text.
/// </summary> /// </summary>
[TemplatePart("PART_TextPresenter", typeof(TextPresenter))]
[PseudoClasses(":empty")] [PseudoClasses(":empty")]
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
{ {
@ -77,6 +78,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<int> MaxLengthProperty = public static readonly StyledProperty<int> MaxLengthProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0); AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
public static readonly DirectProperty<TextBox, string?> TextProperty = public static readonly DirectProperty<TextBox, string?> TextProperty =
TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>( TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
o => o.Text, o => o.Text,
@ -349,6 +353,12 @@ namespace Avalonia.Controls
set { SetValue(MaxLengthProperty, value); } set { SetValue(MaxLengthProperty, value); }
} }
public int MaxLines
{
get { return GetValue(MaxLinesProperty); }
set { SetValue(MaxLinesProperty, value); }
}
[Content] [Content]
public string? Text public string? Text
{ {
@ -670,6 +680,39 @@ namespace Avalonia.Controls
_selectedTextChangesMadeSinceLastUndoSnapshot++; _selectedTextChangesMadeSinceLastUndoSnapshot++;
SnapshotUndoRedo(ignoreChangeCount: false); SnapshotUndoRedo(ignoreChangeCount: false);
if (_presenter != null && MaxLines > 0)
{
var lineCount = _presenter.TextLayout.TextLines.Count;
var length = 0;
var graphemeEnumerator = new GraphemeEnumerator(input.AsMemory());
while (graphemeEnumerator.MoveNext())
{
var grapheme = graphemeEnumerator.Current;
if (grapheme.FirstCodepoint.IsBreakChar)
{
if(lineCount + 1 > MaxLines)
{
break;
}
else
{
lineCount++;
}
}
length += grapheme.Text.Length;
}
if (length < input.Length)
{
input = input.Remove(Math.Max(0, length));
}
}
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd); var newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd);
@ -997,18 +1040,25 @@ namespace Avalonia.Controls
SetSelectionForControlBackspace(); SetSelectionForControlBackspace();
} }
if (!DeleteSelection() && caretIndex > 0) if (!DeleteSelection())
{ {
_presenter.MoveCaretHorizontal(LogicalDirection.Backward); var characterHit = _presenter.GetNextCharacterHit(LogicalDirection.Backward);
var removedCharacters = Math.Max(0, caretIndex - _presenter.CaretIndex); var backspacePosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var length = Math.Max(0, caretIndex - removedCharacters);
SetTextInternal(text.Substring(0, length) + if (caretIndex != backspacePosition)
text.Substring(caretIndex)); {
var start = Math.Min(backspacePosition, caretIndex);
var end = Math.Max(backspacePosition, caretIndex);
CaretIndex = _presenter.CaretIndex; var length = end - start;
var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
SetTextInternal(editedText);
CaretIndex = start;
}
} }
SnapshotUndoRedo(); SnapshotUndoRedo();
@ -1024,15 +1074,21 @@ namespace Avalonia.Controls
SetSelectionForControlDelete(); SetSelectionForControlDelete();
} }
if (!DeleteSelection() && caretIndex < text.Length) if (!DeleteSelection())
{ {
var characterHit = _presenter.GetNextCharacterHit(); var characterHit = _presenter.GetNextCharacterHit();
var removedCharacters = Math.Max(0, var nextPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
characterHit.FirstCharacterIndex + characterHit.TrailingLength - caretIndex);
if(nextPosition != caretIndex)
{
var start = Math.Min(nextPosition, caretIndex);
var end = Math.Max(nextPosition, caretIndex);
SetTextInternal(text.Substring(0, caretIndex) + var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
text.Substring(caretIndex + removedCharacters));
SetTextInternal(editedText);
}
} }
SnapshotUndoRedo(); SnapshotUndoRedo();
@ -1249,17 +1305,17 @@ namespace Avalonia.Controls
private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting) private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting)
{ {
if (_presenter == null)
{
return;
}
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
if (!wholeWord) if (!wholeWord)
{ {
if (_presenter == null)
{
return;
}
if (isSelecting) if (isSelecting)
{ {
_presenter.MoveCaretToTextPosition(selectionEnd); _presenter.MoveCaretToTextPosition(selectionEnd);
@ -1303,10 +1359,16 @@ namespace Avalonia.Controls
SelectionEnd += offset; SelectionEnd += offset;
_presenter.MoveCaretToTextPosition(SelectionEnd);
if (!isSelecting) if (!isSelecting)
{ {
CaretIndex = SelectionEnd; CaretIndex = SelectionEnd;
} }
else
{
SelectionStart = selectionStart;
}
} }
} }
@ -1326,10 +1388,10 @@ namespace Avalonia.Controls
else else
{ {
var textLines = _presenter.TextLayout.TextLines; var textLines = _presenter.TextLayout.TextLines;
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, true); var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, false);
var textLine = textLines[lineIndex]; var textLine = textLines[lineIndex];
_presenter.MoveCaretToTextPosition(textLine.TextRange.Start); _presenter.MoveCaretToTextPosition(textLine.FirstTextSourceIndex);
} }
} }
@ -1350,16 +1412,10 @@ namespace Avalonia.Controls
else else
{ {
var textLines = _presenter.TextLayout.TextLines; var textLines = _presenter.TextLayout.TextLines;
var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, true); var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(caretIndex, false);
var textLine = textLines[lineIndex]; var textLine = textLines[lineIndex];
if (caretIndex == textLine.TextRange.Start + textLine.TextRange.Length - textLine.NewLineLength &&
lineIndex + 1 < textLines.Count)
{
textLine = textLines[++lineIndex];
}
var textPosition = textLine.TextRange.Start + textLine.TextRange.Length - textLine.NewLineLength; var textPosition = textLine.FirstTextSourceIndex + textLine.Length;
_presenter.MoveCaretToTextPosition(textPosition, true); _presenter.MoveCaretToTextPosition(textPosition, true);
} }

2
src/Avalonia.Controls/ToggleSwitch.cs

@ -9,6 +9,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// A Toggle Switch control. /// A Toggle Switch control.
/// </summary> /// </summary>
[TemplatePart("MovingKnobs", typeof(Panel))]
[TemplatePart("SwitchKnob", typeof(Panel))]
[PseudoClasses(":dragging")] [PseudoClasses(":dragging")]
public class ToggleSwitch : ToggleButton public class ToggleSwitch : ToggleButton
{ {

33
src/Avalonia.Controls/TopLevel.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
@ -14,7 +15,6 @@ using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -26,6 +26,7 @@ namespace Avalonia.Controls
/// It handles scheduling layout, styling and rendering as well as /// It handles scheduling layout, styling and rendering as well as
/// tracking the widget's <see cref="ClientSize"/>. /// tracking the widget's <see cref="ClientSize"/>.
/// </remarks> /// </remarks>
[TemplatePart("PART_TransparencyFallback", typeof(Border))]
public abstract class TopLevel : ContentControl, public abstract class TopLevel : ContentControl,
IInputRoot, IInputRoot,
ILayoutRoot, ILayoutRoot,
@ -85,6 +86,8 @@ namespace Avalonia.Controls
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
private readonly IPlatformRenderInterface? _renderInterface; private readonly IPlatformRenderInterface? _renderInterface;
private readonly IGlobalStyles? _globalStyles; private readonly IGlobalStyles? _globalStyles;
private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
private readonly IDisposable? _pointerOverPreProcessorSubscription;
private Size _clientSize; private Size _clientSize;
private Size? _frameSize; private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel; private WindowTransparencyLevel _actualTransparencyLevel;
@ -193,6 +196,9 @@ namespace Avalonia.Controls
} }
impl.LostFocus += PlatformImpl_LostFocus; impl.LostFocus += PlatformImpl_LostFocus;
_pointerOverPreProcessor = new PointerOverPreProcessor(this);
_pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor);
} }
/// <summary> /// <summary>
@ -281,9 +287,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
IKeyboardNavigationHandler IInputRoot.KeyboardNavigationHandler => _keyboardNavigationHandler!; IKeyboardNavigationHandler IInputRoot.KeyboardNavigationHandler => _keyboardNavigationHandler!;
/// <summary> /// <inheritdoc/>
/// Gets or sets the input element that the pointer is currently over.
/// </summary>
IInputElement? IInputRoot.PointerOverElement IInputElement? IInputRoot.PointerOverElement
{ {
get { return GetValue(PointerOverElementProperty); } get { return GetValue(PointerOverElementProperty); }
@ -348,6 +352,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this);
public override void InvalidateMirrorTransform()
{
}
protected override bool BypassFlowDirectionPolicies => true;
/// <summary> /// <summary>
/// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>. /// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary> /// </summary>
@ -370,10 +380,12 @@ namespace Avalonia.Controls
Renderer?.Dispose(); Renderer?.Dispose();
Renderer = null!; Renderer = null!;
(this as IInputRoot).MouseDevice?.TopLevelClosed(this); _pointerOverPreProcessor?.OnCompleted();
_pointerOverPreProcessorSubscription?.Dispose();
PlatformImpl = null; PlatformImpl = null;
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
@ -507,12 +519,17 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void HandleInput(RawInputEventArgs e) private void HandleInput(RawInputEventArgs e)
{ {
if (e is RawPointerEventArgs pointerArgs)
{
pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position);
}
_inputManager?.ProcessInput(e); _inputManager?.ProcessInput(e);
} }
private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e)
{ {
(this as IInputRoot).MouseDevice?.SceneInvalidated(this, e.DirtyRect); _pointerOverPreProcessor?.SceneInvalidated(e.DirtyRect);
} }
void PlatformImpl_LostFocus() void PlatformImpl_LostFocus()

2
src/Avalonia.Controls/TreeView.cs

@ -849,7 +849,7 @@ namespace Avalonia.Controls
/// <param name="desired">The desired items.</param> /// <param name="desired">The desired items.</param>
private static void SynchronizeItems(IList items, IEnumerable<object> desired) private static void SynchronizeItems(IList items, IEnumerable<object> desired)
{ {
var list = items.Cast<object>().ToList(); var list = items.Cast<object>();
var toRemove = list.Except(desired).ToList(); var toRemove = list.Except(desired).ToList();
var toAdd = desired.Except(list).ToList(); var toAdd = desired.Except(list).ToList();

1
src/Avalonia.Controls/TreeViewItem.cs

@ -12,6 +12,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// An item in a <see cref="TreeView"/>. /// An item in a <see cref="TreeView"/>.
/// </summary> /// </summary>
[TemplatePart("PART_Header", typeof(IControl))]
[PseudoClasses(":pressed", ":selected")] [PseudoClasses(":pressed", ":selected")]
public class TreeViewItem : HeaderedItemsControl, ISelectable public class TreeViewItem : HeaderedItemsControl, ISelectable
{ {

4
src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs

@ -105,7 +105,7 @@ namespace Avalonia.Controls.Utils
static void Notify( static void Notify(
INotifyCollectionChanged incc, INotifyCollectionChanged incc,
NotifyCollectionChangedEventArgs args, NotifyCollectionChangedEventArgs args,
List<WeakReference<ICollectionChangedListener>> listeners) WeakReference<ICollectionChangedListener>[] listeners)
{ {
foreach (var l in listeners) foreach (var l in listeners)
{ {
@ -132,7 +132,7 @@ namespace Avalonia.Controls.Utils
} }
} }
var l = Listeners.ToList(); var l = Listeners.ToArray();
if (Dispatcher.UIThread.CheckAccess()) if (Dispatcher.UIThread.CheckAccess())
{ {

7
src/Avalonia.Controls/Window.cs

@ -12,7 +12,6 @@ using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using JetBrains.Annotations;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -258,7 +257,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets a collection of child windows owned by this window. /// Gets a collection of child windows owned by this window.
/// </summary> /// </summary>
public IReadOnlyList<Window> OwnedWindows => _children.Select(x => x.child).ToList(); public IReadOnlyList<Window> OwnedWindows => _children.Select(x => x.child).ToArray();
/// <summary> /// <summary>
/// Gets or sets a value indicating how the window will size itself to fit its content. /// Gets or sets a value indicating how the window will size itself to fit its content.
@ -527,7 +526,7 @@ namespace Avalonia.Controls
private void CloseInternal() private void CloseInternal()
{ {
foreach (var (child, _) in _children.ToList()) foreach (var (child, _) in _children.ToArray())
{ {
child.CloseInternal(); child.CloseInternal();
} }
@ -551,7 +550,7 @@ namespace Avalonia.Controls
bool canClose = true; bool canClose = true;
foreach (var (child, _) in _children.ToList()) foreach (var (child, _) in _children.ToArray())
{ {
if (child.ShouldCancelClose(args)) if (child.ShouldCancelClose(args))
{ {

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

@ -1078,12 +1078,6 @@
"json5": "^2.1.2" "json5": "^2.1.2"
} }
}, },
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"schema-utils": { "schema-utils": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
@ -1636,9 +1630,9 @@
} }
}, },
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true "dev": true
}, },
"mobx": { "mobx": {
@ -1839,12 +1833,6 @@
"ms": "^2.1.1" "ms": "^2.1.1"
} }
}, },
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": { "mkdirp": {
"version": "0.5.5", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
@ -2592,12 +2580,6 @@
"json5": "^2.1.2" "json5": "^2.1.2"
} }
}, },
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"schema-utils": { "schema-utils": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",

1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -34,4 +34,5 @@
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" /> <Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project> </Project>

6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ControlDetailsViewModel : ViewModelBase, IDisposable internal class ControlDetailsViewModel : ViewModelBase, IDisposable
{ {
private readonly IAvaloniaObject _avaloniaObject; private readonly IAvaloniaObject _avaloniaObject;
private IDictionary<object, List<PropertyViewModel>>? _propertyIndex; private IDictionary<object, PropertyViewModel[]>? _propertyIndex;
private PropertyViewModel? _selectedProperty; private PropertyViewModel? _selectedProperty;
private DataGridCollectionView? _propertiesView; private DataGridCollectionView? _propertiesView;
private bool _snapshotStyles; private bool _snapshotStyles;
@ -472,9 +472,9 @@ namespace Avalonia.Diagnostics.ViewModels
.Concat(GetClrProperties(o, _showImplementedInterfaces)) .Concat(GetClrProperties(o, _showImplementedInterfaces))
.OrderBy(x => x, PropertyComparer.Instance) .OrderBy(x => x, PropertyComparer.Instance)
.ThenBy(x => x.Name) .ThenBy(x => x.Name)
.ToList(); .ToArray();
_propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList()); _propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToArray());
var view = new DataGridCollectionView(properties); var view = new DataGridCollectionView(properties);
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))); view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group)));

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

@ -7,7 +7,6 @@ using Avalonia.Diagnostics.Models;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Threading; using Avalonia.Threading;
using System.Reactive.Linq;
using System.Linq; using System.Linq;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
@ -59,8 +58,8 @@ namespace Avalonia.Diagnostics.ViewModels
.Subscribe(e => .Subscribe(e =>
{ {
PointerOverRoot = e.Root; PointerOverRoot = e.Root;
PointerOverElement = e.Root.GetInputElementsAt(e.Position).FirstOrDefault(); PointerOverElement = e.Root.InputHitTest(e.Position);
}); });
#nullable restore #nullable restore
} }
Console = new ConsoleViewModel(UpdateConsoleContext); Console = new ConsoleViewModel(UpdateConsoleContext);

4
src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml

@ -11,7 +11,7 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel Margin="{TemplateBinding Padding}"> <DockPanel Margin="{TemplateBinding Padding}">
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0">></TextBlock> <TextBlock DockPanel.Dock="Left" Margin="0,0,4,0"></TextBlock>
<TextPresenter Name="PART_TextPresenter" <TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}" Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}" CaretIndex="{TemplateBinding CaretIndex}"
@ -44,7 +44,7 @@
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<DockPanel> <DockPanel>
<TextBlock DockPanel.Dock="Left" Margin="0,0,4,0">></TextBlock> <TextBlock DockPanel.Dock="Left" Margin="0,0,4,0"></TextBlock>
<TextBlock Text="{Binding Input}"/> <TextBlock Text="{Binding Input}"/>
</DockPanel> </DockPanel>
<TextBlock Foreground="{Binding Foreground}" Text="{Binding Output}"/> <TextBlock Foreground="{Binding Foreground}" Text="{Binding Output}"/>

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

Loading…
Cancel
Save