committed by
GitHub
519 changed files with 9457 additions and 6400 deletions
@ -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> |
|
||||
@ -1,5 +1,5 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup Condition="'$(TargetFramework)' != 'net6'"> |
||||
<PackageReference Include="System.Memory" Version="4.5.3" /> |
<PackageReference Include="System.Memory" Version="4.5.3" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -1,5 +0,0 @@ |
|||||
<Application xmlns="https://github.com/avaloniaui"> |
|
||||
<Application.Styles> |
|
||||
<SimpleTheme Mode="Light" /> |
|
||||
</Application.Styles> |
|
||||
</Application> |
|
||||
@ -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(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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> |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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> |
|
||||
@ -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); } |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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
|
||||
|
} |
||||
|
} |
||||
@ -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(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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, |
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,25 +1,31 @@ |
|||||
namespace Avalonia.Media |
namespace Avalonia.Media |
||||
{ |
{ |
||||
public readonly struct GlyphRunMetrics |
public readonly record struct GlyphRunMetrics |
||||
{ |
{ |
||||
public GlyphRunMetrics(double width, double widthIncludingTrailingWhitespace, int trailingWhitespaceLength, |
public GlyphRunMetrics(double width, double widthIncludingTrailingWhitespace, double height, |
||||
int newlineLength, double height) |
int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster) |
||||
{ |
{ |
||||
Width = width; |
Width = width; |
||||
WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace; |
WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace; |
||||
TrailingWhitespaceLength = trailingWhitespaceLength; |
|
||||
NewlineLength = newlineLength; |
|
||||
Height = height; |
Height = height; |
||||
|
TrailingWhitespaceLength = trailingWhitespaceLength; |
||||
|
NewLineLength= newLineLength; |
||||
|
FirstCluster = firstCluster; |
||||
|
LastCluster = lastCluster; |
||||
} |
} |
||||
|
|
||||
public double Width { get; } |
public double Width { get; } |
||||
|
|
||||
public double WidthIncludingTrailingWhitespace { get; } |
public double WidthIncludingTrailingWhitespace { get; } |
||||
|
|
||||
|
public double Height { get; } |
||||
|
|
||||
public int TrailingWhitespaceLength { 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; } |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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 |
||||
|
{ |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue