Browse Source

Merge branch 'master' into feature/transparent-web-canvas

pull/9658/head
Max Katz 4 years ago
committed by GitHub
parent
commit
dfb3285e10
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .editorconfig
  2. 12
      Avalonia.sln
  3. 31
      NOTICE.md
  4. 16
      azure-pipelines-integrationtests.yml
  5. 24
      azure-pipelines.yml
  6. 2
      build/Base.props
  7. 5
      build/JetBrains.Annotations.props
  8. 2
      build/System.Memory.props
  9. 2
      global.json
  10. 1
      nukebuild/DotNetConfigHelper.cs
  11. 3
      samples/ControlCatalog/MainView.xaml
  12. 89
      samples/ControlCatalog/Pages/CompositionPage.axaml
  13. 172
      samples/ControlCatalog/Pages/CompositionPage.axaml.cs
  14. 2
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  15. 212
      samples/ControlCatalog/Pages/GesturePage.cs
  16. 117
      samples/ControlCatalog/Pages/GesturePage.xaml
  17. 2
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  18. 5
      samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs
  19. 4
      samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs
  20. 2
      samples/RenderDemo/Pages/TextFormatterPage.axaml.cs
  21. 5
      samples/interop/Direct3DInteropSample/App.paml
  22. 21
      samples/interop/Direct3DInteropSample/App.paml.cs
  23. 32
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  24. 283
      samples/interop/Direct3DInteropSample/MainWindow.cs
  25. 14
      samples/interop/Direct3DInteropSample/MainWindow.paml
  26. 45
      samples/interop/Direct3DInteropSample/MainWindowViewModel.cs
  27. 47
      samples/interop/Direct3DInteropSample/MiniCube.fx
  28. 16
      samples/interop/Direct3DInteropSample/Program.cs
  29. 2
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  30. 11
      src/Android/Avalonia.Android/AndroidPlatform.cs
  31. 14
      src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
  32. 32
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  33. 30
      src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
  34. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  35. 18
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  36. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  37. 1
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  38. 11
      src/Avalonia.Base/Animation/Cue.cs
  39. 4
      src/Avalonia.Base/Avalonia.Base.csproj
  40. 26
      src/Avalonia.Base/Compatibility/OperatingSystem.cs
  41. 34
      src/Avalonia.Base/Contract.cs
  42. 9
      src/Avalonia.Base/CornerRadius.cs
  43. 2
      src/Avalonia.Base/Data/BindingValue.cs
  44. 2
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  45. 2
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  46. 4
      src/Avalonia.Base/Data/InstancedBinding.cs
  47. 128
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  48. 33
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  49. 65
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  50. 375
      src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs
  51. 129
      src/Avalonia.Base/Input/Gestures.cs
  52. 51
      src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs
  53. 16
      src/Avalonia.Base/Input/InputElement.cs
  54. 2
      src/Avalonia.Base/Input/KeyEventArgs.cs
  55. 24
      src/Avalonia.Base/Input/PinchEventArgs.cs
  56. 4
      src/Avalonia.Base/Input/PointerPoint.cs
  57. 6
      src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs
  58. 8
      src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
  59. 2
      src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
  60. 2
      src/Avalonia.Base/Input/TextInputEventArgs.cs
  61. 5
      src/Avalonia.Base/Logging/LogArea.cs
  62. 2
      src/Avalonia.Base/Logging/ParametrizedLogger.cs
  63. 2
      src/Avalonia.Base/Matrix.cs
  64. 17
      src/Avalonia.Base/Media/BoxShadow.cs
  65. 12
      src/Avalonia.Base/Media/BoxShadows.cs
  66. 5
      src/Avalonia.Base/Media/Brush.cs
  67. 4
      src/Avalonia.Base/Media/BrushExtensions.cs
  68. 282
      src/Avalonia.Base/Media/Brushes.cs
  69. 2
      src/Avalonia.Base/Media/ConicGradientBrush.cs
  70. 8
      src/Avalonia.Base/Media/DashStyle.cs
  71. 2
      src/Avalonia.Base/Media/DrawingContext.cs
  72. 7
      src/Avalonia.Base/Media/DrawingGroup.cs
  73. 2
      src/Avalonia.Base/Media/FontMetrics.cs
  74. 10
      src/Avalonia.Base/Media/FormattedText.cs
  75. 4
      src/Avalonia.Base/Media/Geometry.cs
  76. 2
      src/Avalonia.Base/Media/GeometryDrawing.cs
  77. 2
      src/Avalonia.Base/Media/GlyphMetrics.cs
  78. 419
      src/Avalonia.Base/Media/GlyphRun.cs
  79. 2
      src/Avalonia.Base/Media/GlyphRunDrawing.cs
  80. 20
      src/Avalonia.Base/Media/GlyphRunMetrics.cs
  81. 4
      src/Avalonia.Base/Media/GradientBrush.cs
  82. 2
      src/Avalonia.Base/Media/IDashStyle.cs
  83. 9
      src/Avalonia.Base/Media/IImmutableBrush.cs
  84. 4
      src/Avalonia.Base/Media/IMutableBrush.cs
  85. 9
      src/Avalonia.Base/Media/ISolidColorBrush.cs
  86. 4
      src/Avalonia.Base/Media/ImageBrush.cs
  87. 2
      src/Avalonia.Base/Media/ImageDrawing.cs
  88. 4
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  89. 2
      src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs
  90. 373
      src/Avalonia.Base/Media/ImmediateDrawingContext.cs
  91. 4
      src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs
  92. 2
      src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs
  93. 4
      src/Avalonia.Base/Media/Immutable/ImmutablePen.cs
  94. 2
      src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs
  95. 2
      src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs
  96. 6
      src/Avalonia.Base/Media/KnownColors.cs
  97. 2
      src/Avalonia.Base/Media/LinearGradientBrush.cs
  98. 2
      src/Avalonia.Base/Media/RadialGradientBrush.cs
  99. 4
      src/Avalonia.Base/Media/SolidColorBrush.cs
  100. 2
      src/Avalonia.Base/Media/TextCollapsingCreateInfo.cs

8
.editorconfig

@ -140,16 +140,22 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = suggestion
# CS0162: Remove unreachable code
dotnet_diagnostic.CS0162.severity = error
# CA1304: Specify CultureInfo
dotnet_diagnostic.CA1304.severity = warning
# CA1802: Use literals where appropriate
dotnet_diagnostic.CA1802.severity = warning
# CA1815: Override equals and operator equals on value types
dotnet_diagnostic.CA1815.severity = warning
# CA1820: Test for empty strings using string length
dotnet_diagnostic.CA1820.severity = warning
# CA1821: Remove empty finalizers
dotnet_diagnostic.CA1821.severity = warning
# CA1822: Mark members as static
dotnet_diagnostic.CA1822.severity = suggestion
# CA1823: Avoid unused private fields
dotnet_diagnostic.CA1823.severity = warning
dotnet_code_quality.CA1822.api_surface = private, internal
# CA1825: Avoid zero-length array allocations
dotnet_diagnostic.CA1825.severity = warning
@ -163,6 +169,8 @@ dotnet_diagnostic.CA1828.severity = warning
dotnet_diagnostic.CA1829.severity = warning
#CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
dotnet_diagnostic.CA1847.severity = warning
#CACA2211:Non-constant fields should not be visible
dotnet_diagnostic.CA2211.severity = error
# Wrapping preferences
csharp_wrap_before_ternary_opsigns = false

12
Avalonia.sln

@ -100,7 +100,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\ImageSharp.props = build\ImageSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
@ -136,8 +135,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.LinuxFramebuffer", "src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj", "{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.Skia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}"
@ -228,9 +225,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser", "src\Bro
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
@ -366,10 +363,6 @@ Global
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.Build.0 = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -580,7 +573,6 @@ Global
{7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098}

31
NOTICE.md

@ -303,3 +303,34 @@ https://github.com/chromium/chromium
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Flutter
https://github.com/flutter/flutter
//Copyright 2014 The Flutter Authors. All rights reserved.
//Redistribution and use in source and binary forms, with or without modification,
//are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS;
//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

16
azure-pipelines-integrationtests.yml

@ -13,14 +13,14 @@ jobs:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- script: system_profiler SPDisplaysDataType |grep Resolution
@ -51,14 +51,14 @@ jobs:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: Windows Application Driver@0
inputs:

24
azure-pipelines.yml

@ -30,14 +30,14 @@ jobs:
vmImage: 'ubuntu-20.04'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: CmdLine@2
displayName: 'Install Workloads'
@ -67,14 +67,14 @@ jobs:
vmImage: 'macos-12'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: CmdLine@2
displayName: 'Install Workloads'
@ -138,14 +138,14 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 6.0.401'
displayName: 'Use .NET Core SDK 6.0.404'
inputs:
version: 6.0.401
version: 6.0.404
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100'
displayName: 'Use .NET Core SDK 7.0.101'
inputs:
version: 7.0.100
version: 7.0.101
- task: CmdLine@2
displayName: 'Install Workloads'

2
build/Base.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net6'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
</ItemGroup>

5
build/JetBrains.Annotations.props

@ -1,5 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="10.3.0" />
</ItemGroup>
</Project>

2
build/System.Memory.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net6'">
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>
</Project>

2
global.json

@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.100",
"version": "7.0.101",
"rollForward": "latestFeature"
},
"msbuild-sdks": {

1
nukebuild/DotNetConfigHelper.cs

@ -1,5 +1,4 @@
using System.Globalization;
using JetBrains.Annotations;
using Nuke.Common.Tools.DotNet;
// ReSharper disable ReturnValueOfPureMethodIsNotUsed

3
samples/ControlCatalog/MainView.xaml

@ -92,6 +92,9 @@
<TabItem Header="Flyouts">
<pages:FlyoutsPage />
</TabItem>
<TabItem Header="Gestures">
<pages:GesturePage />
</TabItem>
<TabItem Header="Image"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">

89
samples/ControlCatalog/Pages/CompositionPage.axaml

@ -2,44 +2,57 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pages="using:ControlCatalog.Pages"
x:Class="ControlCatalog.Pages.CompositionPage">
<StackPanel>
<TextBlock Classes="h1">Implicit animations</TextBlock>
<TabControl>
<TabItem Header="Implicit animations">
<StackPanel>
<Grid ColumnDefinitions="*,10,40" Margin="0 0 40 0">
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.DataTemplates>
<DataTemplate DataType="pages:CompositionPageColorItem">
<Border
pages:CompositionPage.EnableAnimations="True"
Padding="10" BorderBrush="Gray" BorderThickness="2"
Background="{Binding ColorBrush}" Width="100" Height="100" Margin="10">
<TextBlock Text="{Binding ColorHexValue}"/>
</Border>
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
<GridSplitter Margin="2" BorderThickness="1" BorderBrush="Gray"
Background="#e0e0e0" Grid.Column="1"
ResizeDirection="Columns" ResizeBehavior="PreviousAndNext"
/>
<Border Grid.Column="2">
<LayoutTransformControl
HorizontalAlignment="Center"
MinWidth="30">
<LayoutTransformControl.LayoutTransform>
<RotateTransform Angle="90"/>
</LayoutTransformControl.LayoutTransform>
<TextBlock>Resize me</TextBlock>
</LayoutTransformControl>
</Border>
<ItemsControl x:Name="Items">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.DataTemplates>
<DataTemplate DataType="pages:CompositionPageColorItem">
<Border
pages:CompositionPage.EnableAnimations="True"
Padding="10" BorderBrush="Gray" BorderThickness="2"
Background="{Binding ColorBrush}" Width="100" Height="100" Margin="10">
<TextBlock Text="{Binding ColorHexValue}"/>
</Border>
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
<GridSplitter Margin="2" BorderThickness="1" BorderBrush="Gray"
Background="#e0e0e0" Grid.Column="1"
ResizeDirection="Columns" ResizeBehavior="PreviousAndNext"
/>
<Border Grid.Column="2">
<LayoutTransformControl HorizontalAlignment="Center" MinWidth="30">
<LayoutTransformControl.LayoutTransform>
<RotateTransform Angle="90"/>
</LayoutTransformControl.LayoutTransform>
<TextBlock>Resize me</TextBlock>
</LayoutTransformControl>
</Border>
</Grid>
</StackPanel>
</StackPanel>
</TabItem>
<TabItem Header="Animation">
<DockPanel>
<Button DockPanel.Dock="Top" Margin="10" Click="ButtonThreadSleep">Thread.Sleep(10000);</Button>
<Control x:Name="SolidVisualHost" />
</DockPanel>
</TabItem>
<TabItem Header="Custom">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Margin="10" Click="ButtonThreadSleep">Thread.Sleep(10000);</Button>
<Button Margin="10" Click="ButtonStartCustomVisual">Start</Button>
<Button Margin="10" Click="ButtonStopCustomVisual">Stop</Button>
</StackPanel>
<Control x:Name="CustomVisualHost" />
</DockPanel>
</TabItem>
</TabControl>
</UserControl>

172
samples/ControlCatalog/Pages/CompositionPage.axaml.cs

@ -1,28 +1,39 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.VisualTree;
using Math = System.Math;
namespace ControlCatalog.Pages;
public partial class CompositionPage : UserControl
{
private ImplicitAnimationCollection? _implicitAnimations;
private CompositionCustomVisual? _customVisual;
private CompositionSolidColorVisual? _solidVisual;
public CompositionPage()
{
AvaloniaXamlLoader.Load(this);
AttachAnimatedSolidVisual(this.FindControl<Control>("SolidVisualHost")!);
AttachCustomVisual(this.FindControl<Control>("CustomVisualHost")!);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
this.Get<ItemsControl>("Items").Items = CreateColorItems();
}
private static List<CompositionPageColorItem> CreateColorItems()
@ -126,6 +137,167 @@ public partial class CompositionPage : UserControl
compositionVisual.ImplicitAnimations = page._implicitAnimations;
}
}
void AttachAnimatedSolidVisual(Visual v)
{
void Update()
{
if(_solidVisual == null)
return;
_solidVisual.Size = new Vector2((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3);
_solidVisual.Offset = new Vector3((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3, 0);
}
v.AttachedToVisualTree += delegate
{
var compositor = ElementComposition.GetElementVisual(v)?.Compositor;
if(compositor == null || _solidVisual?.Compositor == compositor)
return;
_solidVisual = compositor.CreateSolidColorVisual();
ElementComposition.SetElementChildVisual(v, _solidVisual);
_solidVisual.Color = Colors.Red;
var animation = _solidVisual.Compositor.CreateColorKeyFrameAnimation();
animation.InsertKeyFrame(0, Colors.Red);
animation.InsertKeyFrame(0.5f, Colors.Blue);
animation.InsertKeyFrame(1, Colors.Green);
animation.Duration = TimeSpan.FromSeconds(5);
animation.IterationBehavior = AnimationIterationBehavior.Forever;
animation.Direction = PlaybackDirection.Alternate;
_solidVisual.StartAnimation("Color", animation);
_solidVisual.AnchorPoint = new Vector2(0, 0);
var scale = _solidVisual.Compositor.CreateVector3KeyFrameAnimation();
scale.Duration = TimeSpan.FromSeconds(5);
scale.IterationBehavior = AnimationIterationBehavior.Forever;
scale.InsertKeyFrame(0, new Vector3(1, 1, 0));
scale.InsertKeyFrame(0.5f, new Vector3(1.5f, 1.5f, 0));
scale.InsertKeyFrame(1, new Vector3(1, 1, 0));
_solidVisual.StartAnimation("Scale", scale);
var center =
_solidVisual.Compositor.CreateExpressionAnimation(
"Vector3(this.Target.Size.X * 0.5, this.Target.Size.Y * 0.5, 1)");
_solidVisual.StartAnimation("CenterPoint", center);
Update();
};
v.PropertyChanged += (_, a) =>
{
if (a.Property == BoundsProperty)
Update();
};
}
void AttachCustomVisual(Visual v)
{
void Update()
{
if (_customVisual == null)
return;
var h = (float)Math.Min(v.Bounds.Height, v.Bounds.Width / 3);
_customVisual.Size = new Vector2((float)v.Bounds.Width, h);
_customVisual.Offset = new Vector3(0, (float)(v.Bounds.Height - h) / 2, 0);
}
v.AttachedToVisualTree += delegate
{
var compositor = ElementComposition.GetElementVisual(v)?.Compositor;
if(compositor == null || _customVisual?.Compositor == compositor)
return;
_customVisual = compositor.CreateCustomVisual(new CustomVisualHandler());
ElementComposition.SetElementChildVisual(v, _customVisual);
_customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage);
Update();
};
v.PropertyChanged += (_, a) =>
{
if (a.Property == BoundsProperty)
Update();
};
}
class CustomVisualHandler : CompositionCustomVisualHandler
{
private TimeSpan _animationElapsed;
private TimeSpan? _lastServerTime;
private bool _running;
public static readonly object StopMessage = new(), StartMessage = new();
public override void OnRender(ImmediateDrawingContext drawingContext)
{
if (_running)
{
if (_lastServerTime.HasValue) _animationElapsed += (CompositionNow - _lastServerTime.Value);
_lastServerTime = CompositionNow;
}
const int cnt = 20;
var maxPointSizeX = EffectiveSize.X / (cnt * 1.6);
var maxPointSizeY = EffectiveSize.Y / 4;
var pointSize = Math.Min(maxPointSizeX, maxPointSizeY);
var animationLength = TimeSpan.FromSeconds(4);
var animationStage = _animationElapsed.TotalSeconds / animationLength.TotalSeconds;
var sinOffset = Math.Cos(_animationElapsed.TotalSeconds) * 1.5;
for (var c = 0; c < cnt; c++)
{
var stage = (animationStage + (double)c / cnt) % 1;
var colorStage =
(animationStage + (Math.Sin(_animationElapsed.TotalSeconds * 2) + 1) / 2 + (double)c / cnt) % 1;
var posX = (EffectiveSize.X + pointSize * 3) * stage - pointSize;
var posY = (EffectiveSize.Y - pointSize) * (1 + Math.Sin(stage * 3.14 * 3 + sinOffset)) / 2 + pointSize / 2;
var opacity = Math.Sin(stage * 3.14);
drawingContext.DrawEllipse(new ImmutableSolidColorBrush(Color.FromArgb(
255,
(byte)(255 - 255 * colorStage),
(byte)(255 * Math.Abs(0.5 - colorStage) * 2),
(byte)(255 * colorStage)
), opacity), null,
new Point(posX, posY), pointSize / 2, pointSize / 2);
}
}
public override void OnMessage(object message)
{
if (message == StartMessage)
{
_running = true;
_lastServerTime = null;
RegisterForNextAnimationFrameUpdate();
}
else if (message == StopMessage)
_running = false;
}
public override void OnAnimationFrameUpdate()
{
if (_running)
{
Invalidate();
RegisterForNextAnimationFrameUpdate();
}
}
}
private void ButtonThreadSleep(object? sender, RoutedEventArgs e)
{
Thread.Sleep(10000);
}
private void ButtonStartCustomVisual(object? sender, RoutedEventArgs e)
{
_customVisual?.SendHandlerMessage(CustomVisualHandler.StartMessage);
}
private void ButtonStopCustomVisual(object? sender, RoutedEventArgs e)
{
_customVisual?.SendHandlerMessage(CustomVisualHandler.StopMessage);
}
}
public class CompositionPageColorItem

2
samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs

@ -17,7 +17,7 @@ namespace ControlCatalog.Pages
this.Get<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";
"12 - hour or 24 - hour clock and visibility of AM / PM is dynamically set based on user time settings, or can be overridden.";
}

212
samples/ControlCatalog/Pages/GesturePage.cs

@ -0,0 +1,212 @@
using System;
using System.Numerics;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Rendering.Composition;
namespace ControlCatalog.Pages
{
public class GesturePage : UserControl
{
private bool _isInit;
private float _currentScale;
public GesturePage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if(_isInit)
{
return;
}
_isInit = true;
SetPullHandlers(this.Find<Border>("TopPullZone"), false);
SetPullHandlers(this.Find<Border>("BottomPullZone"), true);
SetPullHandlers(this.Find<Border>("RightPullZone"), true);
SetPullHandlers(this.Find<Border>("LeftPullZone"), false);
var image = this.Find<Image>("PinchImage");
SetPinchHandlers(image);
var reset = this.Find<Button>("ResetButton");
reset!.Click += (s, e) =>
{
var compositionVisual = ElementComposition.GetElementVisual(image);
if(compositionVisual!= null)
{
_currentScale = 1;
compositionVisual.Scale = new Vector3(1,1,1);
image.InvalidateMeasure();
}
};
}
private void SetPinchHandlers(Control? control)
{
if (control == null)
{
return;
}
_currentScale = 1;
Vector3 currentOffset = default;
bool isZooming = false;
CompositionVisual? compositionVisual = null;
void InitComposition(Control visual)
{
if (compositionVisual != null)
{
return;
}
compositionVisual = ElementComposition.GetElementVisual(visual);
}
control.LayoutUpdated += (s, e) =>
{
InitComposition(control!);
if (compositionVisual != null)
{
compositionVisual.Scale = new(_currentScale, _currentScale, 1);
if(currentOffset == default)
{
currentOffset = compositionVisual.Offset;
}
}
};
control.AddHandler(Gestures.PinchEvent, (s, e) =>
{
InitComposition(control!);
isZooming = true;
if(compositionVisual != null)
{
var scale = _currentScale * (float)e.Scale;
compositionVisual.Scale = new(scale, scale, 1);
}
});
control.AddHandler(Gestures.PinchEndedEvent, (s, e) =>
{
InitComposition(control!);
isZooming = false;
if (compositionVisual != null)
{
_currentScale = compositionVisual.Scale.X;
}
});
control.AddHandler(Gestures.ScrollGestureEvent, (s, e) =>
{
InitComposition(control!);
if (compositionVisual != null && !isZooming)
{
currentOffset -= new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0);
compositionVisual.Offset = currentOffset;
}
});
}
private void SetPullHandlers(Control? control, bool inverse)
{
if (control == null)
{
return;
}
var ball = control.FindLogicalDescendantOfType<Border>();
Vector3 defaultOffset = default;
CompositionVisual? ballCompositionVisual = null;
if (ball != null)
{
InitComposition(ball);
}
else
{
return;
}
control.LayoutUpdated += (s, e) =>
{
InitComposition(ball!);
if (ballCompositionVisual != null)
{
defaultOffset = ballCompositionVisual.Offset;
}
};
control.AddHandler(Gestures.PullGestureEvent, (s, e) =>
{
Vector3 center = new((float)control.Bounds.Center.X, (float)control.Bounds.Center.Y, 0);
InitComposition(ball!);
if (ballCompositionVisual != null)
{
ballCompositionVisual.Offset = defaultOffset + new System.Numerics.Vector3((float)e.Delta.X * 0.4f, (float)e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
}
});
control.AddHandler(Gestures.PullGestureEndedEvent, (s, e) =>
{
InitComposition(ball!);
if (ballCompositionVisual != null)
{
ballCompositionVisual.Offset = defaultOffset;
}
});
void InitComposition(Control control)
{
if (ballCompositionVisual != null)
{
return;
}
ballCompositionVisual = ElementComposition.GetElementVisual(ball);
if (ballCompositionVisual != null)
{
var offsetAnimation = ballCompositionVisual.Compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(100);
var implicitAnimations = ballCompositionVisual.Compositor.CreateImplicitAnimationCollection();
implicitAnimations["Offset"] = offsetAnimation;
ballCompositionVisual.ImplicitAnimations = implicitAnimations;
}
}
}
}
}

117
samples/ControlCatalog/Pages/GesturePage.xaml

@ -0,0 +1,117 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.GesturePage">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pull Gexture (Touch / Pen)</TextBlock>
<TextBlock Margin="5">Pull from colored rectangles</TextBlock>
<Border>
<DockPanel HorizontalAlignment="Stretch"
ClipToBounds="True"
Margin="5"
Height="200">
<Border DockPanel.Dock="Top"
Margin="2"
Name="TopPullZone"
Background="Transparent"
BorderBrush="Red"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="TopToBottom"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Name="TopBall"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Bottom"
BorderBrush="Green"
Margin="2"
Background="Transparent"
Name="BottomPullZone"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="BottomToTop"/>
</Border.GestureRecognizers>
<Border Width="10"
Name="BottomBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="10"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Right"
Margin="2"
Background="Transparent"
Name="RightPullZone"
BorderBrush="Blue"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="RightToLeft"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="RightBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Left"
Margin="2"
Background="Transparent"
Name="LeftPullZone"
BorderBrush="Orange"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="LeftToRight"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="LeftBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
</DockPanel>
</Border>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Zoom Gexture (Multi Touch)</TextBlock>
<Border ClipToBounds="True">
<Image Stretch="UniformToFill"
Margin="5"
Name="PinchImage"
Source="/Assets/delicate-arch-896885_640.jpg">
<Image.GestureRecognizers>
<PinchGestureRecognizer/>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True"/>
</Image.GestureRecognizers>
</Image>
</Border>
<Button HorizontalAlignment="Center" Name="ResetButton">Reset</Button>
</StackPanel>
</UserControl>

2
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -70,7 +70,7 @@ namespace ControlCatalog.Pages
3 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, 0), cropSize),
4 => new PixelRect(new PixelPoint(0, bitmapHeight - cropSize.Height), cropSize),
5 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, bitmapHeight - cropSize.Height), cropSize),
_ => PixelRect.Empty
_ => default
};
}

5
samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs

@ -1,5 +1,8 @@
using Avalonia.Controls;
using System;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Media.Immutable;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages

4
samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs

@ -1,3 +1,5 @@
using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using MiniMvvm;
@ -13,7 +15,7 @@ public class PlatformInformationViewModel : ViewModelBase
if (runtimeInfo is { } info)
{
if (info.IsBrowser)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")))
{
if (info.IsDesktop)
{

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

@ -90,7 +90,7 @@ namespace RenderDemo.Pages
return new ControlRun(_control, _defaultProperties);
}
return new TextCharacters(_text.AsMemory(), _defaultProperties);
return new TextCharacters(_text, _defaultProperties);
}
}

5
samples/interop/Direct3DInteropSample/App.paml

@ -1,5 +0,0 @@
<Application xmlns="https://github.com/avaloniaui">
<Application.Styles>
<SimpleTheme Mode="Light" />
</Application.Styles>
</Application>

21
samples/interop/Direct3DInteropSample/App.paml.cs

@ -1,21 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace Direct3DInteropSample
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
desktop.MainWindow = new MainWindow();
base.OnFrameworkInitializationCompleted();
}
}
}

32
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -1,32 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net461</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpDX.Mathematics" Version="4.0.1" />
<PackageReference Include="SharpDX.D3DCompiler" Version="4.0.1" />
<Compile Update="**\*.paml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<EmbeddedResource Include="**\*.paml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Remove="MiniCube.fx" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MiniCube.fx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
<ProjectReference Include="..\..\MiniMvvm\MiniMvvm.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
</Project>

283
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -1,283 +0,0 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Direct2D1;
using Avalonia.Direct2D1.Media;
using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct2D1;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Buffer = SharpDX.Direct3D11.Buffer;
using DeviceContext = SharpDX.Direct2D1.DeviceContext;
using Factory2 = SharpDX.DXGI.Factory2;
using InputElement = SharpDX.Direct3D11.InputElement;
using Matrix = SharpDX.Matrix;
using PixelFormat = SharpDX.Direct2D1.PixelFormat;
using Resource = SharpDX.Direct3D11.Resource;
namespace Direct3DInteropSample
{
public class MainWindow : Window
{
Texture2D _backBuffer;
RenderTargetView _renderView;
Texture2D _depthBuffer;
DepthStencilView _depthView;
private readonly SwapChain _swapChain;
private SwapChainDescription1 _desc;
private Matrix _proj = Matrix.Identity;
private readonly Matrix _view;
private Buffer _contantBuffer;
private DeviceContext _deviceContext;
private readonly MainWindowViewModel _model;
public MainWindow()
{
DataContext = _model = new MainWindowViewModel();
_desc = new SwapChainDescription1()
{
BufferCount = 1,
Width = (int)ClientSize.Width,
Height = (int)ClientSize.Height,
Format = Format.R8G8B8A8_UNorm,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput
};
using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent<Factory2>())
{
_swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc);
}
_deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None)
{
DotsPerInch = new Size2F(96, 96)
};
CreateMesh();
_view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY);
this.GetObservable(ClientSizeProperty).Subscribe(Resize);
Resize(ClientSize);
AvaloniaXamlLoader.Load(this);
Background = Avalonia.Media.Brushes.Transparent;
}
protected override void HandlePaint(Rect rect)
{
var viewProj = Matrix.Multiply(_view, _proj);
var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Clear views
context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
context.ClearRenderTargetView(_renderView, Color.White);
// Update WorldViewProj Matrix
var worldViewProj = Matrix.RotationX((float)_model.RotationX) * Matrix.RotationY((float)_model.RotationY)
* Matrix.RotationZ((float)_model.RotationZ)
* Matrix.Scaling((float)_model.Zoom)
* viewProj;
worldViewProj.Transpose();
context.UpdateSubresource(ref worldViewProj, _contantBuffer);
// Draw the cube
context.Draw(36, 0);
base.HandlePaint(rect);
// Present!
_swapChain.Present(0, PresentFlags.None);
}
private void CreateMesh()
{
var device = Direct2D1Platform.Direct3D11Device;
// Compile Vertex and Pixel shaders
var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0");
var vertexShader = new VertexShader(device, vertexShaderByteCode);
var pixelShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "PS", "ps_4_0");
var pixelShader = new PixelShader(device, pixelShaderByteCode);
var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode);
var inputElements = new[]
{
new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0)
};
// Layout from VertexShader input signature
var layout = new InputLayout(
device,
signature,
inputElements);
// Instantiate Vertex buffer from vertex data
var vertices = Buffer.Create(
device,
BindFlags.VertexBuffer,
new[]
{
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom
new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left
new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
});
// Create Constant Buffer
_contantBuffer = new Buffer(device, Utilities.SizeOf<Matrix>(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Prepare All the stages
context.InputAssembler.InputLayout = layout;
context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertices, Utilities.SizeOf<Vector4>() * 2, 0));
context.VertexShader.SetConstantBuffer(0, _contantBuffer);
context.VertexShader.Set(vertexShader);
context.PixelShader.Set(pixelShader);
}
private void Resize(Size size)
{
Utilities.Dispose(ref _deviceContext);
Utilities.Dispose(ref _backBuffer);
Utilities.Dispose(ref _renderView);
Utilities.Dispose(ref _depthBuffer);
Utilities.Dispose(ref _depthView);
var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Resize the backbuffer
_swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None);
// Get the backbuffer from the swapchain
_backBuffer = Resource.FromSwapChain<Texture2D>(_swapChain, 0);
// Renderview on the backbuffer
_renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _backBuffer);
// Create the depth buffer
_depthBuffer = new Texture2D(
Direct2D1Platform.Direct3D11Device,
new Texture2DDescription()
{
Format = Format.D32_Float_S8X24_UInt,
ArraySize = 1,
MipLevels = 1,
Width = (int)size.Width,
Height = (int)size.Height,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default,
BindFlags = BindFlags.DepthStencil,
CpuAccessFlags = CpuAccessFlags.None,
OptionFlags = ResourceOptionFlags.None
});
// Create the depth buffer view
_depthView = new DepthStencilView(Direct2D1Platform.Direct3D11Device, _depthBuffer);
// Setup targets and viewport for rendering
context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f));
context.OutputMerger.SetTargets(_depthView, _renderView);
// Setup new projection matrix with correct aspect ratio
_proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f);
using (var dxgiBackBuffer = _swapChain.GetBackBuffer<Surface>(0))
{
var renderTarget = new SharpDX.Direct2D1.RenderTarget(
Direct2D1Platform.Direct2D1Factory,
dxgiBackBuffer,
new RenderTargetProperties
{
DpiX = 96,
DpiY = 96,
Type = RenderTargetType.Default,
PixelFormat = new PixelFormat(
Format.Unknown,
AlphaMode.Premultiplied)
});
_deviceContext = renderTarget.QueryInterface<DeviceContext>();
renderTarget.Dispose();
}
}
private class D3DRenderTarget : IRenderTarget
{
private readonly MainWindow _window;
public D3DRenderTarget(MainWindow window)
{
_window = window;
}
public void Dispose()
{
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext);
}
}
protected override IRenderTarget CreateRenderTarget() => new D3DRenderTarget(this);
}
}

14
samples/interop/Direct3DInteropSample/MainWindow.paml

@ -1,14 +0,0 @@
<Window xmlns="https://github.com/avaloniaui" Background="White" Title="Avalonia Direct3D Demo">
<Grid ColumnDefinitions="*,Auto" Margin="20">
<StackPanel Grid.Column="1" MinWidth="200">
<TextBlock>Rotation X</TextBlock>
<Slider Value="{Binding RotationX, Mode=TwoWay}" Maximum="10"/>
<TextBlock>Rotation Y</TextBlock>
<Slider Value="{Binding RotationY, Mode=TwoWay}" Maximum="10"/>
<TextBlock>Rotation Z</TextBlock>
<Slider Value="{Binding RotationZ, Mode=TwoWay}" Maximum="10"/>
<TextBlock>Zoom</TextBlock>
<Slider Value="{Binding Zoom, Mode=TwoWay}" Maximum="3" Minimum="0.5"/>
</StackPanel>
</Grid>
</Window>

45
samples/interop/Direct3DInteropSample/MainWindowViewModel.cs

@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MiniMvvm;
namespace Direct3DInteropSample
{
public class MainWindowViewModel : ViewModelBase
{
private double _rotationX;
public double RotationX
{
get { return _rotationX; }
set { this.RaiseAndSetIfChanged(ref _rotationX, value); }
}
private double _rotationY = 1;
public double RotationY
{
get { return _rotationY; }
set { this.RaiseAndSetIfChanged(ref _rotationY, value); }
}
private double _rotationZ = 2;
public double RotationZ
{
get { return _rotationZ; }
set { this.RaiseAndSetIfChanged(ref _rotationZ, value); }
}
private double _zoom = 1;
public double Zoom
{
get { return _zoom; }
set { this.RaiseAndSetIfChanged(ref _zoom, value); }
}
}
}

47
samples/interop/Direct3DInteropSample/MiniCube.fx

@ -1,47 +0,0 @@
// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
struct VS_IN
{
float4 pos : POSITION;
float4 col : COLOR;
};
struct PS_IN
{
float4 pos : SV_POSITION;
float4 col : COLOR;
};
float4x4 worldViewProj;
PS_IN VS( VS_IN input )
{
PS_IN output = (PS_IN)0;
output.pos = mul(input.pos, worldViewProj);
output.col = input.col;
return output;
}
float4 PS( PS_IN input ) : SV_Target
{
return input.col;
}

16
samples/interop/Direct3DInteropSample/Program.cs

@ -1,16 +0,0 @@
using Avalonia;
namespace Direct3DInteropSample
{
class Program
{
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.With(new Win32PlatformOptions { UseDeferredRendering = false })
.UseWin32()
.UseDirect2D1();
public static int Main(string[] args)
=> BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
}

2
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -167,7 +167,7 @@ namespace Avalonia.Android
}
}
public readonly struct ComposingRegion
public readonly record struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;

11
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -15,7 +15,7 @@ namespace Avalonia
{
public static class AndroidApplicationExtensions
{
public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
public static AppBuilder UseAndroid(this AppBuilder builder)
{
return builder
.UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android")
@ -32,6 +32,7 @@ namespace Avalonia.Android
public static AndroidPlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; }
internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static void Initialize()
{
@ -51,15 +52,19 @@ namespace Avalonia.Android
if (Options.UseGpu)
{
EglPlatformOpenGlInterface.TryInitialize();
EglPlatformGraphics.TryInitialize();
}
if (Options.UseCompositor)
{
Compositor = new Compositor(
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>());
AvaloniaLocator.Current.GetService<IPlatformGraphics>());
}
else
RenderInterface =
new PlatformRenderInterfaceContextManager(AvaloniaLocator.Current
.GetService<IPlatformGraphics>());
}
}

14
src/Android/Avalonia.Android/AvaloniaSplashActivity.cs

@ -1,6 +1,5 @@
using Android.OS;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
namespace Avalonia.Android
{
@ -8,15 +7,22 @@ namespace Avalonia.Android
{
protected abstract AppBuilder CreateAppBuilder();
private static AppBuilder s_appBuilder;
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
var builder = CreateAppBuilder();
if (s_appBuilder == null)
{
var builder = CreateAppBuilder();
var lifetime = new SingleViewLifetime();
var lifetime = new SingleViewLifetime();
builder.SetupWithLifetime(lifetime);
builder.SetupWithLifetime(lifetime);
s_appBuilder = builder;
}
}
}

32
src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs

@ -1,32 +0,0 @@
using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase
{
private readonly EglPlatformOpenGlInterface _egl;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info)
{
_egl = egl;
_info = info;
}
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() =>
new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle), _info.Handle);
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{
var feature = AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>();
if (feature is EglPlatformOpenGlInterface egl)
{
return new GlPlatformSurface(egl, info);
}
return null;
}
}
}

30
src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs

@ -1,30 +0,0 @@
using System;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo
{
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglSurface _surface;
private readonly IntPtr _handle;
public GlRenderTarget(
EglPlatformOpenGlInterface egl,
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
EglSurface surface,
IntPtr handle)
: base(egl)
{
_info = info;
_surface = surface;
_handle = handle;
}
public bool IsCorrupted => _handle != _info.Handle;
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info);
}
}

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -96,12 +96,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IntPtr bits;
// Do not touch.
#pragma warning disable CA1823 // Avoid unused private fields
uint reserved1;
uint reserved2;
uint reserved3;
uint reserved4;
uint reserved5;
uint reserved6;
#pragma warning restore CA1823 // Avoid unused private fields
}
}
}

18
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -8,7 +8,6 @@ using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
@ -31,7 +30,7 @@ using Android.Graphics.Drawables;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo,
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
private readonly IGlPlatformSurface _gl;
@ -48,7 +47,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_pointerHelper = new AndroidMotionEventsHelper(this);
_gl = GlPlatformSurface.TryCreate(this);
_gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(this);
RenderScaling = _view.Scaling;
@ -107,10 +106,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IRenderer CreateRenderer(IRenderRoot root) =>
AndroidPlatform.Options.UseCompositor
? new CompositingRenderer(root, AndroidPlatform.Compositor)
? new CompositingRenderer(root, AndroidPlatform.Compositor, () => Surfaces)
: AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer((Visual)root);
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
() => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
AndroidPlatform.RenderInterface)
{ RenderOnlyOnRenderThread = true }
: new ImmediateRenderer((Visual)root,
() => AndroidPlatform.RenderInterface.CreateRenderTarget(Surfaces),
AndroidPlatform.RenderInterface);
public virtual void Hide()
{
@ -284,7 +288,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle;
public PixelSize Size => _view.Size;

2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@ -38,7 +38,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return null;
}
var eventTime = (ulong)DateTime.Now.Millisecond;
var eventTime = (ulong)e.EventTime;
var inputRoot = _view.InputRoot;
var actionMasked = e.ActionMasked;
var modifiers = GetModifiers(e.MetaState, e.ButtonState);

1
src/Avalonia.Base/Animation/AnimationInstance`1.cs

@ -5,7 +5,6 @@ using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Reactive;
using JetBrains.Annotations;
namespace Avalonia.Animation
{

11
src/Avalonia.Base/Animation/Cue.cs

@ -8,7 +8,7 @@ namespace Avalonia.Animation
/// Determines the time index for a <see cref="KeyFrame"/>.
/// </summary>
[TypeConverter(typeof(CueTypeConverter))]
public readonly struct Cue : IEquatable<Cue>, IEquatable<double>
public readonly record struct Cue : IEquatable<Cue>, IEquatable<double>
{
/// <summary>
/// The normalized percent value, ranging from 0.0 to 1.0
@ -49,15 +49,6 @@ namespace Avalonia.Animation
}
}
/// <summary>
/// Checks for equality between two <see cref="Cue"/>s.
/// </summary>
/// <param name="other">The second cue.</param>
public bool Equals(Cue other)
{
return CueValue == other.CueValue;
}
/// <summary>
/// Checks for equality between a <see cref="Cue"/>
/// and a <see cref="double"/> value.

4
src/Avalonia.Base/Avalonia.Base.csproj

@ -15,7 +15,6 @@
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\System.Memory.props" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
@ -29,10 +28,13 @@
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Desktop, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.Xaml, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.OpenGL, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.ColorPicker, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.DataGrid, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />

26
src/Avalonia.Base/Compatibility/OperatingSystem.cs

@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Compatibility
{
internal sealed class OperatingSystemEx
{
#if NET6_0_OR_GREATER
public static bool IsWindows() => OperatingSystem.IsWindows();
public static bool IsMacOS() => OperatingSystem.IsMacOS();
public static bool IsLinux() => OperatingSystem.IsLinux();
public static bool IsAndroid() => OperatingSystem.IsAndroid();
public static bool IsIOS() => OperatingSystem.IsIOS();
public static bool IsBrowser() => OperatingSystem.IsBrowser();
public static bool IsOSPlatform(string platform) => OperatingSystem.IsOSPlatform(platform);
#else
public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
public static bool IsAndroid() => IsOSPlatform("ANDROID");
public static bool IsIOS() => IsOSPlatform("IOS");
public static bool IsBrowser() => IsOSPlatform("BROWSER");
public static bool IsOSPlatform(string platform) => RuntimeInformation.IsOSPlatform(OSPlatform.Create(platform));
#endif
}
}

34
src/Avalonia.Base/Contract.cs

@ -1,34 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace Avalonia
{
/// <summary>
/// A stub of Code Contract's Contract class.
/// </summary>
/// <remarks>
/// It would be nice to use Code Contracts on Avalonia but last time I tried it slowed things
/// to a crawl and often crashed. Instead use the same signature for checking preconditions
/// in the hope that it might become usable at some point.
/// </remarks>
public static class Contract
{
/// <summary>
/// Specifies a precondition.
/// </summary>
/// <typeparam name="TException">
/// The exception to throw if <paramref name="condition"/> is false.
/// </typeparam>
/// <param name="condition">The precondition.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[ContractAnnotation("condition:false=>stop")]
public static void Requires<TException>(bool condition) where TException : Exception, new()
{
if (!condition)
{
throw new TException();
}
}
}
}

9
src/Avalonia.Base/CornerRadius.cs

@ -61,9 +61,13 @@ namespace Avalonia
public double BottomLeft { get; }
/// <summary>
/// Gets a value indicating whether all corner radii are set to 0.
/// Gets a value indicating whether the instance has default values (all corner radii are set to 0).
/// </summary>
public bool IsEmpty => TopLeft.Equals(0) && IsUniform;
public bool IsDefault => TopLeft == 0 && TopRight == 0 && BottomLeft == 0 && BottomRight == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
/// <summary>
/// Gets a value indicating whether all corner radii are equal.
@ -79,7 +83,6 @@ namespace Avalonia
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return TopLeft == other.TopLeft &&
TopRight == other.TopRight &&
BottomRight == other.BottomRight &&
BottomLeft == other.BottomLeft;

2
src/Avalonia.Base/Data/BindingValue.cs

@ -80,7 +80,7 @@ namespace Avalonia.Data
/// - For an unset value, use <see cref="Unset"/> or simply `default`
/// - For other types, call one of the static factory methods
/// </remarks>
public readonly struct BindingValue<T>
public readonly record struct BindingValue<T>
{
private readonly T _value;

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

@ -4,8 +4,6 @@ namespace Avalonia.Data.Core
{
public abstract class ExpressionNode
{
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference<object?> UnsetReference =
new WeakReference<object?>(AvaloniaProperty.UnsetValue);

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

@ -49,8 +49,6 @@ namespace Avalonia.Data.Core
new TaskStreamPlugin(),
new ObservableStreamPlugin(),
};
private static readonly object UninitializedValue = new object();
private readonly ExpressionNode _node;
private object? _root;
private Func<object?>? _rootGetter;

4
src/Avalonia.Base/Data/InstancedBinding.cs

@ -28,11 +28,9 @@ namespace Avalonia.Data
/// </remarks>
public InstancedBinding(ISubject<object?> subject, BindingMode mode, BindingPriority priority)
{
Contract.Requires<ArgumentNullException>(subject != null);
Mode = mode;
Priority = priority;
Value = subject;
Value = subject ?? throw new ArgumentNullException(nameof(subject));
}
private InstancedBinding(object? value, BindingMode mode, BindingPriority priority)

128
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@ -0,0 +1,128 @@
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
public class PinchGestureRecognizer : StyledElement, IGestureRecognizer
{
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private float _initialDistance;
private IPointer? _firstContact;
private Point _firstPoint;
private IPointer? _secondContact;
private Point _secondPoint;
private Point _origin;
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
{
RemoveContact(pointer);
}
public void PointerMoved(PointerEventArgs e)
{
if (_target != null && _target is Visual visual)
{
if(_firstContact == e.Pointer)
{
_firstPoint = e.GetPosition(visual);
}
else if (_secondContact == e.Pointer)
{
_secondPoint = e.GetPosition(visual);
}
else
{
return;
}
if (_firstContact != null && _secondContact != null)
{
var distance = GetDistance(_firstPoint, _secondPoint);
var scale = distance / _initialDistance;
_target?.RaiseEvent(new PinchEventArgs(scale, _origin));
}
}
}
public void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
if (_firstContact == null)
{
_firstContact = e.Pointer;
_firstPoint = e.GetPosition(visual);
return;
}
else if (_secondContact == null && _firstContact != e.Pointer)
{
_secondContact = e.Pointer;
_secondPoint = e.GetPosition(visual);
}
else
{
return;
}
if (_firstContact != null && _secondContact != null)
{
_initialDistance = GetDistance(_firstPoint, _secondPoint);
_origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
_actions!.Capture(_firstContact, this);
_actions!.Capture(_secondContact, this);
}
}
}
public void PointerReleased(PointerReleasedEventArgs e)
{
RemoveContact(e.Pointer);
}
private void RemoveContact(IPointer pointer)
{
if (_firstContact == pointer || _secondContact == pointer)
{
if (_secondContact == pointer)
{
_secondContact = null;
}
if (_firstContact == pointer)
{
_firstContact = _secondContact;
_secondContact = null;
}
_target?.RaiseEvent(new PinchEndedEventArgs());
}
}
private float GetDistance(Point a, Point b)
{
var length = _secondPoint - _firstPoint;
return (float)new Vector(length.X, length.Y).Length;
}
}
}

33
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@ -1,15 +1,19 @@
using Avalonia.Input.GestureRecognizers;
using System;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
public class PullGestureRecognizer : StyledElement, IGestureRecognizer
{
internal static int MinPullDetectionSize = 50;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private Point _initialPosition;
private int _gestureId;
private IPointer? _tracking;
private PullDirection _pullDirection;
private bool _pullInProgress;
/// <summary>
/// Defines the <see cref="PullDirection"/> property.
@ -31,23 +35,12 @@ namespace Avalonia.Input
PullDirection = pullDirection;
}
public PullGestureRecognizer() { }
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
_target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
_target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
@ -94,6 +87,7 @@ namespace Avalonia.Input
break;
}
_pullInProgress = true;
_target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection));
}
}
@ -111,16 +105,16 @@ namespace Avalonia.Input
switch (PullDirection)
{
case PullDirection.TopToBottom:
canPull = position.Y < bounds.Height * 0.1;
canPull = position.Y < Math.Max(MinPullDetectionSize, bounds.Height * 0.1);
break;
case PullDirection.BottomToTop:
canPull = position.Y > bounds.Height - (bounds.Height * 0.1);
canPull = position.Y > Math.Min(bounds.Height - MinPullDetectionSize, bounds.Height - (bounds.Height * 0.1));
break;
case PullDirection.LeftToRight:
canPull = position.X < bounds.Width * 0.1;
canPull = position.X < Math.Max(MinPullDetectionSize, bounds.Width * 0.1);
break;
case PullDirection.RightToLeft:
canPull = position.X > bounds.Width - (bounds.Width * 0.1);
canPull = position.X > Math.Min(bounds.Width - MinPullDetectionSize, bounds.Width - (bounds.Width * 0.1));
break;
}
@ -135,7 +129,7 @@ namespace Avalonia.Input
public void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer)
if (_tracking == e.Pointer && _pullInProgress)
{
EndPull();
}
@ -145,6 +139,7 @@ namespace Avalonia.Input
{
_tracking = null;
_initialPosition = default;
_pullInProgress = false;
_target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
}

65
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -16,7 +16,10 @@ namespace Avalonia.Input.GestureRecognizers
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private int _gestureId;
private int _scrollStartDistance = 30;
private Point _pointerPressedPoint;
private VelocityTracker? _velocityTracker;
// Movement per second
private Vector _inertia;
private ulong? _lastMoveTimestamp;
@ -38,6 +41,15 @@ namespace Avalonia.Input.GestureRecognizers
nameof(CanVerticallyScroll),
o => o.CanVerticallyScroll,
(o, v) => o.CanVerticallyScroll = v);
/// <summary>
/// Defines the <see cref="ScrollStartDistance"/> property.
/// </summary>
public static readonly DirectProperty<ScrollGestureRecognizer, int> ScrollStartDistanceProperty =
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, int>(
nameof(ScrollStartDistance),
o => o.ScrollStartDistance,
(o, v) => o.ScrollStartDistance = v);
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
@ -56,6 +68,15 @@ namespace Avalonia.Input.GestureRecognizers
get => _canVerticallyScroll;
set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value);
}
/// <summary>
/// Gets or sets a value indicating the distance the pointer moves before scrolling is started
/// </summary>
public int ScrollStartDistance
{
get => _scrollStartDistance;
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
}
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
@ -72,12 +93,9 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
_tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId();
_trackedRootPoint = e.GetPosition((Visual?)_target);
_trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target);
}
}
// Arbitrary chosen value, probably need to move that to platform settings or something
private const double ScrollStartDistance = 30;
// Pixels per second speed that is considered to be the stop of inertial scroll
private const double InertialScrollSpeedEnd = 5;
@ -95,6 +113,13 @@ namespace Avalonia.Input.GestureRecognizers
_scrolling = true;
if (_scrolling)
{
_velocityTracker = new VelocityTracker();
// Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
_trackedRootPoint = new Point(
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance));
_actions!.Capture(e.Pointer, this);
}
}
@ -102,14 +127,11 @@ namespace Avalonia.Input.GestureRecognizers
if (_scrolling)
{
var vector = _trackedRootPoint - rootPoint;
var elapsed = _lastMoveTimestamp.HasValue && _lastMoveTimestamp < e.Timestamp ?
TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) :
TimeSpan.Zero;
_velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint);
_lastMoveTimestamp = e.Timestamp;
_trackedRootPoint = rootPoint;
if (elapsed.TotalSeconds > 0)
_inertia = vector / elapsed.TotalSeconds;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true;
}
@ -134,12 +156,14 @@ namespace Avalonia.Input.GestureRecognizers
}
}
public void PointerReleased(PointerReleasedEventArgs e)
{
if (e.Pointer == _tracking && _scrolling)
{
_inertia = _velocityTracker?.GetFlingVelocity().PixelsPerSecond ?? Vector.Zero;
e.Handled = true;
if (_inertia == default
|| e.Timestamp == 0
@ -167,9 +191,18 @@ namespace Avalonia.Input.GestureRecognizers
var distance = speed * elapsedSinceLastTick.TotalSeconds;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance));
if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
// EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
{
EndGesture();
return false;
}
else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
{
EndGesture();
return false;
}
else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd)
{
EndGesture();
return false;

375
src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs

@ -0,0 +1,375 @@
// Code in this file is derived from
// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart
using System;
using System.Diagnostics;
using Avalonia.Utilities;
namespace Avalonia.Input.GestureRecognizers
{
// Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'?
internal readonly record struct Velocity(Vector PixelsPerSecond)
{
public Velocity ClampMagnitude(double minValue, double maxValue)
{
Debug.Assert(minValue >= 0.0);
Debug.Assert(maxValue >= 0.0 && maxValue >= minValue);
double valueSquared = PixelsPerSecond.SquaredLength;
if (valueSquared > maxValue * maxValue)
{
double length = PixelsPerSecond.Length;
return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * maxValue : Vector.Zero);
// preventing double.NaN in Vector PixelsPerSecond is important -- if a NaN eventually gets into a
// ScrollGestureEventArgs it results in runtime errors.
}
if (valueSquared < minValue * minValue)
{
double length = PixelsPerSecond.Length;
return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * minValue : Vector.Zero);
}
return this;
}
}
/// A two dimensional velocity estimate.
///
/// VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An
/// estimate's [confidence] measures how well the velocity tracker's position
/// data fit a straight line, [duration] is the time that elapsed between the
/// first and last position sample used to compute the velocity, and [offset]
/// is similarly the difference between the first and last positions.
///
/// See also:
///
/// * [VelocityTracker], which computes [VelocityEstimate]s.
/// * [Velocity], which encapsulates (just) a velocity vector and provides some
/// useful velocity operations.
internal record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset);
internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time);
/// Computes a pointer's velocity based on data from [PointerMoveEvent]s.
///
/// The input data is provided by calling [addPosition]. Adding data is cheap.
///
/// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will
/// compute the velocity based on the data added so far. Only call these when
/// you need to use the velocity, as they are comparatively expensive.
///
/// The quality of the velocity estimation will be better if more data points
/// have been received.
internal class VelocityTracker
{
private const int AssumePointerMoveStoppedMilliseconds = 40;
private const int HistorySize = 20;
private const int HorizonMilliseconds = 100;
private const int MinSampleSize = 3;
private const double MinFlingVelocity = 50.0; // Logical pixels / second
private const double MaxFlingVelocity = 8000.0;
private readonly PointAtTime[] _samples = new PointAtTime[HistorySize];
private int _index = 0;
/// <summary>
/// Adds a position as the given time to the tracker.
/// </summary>
/// <param name="time"></param>
/// <param name="position"></param>
public void AddPosition(TimeSpan time, Vector position)
{
_index++;
if (_index == HistorySize)
{
_index = 0;
}
_samples[_index] = new PointAtTime(true, position, time);
}
/// Returns an estimate of the velocity of the object being tracked by the
/// tracker given the current information available to the tracker.
///
/// Information is added using [addPosition].
///
/// Returns null if there is no data on which to base an estimate.
protected virtual VelocityEstimate? GetVelocityEstimate()
{
Span<double> x = stackalloc double[HistorySize];
Span<double> y = stackalloc double[HistorySize];
Span<double> w = stackalloc double[HistorySize];
Span<double> time = stackalloc double[HistorySize];
int sampleCount = 0;
int index = _index;
var newestSample = _samples[index];
if (!newestSample.Valid)
{
return null;
}
var previousSample = newestSample;
var oldestSample = newestSample;
// Starting with the most recent PointAtTime sample, iterate backwards while
// the samples represent continuous motion.
do
{
var sample = _samples[index];
if (!sample.Valid)
{
break;
}
double age = (newestSample.Time - sample.Time).TotalMilliseconds;
double delta = Math.Abs((sample.Time - previousSample.Time).TotalMilliseconds);
previousSample = sample;
if (age > HorizonMilliseconds || delta > AssumePointerMoveStoppedMilliseconds)
{
break;
}
oldestSample = sample;
var position = sample.Point;
x[sampleCount] = position.X;
y[sampleCount] = position.Y;
w[sampleCount] = 1.0;
time[sampleCount] = -age;
index = (index == 0 ? HistorySize : index) - 1;
sampleCount++;
} while (sampleCount < HistorySize);
if (sampleCount >= MinSampleSize)
{
var xFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), x.Slice(0, sampleCount), w.Slice(0, sampleCount));
if (xFit != null)
{
var yFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), y.Slice(0, sampleCount), w.Slice(0, sampleCount));
if (yFit != null)
{
return new VelocityEstimate( // convert from pixels/ms to pixels/s
PixelsPerSecond: new Vector(xFit.Coefficients[1] * 1000, yFit.Coefficients[1] * 1000),
Confidence: xFit.Confidence * yFit.Confidence,
Duration: newestSample.Time - oldestSample.Time,
Offset: newestSample.Point - oldestSample.Point
);
}
}
}
// We're unable to make a velocity estimate but we did have at least one
// valid pointer position.
return new VelocityEstimate(
PixelsPerSecond: Vector.Zero,
Confidence: 1.0,
Duration: newestSample.Time - oldestSample.Time,
Offset: newestSample.Point - oldestSample.Point
);
}
/// <summary>
/// Computes the velocity of the pointer at the time of the last
/// provided data point.
///
/// This can be expensive. Only call this when you need the velocity.
///
/// Returns [Velocity.zero] if there is no data from which to compute an
/// estimate or if the estimated velocity is zero.///
/// </summary>
/// <returns></returns>
internal Velocity GetVelocity()
{
var estimate = GetVelocityEstimate();
if (estimate == null || estimate.PixelsPerSecond.IsDefault)
{
return new Velocity(Vector.Zero);
}
return new Velocity(estimate.PixelsPerSecond);
}
internal virtual Velocity GetFlingVelocity()
{
return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity);
}
}
/// An nth degree polynomial fit to a dataset.
internal class PolynomialFit
{
/// Creates a polynomial fit of the given degree.
///
/// There are n + 1 coefficients in a fit of degree n.
internal PolynomialFit(int degree)
{
Coefficients = new double[degree + 1];
}
/// The polynomial coefficients of the fit.
public double[] Coefficients { get; }
/// An indicator of the quality of the fit.
///
/// Larger values indicate greater quality.
public double Confidence { get; set; }
}
internal class LeastSquaresSolver
{
private const double PrecisionErrorTolerance = 1e-10;
/// <summary>
/// Fits a polynomial of the given degree to the data points.
/// When there is not enough data to fit a curve null is returned.
/// </summary>
public static PolynomialFit? Solve(int degree, ReadOnlySpan<double> x, ReadOnlySpan<double> y, ReadOnlySpan<double> w)
{
if (degree > x.Length)
{
// Not enough data to fit a curve.
return null;
}
PolynomialFit result = new PolynomialFit(degree);
// Shorthands for the purpose of notation equivalence to original C++ code.
int m = x.Length;
int n = degree + 1;
// Expand the X vector to a matrix A, pre-multiplied by the weights.
_Matrix a = new _Matrix(m, stackalloc double[n * m]);
for (int h = 0; h < m; h += 1)
{
a[0, h] = w[h];
for (int i = 1; i < n; i += 1)
{
a[i, h] = a[i - 1, h] * x[h];
}
}
// Apply the Gram-Schmidt process to A to obtain its QR decomposition.
// Orthonormal basis, column-major order Vector.
_Matrix q = new _Matrix(m, stackalloc double[n * m]);
// Upper triangular matrix, row-major order.
_Matrix r = new _Matrix(n, stackalloc double[n * n]);
for (int j = 0; j < n; j += 1)
{
for (int h = 0; h < m; h += 1)
{
q[j, h] = a[j, h];
}
for (int i = 0; i < j; i += 1)
{
double dot = Multiply(q.GetRow(j), q.GetRow(i));
for (int h = 0; h < m; h += 1)
{
q[j, h] = q[j, h] - dot * q[i, h];
}
}
double norm = Norm(q.GetRow(j));
if (norm < PrecisionErrorTolerance)
{
// Vectors are linearly dependent or zero so no solution.
return null;
}
double inverseNorm = 1.0 / norm;
for (int h = 0; h < m; h += 1)
{
q[j, h] = q[j, h] * inverseNorm;
}
for (int i = 0; i < n; i += 1)
{
r[j, i] = i < j ? 0.0 : Multiply(q.GetRow(j), a.GetRow(i));
}
}
// Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
// We just work from bottom-right to top-left calculating B's coefficients.
// "m" isn't expected to be bigger than HistorySize=20, so allocation on stack is safe.
Span<double> wy = stackalloc double[m];
for (int h = 0; h < m; h += 1)
{
wy[h] = y[h] * w[h];
}
for (int i = n - 1; i >= 0; i -= 1)
{
result.Coefficients[i] = Multiply(q.GetRow(i), wy);
for (int j = n - 1; j > i; j -= 1)
{
result.Coefficients[i] -= r[i, j] * result.Coefficients[j];
}
result.Coefficients[i] /= r[i, i];
}
// Calculate the coefficient of determination (confidence) as:
// 1 - (sumSquaredError / sumSquaredTotal)
// ...where sumSquaredError is the residual sum of squares (variance of the
// error), and sumSquaredTotal is the total sum of squares (variance of the
// data) where each has been weighted.
double yMean = 0.0;
for (int h = 0; h < m; h += 1)
{
yMean += y[h];
}
yMean /= m;
double sumSquaredError = 0.0;
double sumSquaredTotal = 0.0;
for (int h = 0; h < m; h += 1)
{
double term = 1.0;
double err = y[h] - result.Coefficients[0];
for (int i = 1; i < n; i += 1)
{
term *= x[h];
err -= term * result.Coefficients[i];
}
sumSquaredError += w[h] * w[h] * err * err;
double v = y[h] - yMean;
sumSquaredTotal += w[h] * w[h] * v * v;
}
result.Confidence = sumSquaredTotal <= PrecisionErrorTolerance ? 1.0 :
1.0 - (sumSquaredError / sumSquaredTotal);
return result;
}
private static double Multiply(Span<double> v1, Span<double> v2)
{
double result = 0.0;
for (int i = 0; i < v1.Length; i += 1)
{
result += v1[i] * v2[i];
}
return result;
}
private static double Norm(Span<double> v)
{
return Math.Sqrt(Multiply(v, v));
}
private readonly ref struct _Matrix
{
private readonly int _columns;
private readonly Span<double> _elements;
internal _Matrix(int cols, Span<double> elements)
{
_columns = cols;
_elements = elements;
}
public double this[int row, int col]
{
get => _elements[row * _columns + col];
set => _elements[row * _columns + col] = value;
}
public Span<double> GetRow(int row) => _elements.Slice(row * _columns, _columns);
}
}
}

129
src/Avalonia.Base/Input/Gestures.cs

@ -1,6 +1,8 @@
using System;
using System.Threading;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -8,6 +10,21 @@ namespace Avalonia.Input
public static class Gestures
{
private static bool s_isDoubleTapped = false;
private static bool s_isHolding;
private static CancellationTokenSource? s_holdCancellationToken;
/// <summary>
/// Defines the IsHoldingEnabled attached property.
/// </summary>
public static readonly AttachedProperty<bool> IsHoldingEnabledProperty =
AvaloniaProperty.RegisterAttached<StyledElement, bool>("IsHoldingEnabled", typeof(Gestures), true);
/// <summary>
/// Defines the IsHoldWithMouseEnabled attached property.
/// </summary>
public static readonly AttachedProperty<bool> IsHoldWithMouseEnabledProperty =
AvaloniaProperty.RegisterAttached<StyledElement, bool>("IsHoldWithMouseEnabled", typeof(Gestures), false);
public static readonly RoutedEvent<TappedEventArgs> TappedEvent = RoutedEvent.Register<TappedEventArgs>(
"Tapped",
RoutingStrategies.Bubble,
@ -45,19 +62,54 @@ namespace Avalonia.Input
private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint;
private static IPointer? s_lastPointer;
public static readonly RoutedEvent<PinchEventArgs> PinchEvent =
RoutedEvent.Register<PinchEventArgs>(
"PinchEvent", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<PinchEndedEventArgs> PinchEndedEvent =
RoutedEvent.Register<PinchEndedEventArgs>(
"PinchEndedEvent", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<PullGestureEventArgs> PullGestureEvent =
RoutedEvent.Register<PullGestureEventArgs>(
"PullGesture", RoutingStrategies.Bubble, typeof(Gestures));
/// <summary>
/// Occurs when a user performs a press and hold gesture (with a single touch, mouse, or pen/stylus contact).
/// </summary>
public static readonly RoutedEvent<HoldingRoutedEventArgs> HoldingEvent =
RoutedEvent.Register<HoldingRoutedEventArgs>(
"Holding", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<PullGestureEndedEventArgs> PullGestureEndedEvent =
RoutedEvent.Register<PullGestureEndedEventArgs>(
"PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
public static bool GetIsHoldingEnabled(StyledElement element)
{
return element.GetValue(IsHoldingEnabledProperty);
}
public static void SetIsHoldingEnabled(StyledElement element, bool value)
{
element.SetValue(IsHoldingEnabledProperty, value);
}
public static bool GetIsHoldWithMouseEnabled(StyledElement element)
{
return element.GetValue(IsHoldWithMouseEnabledProperty);
}
public static void SetIsHoldWithMouseEnabled(StyledElement element, bool value)
{
element.SetValue(IsHoldWithMouseEnabledProperty, value);
}
static Gestures()
{
InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed);
InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased);
InputElement.PointerMovedEvent.RouteFinished.Subscribe(PointerMoved);
}
public static void AddTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
@ -102,11 +154,42 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source;
if(s_lastPointer != null)
{
if(s_isHolding && ev.Source is Interactive i)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type));
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null;
s_lastPointer = null;
}
s_isHolding = false;
if (e.ClickCount % 2 == 1)
{
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source);
s_lastPointer = e.Pointer;
s_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token;
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
if (settings != null)
{
DispatcherTimer.RunOnce(() =>
{
if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i)))
{
s_isHolding = true;
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type));
}
}, settings.HoldWaitDuration);
}
}
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
@ -140,7 +223,12 @@ namespace Avalonia.Input
if (tapRect.ContainsExclusive(point.Position))
{
if (e.InitialPressMouseButton == MouseButton.Right)
if(s_isHolding)
{
s_isHolding = false;
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type));
}
else if (e.InitialPressMouseButton == MouseButton.Right)
{
i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
}
@ -152,6 +240,45 @@ namespace Avalonia.Input
}
}
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null;
s_lastPointer = null;
}
}
private static void PointerMoved(RoutedEventArgs ev)
{
if (ev.Route == RoutingStrategies.Bubble)
{
var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target))
{
if (e.Pointer == s_lastPointer)
{
var point = e.GetCurrentPoint((Visual)target);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
if (tapRect.ContainsExclusive(point.Position))
{
return;
}
if (s_isHolding && ev.Source is Interactive i)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type));
}
}
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null;
s_isHolding = false;
}
}
}

51
src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs

@ -0,0 +1,51 @@
using System;
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class HoldingRoutedEventArgs : RoutedEventArgs
{
/// <summary>
/// Gets the state of the <see cref="Gestures.HoldingEvent"/> event.
/// </summary>
public HoldingState HoldingState { get; }
/// <summary>
/// Gets the location of the touch, mouse, or pen/stylus contact.
/// </summary>
public Point Position { get; }
/// <summary>
/// Gets the pointer type of the input source.
/// </summary>
public PointerType PointerType { get; }
/// <summary>
/// Initializes a new instance of the <see cref="HoldingRoutedEventArgs"/> class.
/// </summary>
public HoldingRoutedEventArgs(HoldingState holdingState, Point position, PointerType pointerType) : base(Gestures.HoldingEvent)
{
HoldingState = holdingState;
Position = position;
PointerType = pointerType;
}
}
public enum HoldingState
{
/// <summary>
/// A single contact has been detected and a time threshold is crossed without the contact being lifted, another contact detected, or another gesture started.
/// </summary>
Started,
/// <summary>
/// The single contact is lifted.
/// </summary>
Completed,
/// <summary>
/// An additional contact is detected or a subsequent gesture (such as a slide) is detected.
/// </summary>
Cancelled,
}
}

16
src/Avalonia.Base/Input/InputElement.cs

@ -188,11 +188,16 @@ namespace Avalonia.Input
/// </summary>
public static readonly RoutedEvent<TappedEventArgs> TappedEvent = Gestures.TappedEvent;
/// <summary>
/// Defines the <see cref="Holding"/> event.
/// </summary>
public static readonly RoutedEvent<HoldingRoutedEventArgs> HoldingEvent = Gestures.HoldingEvent;
/// <summary>
/// Defines the <see cref="DoubleTapped"/> event.
/// </summary>
public static readonly RoutedEvent<TappedEventArgs> DoubleTappedEvent = Gestures.DoubleTappedEvent;
private bool _isEffectivelyEnabled = true;
private bool _isFocused;
private bool _isKeyboardFocusWithin;
@ -352,6 +357,15 @@ namespace Avalonia.Input
add { AddHandler(TappedEvent, value); }
remove { RemoveHandler(TappedEvent, value); }
}
/// <summary>
/// Occurs when a hold gesture occurs on the control.
/// </summary>
public event EventHandler<HoldingRoutedEventArgs>? Holding
{
add { AddHandler(HoldingEvent, value); }
remove { RemoveHandler(HoldingEvent, value); }
}
/// <summary>
/// Occurs when a double-tap gesture occurs on the control.

2
src/Avalonia.Base/Input/KeyEventArgs.cs

@ -5,7 +5,7 @@ namespace Avalonia.Input
{
public class KeyEventArgs : RoutedEventArgs
{
internal KeyEventArgs()
public KeyEventArgs()
{
}

24
src/Avalonia.Base/Input/PinchEventArgs.cs

@ -0,0 +1,24 @@
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class PinchEventArgs : RoutedEventArgs
{
public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent)
{
Scale = scale;
ScaleOrigin = scaleOrigin;
}
public double Scale { get; } = 1;
public Point ScaleOrigin { get; }
}
public class PinchEndedEventArgs : RoutedEventArgs
{
public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
{
}
}
}

4
src/Avalonia.Base/Input/PointerPoint.cs

@ -5,7 +5,7 @@ namespace Avalonia.Input
/// <summary>
/// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact.
/// </summary>
public struct PointerPoint
public record struct PointerPoint
{
public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
{
@ -33,7 +33,7 @@ namespace Avalonia.Input
/// <summary>
/// Provides extended properties for a PointerPoint object.
/// </summary>
public struct PointerPointProperties
public record struct PointerPointProperties
{
/// <summary>
/// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device.

6
src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs

@ -21,11 +21,9 @@ namespace Avalonia.Input.Raw
/// <param name="root">The root from which the event originates.</param>
public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root)
{
device = device ?? throw new ArgumentNullException(nameof(device));
Device = device;
Device = device ?? throw new ArgumentNullException(nameof(device));
Timestamp = timestamp;
Root = root;
Root = root ?? throw new ArgumentNullException(nameof(root));
}
/// <summary>

8
src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs

@ -53,9 +53,6 @@ namespace Avalonia.Input.Raw
RawInputModifiers inputModifiers)
: base(device, timestamp, root)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
Point = new RawPointerPoint();
Position = position;
Type = type;
@ -80,9 +77,6 @@ namespace Avalonia.Input.Raw
RawInputModifiers inputModifiers)
: base(device, timestamp, root)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
Point = point;
Type = type;
InputModifiers = inputModifiers;
@ -130,7 +124,7 @@ namespace Avalonia.Input.Raw
internal IInputElement? InputHitTestResult { get; set; }
}
public struct RawPointerPoint
public record struct RawPointerPoint
{
/// <summary>
/// Pointer position, in client DIPs.

2
src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs

@ -46,7 +46,7 @@ namespace Avalonia.Input.TextInput
void SelectInSurroundingText(int start, int end);
}
public struct TextInputMethodSurroundingText
public record struct TextInputMethodSurroundingText
{
public string Text { get; set; }
public int CursorOffset { get; set; }

2
src/Avalonia.Base/Input/TextInputEventArgs.cs

@ -4,7 +4,7 @@ namespace Avalonia.Input
{
public class TextInputEventArgs : RoutedEventArgs
{
internal TextInputEventArgs()
public TextInputEventArgs()
{
}

5
src/Avalonia.Base/Logging/LogArea.cs

@ -35,6 +35,11 @@ namespace Avalonia.Logging
/// </summary>
public const string Control = nameof(Control);
/// <summary>
/// The log event comes from Win32 Platform.
/// </summary>
public const string Platform = nameof(Platform);
/// <summary>
/// The log event comes from Win32 Platform.
/// </summary>

2
src/Avalonia.Base/Logging/ParametrizedLogger.cs

@ -5,7 +5,7 @@ namespace Avalonia.Logging
/// <summary>
/// Logger sink parametrized for given logging level.
/// </summary>
public readonly struct ParametrizedLogger
public readonly record struct ParametrizedLogger
{
private readonly ILogSink _sink;
private readonly LogEventLevel _level;

2
src/Avalonia.Base/Matrix.cs

@ -571,7 +571,7 @@ namespace Avalonia
return true;
}
public struct Decomposed
public record struct Decomposed
{
public Vector Translate;
public Vector Scale;

17
src/Avalonia.Base/Media/BoxShadow.cs

@ -45,7 +45,14 @@ namespace Avalonia.Media
}
}
public bool IsEmpty => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0;
/// <summary>
/// Gets a value indicating whether the instance has default values.
/// </summary>
public bool IsDefault => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0;
/// <inheritdoc cref="IsDefault"/>
[Obsolete("Use IsDefault instead.")]
public bool IsEmpty => IsDefault;
private readonly static char[] s_Separator = new char[] { ' ', '\t' };
@ -82,7 +89,7 @@ namespace Avalonia.Media
{
var sb = StringBuilderCache.Acquire();
if (IsEmpty)
if (IsDefault)
{
return "none";
}
@ -171,5 +178,11 @@ namespace Avalonia.Media
public Rect TransformBounds(in Rect rect)
=> IsInset ? rect : rect.Translate(new Vector(OffsetX, OffsetY)).Inflate(Spread + Blur);
public static bool operator ==(BoxShadow left, BoxShadow right) =>
left.Equals(right);
public static bool operator !=(BoxShadow left, BoxShadow right) =>
!(left == right);
}
}

12
src/Avalonia.Base/Media/BoxShadows.cs

@ -21,7 +21,7 @@ namespace Avalonia.Media
{
_first = shadow;
_list = null;
Count = _first.IsEmpty ? 0 : 1;
Count = _first.IsDefault ? 0 : 1;
}
public BoxShadows(BoxShadow first, BoxShadow[] rest)
@ -62,7 +62,9 @@ namespace Avalonia.Media
}
[EditorBrowsable(EditorBrowsableState.Never)]
#pragma warning disable CA1815 // Override equals and operator equals on value types
public struct BoxShadowsEnumerator
#pragma warning restore CA1815 // Override equals and operator equals on value types
{
private int _index;
private BoxShadows _shadows;
@ -118,7 +120,7 @@ namespace Avalonia.Media
get
{
foreach(var boxShadow in this)
if (!boxShadow.IsEmpty && boxShadow.IsInset)
if (!boxShadow.IsDefault && boxShadow.IsInset)
return true;
return false;
}
@ -149,5 +151,11 @@ namespace Avalonia.Media
return hashCode;
}
}
public static bool operator ==(BoxShadows left, BoxShadows right) =>
left.Equals(right);
public static bool operator !=(BoxShadows left, BoxShadows right) =>
!(left == right);
}
}

5
src/Avalonia.Base/Media/Brush.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media
/// Describes how an area is painted.
/// </summary>
[TypeConverter(typeof(BrushConverter))]
public abstract class Brush : Animatable, IMutableBrush
public abstract class Brush : Animatable
{
/// <summary>
/// Defines the <see cref="Opacity"/> property.
@ -92,9 +92,6 @@ namespace Avalonia.Media
throw new FormatException($"Invalid brush string: '{s}'.");
}
/// <inheritdoc/>
public abstract IBrush ToImmutable();
/// <summary>
/// Marks a property as affecting the brush's visual representation.
/// </summary>

4
src/Avalonia.Base/Media/BrushExtensions.cs

@ -16,11 +16,11 @@ namespace Avalonia.Media
/// The result of calling <see cref="IMutableBrush.ToImmutable"/> if the brush is mutable,
/// otherwise <paramref name="brush"/>.
/// </returns>
public static IBrush ToImmutable(this IBrush brush)
public static IImmutableBrush ToImmutable(this IBrush brush)
{
_ = brush ?? throw new ArgumentNullException(nameof(brush));
return (brush as IMutableBrush)?.ToImmutable() ?? brush;
return (brush as IMutableBrush)?.ToImmutable() ?? (IImmutableBrush)brush;
}
/// <summary>

282
src/Avalonia.Base/Media/Brushes.cs

@ -8,706 +8,706 @@ namespace Avalonia.Media
/// <summary>
/// Gets an <see cref="Colors.AliceBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush AliceBlue => KnownColor.AliceBlue.ToBrush();
public static IImmutableSolidColorBrush AliceBlue => KnownColor.AliceBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.AntiqueWhite"/> colored brush.
/// </summary>
public static ISolidColorBrush AntiqueWhite => KnownColor.AntiqueWhite.ToBrush();
public static IImmutableSolidColorBrush AntiqueWhite => KnownColor.AntiqueWhite.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Aqua"/> colored brush.
/// </summary>
public static ISolidColorBrush Aqua => KnownColor.Aqua.ToBrush();
public static IImmutableSolidColorBrush Aqua => KnownColor.Aqua.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Aquamarine"/> colored brush.
/// </summary>
public static ISolidColorBrush Aquamarine => KnownColor.Aquamarine.ToBrush();
public static IImmutableSolidColorBrush Aquamarine => KnownColor.Aquamarine.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Azure"/> colored brush.
/// </summary>
public static ISolidColorBrush Azure => KnownColor.Azure.ToBrush();
public static IImmutableSolidColorBrush Azure => KnownColor.Azure.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Beige"/> colored brush.
/// </summary>
public static ISolidColorBrush Beige => KnownColor.Beige.ToBrush();
public static IImmutableSolidColorBrush Beige => KnownColor.Beige.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Bisque"/> colored brush.
/// </summary>
public static ISolidColorBrush Bisque => KnownColor.Bisque.ToBrush();
public static IImmutableSolidColorBrush Bisque => KnownColor.Bisque.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Black"/> colored brush.
/// </summary>
public static ISolidColorBrush Black => KnownColor.Black.ToBrush();
public static IImmutableSolidColorBrush Black => KnownColor.Black.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.BlanchedAlmond"/> colored brush.
/// </summary>
public static ISolidColorBrush BlanchedAlmond => KnownColor.BlanchedAlmond.ToBrush();
public static IImmutableSolidColorBrush BlanchedAlmond => KnownColor.BlanchedAlmond.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Blue"/> colored brush.
/// </summary>
public static ISolidColorBrush Blue => KnownColor.Blue.ToBrush();
public static IImmutableSolidColorBrush Blue => KnownColor.Blue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.BlueViolet"/> colored brush.
/// </summary>
public static ISolidColorBrush BlueViolet => KnownColor.BlueViolet.ToBrush();
public static IImmutableSolidColorBrush BlueViolet => KnownColor.BlueViolet.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Brown"/> colored brush.
/// </summary>
public static ISolidColorBrush Brown => KnownColor.Brown.ToBrush();
public static IImmutableSolidColorBrush Brown => KnownColor.Brown.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.BurlyWood"/> colored brush.
/// </summary>
public static ISolidColorBrush BurlyWood => KnownColor.BurlyWood.ToBrush();
public static IImmutableSolidColorBrush BurlyWood => KnownColor.BurlyWood.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.CadetBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush CadetBlue => KnownColor.CadetBlue.ToBrush();
public static IImmutableSolidColorBrush CadetBlue => KnownColor.CadetBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Chartreuse"/> colored brush.
/// </summary>
public static ISolidColorBrush Chartreuse => KnownColor.Chartreuse.ToBrush();
public static IImmutableSolidColorBrush Chartreuse => KnownColor.Chartreuse.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Chocolate"/> colored brush.
/// </summary>
public static ISolidColorBrush Chocolate => KnownColor.Chocolate.ToBrush();
public static IImmutableSolidColorBrush Chocolate => KnownColor.Chocolate.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Coral"/> colored brush.
/// </summary>
public static ISolidColorBrush Coral => KnownColor.Coral.ToBrush();
public static IImmutableSolidColorBrush Coral => KnownColor.Coral.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.CornflowerBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush CornflowerBlue => KnownColor.CornflowerBlue.ToBrush();
public static IImmutableSolidColorBrush CornflowerBlue => KnownColor.CornflowerBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Cornsilk"/> colored brush.
/// </summary>
public static ISolidColorBrush Cornsilk => KnownColor.Cornsilk.ToBrush();
public static IImmutableSolidColorBrush Cornsilk => KnownColor.Cornsilk.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Crimson"/> colored brush.
/// </summary>
public static ISolidColorBrush Crimson => KnownColor.Crimson.ToBrush();
public static IImmutableSolidColorBrush Crimson => KnownColor.Crimson.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Cyan"/> colored brush.
/// </summary>
public static ISolidColorBrush Cyan => KnownColor.Cyan.ToBrush();
public static IImmutableSolidColorBrush Cyan => KnownColor.Cyan.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkBlue => KnownColor.DarkBlue.ToBrush();
public static IImmutableSolidColorBrush DarkBlue => KnownColor.DarkBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkCyan"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkCyan => KnownColor.DarkCyan.ToBrush();
public static IImmutableSolidColorBrush DarkCyan => KnownColor.DarkCyan.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkGoldenrod"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkGoldenrod => KnownColor.DarkGoldenrod.ToBrush();
public static IImmutableSolidColorBrush DarkGoldenrod => KnownColor.DarkGoldenrod.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkGray"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkGray => KnownColor.DarkGray.ToBrush();
public static IImmutableSolidColorBrush DarkGray => KnownColor.DarkGray.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkGreen => KnownColor.DarkGreen.ToBrush();
public static IImmutableSolidColorBrush DarkGreen => KnownColor.DarkGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkKhaki"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkKhaki => KnownColor.DarkKhaki.ToBrush();
public static IImmutableSolidColorBrush DarkKhaki => KnownColor.DarkKhaki.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkMagenta"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkMagenta => KnownColor.DarkMagenta.ToBrush();
public static IImmutableSolidColorBrush DarkMagenta => KnownColor.DarkMagenta.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkOliveGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkOliveGreen => KnownColor.DarkOliveGreen.ToBrush();
public static IImmutableSolidColorBrush DarkOliveGreen => KnownColor.DarkOliveGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkOrange"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkOrange => KnownColor.DarkOrange.ToBrush();
public static IImmutableSolidColorBrush DarkOrange => KnownColor.DarkOrange.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkOrchid"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkOrchid => KnownColor.DarkOrchid.ToBrush();
public static IImmutableSolidColorBrush DarkOrchid => KnownColor.DarkOrchid.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkRed"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkRed => KnownColor.DarkRed.ToBrush();
public static IImmutableSolidColorBrush DarkRed => KnownColor.DarkRed.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkSalmon"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkSalmon => KnownColor.DarkSalmon.ToBrush();
public static IImmutableSolidColorBrush DarkSalmon => KnownColor.DarkSalmon.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkSeaGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkSeaGreen => KnownColor.DarkSeaGreen.ToBrush();
public static IImmutableSolidColorBrush DarkSeaGreen => KnownColor.DarkSeaGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkSlateBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkSlateBlue => KnownColor.DarkSlateBlue.ToBrush();
public static IImmutableSolidColorBrush DarkSlateBlue => KnownColor.DarkSlateBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkSlateGray"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkSlateGray => KnownColor.DarkSlateGray.ToBrush();
public static IImmutableSolidColorBrush DarkSlateGray => KnownColor.DarkSlateGray.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkTurquoise"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkTurquoise => KnownColor.DarkTurquoise.ToBrush();
public static IImmutableSolidColorBrush DarkTurquoise => KnownColor.DarkTurquoise.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DarkViolet"/> colored brush.
/// </summary>
public static ISolidColorBrush DarkViolet => KnownColor.DarkViolet.ToBrush();
public static IImmutableSolidColorBrush DarkViolet => KnownColor.DarkViolet.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DeepPink"/> colored brush.
/// </summary>
public static ISolidColorBrush DeepPink => KnownColor.DeepPink.ToBrush();
public static IImmutableSolidColorBrush DeepPink => KnownColor.DeepPink.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DeepSkyBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush DeepSkyBlue => KnownColor.DeepSkyBlue.ToBrush();
public static IImmutableSolidColorBrush DeepSkyBlue => KnownColor.DeepSkyBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DimGray"/> colored brush.
/// </summary>
public static ISolidColorBrush DimGray => KnownColor.DimGray.ToBrush();
public static IImmutableSolidColorBrush DimGray => KnownColor.DimGray.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.DodgerBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush DodgerBlue => KnownColor.DodgerBlue.ToBrush();
public static IImmutableSolidColorBrush DodgerBlue => KnownColor.DodgerBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Firebrick"/> colored brush.
/// </summary>
public static ISolidColorBrush Firebrick => KnownColor.Firebrick.ToBrush();
public static IImmutableSolidColorBrush Firebrick => KnownColor.Firebrick.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.FloralWhite"/> colored brush.
/// </summary>
public static ISolidColorBrush FloralWhite => KnownColor.FloralWhite.ToBrush();
public static IImmutableSolidColorBrush FloralWhite => KnownColor.FloralWhite.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.ForestGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush ForestGreen => KnownColor.ForestGreen.ToBrush();
public static IImmutableSolidColorBrush ForestGreen => KnownColor.ForestGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Fuchsia"/> colored brush.
/// </summary>
public static ISolidColorBrush Fuchsia => KnownColor.Fuchsia.ToBrush();
public static IImmutableSolidColorBrush Fuchsia => KnownColor.Fuchsia.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Gainsboro"/> colored brush.
/// </summary>
public static ISolidColorBrush Gainsboro => KnownColor.Gainsboro.ToBrush();
public static IImmutableSolidColorBrush Gainsboro => KnownColor.Gainsboro.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.GhostWhite"/> colored brush.
/// </summary>
public static ISolidColorBrush GhostWhite => KnownColor.GhostWhite.ToBrush();
public static IImmutableSolidColorBrush GhostWhite => KnownColor.GhostWhite.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Gold"/> colored brush.
/// </summary>
public static ISolidColorBrush Gold => KnownColor.Gold.ToBrush();
public static IImmutableSolidColorBrush Gold => KnownColor.Gold.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Goldenrod"/> colored brush.
/// </summary>
public static ISolidColorBrush Goldenrod => KnownColor.Goldenrod.ToBrush();
public static IImmutableSolidColorBrush Goldenrod => KnownColor.Goldenrod.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Gray"/> colored brush.
/// </summary>
public static ISolidColorBrush Gray => KnownColor.Gray.ToBrush();
public static IImmutableSolidColorBrush Gray => KnownColor.Gray.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Green"/> colored brush.
/// </summary>
public static ISolidColorBrush Green => KnownColor.Green.ToBrush();
public static IImmutableSolidColorBrush Green => KnownColor.Green.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.GreenYellow"/> colored brush.
/// </summary>
public static ISolidColorBrush GreenYellow => KnownColor.GreenYellow.ToBrush();
public static IImmutableSolidColorBrush GreenYellow => KnownColor.GreenYellow.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Honeydew"/> colored brush.
/// </summary>
public static ISolidColorBrush Honeydew => KnownColor.Honeydew.ToBrush();
public static IImmutableSolidColorBrush Honeydew => KnownColor.Honeydew.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.HotPink"/> colored brush.
/// </summary>
public static ISolidColorBrush HotPink => KnownColor.HotPink.ToBrush();
public static IImmutableSolidColorBrush HotPink => KnownColor.HotPink.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.IndianRed"/> colored brush.
/// </summary>
public static ISolidColorBrush IndianRed => KnownColor.IndianRed.ToBrush();
public static IImmutableSolidColorBrush IndianRed => KnownColor.IndianRed.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Indigo"/> colored brush.
/// </summary>
public static ISolidColorBrush Indigo => KnownColor.Indigo.ToBrush();
public static IImmutableSolidColorBrush Indigo => KnownColor.Indigo.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Ivory"/> colored brush.
/// </summary>
public static ISolidColorBrush Ivory => KnownColor.Ivory.ToBrush();
public static IImmutableSolidColorBrush Ivory => KnownColor.Ivory.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Khaki"/> colored brush.
/// </summary>
public static ISolidColorBrush Khaki => KnownColor.Khaki.ToBrush();
public static IImmutableSolidColorBrush Khaki => KnownColor.Khaki.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Lavender"/> colored brush.
/// </summary>
public static ISolidColorBrush Lavender => KnownColor.Lavender.ToBrush();
public static IImmutableSolidColorBrush Lavender => KnownColor.Lavender.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LavenderBlush"/> colored brush.
/// </summary>
public static ISolidColorBrush LavenderBlush => KnownColor.LavenderBlush.ToBrush();
public static IImmutableSolidColorBrush LavenderBlush => KnownColor.LavenderBlush.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LawnGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush LawnGreen => KnownColor.LawnGreen.ToBrush();
public static IImmutableSolidColorBrush LawnGreen => KnownColor.LawnGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LemonChiffon"/> colored brush.
/// </summary>
public static ISolidColorBrush LemonChiffon => KnownColor.LemonChiffon.ToBrush();
public static IImmutableSolidColorBrush LemonChiffon => KnownColor.LemonChiffon.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush LightBlue => KnownColor.LightBlue.ToBrush();
public static IImmutableSolidColorBrush LightBlue => KnownColor.LightBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightCoral"/> colored brush.
/// </summary>
public static ISolidColorBrush LightCoral => KnownColor.LightCoral.ToBrush();
public static IImmutableSolidColorBrush LightCoral => KnownColor.LightCoral.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightCyan"/> colored brush.
/// </summary>
public static ISolidColorBrush LightCyan => KnownColor.LightCyan.ToBrush();
public static IImmutableSolidColorBrush LightCyan => KnownColor.LightCyan.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightGoldenrodYellow"/> colored brush.
/// </summary>
public static ISolidColorBrush LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToBrush();
public static IImmutableSolidColorBrush LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightGray"/> colored brush.
/// </summary>
public static ISolidColorBrush LightGray => KnownColor.LightGray.ToBrush();
public static IImmutableSolidColorBrush LightGray => KnownColor.LightGray.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush LightGreen => KnownColor.LightGreen.ToBrush();
public static IImmutableSolidColorBrush LightGreen => KnownColor.LightGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightPink"/> colored brush.
/// </summary>
public static ISolidColorBrush LightPink => KnownColor.LightPink.ToBrush();
public static IImmutableSolidColorBrush LightPink => KnownColor.LightPink.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightSalmon"/> colored brush.
/// </summary>
public static ISolidColorBrush LightSalmon => KnownColor.LightSalmon.ToBrush();
public static IImmutableSolidColorBrush LightSalmon => KnownColor.LightSalmon.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightSeaGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush LightSeaGreen => KnownColor.LightSeaGreen.ToBrush();
public static IImmutableSolidColorBrush LightSeaGreen => KnownColor.LightSeaGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightSkyBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush LightSkyBlue => KnownColor.LightSkyBlue.ToBrush();
public static IImmutableSolidColorBrush LightSkyBlue => KnownColor.LightSkyBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightSlateGray"/> colored brush.
/// </summary>
public static ISolidColorBrush LightSlateGray => KnownColor.LightSlateGray.ToBrush();
public static IImmutableSolidColorBrush LightSlateGray => KnownColor.LightSlateGray.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightSteelBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush LightSteelBlue => KnownColor.LightSteelBlue.ToBrush();
public static IImmutableSolidColorBrush LightSteelBlue => KnownColor.LightSteelBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LightYellow"/> colored brush.
/// </summary>
public static ISolidColorBrush LightYellow => KnownColor.LightYellow.ToBrush();
public static IImmutableSolidColorBrush LightYellow => KnownColor.LightYellow.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Lime"/> colored brush.
/// </summary>
public static ISolidColorBrush Lime => KnownColor.Lime.ToBrush();
public static IImmutableSolidColorBrush Lime => KnownColor.Lime.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.LimeGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush LimeGreen => KnownColor.LimeGreen.ToBrush();
public static IImmutableSolidColorBrush LimeGreen => KnownColor.LimeGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Linen"/> colored brush.
/// </summary>
public static ISolidColorBrush Linen => KnownColor.Linen.ToBrush();
public static IImmutableSolidColorBrush Linen => KnownColor.Linen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Magenta"/> colored brush.
/// </summary>
public static ISolidColorBrush Magenta => KnownColor.Magenta.ToBrush();
public static IImmutableSolidColorBrush Magenta => KnownColor.Magenta.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Maroon"/> colored brush.
/// </summary>
public static ISolidColorBrush Maroon => KnownColor.Maroon.ToBrush();
public static IImmutableSolidColorBrush Maroon => KnownColor.Maroon.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumAquamarine"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumAquamarine => KnownColor.MediumAquamarine.ToBrush();
public static IImmutableSolidColorBrush MediumAquamarine => KnownColor.MediumAquamarine.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumBlue => KnownColor.MediumBlue.ToBrush();
public static IImmutableSolidColorBrush MediumBlue => KnownColor.MediumBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumOrchid"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumOrchid => KnownColor.MediumOrchid.ToBrush();
public static IImmutableSolidColorBrush MediumOrchid => KnownColor.MediumOrchid.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumPurple"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumPurple => KnownColor.MediumPurple.ToBrush();
public static IImmutableSolidColorBrush MediumPurple => KnownColor.MediumPurple.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumSeaGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumSeaGreen => KnownColor.MediumSeaGreen.ToBrush();
public static IImmutableSolidColorBrush MediumSeaGreen => KnownColor.MediumSeaGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumSlateBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumSlateBlue => KnownColor.MediumSlateBlue.ToBrush();
public static IImmutableSolidColorBrush MediumSlateBlue => KnownColor.MediumSlateBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumSpringGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumSpringGreen => KnownColor.MediumSpringGreen.ToBrush();
public static IImmutableSolidColorBrush MediumSpringGreen => KnownColor.MediumSpringGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumTurquoise"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumTurquoise => KnownColor.MediumTurquoise.ToBrush();
public static IImmutableSolidColorBrush MediumTurquoise => KnownColor.MediumTurquoise.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MediumVioletRed"/> colored brush.
/// </summary>
public static ISolidColorBrush MediumVioletRed => KnownColor.MediumVioletRed.ToBrush();
public static IImmutableSolidColorBrush MediumVioletRed => KnownColor.MediumVioletRed.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MidnightBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush MidnightBlue => KnownColor.MidnightBlue.ToBrush();
public static IImmutableSolidColorBrush MidnightBlue => KnownColor.MidnightBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MintCream"/> colored brush.
/// </summary>
public static ISolidColorBrush MintCream => KnownColor.MintCream.ToBrush();
public static IImmutableSolidColorBrush MintCream => KnownColor.MintCream.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.MistyRose"/> colored brush.
/// </summary>
public static ISolidColorBrush MistyRose => KnownColor.MistyRose.ToBrush();
public static IImmutableSolidColorBrush MistyRose => KnownColor.MistyRose.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Moccasin"/> colored brush.
/// </summary>
public static ISolidColorBrush Moccasin => KnownColor.Moccasin.ToBrush();
public static IImmutableSolidColorBrush Moccasin => KnownColor.Moccasin.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.NavajoWhite"/> colored brush.
/// </summary>
public static ISolidColorBrush NavajoWhite => KnownColor.NavajoWhite.ToBrush();
public static IImmutableSolidColorBrush NavajoWhite => KnownColor.NavajoWhite.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Navy"/> colored brush.
/// </summary>
public static ISolidColorBrush Navy => KnownColor.Navy.ToBrush();
public static IImmutableSolidColorBrush Navy => KnownColor.Navy.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.OldLace"/> colored brush.
/// </summary>
public static ISolidColorBrush OldLace => KnownColor.OldLace.ToBrush();
public static IImmutableSolidColorBrush OldLace => KnownColor.OldLace.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Olive"/> colored brush.
/// </summary>
public static ISolidColorBrush Olive => KnownColor.Olive.ToBrush();
public static IImmutableSolidColorBrush Olive => KnownColor.Olive.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.OliveDrab"/> colored brush.
/// </summary>
public static ISolidColorBrush OliveDrab => KnownColor.OliveDrab.ToBrush();
public static IImmutableSolidColorBrush OliveDrab => KnownColor.OliveDrab.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Orange"/> colored brush.
/// </summary>
public static ISolidColorBrush Orange => KnownColor.Orange.ToBrush();
public static IImmutableSolidColorBrush Orange => KnownColor.Orange.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.OrangeRed"/> colored brush.
/// </summary>
public static ISolidColorBrush OrangeRed => KnownColor.OrangeRed.ToBrush();
public static IImmutableSolidColorBrush OrangeRed => KnownColor.OrangeRed.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Orchid"/> colored brush.
/// </summary>
public static ISolidColorBrush Orchid => KnownColor.Orchid.ToBrush();
public static IImmutableSolidColorBrush Orchid => KnownColor.Orchid.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.PaleGoldenrod"/> colored brush.
/// </summary>
public static ISolidColorBrush PaleGoldenrod => KnownColor.PaleGoldenrod.ToBrush();
public static IImmutableSolidColorBrush PaleGoldenrod => KnownColor.PaleGoldenrod.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.PaleGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush PaleGreen => KnownColor.PaleGreen.ToBrush();
public static IImmutableSolidColorBrush PaleGreen => KnownColor.PaleGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.PaleTurquoise"/> colored brush.
/// </summary>
public static ISolidColorBrush PaleTurquoise => KnownColor.PaleTurquoise.ToBrush();
public static IImmutableSolidColorBrush PaleTurquoise => KnownColor.PaleTurquoise.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.PaleVioletRed"/> colored brush.
/// </summary>
public static ISolidColorBrush PaleVioletRed => KnownColor.PaleVioletRed.ToBrush();
public static IImmutableSolidColorBrush PaleVioletRed => KnownColor.PaleVioletRed.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.PapayaWhip"/> colored brush.
/// </summary>
public static ISolidColorBrush PapayaWhip => KnownColor.PapayaWhip.ToBrush();
public static IImmutableSolidColorBrush PapayaWhip => KnownColor.PapayaWhip.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.PeachPuff"/> colored brush.
/// </summary>
public static ISolidColorBrush PeachPuff => KnownColor.PeachPuff.ToBrush();
public static IImmutableSolidColorBrush PeachPuff => KnownColor.PeachPuff.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Peru"/> colored brush.
/// </summary>
public static ISolidColorBrush Peru => KnownColor.Peru.ToBrush();
public static IImmutableSolidColorBrush Peru => KnownColor.Peru.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Pink"/> colored brush.
/// </summary>
public static ISolidColorBrush Pink => KnownColor.Pink.ToBrush();
public static IImmutableSolidColorBrush Pink => KnownColor.Pink.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Plum"/> colored brush.
/// </summary>
public static ISolidColorBrush Plum => KnownColor.Plum.ToBrush();
public static IImmutableSolidColorBrush Plum => KnownColor.Plum.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.PowderBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush PowderBlue => KnownColor.PowderBlue.ToBrush();
public static IImmutableSolidColorBrush PowderBlue => KnownColor.PowderBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Purple"/> colored brush.
/// </summary>
public static ISolidColorBrush Purple => KnownColor.Purple.ToBrush();
public static IImmutableSolidColorBrush Purple => KnownColor.Purple.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Red"/> colored brush.
/// </summary>
public static ISolidColorBrush Red => KnownColor.Red.ToBrush();
public static IImmutableSolidColorBrush Red => KnownColor.Red.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.RosyBrown"/> colored brush.
/// </summary>
public static ISolidColorBrush RosyBrown => KnownColor.RosyBrown.ToBrush();
public static IImmutableSolidColorBrush RosyBrown => KnownColor.RosyBrown.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.RoyalBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush RoyalBlue => KnownColor.RoyalBlue.ToBrush();
public static IImmutableSolidColorBrush RoyalBlue => KnownColor.RoyalBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SaddleBrown"/> colored brush.
/// </summary>
public static ISolidColorBrush SaddleBrown => KnownColor.SaddleBrown.ToBrush();
public static IImmutableSolidColorBrush SaddleBrown => KnownColor.SaddleBrown.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Salmon"/> colored brush.
/// </summary>
public static ISolidColorBrush Salmon => KnownColor.Salmon.ToBrush();
public static IImmutableSolidColorBrush Salmon => KnownColor.Salmon.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SandyBrown"/> colored brush.
/// </summary>
public static ISolidColorBrush SandyBrown => KnownColor.SandyBrown.ToBrush();
public static IImmutableSolidColorBrush SandyBrown => KnownColor.SandyBrown.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SeaGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush SeaGreen => KnownColor.SeaGreen.ToBrush();
public static IImmutableSolidColorBrush SeaGreen => KnownColor.SeaGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SeaShell"/> colored brush.
/// </summary>
public static ISolidColorBrush SeaShell => KnownColor.SeaShell.ToBrush();
public static IImmutableSolidColorBrush SeaShell => KnownColor.SeaShell.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Sienna"/> colored brush.
/// </summary>
public static ISolidColorBrush Sienna => KnownColor.Sienna.ToBrush();
public static IImmutableSolidColorBrush Sienna => KnownColor.Sienna.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Silver"/> colored brush.
/// </summary>
public static ISolidColorBrush Silver => KnownColor.Silver.ToBrush();
public static IImmutableSolidColorBrush Silver => KnownColor.Silver.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SkyBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush SkyBlue => KnownColor.SkyBlue.ToBrush();
public static IImmutableSolidColorBrush SkyBlue => KnownColor.SkyBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SlateBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush SlateBlue => KnownColor.SlateBlue.ToBrush();
public static IImmutableSolidColorBrush SlateBlue => KnownColor.SlateBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SlateGray"/> colored brush.
/// </summary>
public static ISolidColorBrush SlateGray => KnownColor.SlateGray.ToBrush();
public static IImmutableSolidColorBrush SlateGray => KnownColor.SlateGray.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Snow"/> colored brush.
/// </summary>
public static ISolidColorBrush Snow => KnownColor.Snow.ToBrush();
public static IImmutableSolidColorBrush Snow => KnownColor.Snow.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SpringGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush SpringGreen => KnownColor.SpringGreen.ToBrush();
public static IImmutableSolidColorBrush SpringGreen => KnownColor.SpringGreen.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.SteelBlue"/> colored brush.
/// </summary>
public static ISolidColorBrush SteelBlue => KnownColor.SteelBlue.ToBrush();
public static IImmutableSolidColorBrush SteelBlue => KnownColor.SteelBlue.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Tan"/> colored brush.
/// </summary>
public static ISolidColorBrush Tan => KnownColor.Tan.ToBrush();
public static IImmutableSolidColorBrush Tan => KnownColor.Tan.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Teal"/> colored brush.
/// </summary>
public static ISolidColorBrush Teal => KnownColor.Teal.ToBrush();
public static IImmutableSolidColorBrush Teal => KnownColor.Teal.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Thistle"/> colored brush.
/// </summary>
public static ISolidColorBrush Thistle => KnownColor.Thistle.ToBrush();
public static IImmutableSolidColorBrush Thistle => KnownColor.Thistle.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Tomato"/> colored brush.
/// </summary>
public static ISolidColorBrush Tomato => KnownColor.Tomato.ToBrush();
public static IImmutableSolidColorBrush Tomato => KnownColor.Tomato.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Transparent"/> colored brush.
/// </summary>
public static ISolidColorBrush Transparent => KnownColor.Transparent.ToBrush();
public static IImmutableSolidColorBrush Transparent => KnownColor.Transparent.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Turquoise"/> colored brush.
/// </summary>
public static ISolidColorBrush Turquoise => KnownColor.Turquoise.ToBrush();
public static IImmutableSolidColorBrush Turquoise => KnownColor.Turquoise.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Violet"/> colored brush.
/// </summary>
public static ISolidColorBrush Violet => KnownColor.Violet.ToBrush();
public static IImmutableSolidColorBrush Violet => KnownColor.Violet.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Wheat"/> colored brush.
/// </summary>
public static ISolidColorBrush Wheat => KnownColor.Wheat.ToBrush();
public static IImmutableSolidColorBrush Wheat => KnownColor.Wheat.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.White"/> colored brush.
/// </summary>
public static ISolidColorBrush White => KnownColor.White.ToBrush();
public static IImmutableSolidColorBrush White => KnownColor.White.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.WhiteSmoke"/> colored brush.
/// </summary>
public static ISolidColorBrush WhiteSmoke => KnownColor.WhiteSmoke.ToBrush();
public static IImmutableSolidColorBrush WhiteSmoke => KnownColor.WhiteSmoke.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.Yellow"/> colored brush.
/// </summary>
public static ISolidColorBrush Yellow => KnownColor.Yellow.ToBrush();
public static IImmutableSolidColorBrush Yellow => KnownColor.Yellow.ToBrush();
/// <summary>
/// Gets an <see cref="Colors.YellowGreen"/> colored brush.
/// </summary>
public static ISolidColorBrush YellowGreen => KnownColor.YellowGreen.ToBrush();
public static IImmutableSolidColorBrush YellowGreen => KnownColor.YellowGreen.ToBrush();
}
}

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

@ -47,7 +47,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
public override IBrush ToImmutable()
public override IImmutableBrush ToImmutable()
{
return new ImmutableConicGradientBrush(this);
}

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

@ -17,8 +17,8 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="Dashes"/> property.
/// </summary>
public static readonly StyledProperty<AvaloniaList<double>> DashesProperty =
AvaloniaProperty.Register<DashStyle, AvaloniaList<double>>(nameof(Dashes));
public static readonly StyledProperty<AvaloniaList<double>?> DashesProperty =
AvaloniaProperty.Register<DashStyle, AvaloniaList<double>?>(nameof(Dashes));
/// <summary>
/// Defines the <see cref="Offset"/> property.
@ -83,7 +83,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the length of alternating dashes and gaps.
/// </summary>
public AvaloniaList<double> Dashes
public AvaloniaList<double>? Dashes
{
get => GetValue(DashesProperty);
set => SetValue(DashesProperty, value);
@ -98,7 +98,7 @@ namespace Avalonia.Media
set => SetValue(OffsetProperty, value);
}
IReadOnlyList<double> IDashStyle.Dashes => Dashes;
IReadOnlyList<double>? IDashStyle.Dashes => Dashes;
/// <summary>
/// Raised when the dash style changes.

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

@ -261,7 +261,7 @@ namespace Avalonia.Media
DrawRectangle(brush, null, rect, cornerRadius, cornerRadius);
}
public readonly struct PushedState : IDisposable
public readonly record struct PushedState : IDisposable
{
private readonly int _level;
private readonly DrawingContext _context;

7
src/Avalonia.Base/Media/DrawingGroup.cs

@ -461,9 +461,10 @@ namespace Avalonia.Media
if (_rootDrawing == null)
{
// When a DrawingGroup is set, it should be made the root if
// a root drawing didnt exist.
Contract.Requires<NotSupportedException>(_currentDrawingGroup == null);
if (_currentDrawingGroup != null)
{
throw new NotSupportedException("When a DrawingGroup is set, it should be made the root if a root drawing didnt exist.");
}
// If this is the first Drawing being added, avoid creating a DrawingGroup
// and set this drawing as the root drawing. This optimizes the common

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

@ -3,7 +3,7 @@
/// <summary>
/// The font metrics is holding information about a font's ascent, descent, etc. in design em units.
/// </summary>
public readonly struct FontMetrics
public readonly record struct FontMetrics
{
/// <summary>
/// Gets the font design units per em.

10
src/Avalonia.Base/Media/FormattedText.cs

@ -1,10 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
@ -25,7 +23,7 @@ namespace Avalonia.Media
private const double MaxFontEmSize = RealInfiniteWidth / GreatestMultiplierOfEm;
// properties and format runs
private ReadOnlySlice<char> _text;
private string _text;
private readonly SpanVector _formatRuns = new SpanVector(null);
private SpanPosition _latestPosition;
@ -69,9 +67,7 @@ namespace Avalonia.Media
ValidateFontSize(emSize);
_text = textToFormat != null ?
new ReadOnlySlice<char>(textToFormat.AsMemory()) :
throw new ArgumentNullException(nameof(textToFormat));
_text = textToFormat;
var runProps = new GenericTextRunProperties(
typeface,
@ -1383,7 +1379,7 @@ namespace Avalonia.Media
}
}
if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsEmpty)
if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsDefault)
{
return null;
}

4
src/Avalonia.Base/Media/Geometry.cs

@ -30,7 +30,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets the geometry's bounding rectangle.
/// </summary>
public Rect Bounds => PlatformImpl?.Bounds ?? Rect.Empty;
public Rect Bounds => PlatformImpl?.Bounds ?? default;
/// <summary>
/// Gets the platform-specific implementation of the geometry.
@ -84,7 +84,7 @@ namespace Avalonia.Media
/// </summary>
/// <param name="pen">The stroke thickness.</param>
/// <returns>The bounding rectangle.</returns>
public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty;
public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? default;
/// <summary>
/// Indicates whether the geometry's fill contains the specified point.

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

@ -69,7 +69,7 @@ namespace Avalonia.Media
public override Rect GetBounds()
{
IPen pen = Pen ?? s_boundsPen;
return Geometry?.GetRenderBounds(pen) ?? Rect.Empty;
return Geometry?.GetRenderBounds(pen) ?? default;
}
}
}

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

@ -1,6 +1,6 @@
namespace Avalonia.Media;
public readonly struct GlyphMetrics
public readonly record struct GlyphMetrics
{
/// <summary>
/// Distance from the x-origin to the left extremum of the glyph.

419
src/Avalonia.Base/Media/GlyphRun.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
@ -22,15 +21,12 @@ namespace Avalonia.Media
private Point? _baselineOrigin;
private GlyphRunMetrics? _glyphRunMetrics;
private ReadOnlySlice<char> _characters;
private IReadOnlyList<char> _characters;
private IReadOnlyList<ushort> _glyphIndices;
private IReadOnlyList<double>? _glyphAdvances;
private IReadOnlyList<Vector>? _glyphOffsets;
private IReadOnlyList<int>? _glyphClusters;
private int _offsetToFirstCharacter;
/// <summary>
/// Initializes a new instance of the <see cref="GlyphRun"/> class by specifying properties of the class.
/// </summary>
@ -45,7 +41,7 @@ namespace Avalonia.Media
public GlyphRun(
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
ReadOnlySlice<char> characters,
IReadOnlyList<char> characters,
IReadOnlyList<ushort> glyphIndices,
IReadOnlyList<double>? glyphAdvances = null,
IReadOnlyList<Vector>? glyphOffsets = null,
@ -54,19 +50,19 @@ namespace Avalonia.Media
{
_glyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
_fontRenderingEmSize = fontRenderingEmSize;
Characters = characters;
_characters = characters;
_glyphIndices = glyphIndices;
GlyphAdvances = glyphAdvances;
_glyphAdvances = glyphAdvances;
GlyphOffsets = glyphOffsets;
_glyphOffsets = glyphOffsets;
GlyphClusters = glyphClusters;
_glyphClusters = glyphClusters;
BiDiLevel = biDiLevel;
_biDiLevel = biDiLevel;
}
/// <summary>
@ -145,7 +141,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the list of UTF16 code points that represent the Unicode content of the <see cref="GlyphRun"/>.
/// </summary>
public ReadOnlySlice<char> Characters
public IReadOnlyList<char> Characters
{
get => _characters;
set => Set(ref _characters, value);
@ -219,7 +215,7 @@ namespace Avalonia.Media
/// </returns>
public double GetDistanceFromCharacterHit(CharacterHit characterHit)
{
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength - _offsetToFirstCharacter;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var distance = 0.0;
@ -227,12 +223,12 @@ namespace Avalonia.Media
{
if (GlyphClusters != null)
{
if (characterIndex < GlyphClusters[0])
if (characterIndex < Metrics.FirstCluster)
{
return 0;
}
if (characterIndex > GlyphClusters[GlyphClusters.Count - 1])
if (characterIndex > Metrics.LastCluster)
{
return Metrics.WidthIncludingTrailingWhitespace;
}
@ -268,12 +264,12 @@ namespace Avalonia.Media
if (GlyphClusters != null && GlyphClusters.Count > 0)
{
if (characterIndex > GlyphClusters[0])
if (characterIndex > Metrics.LastCluster)
{
return 0;
}
if (characterIndex <= GlyphClusters[GlyphClusters.Count - 1])
if (characterIndex <= Metrics.FirstCluster)
{
return Size.Width;
}
@ -299,19 +295,12 @@ namespace Avalonia.Media
/// </returns>
public CharacterHit GetCharacterHitFromDistance(double distance, out bool isInside)
{
var characterIndex = 0;
// Before
if (distance <= 0)
{
isInside = false;
if (GlyphClusters != null)
{
characterIndex = GlyphClusters[characterIndex];
}
var firstCharacterHit = FindNearestCharacterHit(characterIndex, out _);
var firstCharacterHit = FindNearestCharacterHit(IsLeftToRight ? Metrics.FirstCluster : Metrics.LastCluster, out _);
return IsLeftToRight ? new CharacterHit(firstCharacterHit.FirstCharacterIndex) : firstCharacterHit;
}
@ -321,18 +310,13 @@ namespace Avalonia.Media
{
isInside = false;
characterIndex = GlyphIndices.Count - 1;
if (GlyphClusters != null)
{
characterIndex = GlyphClusters[characterIndex];
}
var lastCharacterHit = FindNearestCharacterHit(characterIndex, out _);
var lastCharacterHit = FindNearestCharacterHit(IsLeftToRight ? Metrics.LastCluster : Metrics.FirstCluster, out _);
return IsLeftToRight ? lastCharacterHit : new CharacterHit(lastCharacterHit.FirstCharacterIndex);
}
var characterIndex = 0;
//Within
var currentX = 0d;
@ -378,7 +362,7 @@ namespace Avalonia.Media
var characterHit = FindNearestCharacterHit(characterIndex, out var width);
var delta = width / 2;
var offset = IsLeftToRight ? Math.Round(distance - currentX, 3) : Math.Round(currentX - distance, 3);
var isTrailing = offset > delta;
@ -400,24 +384,15 @@ namespace Avalonia.Media
{
characterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex, out _);
var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
return textPosition > _characters.End ?
characterHit :
new CharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength);
}
var nextCharacterHit =
FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
if (characterHit.FirstCharacterIndex == Metrics.LastCluster)
{
return characterHit;
}
if (characterHit == nextCharacterHit)
{
return characterHit;
return new CharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength);
}
return characterHit.TrailingLength > 0 ?
nextCharacterHit :
new CharacterHit(nextCharacterHit.FirstCharacterIndex);
return FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
}
/// <summary>
@ -454,29 +429,24 @@ namespace Avalonia.Media
return characterIndex;
}
if (IsLeftToRight)
if (characterIndex > Metrics.LastCluster)
{
if (characterIndex < GlyphClusters[0])
if (IsLeftToRight)
{
return 0;
return GlyphIndices.Count - 1;
}
if (characterIndex > GlyphClusters[GlyphClusters.Count - 1])
{
return GlyphClusters.Count - 1;
}
return 0;
}
else
{
if (characterIndex < GlyphClusters[GlyphClusters.Count - 1])
{
return GlyphClusters.Count - 1;
}
if (characterIndex > GlyphClusters[0])
if (characterIndex < Metrics.FirstCluster)
{
if (IsLeftToRight)
{
return 0;
}
return GlyphIndices.Count - 1;
}
var comparer = IsLeftToRight ? s_ascendingComparer : s_descendingComparer;
@ -498,7 +468,7 @@ namespace Avalonia.Media
if (start < 0)
{
return -1;
goto result;
}
}
@ -517,6 +487,18 @@ namespace Avalonia.Media
}
}
result:
if (start < 0)
{
return 0;
}
if (start > GlyphIndices.Count - 1)
{
return GlyphIndices.Count - 1;
}
return start;
}
@ -532,20 +514,20 @@ namespace Avalonia.Media
{
width = 0.0;
var start = FindGlyphIndex(index);
var glyphIndex = FindGlyphIndex(index);
if (GlyphClusters == null)
{
width = GetGlyphAdvance(index, out _);
return new CharacterHit(start, 1);
return new CharacterHit(glyphIndex, 1);
}
var cluster = GlyphClusters[start];
var cluster = GlyphClusters[glyphIndex];
var nextCluster = cluster;
var currentIndex = start;
var currentIndex = glyphIndex;
while (nextCluster == cluster)
{
@ -571,20 +553,64 @@ namespace Avalonia.Media
}
nextCluster = GlyphClusters[currentIndex];
}
}
int trailingLength;
var clusterLength = Math.Max(0, nextCluster - cluster);
if (nextCluster == cluster)
{
trailingLength = Characters.Start + Characters.Length - _offsetToFirstCharacter - cluster;
}
else
if (cluster == Metrics.LastCluster && clusterLength == 0)
{
trailingLength = nextCluster - cluster;
var characterLength = 0;
var currentCluster = Metrics.FirstCluster;
if (IsLeftToRight)
{
for (int i = 1; i < GlyphClusters.Count; i++)
{
nextCluster = GlyphClusters[i];
if (currentCluster > cluster)
{
break;
}
var length = nextCluster - currentCluster;
characterLength += length;
currentCluster = nextCluster;
}
}
else
{
for (int i = GlyphClusters.Count - 1; i >= 0; i--)
{
nextCluster = GlyphClusters[i];
if (currentCluster > cluster)
{
break;
}
var length = nextCluster - currentCluster;
characterLength += length;
currentCluster = nextCluster;
}
}
if (Characters != null)
{
clusterLength = Characters.Count - characterLength;
}
else
{
clusterLength = 1;
}
}
return new CharacterHit(_offsetToFirstCharacter + cluster, trailingLength);
return new CharacterHit(cluster, clusterLength);
}
/// <summary>
@ -618,22 +644,25 @@ namespace Avalonia.Media
private GlyphRunMetrics CreateGlyphRunMetrics()
{
var firstCluster = 0;
var lastCluster = Characters.Length - 1;
int firstCluster = 0, lastCluster = 0;
if (!IsLeftToRight)
if (_glyphClusters != null && _glyphClusters.Count > 0)
{
var cluster = firstCluster;
firstCluster = lastCluster;
lastCluster = cluster;
firstCluster = _glyphClusters[0];
lastCluster = _glyphClusters[_glyphClusters.Count - 1];
}
if (GlyphClusters != null && GlyphClusters.Count > 0)
else
{
firstCluster = GlyphClusters[0];
lastCluster = GlyphClusters[GlyphClusters.Count - 1];
if (Characters != null && Characters.Count > 0)
{
firstCluster = 0;
lastCluster = Characters.Count - 1;
}
}
_offsetToFirstCharacter = Math.Max(0, Characters.Start - firstCluster);
if (!IsLeftToRight)
{
(lastCluster, firstCluster) = (firstCluster, lastCluster);
}
var isReversed = firstCluster > lastCluster;
@ -666,12 +695,19 @@ namespace Avalonia.Media
}
}
return new GlyphRunMetrics(width, widthIncludingTrailingWhitespace, trailingWhitespaceLength, newLineLength,
height);
return new GlyphRunMetrics(
width,
widthIncludingTrailingWhitespace,
height,
trailingWhitespaceLength,
newLineLength,
firstCluster,
lastCluster
);
}
private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
{
{
if (isReversed)
{
return GetTralingWhitespaceLengthRightToLeft(out newLineLength, out glyphCount);
@ -681,66 +717,82 @@ namespace Avalonia.Media
newLineLength = 0;
var trailingWhitespaceLength = 0;
if (GlyphClusters == null)
if (Characters != null)
{
for (var i = _characters.Length - 1; i >= 0;)
if (GlyphClusters == null)
{
var codepoint = Codepoint.ReadAt(_characters, i, out var count);
if (!codepoint.IsWhiteSpace)
for (var i = _characters.Count - 1; i >= 0;)
{
break;
}
var codepoint = Codepoint.ReadAt(_characters, i, out var count);
if (codepoint.IsBreakChar)
{
newLineLength++;
}
if (!codepoint.IsWhiteSpace)
{
break;
}
trailingWhitespaceLength++;
if (codepoint.IsBreakChar)
{
newLineLength++;
}
trailingWhitespaceLength++;
i -= count;
glyphCount++;
i -= count;
glyphCount++;
}
}
}
else
{
for (var i = GlyphClusters.Count - 1; i >= 0; i--)
else
{
var currentCluster = GlyphClusters[i];
var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset);
var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _);
if (!codepoint.IsWhiteSpace)
if (Characters.Count > 0)
{
break;
}
var characterIndex = Characters.Count - 1;
var clusterLength = 1;
for (var i = GlyphClusters.Count - 1; i >= 0; i--)
{
var currentCluster = GlyphClusters[i];
var codepoint = Codepoint.ReadAt(_characters, characterIndex, out var characterLength);
while(i - 1 >= 0)
{
var nextCluster = GlyphClusters[i - 1];
characterIndex -= characterLength;
if(currentCluster == nextCluster)
{
clusterLength++;
i--;
if (!codepoint.IsWhiteSpace)
{
break;
}
continue;
}
var clusterLength = 1;
break;
}
while (i - 1 >= 0)
{
var nextCluster = GlyphClusters[i - 1];
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;
}
if (currentCluster == nextCluster)
{
clusterLength++;
i--;
if(characterIndex >= 0)
{
codepoint = Codepoint.ReadAt(_characters, characterIndex, out characterLength);
characterIndex -= characterLength;
}
continue;
}
break;
}
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;
}
trailingWhitespaceLength += clusterLength;
glyphCount++;
trailingWhitespaceLength += clusterLength;
glyphCount++;
}
}
}
}
@ -753,67 +805,73 @@ namespace Avalonia.Media
newLineLength = 0;
var trailingWhitespaceLength = 0;
if (GlyphClusters == null)
if (Characters != null)
{
for (var i = 0; i < Characters.Length;)
if (GlyphClusters == null)
{
var codepoint = Codepoint.ReadAt(_characters, i, out var count);
if (!codepoint.IsWhiteSpace)
for (var i = 0; i < Characters.Count;)
{
break;
}
var codepoint = Codepoint.ReadAt(_characters, i, out var count);
if (codepoint.IsBreakChar)
{
newLineLength++;
}
if (!codepoint.IsWhiteSpace)
{
break;
}
trailingWhitespaceLength++;
if (codepoint.IsBreakChar)
{
newLineLength++;
}
i += count;
glyphCount++;
trailingWhitespaceLength++;
i += count;
glyphCount++;
}
}
}
else
{
for (var i = 0; i < GlyphClusters.Count; i++)
else
{
var currentCluster = GlyphClusters[i];
var characterIndex = Math.Max(0, currentCluster - _characters.BufferOffset);
var codepoint = Codepoint.ReadAt(_characters, characterIndex, out _);
var characterIndex = 0;
if (!codepoint.IsWhiteSpace)
for (var i = 0; i < GlyphClusters.Count; i++)
{
break;
}
var currentCluster = GlyphClusters[i];
var codepoint = Codepoint.ReadAt(_characters, characterIndex, out var characterLength);
var clusterLength = 1;
characterIndex += characterLength;
var j = i;
if (!codepoint.IsWhiteSpace)
{
break;
}
while (j - 1 >= 0)
{
var nextCluster = GlyphClusters[--j];
var clusterLength = 1;
if (currentCluster == nextCluster)
var j = i;
while (j - 1 >= 0)
{
clusterLength++;
var nextCluster = GlyphClusters[--j];
continue;
}
if (currentCluster == nextCluster)
{
clusterLength++;
break;
}
continue;
}
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;
}
break;
}
if (codepoint.IsBreakChar)
{
newLineLength += clusterLength;
}
trailingWhitespaceLength += clusterLength;
trailingWhitespaceLength += clusterLength;
glyphCount += clusterLength;
glyphCount += clusterLength;
}
}
}
@ -855,14 +913,9 @@ namespace Avalonia.Media
throw new InvalidOperationException();
}
_glyphRunImpl = CreateGlyphRunImpl();
}
private IGlyphRunImpl CreateGlyphRunImpl()
{
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
return platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets);
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets);
}
void IDisposable.Dispose()

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

@ -32,7 +32,7 @@
public override Rect GetBounds()
{
return GlyphRun != null ? new Rect(GlyphRun.Size) : Rect.Empty;
return GlyphRun != null ? new Rect(GlyphRun.Size) : default;
}
}
}

20
src/Avalonia.Base/Media/GlyphRunMetrics.cs

@ -1,25 +1,31 @@
namespace Avalonia.Media
{
public readonly struct GlyphRunMetrics
public readonly record struct GlyphRunMetrics
{
public GlyphRunMetrics(double width, double widthIncludingTrailingWhitespace, int trailingWhitespaceLength,
int newlineLength, double height)
public GlyphRunMetrics(double width, double widthIncludingTrailingWhitespace, double height,
int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster)
{
Width = width;
WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace;
TrailingWhitespaceLength = trailingWhitespaceLength;
NewlineLength = newlineLength;
Height = height;
TrailingWhitespaceLength = trailingWhitespaceLength;
NewLineLength= newLineLength;
FirstCluster = firstCluster;
LastCluster = lastCluster;
}
public double Width { get; }
public double WidthIncludingTrailingWhitespace { get; }
public double Height { get; }
public int TrailingWhitespaceLength { get; }
public int NewlineLength { get; }
public int NewLineLength { get; }
public double Height { get; }
public int FirstCluster { get; }
public int LastCluster { get; }
}
}

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

@ -12,7 +12,7 @@ namespace Avalonia.Media
/// <summary>
/// Base class for brushes that draw with a gradient.
/// </summary>
public abstract class GradientBrush : Brush, IGradientBrush
public abstract class GradientBrush : Brush, IGradientBrush, IMutableBrush
{
/// <summary>
/// Defines the <see cref="SpreadMethod"/> property.
@ -92,5 +92,7 @@ namespace Avalonia.Media
{
RaiseInvalidated(EventArgs.Empty);
}
public abstract IImmutableBrush ToImmutable();
}
}

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

@ -12,7 +12,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the length of alternating dashes and gaps.
/// </summary>
IReadOnlyList<double> Dashes { get; }
IReadOnlyList<double>? Dashes { get; }
/// <summary>
/// Gets or sets how far in the dash sequence the stroke will start.

9
src/Avalonia.Base/Media/IImmutableBrush.cs

@ -0,0 +1,9 @@
namespace Avalonia.Media;
/// <summary>
/// Represents an immutable brush which can be safely used with various threading contexts
/// </summary>
public interface IImmutableBrush : IBrush
{
}

4
src/Avalonia.Base/Media/IMutableBrush.cs

@ -7,12 +7,12 @@ namespace Avalonia.Media
/// Represents a mutable brush which can return an immutable clone of itself.
/// </summary>
[NotClientImplementable]
public interface IMutableBrush : IBrush, IAffectsRender
internal interface IMutableBrush : IBrush, IAffectsRender
{
/// <summary>
/// Creates an immutable clone of the brush.
/// </summary>
/// <returns>The immutable clone.</returns>
IBrush ToImmutable();
internal IImmutableBrush ToImmutable();
}
}

9
src/Avalonia.Base/Media/ISolidColorBrush.cs

@ -13,4 +13,13 @@ namespace Avalonia.Media
/// </summary>
Color Color { get; }
}
/// <summary>
/// Fills an area with a solid color.
/// </summary>
[NotClientImplementable]
public interface IImmutableSolidColorBrush : ISolidColorBrush, IImmutableBrush
{
}
}

4
src/Avalonia.Base/Media/ImageBrush.cs

@ -6,7 +6,7 @@ namespace Avalonia.Media
/// <summary>
/// Paints an area with an <see cref="IBitmap"/>.
/// </summary>
public class ImageBrush : TileBrush, IImageBrush
public class ImageBrush : TileBrush, IImageBrush, IMutableBrush
{
/// <summary>
/// Defines the <see cref="Visual"/> property.
@ -45,7 +45,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
public override IBrush ToImmutable()
public IImmutableBrush ToImmutable()
{
return new ImmutableImageBrush(this);
}

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

@ -42,7 +42,7 @@ namespace Avalonia.Media
var imageSource = ImageSource;
var rect = Rect;
if (imageSource is object && !rect.IsEmpty)
if (imageSource is object && !rect.IsDefault)
{
context.DrawImage(imageSource, rect);
}

4
src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs

@ -78,8 +78,8 @@ namespace Avalonia.Media.Imaging
get
{
if (Source is not IBitmap bmp)
return Size.Empty;
if (SourceRect.IsEmpty)
return default;
if (SourceRect.IsDefault)
return Source.Size;
return SourceRect.Size.ToSizeWithDpi(bmp.Dpi);
}

2
src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs

@ -60,5 +60,7 @@ namespace Avalonia.Media.Imaging
/// <inheritdoc/>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer? vbr) => PlatformImpl.Item.CreateDrawingContext(vbr);
bool IRenderTarget.IsCorrupted => false;
}
}

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

@ -0,0 +1,373 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
public sealed class ImmediateDrawingContext : IDisposable, IOptionalFeatureProvider
{
private readonly bool _ownsImpl;
private int _currentLevel;
private static ThreadSafeObjectPool<Stack<PushedState>> StateStackPool { get; } =
ThreadSafeObjectPool<Stack<PushedState>>.Default;
private static ThreadSafeObjectPool<Stack<TransformContainer>> TransformStackPool { get; } =
ThreadSafeObjectPool<Stack<TransformContainer>>.Default;
private Stack<PushedState>? _states = StateStackPool.Get();
private Stack<TransformContainer>? _transformContainers = TransformStackPool.Get();
readonly struct TransformContainer
{
public readonly Matrix LocalTransform;
public readonly Matrix ContainerTransform;
public TransformContainer(Matrix localTransform, Matrix containerTransform)
{
LocalTransform = localTransform;
ContainerTransform = containerTransform;
}
}
internal ImmediateDrawingContext(IDrawingContextImpl impl, bool ownsImpl)
{
_ownsImpl = ownsImpl;
PlatformImpl = impl;
_currentContainerTransform = impl.Transform;
}
public IDrawingContextImpl PlatformImpl { get; }
private Matrix _currentTransform = Matrix.Identity;
private Matrix _currentContainerTransform;
/// <summary>
/// Gets the current transform of the drawing context.
/// </summary>
public Matrix CurrentTransform
{
get { return _currentTransform; }
private set
{
_currentTransform = value;
var transform = _currentTransform * _currentContainerTransform;
PlatformImpl.Transform = transform;
}
}
/// <summary>
/// Draws an bitmap.
/// </summary>
/// <param name="source">The bitmap.</param>
/// <param name="rect">The rect in the output to draw to.</param>
public void DrawBitmap(IBitmap source, Rect rect)
{
_ = source ?? throw new ArgumentNullException(nameof(source));
DrawBitmap(source, new Rect(source.Size), rect);
}
/// <summary>
/// Draws an image.
/// </summary>
/// <param name="source">The bitmap.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
{
_ = source ?? throw new ArgumentNullException(nameof(source));
PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect, bitmapInterpolationMode);
}
/// <summary>
/// Draws a line.
/// </summary>
/// <param name="pen">The stroke pen.</param>
/// <param name="p1">The first point of the line.</param>
/// <param name="p2">The second point of the line.</param>
public void DrawLine(ImmutablePen pen, Point p1, Point p2)
{
if (PenIsVisible(pen))
{
PlatformImpl.DrawLine(pen, p1, p2);
}
}
/// <summary>
/// Draws a rectangle with the specified Brush and Pen.
/// </summary>
/// <param name="brush">The brush used to fill the rectangle, or <c>null</c> for no fill.</param>
/// <param name="pen">The pen used to stroke the rectangle, or <c>null</c> for no stroke.</param>
/// <param name="rect">The rectangle bounds.</param>
/// <param name="radiusX">The radius in the X dimension of the rounded corners.
/// This value will be clamped to the range of 0 to Width/2
/// </param>
/// <param name="radiusY">The radius in the Y dimension of the rounded corners.
/// This value will be clamped to the range of 0 to Height/2
/// </param>
/// <param name="boxShadows">Box shadow effect parameters</param>
/// <remarks>
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
/// </remarks>
public void DrawRectangle(IImmutableBrush? brush, ImmutablePen? pen, Rect rect, double radiusX = 0, double radiusY = 0,
BoxShadows boxShadows = default)
{
if (brush == null && !PenIsVisible(pen))
{
return;
}
if (!MathUtilities.IsZero(radiusX))
{
radiusX = Math.Min(radiusX, rect.Width / 2);
}
if (!MathUtilities.IsZero(radiusY))
{
radiusY = Math.Min(radiusY, rect.Height / 2);
}
PlatformImpl.DrawRectangle(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows);
}
/// <summary>
/// Draws the outline of a rectangle.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="rect">The rectangle bounds.</param>
/// <param name="cornerRadius">The corner radius.</param>
public void DrawRectangle(ImmutablePen pen, Rect rect, float cornerRadius = 0.0f)
{
DrawRectangle(null, pen, rect, cornerRadius, cornerRadius);
}
/// <summary>
/// Draws an ellipse with the specified Brush and Pen.
/// </summary>
/// <param name="brush">The brush used to fill the ellipse, or <c>null</c> for no fill.</param>
/// <param name="pen">The pen used to stroke the ellipse, or <c>null</c> for no stroke.</param>
/// <param name="center">The location of the center of the ellipse.</param>
/// <param name="radiusX">The horizontal radius of the ellipse.</param>
/// <param name="radiusY">The vertical radius of the ellipse.</param>
/// <remarks>
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
/// </remarks>
public void DrawEllipse(IImmutableBrush? brush, ImmutablePen? pen, Point center, double radiusX, double radiusY)
{
if (brush == null && !PenIsVisible(pen))
{
return;
}
var originX = center.X - radiusX;
var originY = center.Y - radiusY;
var width = radiusX * 2;
var height = radiusY * 2;
PlatformImpl.DrawEllipse(brush, pen, new Rect(originX, originY, width, height));
}
/// <summary>
/// Draws a glyph run.
/// </summary>
/// <param name="foreground">The foreground brush.</param>
/// <param name="glyphRun">The glyph run.</param>
public void DrawGlyphRun(IImmutableBrush foreground, GlyphRun glyphRun)
{
_ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun));
PlatformImpl.DrawGlyphRun(foreground, glyphRun);
}
/// <summary>
/// Draws a filled rectangle.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="rect">The rectangle bounds.</param>
/// <param name="cornerRadius">The corner radius.</param>
public void FillRectangle(IImmutableBrush brush, Rect rect, float cornerRadius = 0.0f)
{
DrawRectangle(brush, null, rect, cornerRadius, cornerRadius);
}
public readonly record struct PushedState : IDisposable
{
private readonly int _level;
private readonly ImmediateDrawingContext _context;
private readonly Matrix _matrix;
private readonly PushedStateType _type;
public enum PushedStateType
{
None,
Matrix,
Opacity,
Clip,
MatrixContainer,
GeometryClip,
OpacityMask,
}
internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default(Matrix))
{
if (context._states is null)
throw new ObjectDisposedException(nameof(ImmediateDrawingContext));
_context = context;
_type = type;
_matrix = matrix;
_level = context._currentLevel += 1;
context._states.Push(this);
}
public void Dispose()
{
if (_type == PushedStateType.None)
return;
if (_context._states is null || _context._transformContainers is null)
throw new ObjectDisposedException(nameof(DrawingContext));
if (_context._currentLevel != _level)
throw new InvalidOperationException("Wrong Push/Pop state order");
_context._currentLevel--;
_context._states.Pop();
if (_type == PushedStateType.Matrix)
_context.CurrentTransform = _matrix;
else if (_type == PushedStateType.Clip)
_context.PlatformImpl.PopClip();
else if (_type == PushedStateType.Opacity)
_context.PlatformImpl.PopOpacity();
else if (_type == PushedStateType.GeometryClip)
_context.PlatformImpl.PopGeometryClip();
else if (_type == PushedStateType.OpacityMask)
_context.PlatformImpl.PopOpacityMask();
else if (_type == PushedStateType.MatrixContainer)
{
var cont = _context._transformContainers.Pop();
_context._currentContainerTransform = cont.ContainerTransform;
_context.CurrentTransform = cont.LocalTransform;
}
}
}
public PushedState PushClip(RoundedRect clip)
{
PlatformImpl.PushClip(clip);
return new PushedState(this, PushedState.PushedStateType.Clip);
}
/// <summary>
/// Pushes a clip rectangle.
/// </summary>
/// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns>
public PushedState PushClip(Rect clip)
{
PlatformImpl.PushClip(clip);
return new PushedState(this, PushedState.PushedStateType.Clip);
}
/// <summary>
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns>
public PushedState PushOpacity(double opacity)
//TODO: Eliminate platform-specific push opacity call
{
PlatformImpl.PushOpacity(opacity);
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
/// <summary>
/// Pushes an opacity mask.
/// </summary>
/// <param name="mask">The opacity mask.</param>
/// <param name="bounds">
/// The size of the brush's target area. TODO: Are we sure this is needed?
/// </param>
/// <returns>A disposable to undo the opacity mask.</returns>
public PushedState PushOpacityMask(IImmutableBrush mask, Rect bounds)
{
PlatformImpl.PushOpacityMask(mask, bounds);
return new PushedState(this, PushedState.PushedStateType.OpacityMask);
}
/// <summary>
/// Pushes a matrix post-transformation.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>A disposable used to undo the transformation.</returns>
public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform * matrix);
/// <summary>
/// Pushes a matrix pre-transformation.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>A disposable used to undo the transformation.</returns>
public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix * CurrentTransform);
/// <summary>
/// Sets the current matrix transformation.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>A disposable used to undo the transformation.</returns>
public PushedState PushSetTransform(Matrix matrix)
{
var oldMatrix = CurrentTransform;
CurrentTransform = matrix;
return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix);
}
/// <summary>
/// Pushes a new transform context.
/// </summary>
/// <returns>A disposable used to undo the transformation.</returns>
public PushedState PushTransformContainer()
{
if (_transformContainers is null)
throw new ObjectDisposedException(nameof(DrawingContext));
_transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform));
_currentContainerTransform = CurrentTransform * _currentContainerTransform;
_currentTransform = Matrix.Identity;
return new PushedState(this, PushedState.PushedStateType.MatrixContainer);
}
/// <summary>
/// Disposes of any resources held by the <see cref="DrawingContext"/>.
/// </summary>
public void Dispose()
{
if (_states is null || _transformContainers is null)
throw new ObjectDisposedException(nameof(DrawingContext));
while (_states.Count != 0)
_states.Peek().Dispose();
StateStackPool.Return(_states);
_states = null;
if (_transformContainers.Count != 0)
throw new InvalidOperationException("Transform container stack is non-empty");
TransformStackPool.Return(_transformContainers);
_transformContainers = null;
if (_ownsImpl)
PlatformImpl.Dispose();
}
private static bool PenIsVisible(IPen? pen)
{
return pen?.Brush != null && pen.Thickness > 0;
}
public object? TryGetFeature(Type type) => PlatformImpl.GetFeature(type);
}
}

4
src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs

@ -17,7 +17,7 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="dashes">The dashes collection.</param>
/// <param name="offset">The dash sequence offset.</param>
public ImmutableDashStyle(IEnumerable<double> dashes, double offset)
public ImmutableDashStyle(IEnumerable<double>? dashes, double offset)
{
_dashes = dashes?.ToArray() ?? Array.Empty<double>();
Offset = offset;
@ -69,7 +69,7 @@ namespace Avalonia.Media.Immutable
return hashCode;
}
private static bool SequenceEqual(IReadOnlyList<double> left, IReadOnlyList<double> right)
private static bool SequenceEqual(IReadOnlyList<double> left, IReadOnlyList<double>? right)
{
if (ReferenceEquals(left, right))
{

2
src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs

@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable
/// <summary>
/// A brush that draws with a gradient.
/// </summary>
public abstract class ImmutableGradientBrush : IGradientBrush
public abstract class ImmutableGradientBrush : IGradientBrush, IImmutableBrush
{
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableGradientBrush"/> class.

4
src/Avalonia.Base/Media/Immutable/ImmutablePen.cs

@ -38,15 +38,13 @@ namespace Avalonia.Media.Immutable
/// <param name="lineJoin">The line join.</param>
/// <param name="miterLimit">The miter limit.</param>
public ImmutablePen(
IBrush? brush,
IImmutableBrush? brush,
double thickness = 1.0,
ImmutableDashStyle? dashStyle = null,
PenLineCap lineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0)
{
Debug.Assert(!(brush is IMutableBrush));
Brush = brush;
Thickness = thickness;
LineCap = lineCap;

2
src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs

@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable
/// <summary>
/// Fills an area with a solid color.
/// </summary>
public class ImmutableSolidColorBrush : ISolidColorBrush, IEquatable<ImmutableSolidColorBrush>
public class ImmutableSolidColorBrush : IImmutableSolidColorBrush, IEquatable<ImmutableSolidColorBrush>
{
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableSolidColorBrush"/> class.

2
src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs

@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable
/// <summary>
/// A brush which displays a repeating image.
/// </summary>
public abstract class ImmutableTileBrush : ITileBrush
public abstract class ImmutableTileBrush : ITileBrush, IImmutableBrush
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush"/> class.

6
src/Avalonia.Base/Media/KnownColors.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media
private static readonly IReadOnlyDictionary<string, KnownColor> _knownColorNames;
private static readonly IReadOnlyDictionary<uint, string> _knownColors;
#if !BUILDTASK
private static readonly Dictionary<KnownColor, ISolidColorBrush> _knownBrushes;
private static readonly Dictionary<KnownColor, IImmutableSolidColorBrush> _knownBrushes;
#endif
[GenerateEnumValueDictionary()]
@ -39,7 +39,7 @@ namespace Avalonia.Media
_knownColors = knownColors;
#if !BUILDTASK
_knownBrushes = new Dictionary<KnownColor, ISolidColorBrush>();
_knownBrushes = new ();
#endif
}
@ -72,7 +72,7 @@ namespace Avalonia.Media
}
#if !BUILDTASK
public static ISolidColorBrush ToBrush(this KnownColor color)
public static IImmutableSolidColorBrush ToBrush(this KnownColor color)
{
lock (_knownBrushes)
{

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

@ -47,7 +47,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
public override IBrush ToImmutable()
public override IImmutableBrush ToImmutable()
{
return new ImmutableLinearGradientBrush(this);
}

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

@ -67,7 +67,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
public override IBrush ToImmutable()
public override IImmutableBrush ToImmutable()
{
return new ImmutableRadialGradientBrush(this);
}

4
src/Avalonia.Base/Media/SolidColorBrush.cs

@ -6,7 +6,7 @@ namespace Avalonia.Media
/// <summary>
/// Fills an area with a solid color.
/// </summary>
public class SolidColorBrush : Brush, ISolidColorBrush
public class SolidColorBrush : Brush, ISolidColorBrush, IMutableBrush
{
/// <summary>
/// Defines the <see cref="Color"/> property.
@ -80,7 +80,7 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
public override IBrush ToImmutable()
public IImmutableBrush ToImmutable()
{
return new ImmutableSolidColorBrush(this);
}

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

@ -2,7 +2,7 @@
namespace Avalonia.Media
{
public readonly struct TextCollapsingCreateInfo
public readonly record struct TextCollapsingCreateInfo
{
public readonly double Width;
public readonly TextRunProperties TextRunProperties;

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

Loading…
Cancel
Save