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\Binding.props = build\Binding.props
build\CoreLibraries.props = build\CoreLibraries.props
build\DevAnalyzers.props = build\DevAnalyzers.props
build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
@ -234,7 +235,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PlatformSupport",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}"
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
Global
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|iPhoneSimulator.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2303,6 +2330,7 @@ Global
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {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}
{2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
NuGet.Config

@ -5,5 +5,6 @@
<clear />
<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="skiasharp" value="https://aka.ms/skiasharp-eap/index.json" />
</packageSources>
</configuration>

26
azure-pipelines.yml

@ -31,14 +31,14 @@ jobs:
vmImage: 'ubuntu-20.04'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414'
displayName: 'Use .NET Core SDK 3.1.418'
inputs:
version: 3.1.414
version: 3.1.418
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100'
displayName: 'Use .NET Core SDK 6.0.202'
inputs:
version: 6.0.200
version: 6.0.202
- task: CmdLine@2
displayName: 'Run Build'
@ -62,14 +62,14 @@ jobs:
vmImage: 'macOS-10.15'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414'
displayName: 'Use .NET Core SDK 3.1.418'
inputs:
version: 3.1.414
version: 3.1.418
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100'
displayName: 'Use .NET Core SDK 6.0.202'
inputs:
version: 6.0.200
version: 6.0.202
- task: CmdLine@2
displayName: 'Install Mono 5.18'
@ -134,20 +134,20 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414'
displayName: 'Use .NET Core SDK 3.1.418'
inputs:
version: 3.1.414
version: 3.1.418
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.100'
displayName: 'Use .NET Core SDK 6.0.202'
inputs:
version: 6.0.200
version: 6.0.202
- task: CmdLine@2
displayName: 'Install Workloads'
inputs:
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
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">
<ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.8.2-preview.209" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2-preview.209" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" 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.254" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2-preview.254"/>
</ItemGroup>
</Project>

6
build/SkiaSharp.props

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

3
global.json

@ -1,11 +1,10 @@
{
"sdk": {
"version": "6.0.200",
"version": "6.0.202",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43",
"Xamarin.Legacy.Sdk": "0.1.2-alpha6",
"MSBuild.Sdk.Extras": "3.0.22",
"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)
{
auto cparent = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(cparent != nullptr)
{
if(cparent->WindowState() == Maximized)
{
cparent->SetWindowState(Normal);
}
}
_parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position);
}

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

@ -1,15 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
<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>
<ItemGroup>
<BlazorLinkerDescriptor Include="LinkerConfig.xml" />
</ItemGroup>
<!-- In debug, make builds faster by reducing optimizations -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<WasmNativeStrip>false</WasmNativeStrip>
@ -23,19 +22,36 @@
<EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
<EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
<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>
<ItemGroup>
<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" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
</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>
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj"/>
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj"/>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</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>

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>
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="FlowDirection"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBox.Items>
<FlowDirection>LeftToRight</FlowDirection>
<FlowDirection>RightToLeft</FlowDirection>
</ComboBox.Items>
</ComboBox>
<ComboBox HorizontalAlignment="Stretch"
Items="{Binding WindowStates}"
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");
decorations.SelectionChanged += (sender, e) =>
{

32
samples/ControlCatalog/Pages/ButtonsPage.xaml

@ -147,6 +147,35 @@
</StackPanel>
</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 -->
<Border Classes="header-border">
<StackPanel Orientation="Vertical"
@ -169,7 +198,8 @@
<TextBlock Text="Disabled" />
</SplitButton>
<SplitButton Flyout="{StaticResource SharedMenuFlyout}"
Content="Re-themed">
Content="Re-themed"
Foreground="White">
<SplitButton.Styles>
<Style>
<Style.Resources>

5
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -4,7 +4,7 @@
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>
<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>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
@ -12,8 +12,9 @@
<GradientStop Offset="1" Color="Transparent"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.OpacityMask>
</Rectangle.OpacityMask>
</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"/>
<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">

3
samples/ControlCatalog/Pages/PointersPage.cs

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

2
samples/ControlCatalog/Pages/ScreenPage.cs

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

8
samples/IntegrationTestApp/MainWindow.axaml

@ -82,12 +82,16 @@
<Menu DockPanel.Dock="Top">
<MenuItem Name="RootMenuItem" Header="_Root">
<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>
</MenuItem>
</Menu>
<TextBlock Name="ClickedMenuItem">None</TextBlock>
<StackPanel>
<TextBlock Name="ClickedMenuItem">None</TextBlock>
<Button Name="MenuClickedMenuItemReset">Reset</Button>
<TextBox Name="MenuFocusTest"/>
</StackPanel>
</DockPanel>
</TabItem>
</TabControl>

2
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -62,6 +62,8 @@ namespace IntegrationTestApp
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = 0;
if (source?.Name == "ListBoxSelectionClear")
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">
<pages:FormattedTextPage />
</TabItem>
<TabItem Header="TextFormatter">
<pages:TextFormatterPage />
</TabItem>
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</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>
<ItemGroup>
<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>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />

18
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -195,9 +195,9 @@
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
</Panel>
</Border>
</ControlTemplate>
@ -216,25 +216,25 @@
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
TextElement.FontFamily="{TemplateBinding FontFamily}"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}" />
</ControlTemplate>
</Setter>
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton /template/ ContentPresenter">
<Setter Property="Border.Background" Value="Transparent" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemBaseHighColor}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemBaseHighColor}" />
</Style>
<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.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
<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.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
<Style Selector=":is(Button).NavigationButton:pressed">
<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>
<TargetFrameworks>net6.0-android</TargetFrameworks>
<TargetFrameworks Condition="'$(MSBuildRuntimeType)' != 'Core'">$(TargetFrameworks);monoandroid11.0</TargetFrameworks>
<TargetFramework>net6.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
@ -16,4 +15,5 @@
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\DevAnalyzers.props" />
</Project>

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

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

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

@ -39,7 +39,7 @@ namespace Avalonia.Animation.Easings
var derivedTypes = typeof(Easing).Assembly.GetTypes()
.Where(p => p.Namespace == s_thisType.Namespace)
.Where(p => p.IsSubclassOf(s_thisType))
.Select(p => p).ToList();
.Select(p => p);
foreach (var easingType in derivedTypes)
_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\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project>

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

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

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

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

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

@ -10,7 +10,7 @@ namespace Avalonia.Data.Core
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);
XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value;
List<AvaloniaResourcesIndexEntry> entries=
List<AvaloniaResourcesIndexEntry> entries =
(from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry")
select new AvaloniaResourcesIndexEntry
{

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

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

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

@ -105,7 +105,7 @@ namespace Avalonia.Build.Tasks
{
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"))
{

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

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

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

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

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

@ -2060,6 +2060,25 @@ namespace Avalonia.Controls
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>
/// 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 Avalonia.Layout;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Controls.Documents;
namespace Avalonia.Controls
{
@ -32,7 +33,7 @@ namespace Avalonia.Controls
/// Identifies the FontFamily dependency property.
/// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<DataGridTextColumn>();
TextElement.FontFamilyProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font name.
@ -47,7 +48,7 @@ namespace Avalonia.Controls
/// Identifies the FontSize dependency property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<DataGridTextColumn>();
TextElement.FontSizeProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font size.
@ -64,7 +65,7 @@ namespace Avalonia.Controls
/// Identifies the FontStyle dependency property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<DataGridTextColumn>();
TextElement.FontStyleProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font style.
@ -79,7 +80,7 @@ namespace Avalonia.Controls
/// Identifies the FontWeight dependency property.
/// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<DataGridTextColumn>();
TextElement.FontWeightProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// Gets or sets the font weight or thickness.
@ -90,11 +91,26 @@ namespace Avalonia.Controls
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>
/// Identifies the Foreground dependency property.
/// </summary>
public static readonly AttachedProperty<IBrush> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<DataGridTextColumn>();
TextElement.ForegroundProperty.AddOwner<DataGridTextColumn>();
/// <summary>
/// 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}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
TextElement.Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>

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

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

24
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -1,4 +1,6 @@
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.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.
@ -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.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.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.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.
@ -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.
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.
Total Issues: 73
Total Issues: 95

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

@ -65,7 +65,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// <inheritdoc/>
public Window? MainWindow { get; set; }
public IReadOnlyList<Window> Windows => _windows.ToList();
public IReadOnlyList<Window> Windows => _windows.ToArray();
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
/// box.
/// </summary>
[TemplatePart(ElementPopup, typeof(Popup))]
[TemplatePart(ElementSelector, typeof(SelectingItemsControl))]
[TemplatePart(ElementSelectionAdapter, typeof(ISelectionAdapter))]
[TemplatePart(ElementTextBox, typeof(TextBox))]
[PseudoClasses(":dropdownopen")]
public class AutoCompleteBox : TemplatedControl
{
@ -2180,7 +2184,7 @@ namespace Avalonia.Controls
}
// 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
ClearView();
@ -2239,7 +2243,7 @@ namespace Avalonia.Controls
ClearView();
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\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project>

123
src/Avalonia.Controls/Button.cs

@ -29,11 +29,14 @@ namespace Avalonia.Controls
}
/// <summary>
/// A button control.
/// A standard button control.
/// </summary>
[PseudoClasses(":pressed")]
public class Button : ContentControl, ICommandSource
[PseudoClasses(pcFlyoutOpen, pcPressed)]
public class Button : ContentControl, ICommandSource, IClickableControl
{
protected const string pcPressed = ":pressed";
protected const string pcFlyoutOpen = ":flyout-open";
/// <summary>
/// Defines the <see cref="ClickMode"/> property.
/// </summary>
@ -92,6 +95,7 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private KeyGesture? _hotkey;
private bool _isFlyoutOpen = false;
/// <summary>
/// Initializes static members of the <see cref="Button"/> class.
@ -107,7 +111,6 @@ namespace Avalonia.Controls
/// </summary>
public Button()
{
UpdatePseudoClasses(IsPressed);
}
/// <summary>
@ -328,11 +331,30 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Opens the button's flyout.
/// </summary>
protected virtual void OpenFlyout()
{
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/>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
@ -382,6 +404,14 @@ namespace Avalonia.Controls
IsPressed = false;
}
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
UnregisterFlyoutEvents(Flyout);
RegisterFlyoutEvents(Flyout);
UpdatePseudoClasses();
}
/// <inheritdoc/>
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
@ -442,17 +472,26 @@ namespace Avalonia.Controls
}
else if (change.Property == IsPressedProperty)
{
UpdatePseudoClasses(change.NewValue.GetValueOrDefault<bool>());
UpdatePseudoClasses();
}
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
// close the old one first
if (change.OldValue.GetValueOrDefault() is FlyoutBase oldFlyout &&
if (oldFlyout != null &&
oldFlyout.IsOpen)
{
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>
/// Starts listening for the Enter key when the button <see cref="IsDefault"/>.
/// </summary>
@ -560,11 +625,53 @@ namespace Avalonia.Controls
/// <summary>
/// Updates the visual state of the control by applying latest PseudoClasses.
/// </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 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>
/// Represents a spinner control that includes two Buttons.
/// </summary>
[TemplatePart("PART_DecreaseButton", typeof(Button))]
[TemplatePart("PART_IncreaseButton", typeof(Button))]
[PseudoClasses(":left", ":right")]
public class ButtonSpinner : Spinner
{

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

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

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

@ -8,6 +8,10 @@ namespace Avalonia.Controls.Chrome
/// <summary>
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
/// </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")]
public class CaptionButtons : TemplatedControl
{

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

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

21
src/Avalonia.Controls/ComboBox.cs

@ -13,12 +13,14 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
using Avalonia.Controls.Metadata;
namespace Avalonia.Controls
{
/// <summary>
/// A drop-down list control.
/// </summary>
[TemplatePart("PART_Popup", typeof(Popup))]
public class ComboBox : SelectingItemsControl
{
/// <summary>
@ -182,6 +184,25 @@ namespace Avalonia.Controls
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/>
protected override void OnKeyDown(KeyEventArgs e)
{

2
src/Avalonia.Controls/ContentControl.cs

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

92
src/Avalonia.Controls/Control.cs

@ -1,7 +1,7 @@
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
@ -67,7 +67,7 @@ namespace Avalonia.Controls
/// </summary>
public static readonly AttachedProperty<FlowDirection> FlowDirectionProperty =
AvaloniaProperty.RegisterAttached<Control, Control, FlowDirection>(nameof(FlowDirection), inherits: true);
private DataTemplates? _dataTemplates;
private IControl? _focusAdorner;
private AutomationPeer? _automationPeer;
@ -137,9 +137,39 @@ namespace Avalonia.Controls
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/>
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/>
void ISetterValue.Initialize(ISetter setter)
{
@ -199,6 +229,14 @@ namespace Avalonia.Controls
base.OnDetachedFromVisualTreeCore(e);
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
InvalidateMirrorTransform();
}
/// <inheritdoc/>
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>
/// A control to allow the user to select a date
/// </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")]
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.Input;
using Avalonia.Interactivity;
@ -12,6 +13,23 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a date for a
/// <see cref="DatePicker"/>
/// </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
{
/// <summary>

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

@ -1,7 +1,9 @@
using System;
using System.Globalization;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.VisualTree;
@ -60,6 +62,7 @@ namespace Avalonia.Controls.Primitives
private Vector _offset;
private bool _hasInit;
private bool _suppressUpdateOffset;
private ScrollContentPresenter? _parentScroller;
public DateTimePickerPanel()
{
@ -255,6 +258,8 @@ namespace Avalonia.Controls.Primitives
_suppressUpdateOffset = true;
SelectedValue = (int)newSel * Increment + MinimumValue;
_suppressUpdateOffset = false;
System.Diagnostics.Debug.WriteLine($"Offset: {_offset} ItemHeight: {ItemHeight}");
}
}
@ -270,7 +275,7 @@ namespace Avalonia.Controls.Primitives
public Size Extent => _extent;
public Size Viewport => new Size(0, ItemHeight);
public Size Viewport => Bounds.Size;
public event EventHandler? ScrollInvalidated;
@ -341,6 +346,20 @@ namespace Avalonia.Controls.Primitives
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)
{
switch (e.Key)
@ -554,5 +573,15 @@ namespace Avalonia.Controls.Primitives
{
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>
/// A control to allow the user to select a time.
/// </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")]
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.Input;
using Avalonia.Interactivity;
@ -10,6 +11,20 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a time. Intended for use with
/// <see cref="TimePicker"/> but can be used independently
/// </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
{
/// <summary>

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

@ -13,37 +13,60 @@ namespace Avalonia.Controls.Documents
/// Defines the <see cref="Background"/> property.
/// </summary>
public static readonly StyledProperty<IBrush?> BackgroundProperty =
AvaloniaProperty.Register<TextElement, IBrush?>(nameof(Background), inherits: true);
Border.BackgroundProperty.AddOwner<TextElement>();
/// <summary>
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, FontFamily>(
nameof(FontFamily),
defaultValue: FontFamily.Default,
inherits: true);
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, double>(
nameof(FontSize),
defaultValue: 12,
inherits: true);
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, FontStyle>(
nameof(FontStyle),
inherits: true);
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
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>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly AttachedProperty<IBrush?> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TextElement>();
AvaloniaProperty.RegisterAttached<TextElement, Control, IBrush?>(
nameof(Foreground),
Brushes.Black,
inherits: true);
/// <summary>
/// Gets or sets a brush used to paint the control's background.
@ -90,6 +113,15 @@ namespace Avalonia.Controls.Documents
set { SetValue(FontWeightProperty, value); }
}
/// <summary>
/// Gets or sets the font stretch.
/// </summary>
public FontStretch FontStretch
{
get { return GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary>
/// Gets or sets a brush used to paint the text.
/// </summary>
@ -98,7 +130,127 @@ namespace Avalonia.Controls.Documents
get { return GetValue(ForegroundProperty); }
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>
/// Raised when the visual representation of the text element changes.
/// </summary>
@ -115,6 +267,7 @@ namespace Avalonia.Controls.Documents
case nameof(FontSize):
case nameof(FontStyle):
case nameof(FontWeight):
case nameof(FontStretch):
case nameof(Foreground):
Invalidate();
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)
.OrderByDescending(x => x.Key)
.ToList();
.ToArray();
foreach (var i in toMove)
{
@ -111,7 +111,7 @@ namespace Avalonia.Controls.Generators
}
var toMove = _containers.Where(x => x.Key >= startingIndex)
.OrderBy(x => x.Key).ToList();
.OrderBy(x => x.Key).ToArray();
foreach (var i in toMove)
{
@ -122,9 +122,9 @@ namespace Avalonia.Controls.Generators
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));
}
}
@ -138,10 +138,10 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/>
public virtual IEnumerable<ItemContainerInfo> Clear()
{
var result = Containers.ToList();
var result = Containers.ToArray();
_containers.Clear();
if (result.Count > 0)
if (result.Length > 0)
{
Dematerialized?.Invoke(this, new ItemContainerEventArgs(0, result));
}

67
src/Avalonia.Controls/HotkeyManager.cs

@ -12,21 +12,61 @@ namespace Avalonia.Controls
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) =>
CommandSource?.Command?.CanExecute(CommandSource.CommandParameter) == true
&& CommandSource.IsEffectivelyEnabled;
public void Execute(object? parameter)
{
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
public event EventHandler? CanExecuteChanged;
@ -47,7 +87,7 @@ namespace Avalonia.Controls
public Manager(IControl control)
{
_control = control;
_wrapper = new HotkeyCommandWrapper(_control as ICommandSource);
_wrapper = new HotkeyCommandWrapper(_control);
}
public void Init()
@ -104,13 +144,14 @@ namespace Avalonia.Controls
{
HotKeyProperty.Changed.Subscribe(args =>
{
if (args.NewValue.Value is null) return;
if (args.NewValue.Value is null)
return;
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,
$"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;
}

2
src/Avalonia.Controls/Image.cs

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

2
src/Avalonia.Controls/ListBox.cs

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

15
src/Avalonia.Controls/MenuItem.cs

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

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

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

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

@ -2,6 +2,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
@ -15,6 +16,8 @@ namespace Avalonia.Controls
/// <summary>
/// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
/// </summary>
[TemplatePart("PART_Spinner", typeof(Spinner))]
[TemplatePart("PART_TextBox", typeof(TextBox))]
public class NumericUpDown : TemplatedControl
{
/// <summary>
@ -1051,7 +1054,7 @@ namespace Avalonia.Controls
var currentValueTextSpecialCharacters = currentValueText.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.
if (currentValueTextSpecialCharacters.Except(textSpecialCharacters).ToList().Count == 0)
if (!currentValueTextSpecialCharacters.Except(textSpecialCharacters).Any())
{
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.Down:
{
if (item?.IsTopLevel == true)
if (item?.IsTopLevel == true && item.HasSubMenu)
{
if (item.HasSubMenu && !item.IsSubMenuOpen)
if (!item.IsSubMenuOpen)
{
Open(item, true);
e.Handled = true;
}
else
{
item.MoveSelection(NavigationDirection.First, true);
}
e.Handled = true;
}
else
{
@ -247,7 +252,8 @@ namespace Avalonia.Controls.Platform
// new menu.
if (item.IsSubMenuOpen &&
item.Parent is IMenu &&
item.Parent.SelectedItem is object)
item.Parent.SelectedItem is object &&
item.Parent.SelectedItem != item)
{
item.Close();
Open(item.Parent.SelectedItem, true);

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

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -46,7 +47,73 @@ namespace Avalonia.Controls.Presenters
/// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
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>
/// Defines the <see cref="Child"/> property.
/// </summary>
@ -159,6 +226,105 @@ namespace Avalonia.Controls.Presenters
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>
/// Gets the control displayed by the presenter.
/// </summary>

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

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

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

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

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

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

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

@ -46,6 +46,7 @@ Copyright © 2019 Nikita Tsukanov
using System;
using Avalonia.VisualTree;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives.PopupPositioning
{
@ -444,7 +445,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
TopLevel topLevel,
IVisual target, PlacementMode placement, Point offset,
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
#pragma warning disable CS0618 // Type or member is obsolete
@ -503,6 +505,32 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
else
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 =>
_parent.Screen.AllScreens.Select(s => new ManagedPopupPositionerScreenInfo(
s.Bounds.ToRect(1), s.WorkingArea.ToRect(1))).ToList();
_parent.Screen.AllScreens
.Select(s => new ManagedPopupPositionerScreenInfo(s.Bounds.ToRect(1), s.WorkingArea.ToRect(1)))
.ToArray();
public Rect ParentClientAreaScreenGeometry
{

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

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

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

@ -22,6 +22,10 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// A scrollbar control.
/// </summary>
[TemplatePart("PART_LineDownButton", typeof(Button))]
[TemplatePart("PART_LineUpButton", typeof(Button))]
[TemplatePart("PART_PageDownButton", typeof(Button))]
[TemplatePart("PART_PageUpButton", typeof(Button))]
[PseudoClasses(":vertical", ":horizontal")]
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.");
}
var oldSelection = _selection?.SelectedItems.ToList();
var oldSelection = _selection?.SelectedItems.ToArray();
DeinitializeSelectionModel(_selection);
_selection = value;
if (oldSelection?.Count > 0)
if (oldSelection?.Length > 0)
{
RaiseEvent(new SelectionChangedEventArgs(
SelectionChangedEvent,
@ -845,8 +845,8 @@ namespace Avalonia.Controls.Primitives
{
var ev = new SelectionChangedEventArgs(
SelectionChangedEvent,
e.DeselectedItems.ToList(),
e.SelectedItems.ToList());
e.DeselectedItems.ToArray(),
e.SelectedItems.ToArray());
RaiseEvent(ev);
}
}
@ -988,7 +988,7 @@ namespace Avalonia.Controls.Primitives
RaiseEvent(new SelectionChangedEventArgs(
SelectionChangedEvent,
Array.Empty<object>(),
Selection.SelectedItems.ToList()));
Selection.SelectedItems.ToArray()));
}
}

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

@ -1,4 +1,5 @@
using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
using Avalonia.Logging;
@ -41,31 +42,37 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="FontFamily"/> property.
/// </summary>
public static readonly StyledProperty<FontFamily> FontFamilyProperty =
TextBlock.FontFamilyProperty.AddOwner<TemplatedControl>();
TextElement.FontFamilyProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontSize"/> property.
/// </summary>
public static readonly StyledProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<TemplatedControl>();
TextElement.FontSizeProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontStyle"/> property.
/// </summary>
public static readonly StyledProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<TemplatedControl>();
TextElement.FontStyleProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="FontWeight"/> property.
/// </summary>
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>
/// Defines the <see cref="Foreground"/> property.
/// </summary>
public static readonly StyledProperty<IBrush?> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<TemplatedControl>();
TextElement.ForegroundProperty.AddOwner<TemplatedControl>();
/// <summary>
/// Defines the <see cref="Padding"/> property.
@ -185,6 +192,15 @@ namespace Avalonia.Controls.Primitives
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>
/// Gets or sets the brush used to draw the control's text and other foreground elements.
/// </summary>
@ -340,6 +356,11 @@ namespace Avalonia.Controls.Primitives
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)
{
}

1
src/Avalonia.Controls/ProgressBar.cs

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

3
src/Avalonia.Controls/ScrollViewer.cs

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

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

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

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

@ -2,19 +2,139 @@ using Avalonia.Media;
namespace Avalonia.Controls.Shapes
{
/// <summary>
/// Represents a rectangle with optional rounded corners.
/// </summary>
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()
{
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()
{
var rect = new Rect(Bounds.Size).Deflate(StrokeThickness / 2);
return new RectangleGeometry(rect);
// TODO: If RectangleGeometry ever supports RadiusX/Y like in WPF,
// 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)
{
return new Size(StrokeThickness, StrokeThickness);

3
src/Avalonia.Controls/Slider.cs

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

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

@ -1,7 +1,6 @@
using System;
using System.Windows.Input;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -13,6 +12,8 @@ namespace Avalonia.Controls
/// 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.
/// </summary>
[TemplatePart("PART_PrimaryButton", typeof(Button))]
[TemplatePart("PART_SecondaryButton", typeof(Button))]
[PseudoClasses(pcFlyoutOpen, pcPressed)]
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
if (oldFlyout != null)
{
UnregisterFlyoutEvents(oldFlyout);
}
if (newFlyout != null)
{
RegisterFlyoutEvents(newFlyout);
}
UnregisterFlyoutEvents(oldFlyout);
RegisterFlyoutEvents(newFlyout);
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
////////////////////////////////////////////////////////////////////////
@ -464,6 +474,8 @@ namespace Avalonia.Controls
{
_isFlyoutOpen = true;
UpdatePseudoClasses();
OnFlyoutOpened();
}
}
@ -479,6 +491,8 @@ namespace Avalonia.Controls
{
_isFlyoutOpen = false;
UpdatePseudoClasses();
OnFlyoutClosed();
}
}
}

1
src/Avalonia.Controls/SplitView.cs

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

2
src/Avalonia.Controls/TabControl.cs

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

297
src/Avalonia.Controls/TextBlock.cs

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

112
src/Avalonia.Controls/TextBox.cs

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

2
src/Avalonia.Controls/ToggleSwitch.cs

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

33
src/Avalonia.Controls/TopLevel.cs

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

2
src/Avalonia.Controls/TreeView.cs

@ -849,7 +849,7 @@ namespace Avalonia.Controls
/// <param name="desired">The desired items.</param>
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 toAdd = desired.Except(list).ToList();

1
src/Avalonia.Controls/TreeViewItem.cs

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

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

@ -105,7 +105,7 @@ namespace Avalonia.Controls.Utils
static void Notify(
INotifyCollectionChanged incc,
NotifyCollectionChangedEventArgs args,
List<WeakReference<ICollectionChangedListener>> listeners)
WeakReference<ICollectionChangedListener>[] 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())
{

7
src/Avalonia.Controls/Window.cs

@ -12,7 +12,6 @@ using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
using JetBrains.Annotations;
namespace Avalonia.Controls
{
@ -258,7 +257,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a collection of child windows owned by this window.
/// </summary>
public IReadOnlyList<Window> OwnedWindows => _children.Select(x => x.child).ToList();
public IReadOnlyList<Window> OwnedWindows => _children.Select(x => x.child).ToArray();
/// <summary>
/// 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()
{
foreach (var (child, _) in _children.ToList())
foreach (var (child, _) in _children.ToArray())
{
child.CloseInternal();
}
@ -551,7 +550,7 @@ namespace Avalonia.Controls
bool canClose = true;
foreach (var (child, _) in _children.ToList())
foreach (var (child, _) in _children.ToArray())
{
if (child.ShouldCancelClose(args))
{

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

@ -1078,12 +1078,6 @@
"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": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
@ -1636,9 +1630,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"mobx": {
@ -1839,12 +1833,6 @@
"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": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
@ -2592,12 +2580,6 @@
"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": {
"version": "3.1.1",
"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\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project>

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

@ -18,7 +18,7 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ControlDetailsViewModel : ViewModelBase, IDisposable
{
private readonly IAvaloniaObject _avaloniaObject;
private IDictionary<object, List<PropertyViewModel>>? _propertyIndex;
private IDictionary<object, PropertyViewModel[]>? _propertyIndex;
private PropertyViewModel? _selectedProperty;
private DataGridCollectionView? _propertiesView;
private bool _snapshotStyles;
@ -472,9 +472,9 @@ namespace Avalonia.Diagnostics.ViewModels
.Concat(GetClrProperties(o, _showImplementedInterfaces))
.OrderBy(x => x, PropertyComparer.Instance)
.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);
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.Metadata;
using Avalonia.Threading;
using System.Reactive.Linq;
using System.Linq;
namespace Avalonia.Diagnostics.ViewModels
@ -59,8 +58,8 @@ namespace Avalonia.Diagnostics.ViewModels
.Subscribe(e =>
{
PointerOverRoot = e.Root;
PointerOverElement = e.Root.GetInputElementsAt(e.Position).FirstOrDefault();
});
PointerOverElement = e.Root.InputHitTest(e.Position);
});
#nullable restore
}
Console = new ConsoleViewModel(UpdateConsoleContext);

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

@ -11,7 +11,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<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"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"
@ -44,7 +44,7 @@
<DataTemplate>
<StackPanel Orientation="Vertical">
<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}"/>
</DockPanel>
<TextBlock Foreground="{Binding Foreground}" Text="{Binding Output}"/>

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

Loading…
Cancel
Save