committed by
GitHub
69 changed files with 2230 additions and 1554 deletions
@ -0,0 +1,6 @@ |
|||||
|
<Application xmlns="https://github.com/avaloniaui"> |
||||
|
<Application.Styles> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> |
||||
|
</Application.Styles> |
||||
|
</Application> |
||||
@ -0,0 +1,13 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace Direct3DInteropSample |
||||
|
{ |
||||
|
public class App : Application |
||||
|
{ |
||||
|
public override void Initialize() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>net461</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="SharpDX.Mathematics" Version="3.1.1" /> |
||||
|
<PackageReference Include="SharpDX.D3DCompiler" Version="3.1.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" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> |
||||
|
</ItemGroup> |
||||
|
<Import Project="..\..\..\build\Serilog.props" /> |
||||
|
<Import Project="..\..\..\build\Serilog.Sinks.Trace.props" /> |
||||
|
<Import Project="..\..\..\build\Splat.props" /> |
||||
|
<Import Project="..\..\..\build\Rx.props" /> |
||||
|
<Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" /> |
||||
|
</Project> |
||||
@ -0,0 +1,264 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
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 SharpDX.WIC; |
||||
|
using SharpDX.Mathematics; |
||||
|
using AlphaMode = SharpDX.Direct2D1.AlphaMode; |
||||
|
using Buffer = SharpDX.Direct3D11.Buffer; |
||||
|
using DeviceContext = SharpDX.Direct3D11.DeviceContext; |
||||
|
using Factory1 = SharpDX.DXGI.Factory1; |
||||
|
using InputElement = SharpDX.Direct3D11.InputElement; |
||||
|
using Matrix = SharpDX.Matrix; |
||||
|
using PixelFormat = SharpDX.Direct2D1.PixelFormat; |
||||
|
|
||||
|
namespace Direct3DInteropSample |
||||
|
{ |
||||
|
class MainWindow : Window |
||||
|
{ |
||||
|
private SharpDX.Direct3D11.Device _d3dDevice; |
||||
|
private SharpDX.DXGI.Device _dxgiDevice; |
||||
|
Texture2D backBuffer = null; |
||||
|
RenderTargetView renderView = null; |
||||
|
Texture2D depthBuffer = null; |
||||
|
DepthStencilView depthView = null; |
||||
|
private readonly SwapChain _swapChain; |
||||
|
private SwapChainDescription _desc; |
||||
|
private Matrix _proj = Matrix.Identity; |
||||
|
private Matrix _view; |
||||
|
private Buffer _contantBuffer; |
||||
|
private SharpDX.Direct2D1.Device _d2dDevice; |
||||
|
private SharpDX.Direct2D1.DeviceContext _d2dContext; |
||||
|
private RenderTarget _d2dRenderTarget; |
||||
|
private MainWindowViewModel _model; |
||||
|
|
||||
|
public MainWindow() |
||||
|
{ |
||||
|
_dxgiDevice = AvaloniaLocator.Current.GetService<SharpDX.DXGI.Device>(); |
||||
|
_d3dDevice = _dxgiDevice.QueryInterface<SharpDX.Direct3D11.Device>(); |
||||
|
_d2dDevice = AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Device>(); |
||||
|
DataContext = _model = new MainWindowViewModel(); |
||||
|
_desc = new SwapChainDescription() |
||||
|
{ |
||||
|
BufferCount = 1, |
||||
|
ModeDescription = |
||||
|
new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, |
||||
|
new Rational(60, 1), Format.R8G8B8A8_UNorm), |
||||
|
IsWindowed = true, |
||||
|
OutputHandle = PlatformImpl.Handle.Handle, |
||||
|
SampleDescription = new SampleDescription(1, 0), |
||||
|
SwapEffect = SwapEffect.Discard, |
||||
|
Usage = Usage.RenderTargetOutput |
||||
|
}; |
||||
|
|
||||
|
_swapChain = new SwapChain(new Factory1(), _d3dDevice, _desc); |
||||
|
|
||||
|
_d2dContext = new SharpDX.Direct2D1.DeviceContext(_d2dDevice, 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 = _d3dDevice.ImmediateContext; |
||||
|
// Clear views
|
||||
|
context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0); |
||||
|
context.ClearRenderTargetView(renderView, Color.White); |
||||
|
|
||||
|
var time = 50; |
||||
|
// 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); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void CreateMesh() |
||||
|
{ |
||||
|
var device = _d3dDevice; |
||||
|
// 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); |
||||
|
// Layout from VertexShader input signature
|
||||
|
var layout = new InputLayout(device, signature, new[] |
||||
|
{ |
||||
|
new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), |
||||
|
new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) |
||||
|
}); |
||||
|
|
||||
|
// Instantiate Vertex buiffer 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 = _d3dDevice.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); |
||||
|
} |
||||
|
|
||||
|
void Resize(Size size) |
||||
|
{ |
||||
|
Utilities.Dispose(ref _d2dRenderTarget); |
||||
|
Utilities.Dispose(ref backBuffer); |
||||
|
Utilities.Dispose(ref renderView); |
||||
|
Utilities.Dispose(ref depthBuffer); |
||||
|
Utilities.Dispose(ref depthView); |
||||
|
var context = _d3dDevice.ImmediateContext; |
||||
|
// Resize the backbuffer
|
||||
|
_swapChain.ResizeBuffers(_desc.BufferCount, (int)size.Width, (int)size.Height, Format.Unknown, SwapChainFlags.None); |
||||
|
|
||||
|
// Get the backbuffer from the swapchain
|
||||
|
backBuffer = Texture2D.FromSwapChain<Texture2D>(_swapChain, 0); |
||||
|
|
||||
|
// Renderview on the backbuffer
|
||||
|
renderView = new RenderTargetView(_d3dDevice, backBuffer); |
||||
|
|
||||
|
// Create the depth buffer
|
||||
|
depthBuffer = new Texture2D(_d3dDevice, 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(_d3dDevice, 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)) |
||||
|
{ |
||||
|
_d2dRenderTarget = new RenderTarget(AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Factory>() |
||||
|
, dxgiBackBuffer, new RenderTargetProperties |
||||
|
{ |
||||
|
DpiX = 96, |
||||
|
DpiY = 96, |
||||
|
Type = RenderTargetType.Default, |
||||
|
PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Premultiplied) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
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, _window._d2dRenderTarget, |
||||
|
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
protected override IRenderTarget CreateRenderTarget() => new D3DRenderTarget(this); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
<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> |
||||
@ -0,0 +1,45 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using ReactiveUI; |
||||
|
|
||||
|
namespace Direct3DInteropSample |
||||
|
{ |
||||
|
public class MainWindowViewModel : ReactiveObject |
||||
|
{ |
||||
|
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); } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// 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; |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia; |
||||
|
|
||||
|
namespace Direct3DInteropSample |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
static void Main(string[] args) |
||||
|
{ |
||||
|
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().Start<MainWindow>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,41 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
using Avalonia.Platform; |
|
||||
|
|
||||
namespace Avalonia |
|
||||
{ |
|
||||
public abstract class AvaloniaDisposable : IDisposable |
|
||||
{ |
|
||||
#if DEBUG_DISPOSE
|
|
||||
public string DisposedAt { get; private set; } |
|
||||
#endif
|
|
||||
|
|
||||
|
|
||||
public bool IsDisposed { get; private set; } |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
IsDisposed = true; |
|
||||
#if DEBUG_DISPOSE
|
|
||||
DisposedAt = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetStackTrace(); |
|
||||
#endif
|
|
||||
DoDispose(); |
|
||||
} |
|
||||
|
|
||||
protected void CheckDisposed() |
|
||||
{ |
|
||||
if (IsDisposed) |
|
||||
throw new ObjectDisposedException(GetType().FullName |
|
||||
#if DEBUG_DISPOSE
|
|
||||
, "Disposed at: \n" + DisposedAt |
|
||||
#endif
|
|
||||
|
|
||||
); |
|
||||
} |
|
||||
|
|
||||
protected abstract void DoDispose(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,20 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Rendering |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Internal interface for initializing controls that are to be used as the viusal in a
|
||||
|
/// <see cref="VisualBrush"/>.
|
||||
|
/// </summary>
|
||||
|
public interface IVisualBrushInitialize |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Ensures that the control is ready to use as the visual in a visual brush.
|
||||
|
/// </summary>
|
||||
|
void EnsureInitialized(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Rendering |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a renderer used to render a visual brush to a bitmap.
|
||||
|
/// </summary>
|
||||
|
public interface IVisualBrushRenderer |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the size of the intermediate render target to which the visual brush should be
|
||||
|
/// drawn.
|
||||
|
/// </summary>
|
||||
|
/// <param name="brush">The visual brush.</param>
|
||||
|
/// <returns>The size of the intermediate render target to create.</returns>
|
||||
|
Size GetRenderTargetSize(IVisualBrush brush); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Renders a visual brush to a bitmap.
|
||||
|
/// </summary>
|
||||
|
/// <param name="context">The drawing context to render to.</param>
|
||||
|
/// <param name="brush">The visual brush.</param>
|
||||
|
/// <returns>A bitmap containing the rendered brush.</returns>
|
||||
|
void RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,278 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.VisualTree; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Media; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Avalonia.Rendering |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A renderer which renders the state of the visual tree without an intermediate scene graph
|
||||
|
/// representation.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The immediate renderer supports only clip-bound-based hit testing; a control's geometry is
|
||||
|
/// not taken into account.
|
||||
|
/// </remarks>
|
||||
|
public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer |
||||
|
{ |
||||
|
private readonly IVisual _root; |
||||
|
private readonly IRenderRoot _renderRoot; |
||||
|
private IRenderTarget _renderTarget; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ImmediateRenderer"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="root">The control to render.</param>
|
||||
|
public ImmediateRenderer(IVisual root) |
||||
|
{ |
||||
|
Contract.Requires<ArgumentNullException>(root != null); |
||||
|
|
||||
|
_root = root; |
||||
|
_renderRoot = root as IRenderRoot; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool DrawFps { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public bool DrawDirtyRects { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Paint(Rect rect) |
||||
|
{ |
||||
|
if (_renderTarget == null) |
||||
|
{ |
||||
|
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); |
||||
|
} |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
using (var context = new DrawingContext(_renderTarget.CreateDrawingContext(this))) |
||||
|
{ |
||||
|
using (context.PushTransformContainer()) |
||||
|
{ |
||||
|
Render(context, _root, _root.Bounds); |
||||
|
} |
||||
|
|
||||
|
if (DrawDirtyRects) |
||||
|
{ |
||||
|
var color = (uint)new Random().Next(0xffffff) | 0x44000000; |
||||
|
context.FillRectangle( |
||||
|
new SolidColorBrush(color), |
||||
|
rect); |
||||
|
} |
||||
|
|
||||
|
if (DrawFps) |
||||
|
{ |
||||
|
RenderFps(context.PlatformImpl, _root.Bounds, true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch (RenderTargetCorruptedException ex) |
||||
|
{ |
||||
|
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); |
||||
|
_renderTarget.Dispose(); |
||||
|
_renderTarget = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Resized(Size size) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Renders a visual to a render target.
|
||||
|
/// </summary>
|
||||
|
/// <param name="visual">The visual.</param>
|
||||
|
/// <param name="target">The render target.</param>
|
||||
|
public static void Render(IVisual visual, IRenderTarget target) |
||||
|
{ |
||||
|
using (var renderer = new ImmediateRenderer(visual)) |
||||
|
using (var context = new DrawingContext(target.CreateDrawingContext(renderer))) |
||||
|
{ |
||||
|
renderer.Render(context, visual, visual.Bounds); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Renders a visual to a drawing context.
|
||||
|
/// </summary>
|
||||
|
/// <param name="visual">The visual.</param>
|
||||
|
/// <param name="context">The drawing context.</param>
|
||||
|
public static void Render(IVisual visual, DrawingContext context) |
||||
|
{ |
||||
|
using (var renderer = new ImmediateRenderer(visual)) |
||||
|
{ |
||||
|
renderer.Render(context, visual, visual.Bounds); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void AddDirty(IVisual visual) |
||||
|
{ |
||||
|
if (visual.Bounds != Rect.Empty) |
||||
|
{ |
||||
|
var m = visual.TransformToVisual(_root); |
||||
|
|
||||
|
if (m.HasValue) |
||||
|
{ |
||||
|
var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value); |
||||
|
_renderRoot?.Invalidate(bounds); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Ends the operation of the renderer.
|
||||
|
/// </summary>
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter) |
||||
|
{ |
||||
|
return HitTest(_root, p, filter); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) |
||||
|
{ |
||||
|
(brush.Visual as IVisualBrushInitialize)?.EnsureInitialized(); |
||||
|
return brush.Visual?.Bounds.Size ?? Size.Empty; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) |
||||
|
{ |
||||
|
var visual = brush.Visual; |
||||
|
Render(new DrawingContext(context), visual, visual.Bounds); |
||||
|
} |
||||
|
|
||||
|
private static void ClearTransformedBounds(IVisual visual) |
||||
|
{ |
||||
|
foreach (var e in visual.GetSelfAndVisualDescendents()) |
||||
|
{ |
||||
|
BoundsTracker.SetTransformedBounds((Visual)visual, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static Rect GetTransformedBounds(IVisual visual) |
||||
|
{ |
||||
|
if (visual.RenderTransform == null) |
||||
|
{ |
||||
|
return visual.Bounds; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); |
||||
|
var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin); |
||||
|
var m = (-offset) * visual.RenderTransform.Value * (offset); |
||||
|
return visual.Bounds.TransformToAABB(m); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static IEnumerable<IVisual> HitTest( |
||||
|
IVisual visual, |
||||
|
Point p, |
||||
|
Func<IVisual, bool> filter) |
||||
|
{ |
||||
|
Contract.Requires<ArgumentNullException>(visual != null); |
||||
|
|
||||
|
if (filter?.Invoke(visual) != false) |
||||
|
{ |
||||
|
bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.Contains(p) == true; |
||||
|
|
||||
|
if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0) |
||||
|
{ |
||||
|
foreach (var child in visual.VisualChildren.SortByZIndex()) |
||||
|
{ |
||||
|
foreach (var result in HitTest(child, p, filter)) |
||||
|
{ |
||||
|
yield return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (containsPoint) |
||||
|
{ |
||||
|
yield return visual; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void Render(DrawingContext context, IVisual visual, Rect clipRect) |
||||
|
{ |
||||
|
var opacity = visual.Opacity; |
||||
|
var clipToBounds = visual.ClipToBounds; |
||||
|
var bounds = new Rect(visual.Bounds.Size); |
||||
|
|
||||
|
if (visual.IsVisible && opacity > 0) |
||||
|
{ |
||||
|
var m = Matrix.CreateTranslation(visual.Bounds.Position); |
||||
|
|
||||
|
var renderTransform = Matrix.Identity; |
||||
|
|
||||
|
if (visual.RenderTransform != null) |
||||
|
{ |
||||
|
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); |
||||
|
var offset = Matrix.CreateTranslation(origin); |
||||
|
renderTransform = (-offset) * visual.RenderTransform.Value * (offset); |
||||
|
} |
||||
|
|
||||
|
m = renderTransform * m; |
||||
|
|
||||
|
if (clipToBounds) |
||||
|
{ |
||||
|
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); |
||||
|
} |
||||
|
|
||||
|
using (context.PushPostTransform(m)) |
||||
|
using (context.PushOpacity(opacity)) |
||||
|
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState)) |
||||
|
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState)) |
||||
|
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState)) |
||||
|
using (context.PushTransformContainer()) |
||||
|
{ |
||||
|
visual.Render(context); |
||||
|
|
||||
|
#pragma warning disable 0618
|
||||
|
var transformed = |
||||
|
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform); |
||||
|
#pragma warning restore 0618
|
||||
|
|
||||
|
if (visual is Visual) |
||||
|
{ |
||||
|
BoundsTracker.SetTransformedBounds((Visual)visual, transformed); |
||||
|
} |
||||
|
|
||||
|
foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)) |
||||
|
{ |
||||
|
var childBounds = GetTransformedBounds(child); |
||||
|
|
||||
|
if (!child.ClipToBounds || clipRect.Intersects(childBounds)) |
||||
|
{ |
||||
|
var childClipRect = clipRect.Translate(-childBounds.Position); |
||||
|
Render(context, child, childClipRect); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ClearTransformedBounds(child); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!visual.IsVisible) |
||||
|
{ |
||||
|
ClearTransformedBounds(visual); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,67 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using Avalonia.Platform; |
|
||||
using Avalonia.VisualTree; |
|
||||
|
|
||||
namespace Avalonia.Rendering |
|
||||
{ |
|
||||
public class Renderer : IDisposable, IRenderer |
|
||||
{ |
|
||||
private readonly IRenderLoop _renderLoop; |
|
||||
private readonly IRenderRoot _root; |
|
||||
private IRenderTarget _renderTarget; |
|
||||
private bool _dirty; |
|
||||
|
|
||||
public Renderer(IRenderRoot root, IRenderLoop renderLoop) |
|
||||
{ |
|
||||
Contract.Requires<ArgumentNullException>(root != null); |
|
||||
|
|
||||
_root = root; |
|
||||
_renderLoop = renderLoop; |
|
||||
_renderLoop.Tick += OnRenderLoopTick; |
|
||||
} |
|
||||
|
|
||||
public void AddDirty(IVisual visual) |
|
||||
{ |
|
||||
_dirty = true; |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
_renderLoop.Tick -= OnRenderLoopTick; |
|
||||
} |
|
||||
|
|
||||
public void Render(Rect rect) |
|
||||
{ |
|
||||
if (_renderTarget == null) |
|
||||
{ |
|
||||
_renderTarget = _root.CreateRenderTarget(); |
|
||||
} |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
_renderTarget.Render(_root); |
|
||||
} |
|
||||
catch (RenderTargetCorruptedException ex) |
|
||||
{ |
|
||||
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); |
|
||||
_renderTarget.Dispose(); |
|
||||
_renderTarget = null; |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
_dirty = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void OnRenderLoopTick(object sender, EventArgs e) |
|
||||
{ |
|
||||
if (_dirty) |
|
||||
{ |
|
||||
_root.Invalidate(new Rect(_root.ClientSize)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,51 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Rendering |
||||
|
{ |
||||
|
public class RendererBase |
||||
|
{ |
||||
|
private static readonly Typeface s_fpsTypeface = new Typeface("Arial", 18); |
||||
|
private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); |
||||
|
private int _framesThisSecond; |
||||
|
private int _fps; |
||||
|
private FormattedText _fpsText; |
||||
|
private TimeSpan _lastFpsUpdate; |
||||
|
|
||||
|
public RendererBase() |
||||
|
{ |
||||
|
_fpsText = new FormattedText |
||||
|
{ |
||||
|
Typeface = new Typeface(null, 18), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount) |
||||
|
{ |
||||
|
var now = _stopwatch.Elapsed; |
||||
|
var elapsed = now - _lastFpsUpdate; |
||||
|
|
||||
|
if (incrementFrameCount) |
||||
|
{ |
||||
|
++_framesThisSecond; |
||||
|
} |
||||
|
|
||||
|
if (elapsed.TotalSeconds > 1) |
||||
|
{ |
||||
|
_fps = (int)(_framesThisSecond / elapsed.TotalSeconds); |
||||
|
_framesThisSecond = 0; |
||||
|
_lastFpsUpdate = now; |
||||
|
} |
||||
|
|
||||
|
_fpsText.Text = string.Format("FPS: {0:000}", _fps); |
||||
|
var size = _fpsText.Measure(); |
||||
|
var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height); |
||||
|
|
||||
|
context.Transform = Matrix.Identity; |
||||
|
context.FillRectangle(Brushes.Black, rect); |
||||
|
context.DrawText(Brushes.White, rect.TopLeft, _fpsText.PlatformImpl); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,205 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Diagnostics; |
|
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using Avalonia.Media; |
|
||||
using Avalonia.Platform; |
|
||||
using Avalonia.VisualTree; |
|
||||
|
|
||||
namespace Avalonia.Rendering |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Extension methods for rendering.
|
|
||||
/// </summary>
|
|
||||
/// <remarks>
|
|
||||
/// This class provides implements the platform-independent parts of <see cref="IRenderTarget"/>.
|
|
||||
/// </remarks>
|
|
||||
[SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] |
|
||||
[SuppressMessage("ReSharper", "ForCanBeConvertedToForeach")] |
|
||||
public static class RendererMixin |
|
||||
{ |
|
||||
private static int s_frameNum; |
|
||||
private static int s_fps; |
|
||||
private static int s_currentFrames; |
|
||||
private static TimeSpan s_lastMeasure; |
|
||||
private static readonly Stopwatch s_stopwatch = Stopwatch.StartNew(); |
|
||||
private static readonly Stack<List<IVisual>> s_listPool = new Stack<List<IVisual>>(); |
|
||||
private static readonly ZIndexComparer s_visualComparer = new ZIndexComparer(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets or sets a value which determines whether an FPS counted will be drawn on each
|
|
||||
/// rendered frame.
|
|
||||
/// </summary>
|
|
||||
public static bool DrawFpsCounter { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Renders the specified visual.
|
|
||||
/// </summary>
|
|
||||
/// <param name="renderTarget">IRenderer instance</param>
|
|
||||
/// <param name="visual">The visual to render.</param>
|
|
||||
public static void Render(this IRenderTarget renderTarget, IVisual visual) |
|
||||
{ |
|
||||
using (var ctx = renderTarget.CreateDrawingContext()) |
|
||||
{ |
|
||||
ctx.Render(visual); |
|
||||
s_frameNum++; |
|
||||
if (DrawFpsCounter) |
|
||||
{ |
|
||||
s_currentFrames++; |
|
||||
var now = s_stopwatch.Elapsed; |
|
||||
var elapsed = now - s_lastMeasure; |
|
||||
if (elapsed.TotalSeconds > 1) |
|
||||
{ |
|
||||
s_fps = (int) (s_currentFrames/elapsed.TotalSeconds); |
|
||||
s_currentFrames = 0; |
|
||||
s_lastMeasure = now; |
|
||||
} |
|
||||
var pt = new Point(40, 40); |
|
||||
var txt = new FormattedText |
|
||||
{ |
|
||||
Text = "Frame #" + s_frameNum + " FPS: " + s_fps, |
|
||||
Typeface = new Typeface("Arial", 18) |
|
||||
}; |
|
||||
ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure())); |
|
||||
ctx.DrawText(Brushes.Black, pt, txt); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Renders the specified visual.
|
|
||||
/// </summary>
|
|
||||
/// <param name="visual">The visual to render.</param>
|
|
||||
/// <param name="context">The drawing context.</param>
|
|
||||
public static void Render(this DrawingContext context, IVisual visual) |
|
||||
{ |
|
||||
context.Render(visual, visual.Bounds); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Renders the specified visual.
|
|
||||
/// </summary>
|
|
||||
/// <param name="visual">The visual to render.</param>
|
|
||||
/// <param name="context">The drawing context.</param>
|
|
||||
/// <param name="clipRect">
|
|
||||
/// The current clip rect, in coordinates relative to <paramref name="visual"/>.
|
|
||||
/// </param>
|
|
||||
private static void Render(this DrawingContext context, IVisual visual, Rect clipRect) |
|
||||
{ |
|
||||
var opacity = visual.Opacity; |
|
||||
var clipToBounds = visual.ClipToBounds; |
|
||||
var bounds = new Rect(visual.Bounds.Size); |
|
||||
|
|
||||
if (visual.IsVisible && opacity > 0) |
|
||||
{ |
|
||||
var m = Matrix.CreateTranslation(visual.Bounds.Position); |
|
||||
|
|
||||
var renderTransform = Matrix.Identity; |
|
||||
|
|
||||
if (visual.RenderTransform != null) |
|
||||
{ |
|
||||
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); |
|
||||
var offset = Matrix.CreateTranslation(origin); |
|
||||
renderTransform = (-offset) * visual.RenderTransform.Value * (offset); |
|
||||
} |
|
||||
|
|
||||
m = renderTransform * m; |
|
||||
|
|
||||
if (clipToBounds) |
|
||||
{ |
|
||||
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); |
|
||||
} |
|
||||
|
|
||||
using (context.PushPostTransform(m)) |
|
||||
using (context.PushOpacity(opacity)) |
|
||||
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState)) |
|
||||
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState)) |
|
||||
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState)) |
|
||||
using (context.PushTransformContainer()) |
|
||||
{ |
|
||||
visual.Render(context); |
|
||||
|
|
||||
#pragma warning disable 0618
|
|
||||
var transformed = |
|
||||
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform); |
|
||||
#pragma warning restore 0618
|
|
||||
|
|
||||
if (visual is Visual) |
|
||||
{ |
|
||||
BoundsTracker.SetTransformedBounds((Visual)visual, transformed); |
|
||||
} |
|
||||
|
|
||||
var lst = GetSortedVisualList(visual.VisualChildren); |
|
||||
|
|
||||
foreach (var child in lst) |
|
||||
{ |
|
||||
var childBounds = GetTransformedBounds(child); |
|
||||
|
|
||||
if (!child.ClipToBounds || clipRect.Intersects(childBounds)) |
|
||||
{ |
|
||||
var childClipRect = clipRect.Translate(-childBounds.Position); |
|
||||
context.Render(child, childClipRect); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
ClearTransformedBounds(child); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ReturnListToPool(lst); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!visual.IsVisible) |
|
||||
{ |
|
||||
ClearTransformedBounds(visual); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static void ClearTransformedBounds(IVisual visual) |
|
||||
{ |
|
||||
foreach (var e in visual.GetSelfAndVisualDescendents()) |
|
||||
{ |
|
||||
BoundsTracker.SetTransformedBounds((Visual)visual, null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static void ReturnListToPool(List<IVisual> lst) |
|
||||
{ |
|
||||
lst.Clear(); |
|
||||
s_listPool.Push(lst); |
|
||||
} |
|
||||
|
|
||||
private static List<IVisual> GetSortedVisualList(IReadOnlyList<IVisual> source) |
|
||||
{ |
|
||||
var lst = s_listPool.Count == 0 ? new List<IVisual>() : s_listPool.Pop(); |
|
||||
for (var c = 0; c < source.Count; c++) |
|
||||
lst.Add(source[c]); |
|
||||
lst.Sort(s_visualComparer); |
|
||||
return lst; |
|
||||
} |
|
||||
|
|
||||
private static Rect GetTransformedBounds(IVisual visual) |
|
||||
{ |
|
||||
if (visual.RenderTransform == null) |
|
||||
{ |
|
||||
return visual.Bounds; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); |
|
||||
var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin); |
|
||||
var m = (-offset) * visual.RenderTransform.Value * (offset); |
|
||||
return visual.Bounds.TransformToAABB(m); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class ZIndexComparer : IComparer<IVisual> |
|
||||
{ |
|
||||
public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,193 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Utilities |
||||
|
{ |
||||
|
public class TileBrushCalculator |
||||
|
{ |
||||
|
private readonly Size _imageSize; |
||||
|
private readonly Rect _drawRect; |
||||
|
|
||||
|
public bool IsValid { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TileBrushCalculator"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="brush">The brush to be rendered.</param>
|
||||
|
/// <param name="contentSize">The size of the content of the tile brush.</param>
|
||||
|
/// <param name="targetSize">The size of the control to which the brush is being rendered.</param>
|
||||
|
public TileBrushCalculator(ITileBrush brush, Size contentSize, Size targetSize) |
||||
|
: this( |
||||
|
brush.TileMode, |
||||
|
brush.Stretch, |
||||
|
brush.AlignmentX, |
||||
|
brush.AlignmentY, |
||||
|
brush.SourceRect, |
||||
|
brush.DestinationRect, |
||||
|
contentSize, |
||||
|
targetSize) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TileBrushCalculator"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="tileMode">The brush's tile mode.</param>
|
||||
|
/// <param name="stretch">The brush's stretch.</param>
|
||||
|
/// <param name="alignmentX">The brush's horizontal alignment.</param>
|
||||
|
/// <param name="alignmentY">The brush's vertical alignment.</param>
|
||||
|
/// <param name="sourceRect">The brush's source rect</param>
|
||||
|
/// <param name="destinationRect">The brush's destination rect.</param>
|
||||
|
/// <param name="contentSize">The size of the content of the tile brush.</param>
|
||||
|
/// <param name="targetSize">The size of the control to which the brush is being rendered.</param>
|
||||
|
public TileBrushCalculator( |
||||
|
TileMode tileMode, |
||||
|
Stretch stretch, |
||||
|
AlignmentX alignmentX, |
||||
|
AlignmentY alignmentY, |
||||
|
RelativeRect sourceRect, |
||||
|
RelativeRect destinationRect, |
||||
|
Size contentSize, |
||||
|
Size targetSize) |
||||
|
{ |
||||
|
_imageSize = contentSize; |
||||
|
|
||||
|
SourceRect = sourceRect.ToPixels(_imageSize); |
||||
|
DestinationRect = destinationRect.ToPixels(targetSize); |
||||
|
|
||||
|
var scale = stretch.CalculateScaling(DestinationRect.Size, SourceRect.Size); |
||||
|
var translate = CalculateTranslate(alignmentX, alignmentY, SourceRect, DestinationRect, scale); |
||||
|
|
||||
|
IntermediateSize = tileMode == TileMode.None ? targetSize : DestinationRect.Size; |
||||
|
IntermediateTransform = CalculateIntermediateTransform( |
||||
|
tileMode, |
||||
|
SourceRect, |
||||
|
DestinationRect, |
||||
|
scale, |
||||
|
translate, |
||||
|
out _drawRect); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the rectangle on the destination control to which content should be rendered.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// If <see cref="TileMode"/> of the brush is repeating then this is describes rectangle
|
||||
|
/// of a single repeat of the tiled content.
|
||||
|
/// </remarks>
|
||||
|
public Rect DestinationRect { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the clip rectangle on the intermediate image with which the brush content should be
|
||||
|
/// drawn when <see cref="NeedsIntermediate"/> is true.
|
||||
|
/// </summary>
|
||||
|
public Rect IntermediateClip => _drawRect; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the size of the intermediate image that should be created when
|
||||
|
/// <see cref="NeedsIntermediate"/> is true.
|
||||
|
/// </summary>
|
||||
|
public Size IntermediateSize { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the transform to be used when rendering to the intermediate image when
|
||||
|
/// <see cref="NeedsIntermediate"/> is true.
|
||||
|
/// </summary>
|
||||
|
public Matrix IntermediateTransform { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether an intermediate image should be created in order to
|
||||
|
/// render the tile brush.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Intermediate images are required when a brush's <see cref="TileMode"/> is not repeating
|
||||
|
/// but the source and destination aspect ratios are unequal, as all of the currently
|
||||
|
/// supported rendering backends do not support non-tiled image brushes.
|
||||
|
/// </remarks>
|
||||
|
public bool NeedsIntermediate |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (IntermediateTransform != Matrix.Identity) |
||||
|
return true; |
||||
|
if (SourceRect.Position != default(Point)) |
||||
|
return true; |
||||
|
if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) |
||||
|
return false; |
||||
|
if ((int)SourceRect.Width != _imageSize.Width || |
||||
|
(int)SourceRect.Height != _imageSize.Height) |
||||
|
return true; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the area of the source content to be rendered.
|
||||
|
/// </summary>
|
||||
|
public Rect SourceRect { get; } |
||||
|
|
||||
|
public static Vector CalculateTranslate( |
||||
|
AlignmentX alignmentX, |
||||
|
AlignmentY alignmentY, |
||||
|
Rect sourceRect, |
||||
|
Rect destinationRect, |
||||
|
Vector scale) |
||||
|
{ |
||||
|
var x = 0.0; |
||||
|
var y = 0.0; |
||||
|
var size = sourceRect.Size * scale; |
||||
|
|
||||
|
switch (alignmentX) |
||||
|
{ |
||||
|
case AlignmentX.Center: |
||||
|
x += (destinationRect.Width - size.Width) / 2; |
||||
|
break; |
||||
|
case AlignmentX.Right: |
||||
|
x += destinationRect.Width - size.Width; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
switch (alignmentY) |
||||
|
{ |
||||
|
case AlignmentY.Center: |
||||
|
y += (destinationRect.Height - size.Height) / 2; |
||||
|
break; |
||||
|
case AlignmentY.Bottom: |
||||
|
y += destinationRect.Height - size.Height; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return new Vector(x, y); |
||||
|
} |
||||
|
|
||||
|
public static Matrix CalculateIntermediateTransform( |
||||
|
TileMode tileMode, |
||||
|
Rect sourceRect, |
||||
|
Rect destinationRect, |
||||
|
Vector scale, |
||||
|
Vector translate, |
||||
|
out Rect drawRect) |
||||
|
{ |
||||
|
var transform = Matrix.CreateTranslation(-sourceRect.Position) * |
||||
|
Matrix.CreateScale(scale) * |
||||
|
Matrix.CreateTranslation(translate); |
||||
|
Rect dr; |
||||
|
|
||||
|
if (tileMode == TileMode.None) |
||||
|
{ |
||||
|
dr = destinationRect; |
||||
|
transform *= Matrix.CreateTranslation(destinationRect.Position); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
dr = new Rect(destinationRect.Size); |
||||
|
} |
||||
|
|
||||
|
drawRect = dr; |
||||
|
|
||||
|
return transform; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Rendering |
||||
|
{ |
||||
|
public class ZIndexComparer : IComparer<IVisual> |
||||
|
{ |
||||
|
public static readonly ZIndexComparer Instance = new ZIndexComparer(); |
||||
|
|
||||
|
public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex); |
||||
|
} |
||||
|
} |
||||
@ -1,15 +1,56 @@ |
|||||
using System; |
|
||||
using Avalonia.Media; |
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Utilities; |
||||
using global::Cairo; |
using global::Cairo; |
||||
|
|
||||
namespace Avalonia.Cairo.Media |
namespace Avalonia.Cairo.Media |
||||
{ |
{ |
||||
public class ImageBrushImpl : BrushImpl |
public class ImageBrushImpl : BrushImpl |
||||
{ |
{ |
||||
public ImageBrushImpl(IImageBrush brush, Size destinationSize) |
public ImageBrushImpl( |
||||
{ |
ITileBrush brush, |
||||
this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize); |
IBitmapImpl bitmap, |
||||
} |
Size targetSize) |
||||
} |
{ |
||||
|
var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize); |
||||
|
|
||||
|
using (var intermediate = new ImageSurface(Format.ARGB32, (int)calc.IntermediateSize.Width, (int)calc.IntermediateSize.Height)) |
||||
|
{ |
||||
|
using (var context = new RenderTarget(intermediate).CreateDrawingContext(null)) |
||||
|
{ |
||||
|
var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); |
||||
|
|
||||
|
context.Clear(Colors.Transparent); |
||||
|
context.PushClip(calc.IntermediateClip); |
||||
|
context.Transform = calc.IntermediateTransform; |
||||
|
context.DrawImage(bitmap, 1, rect, rect); |
||||
|
context.PopClip(); |
||||
|
} |
||||
|
|
||||
|
var result = new SurfacePattern(intermediate); |
||||
|
|
||||
|
if ((brush.TileMode & TileMode.FlipXY) != 0) |
||||
|
{ |
||||
|
// TODO: Currently always FlipXY as that's all cairo supports natively.
|
||||
|
// Support separate FlipX and FlipY by drawing flipped images to intermediate
|
||||
|
// surface.
|
||||
|
result.Extend = Extend.Reflect; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
result.Extend = Extend.Repeat; |
||||
|
} |
||||
|
|
||||
|
if (brush.TileMode != TileMode.None) |
||||
|
{ |
||||
|
var matrix = result.Matrix; |
||||
|
matrix.InitTranslate(-calc.DestinationRect.X, -calc.DestinationRect.Y); |
||||
|
result.Matrix = matrix; |
||||
|
} |
||||
|
|
||||
|
PlatformBrush = result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
|
|||||
@ -1,55 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using Cairo; |
|
||||
using Avalonia.Cairo.Media.Imaging; |
|
||||
using Avalonia.Layout; |
|
||||
using Avalonia.Media; |
|
||||
using Avalonia.Platform; |
|
||||
using Avalonia.Rendering; |
|
||||
using Avalonia.RenderHelpers; |
|
||||
|
|
||||
namespace Avalonia.Cairo.Media |
|
||||
{ |
|
||||
internal static class TileBrushes |
|
||||
{ |
|
||||
public static SurfacePattern CreateTileBrush(ITileBrush brush, Size targetSize) |
|
||||
{ |
|
||||
var helper = new TileBrushImplHelper(brush, targetSize); |
|
||||
if (!helper.IsValid) |
|
||||
return null; |
|
||||
|
|
||||
using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height)) |
|
||||
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext()) |
|
||||
{ |
|
||||
helper.DrawIntermediate(ctx); |
|
||||
|
|
||||
var result = new SurfacePattern(intermediate); |
|
||||
|
|
||||
if ((brush.TileMode & TileMode.FlipXY) != 0) |
|
||||
{ |
|
||||
// TODO: Currently always FlipXY as that's all cairo supports natively.
|
|
||||
// Support separate FlipX and FlipY by drawing flipped images to intermediate
|
|
||||
// surface.
|
|
||||
result.Extend = Extend.Reflect; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
result.Extend = Extend.Repeat; |
|
||||
} |
|
||||
|
|
||||
if (brush.TileMode != TileMode.None) |
|
||||
{ |
|
||||
var matrix = result.Matrix; |
|
||||
matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y); |
|
||||
result.Matrix = matrix; |
|
||||
} |
|
||||
|
|
||||
return result; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,15 +0,0 @@ |
|||||
using System; |
|
||||
using Avalonia.Media; |
|
||||
using global::Cairo; |
|
||||
|
|
||||
namespace Avalonia.Cairo.Media |
|
||||
{ |
|
||||
public class VisualBrushImpl : BrushImpl |
|
||||
{ |
|
||||
public VisualBrushImpl(IVisualBrush brush, Size destinationSize) |
|
||||
{ |
|
||||
this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@ -1,209 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Media; |
|
||||
using Avalonia.Rendering; |
|
||||
using Avalonia.VisualTree; |
|
||||
|
|
||||
namespace Avalonia.RenderHelpers |
|
||||
{ |
|
||||
internal class TileBrushImplHelper |
|
||||
{ |
|
||||
public Size IntermediateSize { get; } |
|
||||
public Rect DestinationRect { get; } |
|
||||
private readonly TileMode _tileMode; |
|
||||
private readonly Rect _sourceRect; |
|
||||
private readonly Vector _scale; |
|
||||
private readonly Vector _translate; |
|
||||
private readonly Size _imageSize; |
|
||||
private readonly IVisualBrush _visualBrush; |
|
||||
private readonly IImageBrush _imageBrush; |
|
||||
private readonly Matrix _transform; |
|
||||
private readonly Rect _drawRect; |
|
||||
|
|
||||
public bool IsValid { get; } |
|
||||
|
|
||||
public TileBrushImplHelper(ITileBrush brush, Size targetSize) |
|
||||
{ |
|
||||
_imageBrush = brush as IImageBrush; |
|
||||
_visualBrush = brush as IVisualBrush; |
|
||||
if (_imageBrush != null) |
|
||||
{ |
|
||||
if (_imageBrush.Source == null) |
|
||||
return; |
|
||||
_imageSize = new Size(_imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight); |
|
||||
IsValid = true; |
|
||||
} |
|
||||
else if (_visualBrush != null) |
|
||||
{ |
|
||||
var control = _visualBrush.Visual as IControl; |
|
||||
|
|
||||
if (control != null) |
|
||||
{ |
|
||||
EnsureInitialized(control); |
|
||||
|
|
||||
if (control.IsArrangeValid == false) |
|
||||
{ |
|
||||
control.Measure(Size.Infinity); |
|
||||
control.Arrange(new Rect(control.DesiredSize)); |
|
||||
} |
|
||||
|
|
||||
_imageSize = control.Bounds.Size; |
|
||||
IsValid = true; |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
return; |
|
||||
|
|
||||
_tileMode = brush.TileMode; |
|
||||
_sourceRect = brush.SourceRect.ToPixels(_imageSize); |
|
||||
DestinationRect = brush.DestinationRect.ToPixels(targetSize); |
|
||||
_scale = brush.Stretch.CalculateScaling(DestinationRect.Size, _sourceRect.Size); |
|
||||
_translate = CalculateTranslate(brush, _sourceRect, DestinationRect, _scale); |
|
||||
IntermediateSize = CalculateIntermediateSize(_tileMode, targetSize, DestinationRect.Size); |
|
||||
_transform = CalculateIntermediateTransform( |
|
||||
_tileMode, |
|
||||
_sourceRect, |
|
||||
DestinationRect, |
|
||||
_scale, |
|
||||
_translate, |
|
||||
out _drawRect); |
|
||||
} |
|
||||
|
|
||||
public bool NeedsIntermediateSurface |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
if (_imageBrush == null) |
|
||||
return true; |
|
||||
if (_transform != Matrix.Identity) |
|
||||
return true; |
|
||||
if (_sourceRect.Position != default(Point)) |
|
||||
return true; |
|
||||
if ((int) _sourceRect.Width != _imageBrush.Source.PixelWidth || |
|
||||
(int) _sourceRect.Height != _imageBrush.Source.PixelHeight) |
|
||||
return true; |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public T GetDirect<T>() => (T) _imageBrush?.Source.PlatformImpl; |
|
||||
|
|
||||
public void DrawIntermediate(DrawingContext ctx) |
|
||||
{ |
|
||||
using (ctx.PushClip(_drawRect)) |
|
||||
using (ctx.PushPostTransform(_transform)) |
|
||||
{ |
|
||||
if (_imageBrush != null) |
|
||||
{ |
|
||||
var bmpRc = new Rect(0, 0, _imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight); |
|
||||
ctx.DrawImage(_imageBrush.Source, 1, bmpRc, bmpRc); |
|
||||
} |
|
||||
else if (_visualBrush != null) |
|
||||
{ |
|
||||
using (ctx.PushPostTransform(Matrix.CreateTranslation(-_visualBrush.Visual.Bounds.Position))) |
|
||||
{ |
|
||||
ctx.Render(_visualBrush.Visual); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Calculates a translate based on an <see cref="ITileBrush"/>, a source and destination
|
|
||||
/// rectangle and a scale.
|
|
||||
/// </summary>
|
|
||||
/// <param name="brush">The brush.</param>
|
|
||||
/// <param name="sourceRect">The source rectangle.</param>
|
|
||||
/// <param name="destinationRect">The destination rectangle.</param>
|
|
||||
/// <param name="scale">The _scale factor.</param>
|
|
||||
/// <returns>A vector with the X and Y _translate.</returns>
|
|
||||
|
|
||||
public static Vector CalculateTranslate( |
|
||||
ITileBrush brush, |
|
||||
Rect sourceRect, |
|
||||
Rect destinationRect, |
|
||||
Vector scale) |
|
||||
{ |
|
||||
var x = 0.0; |
|
||||
var y = 0.0; |
|
||||
var size = sourceRect.Size*scale; |
|
||||
|
|
||||
switch (brush.AlignmentX) |
|
||||
{ |
|
||||
case AlignmentX.Center: |
|
||||
x += (destinationRect.Width - size.Width)/2; |
|
||||
break; |
|
||||
case AlignmentX.Right: |
|
||||
x += destinationRect.Width - size.Width; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
switch (brush.AlignmentY) |
|
||||
{ |
|
||||
case AlignmentY.Center: |
|
||||
y += (destinationRect.Height - size.Height)/2; |
|
||||
break; |
|
||||
case AlignmentY.Bottom: |
|
||||
y += destinationRect.Height - size.Height; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
return new Vector(x, y); |
|
||||
} |
|
||||
|
|
||||
public static Matrix CalculateIntermediateTransform( |
|
||||
TileMode tileMode, |
|
||||
Rect sourceRect, |
|
||||
Rect destinationRect, |
|
||||
Vector scale, |
|
||||
Vector translate, |
|
||||
out Rect drawRect) |
|
||||
{ |
|
||||
var transform = Matrix.CreateTranslation(-sourceRect.Position)* |
|
||||
Matrix.CreateScale(scale)* |
|
||||
Matrix.CreateTranslation(translate); |
|
||||
Rect dr; |
|
||||
|
|
||||
if (tileMode == TileMode.None) |
|
||||
{ |
|
||||
dr = destinationRect; |
|
||||
transform *= Matrix.CreateTranslation(destinationRect.Position); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
dr = new Rect(destinationRect.Size); |
|
||||
} |
|
||||
|
|
||||
drawRect = dr; |
|
||||
|
|
||||
return transform; |
|
||||
} |
|
||||
|
|
||||
private static Size CalculateIntermediateSize( |
|
||||
TileMode tileMode, |
|
||||
Size targetSize, |
|
||||
Size destinationSize) => tileMode == TileMode.None ? targetSize : destinationSize; |
|
||||
|
|
||||
private static void EnsureInitialized(IControl control) |
|
||||
{ |
|
||||
foreach (var i in control.GetSelfAndVisualDescendents()) |
|
||||
{ |
|
||||
var c = i as IControl; |
|
||||
|
|
||||
if (c?.IsInitialized == false) |
|
||||
{ |
|
||||
var init = c as ISupportInitialize; |
|
||||
|
|
||||
if (init != null) |
|
||||
{ |
|
||||
init.BeginInit(); |
|
||||
init.EndInit(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,509 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Controls.Presenters; |
|
||||
using Avalonia.Layout; |
|
||||
using Avalonia.Media; |
|
||||
using Avalonia.Platform; |
|
||||
using Avalonia.Rendering; |
|
||||
using Avalonia.UnitTests; |
|
||||
using Moq; |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.IO; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Avalonia.Input.UnitTests |
|
||||
{ |
|
||||
public class InputElement_HitTesting |
|
||||
{ |
|
||||
[Fact] |
|
||||
public void InputHitTest_Should_Find_Control_At_Point() |
|
||||
{ |
|
||||
using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) |
|
||||
{ |
|
||||
var container = new Decorator |
|
||||
{ |
|
||||
Width = 200, |
|
||||
Height = 200, |
|
||||
Child = new Border |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
HorizontalAlignment = HorizontalAlignment.Center, |
|
||||
VerticalAlignment = VerticalAlignment.Center |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
container.Measure(Size.Infinity); |
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
|
|
||||
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>()); |
|
||||
context.Render(container); |
|
||||
|
|
||||
var result = container.InputHitTest(new Point(100, 100)); |
|
||||
|
|
||||
Assert.Equal(container.Child, result); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void InputHitTest_Should_Not_Find_Control_Outside_Point() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) |
|
||||
{ |
|
||||
var container = new Decorator |
|
||||
{ |
|
||||
Width = 200, |
|
||||
Height = 200, |
|
||||
Child = new Border |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
HorizontalAlignment = HorizontalAlignment.Center, |
|
||||
VerticalAlignment = VerticalAlignment.Center |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
container.Measure(Size.Infinity); |
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
|
|
||||
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>()); |
|
||||
context.Render(container); |
|
||||
|
|
||||
var result = container.InputHitTest(new Point(10, 10)); |
|
||||
|
|
||||
Assert.Equal(container, result); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void InputHitTest_Should_Find_Top_Control_At_Point() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) |
|
||||
{ |
|
||||
var container = new Panel |
|
||||
{ |
|
||||
Width = 200, |
|
||||
Height = 200, |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
new Border |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
HorizontalAlignment = HorizontalAlignment.Center, |
|
||||
VerticalAlignment = VerticalAlignment.Center |
|
||||
}, |
|
||||
new Border |
|
||||
{ |
|
||||
Width = 50, |
|
||||
Height = 50, |
|
||||
HorizontalAlignment = HorizontalAlignment.Center, |
|
||||
VerticalAlignment = VerticalAlignment.Center |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
container.Measure(Size.Infinity); |
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
|
|
||||
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>()); |
|
||||
context.Render(container); |
|
||||
|
|
||||
var result = container.InputHitTest(new Point(100, 100)); |
|
||||
|
|
||||
Assert.Equal(container.Children[1], result); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void InputHitTest_Should_Find_Top_Control_At_Point_With_ZOrder() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) |
|
||||
{ |
|
||||
var container = new Panel |
|
||||
{ |
|
||||
Width = 200, |
|
||||
Height = 200, |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
new Border |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
ZIndex = 1, |
|
||||
HorizontalAlignment = HorizontalAlignment.Center, |
|
||||
VerticalAlignment = VerticalAlignment.Center |
|
||||
}, |
|
||||
new Border |
|
||||
{ |
|
||||
Width = 50, |
|
||||
Height = 50, |
|
||||
HorizontalAlignment = HorizontalAlignment.Center, |
|
||||
VerticalAlignment = VerticalAlignment.Center |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
container.Measure(Size.Infinity); |
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
|
|
||||
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>()); |
|
||||
context.Render(container); |
|
||||
|
|
||||
var result = container.InputHitTest(new Point(100, 100)); |
|
||||
|
|
||||
Assert.Equal(container.Children[0], result); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void InputHitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) |
|
||||
{ |
|
||||
Border target; |
|
||||
var container = new Panel |
|
||||
{ |
|
||||
Width = 200, |
|
||||
Height = 200, |
|
||||
ClipToBounds = false, |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
new Border |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
ZIndex = 1, |
|
||||
HorizontalAlignment = HorizontalAlignment.Left, |
|
||||
VerticalAlignment = VerticalAlignment.Top, |
|
||||
Child = target = new Border |
|
||||
{ |
|
||||
Width = 50, |
|
||||
Height = 50, |
|
||||
HorizontalAlignment = HorizontalAlignment.Left, |
|
||||
VerticalAlignment = VerticalAlignment.Top, |
|
||||
RenderTransform = new TranslateTransform(110, 110), |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
container.Measure(Size.Infinity); |
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
|
|
||||
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>()); |
|
||||
context.Render(container); |
|
||||
|
|
||||
var result = container.InputHitTest(new Point(120, 120)); |
|
||||
|
|
||||
Assert.Equal(target, result); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void InputHitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) |
|
||||
{ |
|
||||
Border target; |
|
||||
|
|
||||
var container = new Panel |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 200, |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
new Panel() |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
Margin = new Thickness(0, 100, 0, 0), |
|
||||
ClipToBounds = true, |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
(target = new Border() |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
Margin = new Thickness(0, -100, 0, 0) |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
container.Measure(Size.Infinity); |
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
|
|
||||
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>()); |
|
||||
context.Render(container); |
|
||||
|
|
||||
var result = container.InputHitTest(new Point(50, 50)); |
|
||||
|
|
||||
Assert.NotEqual(target, result); |
|
||||
Assert.Equal(container, result); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void InputHitTest_Should_Not_Find_Control_Outside_Scroll_ViewPort() |
|
||||
{ |
|
||||
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) |
|
||||
{ |
|
||||
Border target; |
|
||||
Border item1; |
|
||||
Border item2; |
|
||||
ScrollContentPresenter scroll; |
|
||||
|
|
||||
var container = new Panel |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 200, |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
(target = new Border() |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100 |
|
||||
}), |
|
||||
new Border() |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
Margin = new Thickness(0, 100, 0, 0), |
|
||||
Child = scroll = new ScrollContentPresenter() |
|
||||
{ |
|
||||
Content = new StackPanel() |
|
||||
{ |
|
||||
Children = new Controls.Controls |
|
||||
{ |
|
||||
(item1 = new Border() |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
}), |
|
||||
(item2 = new Border() |
|
||||
{ |
|
||||
Width = 100, |
|
||||
Height = 100, |
|
||||
}), |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
scroll.UpdateChild(); |
|
||||
|
|
||||
container.Measure(Size.Infinity); |
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
|
|
||||
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>()); |
|
||||
context.Render(container); |
|
||||
|
|
||||
var result = container.InputHitTest(new Point(50, 150)); |
|
||||
|
|
||||
Assert.Equal(item1, result); |
|
||||
|
|
||||
result = container.InputHitTest(new Point(50, 50)); |
|
||||
|
|
||||
Assert.Equal(target, result); |
|
||||
|
|
||||
scroll.Offset = new Vector(0, 100); |
|
||||
|
|
||||
//we don't have setup LayoutManager so we will make it manually
|
|
||||
scroll.Parent.InvalidateArrange(); |
|
||||
container.InvalidateArrange(); |
|
||||
|
|
||||
container.Arrange(new Rect(container.DesiredSize)); |
|
||||
context.Render(container); |
|
||||
|
|
||||
result = container.InputHitTest(new Point(50, 150)); |
|
||||
|
|
||||
Assert.Equal(item2, result); |
|
||||
|
|
||||
result = container.InputHitTest(new Point(50, 50)); |
|
||||
|
|
||||
Assert.NotEqual(item1, result); |
|
||||
Assert.Equal(target, result); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
class MockRenderInterface : IPlatformRenderInterface |
|
||||
{ |
|
||||
public IFormattedTextImpl CreateFormattedText(string text, Typeface typeface, TextAlignment textAlignment, TextWrapping wrapping, Size constraint, IReadOnlyList<FormattedTextStyleSpan> spans) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IStreamGeometryImpl CreateStreamGeometry() |
|
||||
{ |
|
||||
return new MockStreamGeometry(); |
|
||||
} |
|
||||
|
|
||||
public IBitmapImpl LoadBitmap(Stream stream) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IBitmapImpl LoadBitmap(string fileName) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
class MockStreamGeometry : Avalonia.Platform.IStreamGeometryImpl |
|
||||
{ |
|
||||
private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); |
|
||||
public Rect Bounds |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public Matrix Transform |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
set |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public IStreamGeometryImpl Clone() |
|
||||
{ |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
public bool FillContains(Point point) |
|
||||
{ |
|
||||
return _impl.FillContains(point); |
|
||||
} |
|
||||
|
|
||||
public Rect GetRenderBounds(double strokeThickness) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IGeometryImpl Intersect(IGeometryImpl geometry) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IStreamGeometryContextImpl Open() |
|
||||
{ |
|
||||
return _impl; |
|
||||
} |
|
||||
|
|
||||
public bool StrokeContains(Pen pen, Point point) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IGeometryImpl WithTransform(Matrix transform) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
class MockStreamGeometryContext : IStreamGeometryContextImpl |
|
||||
{ |
|
||||
private List<Point> points = new List<Point>(); |
|
||||
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public void BeginFigure(Point startPoint, bool isFilled) |
|
||||
{ |
|
||||
points.Add(startPoint); |
|
||||
} |
|
||||
|
|
||||
public void CubicBezierTo(Point point1, Point point2, Point point3) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public void EndFigure(bool isClosed) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public void LineTo(Point point) |
|
||||
{ |
|
||||
points.Add(point); |
|
||||
} |
|
||||
|
|
||||
public void QuadraticBezierTo(Point control, Point endPoint) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public void SetFillRule(FillRule fillRule) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public bool FillContains(Point point) |
|
||||
{ |
|
||||
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
|
|
||||
// to determine if the point is in the geometry (since it will always be convex in this situation)
|
|
||||
for (int i = 0; i < points.Count; i++) |
|
||||
{ |
|
||||
var a = points[i]; |
|
||||
var b = points[(i + 1) % points.Count]; |
|
||||
var c = points[(i + 2) % points.Count]; |
|
||||
|
|
||||
Vector v0 = c - a; |
|
||||
Vector v1 = b - a; |
|
||||
Vector v2 = point - a; |
|
||||
|
|
||||
var dot00 = v0 * v0; |
|
||||
var dot01 = v0 * v1; |
|
||||
var dot02 = v0 * v2; |
|
||||
var dot11 = v1 * v1; |
|
||||
var dot12 = v1 * v2; |
|
||||
|
|
||||
|
|
||||
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); |
|
||||
var u = (dot11 * dot02 - dot01 * dot12) * invDenom; |
|
||||
var v = (dot00 * dot12 - dot01 * dot02) * invDenom; |
|
||||
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true; |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,62 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using Moq; |
||||
|
|
||||
|
namespace Avalonia.UnitTests |
||||
|
{ |
||||
|
public class MockPlatformRenderInterface : IPlatformRenderInterface |
||||
|
{ |
||||
|
public IFormattedTextImpl CreateFormattedText( |
||||
|
string text, |
||||
|
Typeface typeface, |
||||
|
TextAlignment textAlignment, |
||||
|
TextWrapping wrapping, |
||||
|
Size constraint, |
||||
|
IReadOnlyList<FormattedTextStyleSpan> spans) |
||||
|
{ |
||||
|
return Mock.Of<IFormattedTextImpl>(); |
||||
|
} |
||||
|
|
||||
|
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces) |
||||
|
{ |
||||
|
return Mock.Of<IRenderTarget>(); |
||||
|
} |
||||
|
|
||||
|
public IRenderTargetBitmapImpl CreateRenderTargetBitmap( |
||||
|
int width, |
||||
|
int height, |
||||
|
double dpiX, |
||||
|
double dpiY) |
||||
|
{ |
||||
|
return Mock.Of<IRenderTargetBitmapImpl>(); |
||||
|
} |
||||
|
|
||||
|
public IStreamGeometryImpl CreateStreamGeometry() |
||||
|
{ |
||||
|
return new MockStreamGeometryImpl(); |
||||
|
} |
||||
|
|
||||
|
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = default(PixelFormat?)) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public IBitmapImpl LoadBitmap(Stream stream) |
||||
|
{ |
||||
|
return Mock.Of<IBitmapImpl>(); |
||||
|
} |
||||
|
|
||||
|
public IBitmapImpl LoadBitmap(string fileName) |
||||
|
{ |
||||
|
return Mock.Of<IBitmapImpl>(); |
||||
|
} |
||||
|
|
||||
|
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,152 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.UnitTests |
||||
|
{ |
||||
|
public class MockStreamGeometryImpl : IStreamGeometryImpl |
||||
|
{ |
||||
|
private MockStreamGeometryContext _context; |
||||
|
|
||||
|
public MockStreamGeometryImpl() |
||||
|
{ |
||||
|
Transform = Matrix.Identity; |
||||
|
_context = new MockStreamGeometryContext(); |
||||
|
} |
||||
|
|
||||
|
public MockStreamGeometryImpl(Matrix transform) |
||||
|
{ |
||||
|
Transform = transform; |
||||
|
_context = new MockStreamGeometryContext(); |
||||
|
} |
||||
|
|
||||
|
private MockStreamGeometryImpl(Matrix transform, MockStreamGeometryContext context) |
||||
|
{ |
||||
|
Transform = transform; |
||||
|
_context = context; |
||||
|
} |
||||
|
|
||||
|
public Rect Bounds => _context.CalculateBounds(); |
||||
|
|
||||
|
public Matrix Transform { get; } |
||||
|
|
||||
|
public IStreamGeometryImpl Clone() |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public bool FillContains(Point point) |
||||
|
{ |
||||
|
return _context.FillContains(point); |
||||
|
} |
||||
|
|
||||
|
public bool StrokeContains(Pen pen, Point point) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public Rect GetRenderBounds(double strokeThickness) => Bounds; |
||||
|
|
||||
|
public IGeometryImpl Intersect(IGeometryImpl geometry) |
||||
|
{ |
||||
|
return new MockStreamGeometryImpl(Transform); |
||||
|
} |
||||
|
|
||||
|
public IStreamGeometryContextImpl Open() |
||||
|
{ |
||||
|
return _context; |
||||
|
} |
||||
|
|
||||
|
public IGeometryImpl WithTransform(Matrix transform) |
||||
|
{ |
||||
|
return new MockStreamGeometryImpl(transform, _context); |
||||
|
} |
||||
|
|
||||
|
class MockStreamGeometryContext : IStreamGeometryContextImpl |
||||
|
{ |
||||
|
private List<Point> points = new List<Point>(); |
||||
|
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void BeginFigure(Point startPoint, bool isFilled) |
||||
|
{ |
||||
|
points.Add(startPoint); |
||||
|
} |
||||
|
|
||||
|
public Rect CalculateBounds() |
||||
|
{ |
||||
|
var left = double.MaxValue; |
||||
|
var right = double.MinValue; |
||||
|
var top = double.MaxValue; |
||||
|
var bottom = double.MinValue; |
||||
|
|
||||
|
foreach (var p in points) |
||||
|
{ |
||||
|
left = Math.Min(p.X, left); |
||||
|
right = Math.Max(p.X, right); |
||||
|
top = Math.Min(p.Y, top); |
||||
|
bottom = Math.Max(p.Y, bottom); |
||||
|
} |
||||
|
|
||||
|
return new Rect(new Point(left, top), new Point(right, bottom)); |
||||
|
} |
||||
|
|
||||
|
public void CubicBezierTo(Point point1, Point point2, Point point3) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void EndFigure(bool isClosed) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void LineTo(Point point) |
||||
|
{ |
||||
|
points.Add(point); |
||||
|
} |
||||
|
|
||||
|
public void QuadraticBezierTo(Point control, Point endPoint) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public void SetFillRule(FillRule fillRule) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool FillContains(Point point) |
||||
|
{ |
||||
|
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
|
||||
|
// to determine if the point is in the geometry (since it will always be convex in this situation)
|
||||
|
for (int i = 0; i < points.Count; i++) |
||||
|
{ |
||||
|
var a = points[i]; |
||||
|
var b = points[(i + 1) % points.Count]; |
||||
|
var c = points[(i + 2) % points.Count]; |
||||
|
|
||||
|
Vector v0 = c - a; |
||||
|
Vector v1 = b - a; |
||||
|
Vector v2 = point - a; |
||||
|
|
||||
|
var dot00 = v0 * v0; |
||||
|
var dot01 = v0 * v1; |
||||
|
var dot02 = v0 * v2; |
||||
|
var dot11 = v1 * v1; |
||||
|
var dot12 = v1 * v2; |
||||
|
|
||||
|
|
||||
|
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); |
||||
|
var u = (dot11 * dot02 - dot01 * dot12) * invDenom; |
||||
|
var v = (dot00 * dot12 - dot01 * dot02) * invDenom; |
||||
|
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,39 +0,0 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|
||||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
||||
|
|
||||
using System; |
|
||||
using Avalonia.Platform; |
|
||||
using Avalonia.Rendering; |
|
||||
|
|
||||
namespace Avalonia.Visuals.UnitTests |
|
||||
{ |
|
||||
public class TestRoot : TestVisual, IRenderRoot |
|
||||
{ |
|
||||
public Size ClientSize { get; } |
|
||||
|
|
||||
public IRenderTarget CreateRenderTarget() |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public void Invalidate(Rect rect) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public IRenderer Renderer |
|
||||
{ |
|
||||
get { throw new NotImplementedException(); } |
|
||||
} |
|
||||
|
|
||||
public Point PointToClient(Point p) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public Point PointToScreen(Point p) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue