Browse Source

Merge branch 'master' into unify-render-animation-timers

pull/1715/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
1765e1691e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      samples/ControlCatalog/App.xaml
  2. 59
      samples/ControlCatalog/Pages/MenuPage.xaml
  3. 38
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  4. 278
      samples/interop/Direct3DInteropSample/MainWindow.cs
  5. 7
      src/Avalonia.Base/PriorityBindingEntry.cs
  6. 14
      src/Avalonia.Base/PriorityLevel.cs
  7. 8
      src/Avalonia.Controls/Border.cs
  8. 2
      src/Avalonia.Controls/Button.cs
  9. 6
      src/Avalonia.Controls/ButtonSpinner.cs
  10. 4
      src/Avalonia.Controls/ContentControl.cs
  11. 2
      src/Avalonia.Controls/Decorator.cs
  12. 8
      src/Avalonia.Controls/DrawingPresenter.cs
  13. 10
      src/Avalonia.Controls/Expander.cs
  14. 3
      src/Avalonia.Controls/Image.cs
  15. 38
      src/Avalonia.Controls/MenuItem.cs
  16. 1
      src/Avalonia.Controls/Panel.cs
  17. 4
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  18. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  19. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  20. 2
      src/Avalonia.Controls/Primitives/AccessText.cs
  21. 4
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  22. 6
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  23. 4
      src/Avalonia.Controls/Primitives/Track.cs
  24. 6
      src/Avalonia.Controls/ProgressBar.cs
  25. 5
      src/Avalonia.Controls/Shapes/Shape.cs
  26. 2
      src/Avalonia.Controls/Slider.cs
  27. 4
      src/Avalonia.Controls/StackPanel.cs
  28. 2
      src/Avalonia.Controls/TabControl.cs
  29. 13
      src/Avalonia.Controls/TextBlock.cs
  30. 2
      src/Avalonia.Controls/TopLevel.cs
  31. 2
      src/Avalonia.Controls/WrapPanel.cs
  32. 6
      src/Avalonia.Input/InputElement.cs
  33. 68
      src/Avalonia.Layout/Layoutable.cs
  34. 45
      src/Avalonia.Styling/StyledElement.cs
  35. 12
      src/Avalonia.Themes.Default/MenuItem.xaml
  36. 8
      src/Avalonia.Visuals/Animation/CrossFade.cs
  37. 14
      src/Avalonia.Visuals/Animation/PageSlide.cs
  38. 36
      src/Avalonia.Visuals/Media/Brush.cs
  39. 65
      src/Avalonia.Visuals/Media/GradientBrush.cs
  40. 40
      src/Avalonia.Visuals/Media/GradientStop.cs
  41. 23
      src/Avalonia.Visuals/Media/GradientStops.cs
  42. 16
      src/Avalonia.Visuals/Media/IAffectsRender.cs
  43. 4
      src/Avalonia.Visuals/Media/IGradientBrush.cs
  44. 18
      src/Avalonia.Visuals/Media/IGradientStop.cs
  45. 6
      src/Avalonia.Visuals/Media/IMutableBrush.cs
  46. 12
      src/Avalonia.Visuals/Media/ImageBrush.cs
  47. 9
      src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
  48. 20
      src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs
  49. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
  50. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
  51. 13
      src/Avalonia.Visuals/Media/LinearGradientBrush.cs
  52. 10
      src/Avalonia.Visuals/Media/RadialGradientBrush.cs
  53. 13
      src/Avalonia.Visuals/Media/SolidColorBrush.cs
  54. 7
      src/Avalonia.Visuals/Media/TileBrush.cs
  55. 12
      src/Avalonia.Visuals/Media/VisualBrush.cs
  56. 51
      src/Avalonia.Visuals/Visual.cs
  57. 92
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  58. 19
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  59. 23
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  60. 2
      src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs
  61. 40
      src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
  62. 13
      src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs
  63. 113
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  64. 32
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
  65. 3
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  66. 7
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  67. 47
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  68. 41
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  69. 33
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  70. 22
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  71. 9
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
  72. 6
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
  73. 18
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  74. 142
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  75. 14
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  76. 21
      tests/Avalonia.Controls.UnitTests/BorderTests.cs
  77. 26
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  78. 21
      tests/Avalonia.Controls.UnitTests/PanelTests.cs
  79. 20
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  80. 48
      tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs
  81. 38
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
  82. 2
      tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs
  83. 4
      tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
  84. 2
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
  85. 4
      tests/Avalonia.RenderTests/OpacityMaskTests.cs
  86. 7
      tests/Avalonia.UnitTests/TestRoot.cs
  87. 27
      tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs
  88. 86
      tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs
  89. 21
      tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs

9
samples/ControlCatalog/App.xaml

@ -14,6 +14,11 @@
<Setter Property="FontSize" Value="13"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
<Style Selector="TextBlock.h3">
<Setter Property="Foreground" Value="#a2a2a2"/>
<Setter Property="FontSize" Value="13"/>
</Style>
<StyleInclude Source="resm:ControlCatalog.SideBar.xaml"/>
</Application.Styles>
</Application>
</Application>

59
samples/ControlCatalog/Pages/MenuPage.xaml

@ -7,29 +7,44 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Menu>
<MenuItem Header="_First">
<MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Defined in XAML</TextBlock>
<Menu>
<MenuItem Header="_First">
<MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
<MenuItem Header="_Second">
<MenuItem Header="Second _Menu Item"/>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_Second">
<MenuItem Header="Second _Menu Item"/>
</MenuItem>
</Menu>
</Menu>
</StackPanel>
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Dyanamically generated</TextBlock>
<Menu Items="{Binding}">
<Menu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
</Style>
</Menu.Styles>
</Menu>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>
</UserControl>

38
samples/ControlCatalog/Pages/MenuPage.xaml.cs

@ -1,3 +1,4 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
@ -8,6 +9,37 @@ namespace ControlCatalog.Pages
public MenuPage()
{
this.InitializeComponent();
DataContext = new[]
{
new MenuItemViewModel
{
Header = "_File",
Items = new[]
{
new MenuItemViewModel { Header = "_Open..." },
new MenuItemViewModel { Header = "Save" },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel { Header = "File1.txt" },
new MenuItemViewModel { Header = "File2.txt" },
}
},
}
},
new MenuItemViewModel
{
Header = "_Edit",
Items = new[]
{
new MenuItemViewModel { Header = "_Copy" },
new MenuItemViewModel { Header = "_Paste" },
}
}
};
}
private void InitializeComponent()
@ -15,4 +47,10 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
}
public class MenuItemViewModel
{
public string Header { get; set; }
public IList<MenuItemViewModel> Items { get; set; }
}
}

278
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -1,81 +1,83 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 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;
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 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 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
{
class MainWindow : Window
public 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;
Texture2D _backBuffer;
RenderTargetView _renderView;
Texture2D _depthBuffer;
DepthStencilView _depthView;
private readonly SwapChain _swapChain;
private SwapChainDescription _desc;
private SwapChainDescription1 _desc;
private Matrix _proj = Matrix.Identity;
private Matrix _view;
private readonly Matrix _view;
private Buffer _contantBuffer;
private SharpDX.Direct2D1.Device _d2dDevice;
private SharpDX.Direct2D1.DeviceContext _d2dContext;
private RenderTarget _d2dRenderTarget;
private MainWindowViewModel _model;
private DeviceContext _deviceContext;
private readonly 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()
_desc = new SwapChainDescription1()
{
BufferCount = 1,
ModeDescription =
new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height,
new Rational(60, 1), Format.R8G8B8A8_UNorm),
IsWindowed = true,
OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero,
Width = (int)ClientSize.Width,
Height = (int)ClientSize.Height,
Format = Format.R8G8B8A8_UNorm,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput
};
_swapChain = new SwapChain(new Factory1(), _d3dDevice, _desc);
using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent<Factory2>())
{
_swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc);
}
_d2dContext = new SharpDX.Direct2D1.DeviceContext(_d2dDevice, DeviceContextOptions.None)
_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;
}
@ -83,29 +85,32 @@ namespace Direct3DInteropSample
protected override void HandlePaint(Rect rect)
{
var viewProj = Matrix.Multiply(_view, _proj);
var context = _d3dDevice.ImmediateContext;
var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Clear views
context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
context.ClearRenderTargetView(renderView, Color.White);
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;
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()
private void CreateMesh()
{
var device = _d3dDevice;
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);
@ -114,63 +119,72 @@ namespace Direct3DInteropSample
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, 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),
});
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 = _d3dDevice.ImmediateContext;
var context = Direct2D1Platform.Direct3D11Device.ImmediateContext;
// Prepare All the stages
context.InputAssembler.InputLayout = layout;
@ -181,63 +195,73 @@ namespace Direct3DInteropSample
context.PixelShader.Set(pixelShader);
}
void Resize(Size size)
private 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;
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(_desc.BufferCount, (int)size.Width, (int)size.Height, Format.Unknown, SwapChainFlags.None);
_swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None);
// Get the backbuffer from the swapchain
backBuffer = Texture2D.FromSwapChain<Texture2D>(_swapChain, 0);
_backBuffer = Resource.FromSwapChain<Texture2D>(_swapChain, 0);
// Renderview on the backbuffer
renderView = new RenderTargetView(_d3dDevice, backBuffer);
_renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _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
});
_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(_d3dDevice, depthBuffer);
_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);
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
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)
PixelFormat = new PixelFormat(
Format.Unknown,
AlphaMode.Premultiplied)
});
}
_deviceContext = renderTarget.QueryInterface<DeviceContext>();
renderTarget.Dispose();
}
}
class D3DRenderTarget: IRenderTarget
private class D3DRenderTarget : IRenderTarget
{
private readonly MainWindow _window;
@ -245,16 +269,14 @@ namespace Direct3DInteropSample
{
_window = window;
}
public void Dispose()
{
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(),
AvaloniaLocator.Current.GetService<ImagingFactory>());
return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext);
}
}

7
src/Avalonia.Base/PriorityBindingEntry.cs

@ -50,6 +50,11 @@ namespace Avalonia
get;
}
/// <summary>
/// Gets a value indicating whether the binding has completed.
/// </summary>
public bool HasCompleted { get; private set; }
/// <summary>
/// The current value of the binding.
/// </summary>
@ -129,6 +134,8 @@ namespace Avalonia
private void Completed()
{
HasCompleted = true;
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Completed(this);

14
src/Avalonia.Base/PriorityLevel.cs

@ -112,12 +112,16 @@ namespace Avalonia
return Disposable.Create(() =>
{
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
if (!entry.HasCompleted)
{
ActivateFirstBinding();
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{
ActivateFirstBinding();
}
}
});
}

8
src/Avalonia.Controls/Border.cs

@ -43,8 +43,12 @@ namespace Avalonia.Controls
/// </summary>
static Border()
{
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure(BorderThicknessProperty);
AffectsRender<Border>(
BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty);
AffectsMeasure<Border>(BorderThicknessProperty);
}
/// <summary>

2
src/Avalonia.Controls/Button.cs

@ -80,7 +80,7 @@ namespace Avalonia.Controls
FocusableProperty.OverrideDefaultValue(typeof(Button), true);
CommandProperty.Changed.Subscribe(CommandChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
PseudoClass(IsPressedProperty, ":pressed");
PseudoClass<Button>(IsPressedProperty, ":pressed");
}
/// <summary>

6
src/Avalonia.Controls/ButtonSpinner.cs

@ -85,8 +85,8 @@ namespace Avalonia.Controls
static ButtonSpinner()
{
AllowSpinProperty.Changed.Subscribe(AllowSpinChanged);
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
}
/// <summary>
@ -260,4 +260,4 @@ namespace Avalonia.Controls
}
}
}
}
}

4
src/Avalonia.Controls/ContentControl.cs

@ -45,8 +45,8 @@ namespace Avalonia.Controls
static ContentControl()
{
ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
PseudoClass(ContentProperty, x => x != null, ":valid");
PseudoClass(ContentProperty, x => x == null, ":invalid");
PseudoClass<ContentControl, object>(ContentProperty, x => x != null, ":valid");
PseudoClass<ContentControl, object>(ContentProperty, x => x == null, ":invalid");
}
/// <summary>

2
src/Avalonia.Controls/Decorator.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls
/// </summary>
static Decorator()
{
AffectsMeasure(ChildProperty, PaddingProperty);
AffectsMeasure<Decorator>(ChildProperty, PaddingProperty);
ChildProperty.Changed.AddClassHandler<Decorator>(x => x.ChildChanged);
}

8
src/Avalonia.Controls/DrawingPresenter.cs

@ -8,8 +8,8 @@ namespace Avalonia.Controls
{
static DrawingPresenter()
{
AffectsMeasure(DrawingProperty);
AffectsRender(DrawingProperty);
AffectsMeasure<DrawingPresenter>(DrawingProperty);
AffectsRender<DrawingPresenter>(DrawingProperty);
}
public static readonly StyledProperty<Drawing> DrawingProperty =
@ -49,11 +49,11 @@ namespace Avalonia.Controls
if (Drawing != null)
{
using (context.PushPreTransform(_transform))
using (context.PushClip(Bounds))
using (context.PushClip(new Rect(Bounds.Size)))
{
Drawing.Draw(context);
}
}
}
}
}
}

10
src/Avalonia.Controls/Expander.cs

@ -35,12 +35,12 @@ namespace Avalonia.Controls
static Expander()
{
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up");
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left");
PseudoClass(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right");
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up");
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left");
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right");
PseudoClass(IsExpandedProperty, ":expanded");
PseudoClass<Expander>(IsExpandedProperty, ":expanded");
IsExpandedProperty.Changed.AddClassHandler<Expander>(x => x.OnIsExpandedChanged);
}

3
src/Avalonia.Controls/Image.cs

@ -25,8 +25,7 @@ namespace Avalonia.Controls
static Image()
{
AffectsRender(SourceProperty);
AffectsRender(StretchProperty);
AffectsRender<Image>(SourceProperty, StretchProperty);
}
/// <summary>

38
src/Avalonia.Controls/MenuItem.cs

@ -99,6 +99,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>(x => x.HeaderChanged);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
@ -357,10 +358,21 @@ namespace Avalonia.Controls
{
base.OnTemplateApplied(e);
_popup = e.NameScope.Get<Popup>("PART_Popup");
_popup.DependencyResolver = DependencyResolver.Instance;
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
if (_popup != null)
{
_popup.Opened -= PopupOpened;
_popup.Closed -= PopupClosed;
_popup.DependencyResolver = null;
}
_popup = e.NameScope.Find<Popup>("PART_Popup");
if (_popup != null)
{
_popup.DependencyResolver = DependencyResolver.Instance;
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
}
}
/// <summary>
@ -408,6 +420,24 @@ namespace Avalonia.Controls
IsEnabled = Command == null || Command.CanExecute(CommandParameter);
}
/// <summary>
/// Called when the <see cref="Header"/> property changes.
/// </summary>
/// <param name="e">The property change event.</param>
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.NewValue is string newValue && newValue == "-")
{
PseudoClasses.Add(":separator");
Focusable = false;
}
else if (e.OldValue is string oldValue && oldValue == "-")
{
PseudoClasses.Remove(":separator");
Focusable = true;
}
}
/// <summary>
/// Called when the <see cref="Icon"/> property changes.
/// </summary>

1
src/Avalonia.Controls/Panel.cs

@ -30,6 +30,7 @@ namespace Avalonia.Controls
/// </summary>
static Panel()
{
AffectsRender<Panel>(BackgroundProperty);
ClipToBoundsProperty.OverrideDefaultValue<Panel>(true);
}

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

@ -90,8 +90,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
static ContentPresenter()
{
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure(BorderThicknessProperty, PaddingProperty);
AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);

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

@ -72,7 +72,7 @@ namespace Avalonia.Controls.Presenters
{
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged);
AffectsArrange(OffsetProperty);
AffectsArrange<ScrollContentPresenter>(OffsetProperty);
}
/// <summary>

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

@ -38,7 +38,7 @@ namespace Avalonia.Controls.Presenters
static TextPresenter()
{
AffectsRender(PasswordCharProperty);
AffectsRender<TextPresenter>(PasswordCharProperty);
}
public TextPresenter()

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

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static AccessText()
{
AffectsRender(ShowAccessKeyProperty);
AffectsRender<AccessText>(ShowAccessKeyProperty);
}
/// <summary>

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

@ -54,8 +54,8 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static ScrollBar()
{
PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragComplete, RoutingStrategies.Bubble);

6
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -24,9 +24,9 @@ namespace Avalonia.Controls.Primitives
static ToggleButton()
{
PseudoClass(IsCheckedProperty, c => c == true, ":checked");
PseudoClass(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass(IsCheckedProperty, c => c == null, ":indeterminate");
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == true, ":checked");
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == null, ":indeterminate");
}
public bool? IsChecked

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

@ -39,10 +39,12 @@ namespace Avalonia.Controls.Primitives
static Track()
{
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
ThumbProperty.Changed.AddClassHandler<Track>(x => x.ThumbChanged);
IncreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
DecreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
AffectsArrange(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
}
public double Minimum

6
src/Avalonia.Controls/ProgressBar.cs

@ -33,9 +33,9 @@ namespace Avalonia.Controls
static ProgressBar()
{
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
PseudoClass(IsIndeterminateProperty, ":indeterminate");
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
}

5
src/Avalonia.Controls/Shapes/Shape.cs

@ -30,11 +30,10 @@ namespace Avalonia.Controls.Shapes
private Geometry _renderedGeometry;
bool _calculateTransformOnArrange = false;
static Shape()
{
AffectsMeasure(StretchProperty, StrokeThicknessProperty);
AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty);
AffectsMeasure<Shape>(StretchProperty, StrokeThicknessProperty);
AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty);
}
public Geometry DefiningGeometry

2
src/Avalonia.Controls/Slider.cs

@ -42,6 +42,8 @@ namespace Avalonia.Controls
static Slider()
{
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragStartedEvent.AddClassHandler<Slider>(x => x.OnThumbDragStarted, RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<Slider>(x => x.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble);

4
src/Avalonia.Controls/StackPanel.cs

@ -29,8 +29,8 @@ namespace Avalonia.Controls
/// </summary>
static StackPanel()
{
AffectsMeasure(SpacingProperty);
AffectsMeasure(OrientationProperty);
AffectsMeasure<StackPanel>(SpacingProperty);
AffectsMeasure<StackPanel>(OrientationProperty);
}
/// <summary>

2
src/Avalonia.Controls/TabControl.cs

@ -44,7 +44,7 @@ namespace Avalonia.Controls
{
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
FocusableProperty.OverrideDefaultValue<TabControl>(false);
AffectsMeasure(TabStripPlacementProperty);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
}
/// <summary>

13
src/Avalonia.Controls/TextBlock.cs

@ -6,6 +6,7 @@ using System.Reactive;
using System.Reactive.Linq;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata;
namespace Avalonia.Controls
@ -65,7 +66,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<IBrush> ForegroundProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush>(
nameof(Foreground),
new SolidColorBrush(0xff000000),
Brushes.Black,
inherits: true);
/// <summary>
@ -99,10 +100,12 @@ namespace Avalonia.Controls
static TextBlock()
{
ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
AffectsRender(ForegroundProperty);
AffectsRender(FontWeightProperty);
AffectsRender(FontSizeProperty);
AffectsRender(FontStyleProperty);
AffectsRender<TextBlock>(
BackgroundProperty,
ForegroundProperty,
FontWeightProperty,
FontSizeProperty,
FontStyleProperty);
}
/// <summary>

2
src/Avalonia.Controls/TopLevel.cs

@ -59,7 +59,7 @@ namespace Avalonia.Controls
/// </summary>
static TopLevel()
{
AffectsMeasure(ClientSizeProperty);
AffectsMeasure<TopLevel>(ClientSizeProperty);
}
/// <summary>

2
src/Avalonia.Controls/WrapPanel.cs

@ -30,7 +30,7 @@ namespace Avalonia.Controls
/// </summary>
static WrapPanel()
{
AffectsMeasure(OrientationProperty);
AffectsMeasure<WrapPanel>(OrientationProperty);
}
/// <summary>

6
src/Avalonia.Input/InputElement.cs

@ -168,9 +168,9 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler<InputElement>(x => x.OnPointerReleased);
PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged);
PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass(IsFocusedProperty, ":focus");
PseudoClass(IsPointerOverProperty, ":pointerover");
PseudoClass<InputElement, bool>(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass<InputElement>(IsFocusedProperty, ":focus");
PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover");
}
/// <summary>

68
src/Avalonia.Layout/Layoutable.cs

@ -140,7 +140,7 @@ namespace Avalonia.Layout
/// </summary>
static Layoutable()
{
AffectsMeasure(
AffectsMeasure<Layoutable>(
IsVisibleProperty,
WidthProperty,
HeightProperty,
@ -427,11 +427,32 @@ namespace Avalonia.Layout
/// After a call to this method in a control's static constructor, any change to the
/// property will cause <see cref="InvalidateMeasure"/> to be called on the element.
/// </remarks>
[Obsolete("Use AffectsMeasure<T> and specify the control type.")]
protected static void AffectsMeasure(params AvaloniaProperty[] properties)
{
AffectsMeasure<Layoutable>(properties);
}
/// <summary>
/// Marks a property as affecting the control's measurement.
/// </summary>
/// <typeparam name="T">The control which the property affects.</typeparam>
/// <param name="properties">The properties.</param>
/// <remarks>
/// After a call to this method in a control's static constructor, any change to the
/// property will cause <see cref="InvalidateMeasure"/> to be called on the element.
/// </remarks>
protected static void AffectsMeasure<T>(params AvaloniaProperty[] properties)
where T : class, ILayoutable
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.InvalidateMeasure();
}
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsMeasureInvalidate);
property.Changed.Subscribe(Invalidate);
}
}
@ -443,11 +464,32 @@ namespace Avalonia.Layout
/// After a call to this method in a control's static constructor, any change to the
/// property will cause <see cref="InvalidateArrange"/> to be called on the element.
/// </remarks>
[Obsolete("Use AffectsArrange<T> and specify the control type.")]
protected static void AffectsArrange(params AvaloniaProperty[] properties)
{
AffectsArrange<Layoutable>(properties);
}
/// <summary>
/// Marks a property as affecting the control's arrangement.
/// </summary>
/// <typeparam name="T">The control which the property affects.</typeparam>
/// <param name="properties">The properties.</param>
/// <remarks>
/// After a call to this method in a control's static constructor, any change to the
/// property will cause <see cref="InvalidateArrange"/> to be called on the element.
/// </remarks>
protected static void AffectsArrange<T>(params AvaloniaProperty[] properties)
where T : class, ILayoutable
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.InvalidateArrange();
}
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsArrangeInvalidate);
property.Changed.Subscribe(Invalidate);
}
}
@ -632,26 +674,6 @@ namespace Avalonia.Layout
base.OnVisualParentChanged(oldParent, newParent);
}
/// <summary>
/// Calls <see cref="InvalidateMeasure"/> on the control on which a property changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void AffectsMeasureInvalidate(AvaloniaPropertyChangedEventArgs e)
{
ILayoutable control = e.Sender as ILayoutable;
control?.InvalidateMeasure();
}
/// <summary>
/// Calls <see cref="InvalidateArrange"/> on the control on which a property changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void AffectsArrangeInvalidate(AvaloniaPropertyChangedEventArgs e)
{
ILayoutable control = e.Sender as ILayoutable;
control?.InvalidateArrange();
}
/// <summary>
/// Tests whether any of a <see cref="Rect"/>'s properties include negative values,
/// a NaN or Infinity.

45
src/Avalonia.Styling/StyledElement.cs

@ -493,22 +493,53 @@ namespace Avalonia
/// </summary>
/// <param name="property">The property.</param>
/// <param name="className">The pseudo-class.</param>
[Obsolete("Use PseudoClass<TOwner> and specify the control type.")]
protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
{
PseudoClass(property, x => x, className);
PseudoClass<StyledElement>(property, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>
/// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
/// <param name="property">The property.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass<TOwner>(AvaloniaProperty<bool> property, string className)
where TOwner : class, IStyledElement
{
PseudoClass<TOwner, bool>(property, x => x, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property equals a certain value.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="selector">Returns a boolean value based on the property value.</param>
/// <param name="className">The pseudo-class.</param>
[Obsolete("Use PseudoClass<TOwner, TProperty> and specify the control type.")]
protected static void PseudoClass<TProperty>(
AvaloniaProperty<TProperty> property,
Func<TProperty, bool> selector,
string className)
{
PseudoClass<StyledElement, TProperty>(property, selector, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property equals a certain value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
/// <param name="property">The property.</param>
/// <param name="selector">Returns a boolean value based on the property value.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass<T>(
AvaloniaProperty<T> property,
Func<T, bool> selector,
protected static void PseudoClass<TOwner, TProperty>(
AvaloniaProperty<TProperty> property,
Func<TProperty, bool> selector,
string className)
where TOwner : class, IStyledElement
{
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(selector != null);
@ -520,10 +551,10 @@ namespace Avalonia
}
property.Changed.Merge(property.Initialized)
.Where(e => e.Sender is StyledElement)
.Where(e => e.Sender is TOwner)
.Subscribe(e =>
{
if (selector((T)e.NewValue))
if (selector((TProperty)e.NewValue))
{
((StyledElement)e.Sender).PseudoClasses.Add(className);
}

12
src/Avalonia.Themes.Default/MenuItem.xaml

@ -73,7 +73,17 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="MenuItem:separator">
<Setter Property="Template">
<ControlTemplate>
<Separator Background="{DynamicResource ThemeControlMidBrush}"
Margin="29,1,0,1"
Height="1"/>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Menu > MenuItem">
<Setter Property="Template">
<ControlTemplate>

8
src/Avalonia.Visuals/Animation/CrossFade.cs

@ -38,11 +38,11 @@ namespace Avalonia.Animation
new Setter
{
Property = Visual.OpacityProperty,
Value = 0.0
Value = 0d
}
)
{
Cue = new Cue(1.0)
Cue = new Cue(1d)
}
};
_fadeInAnimation = new Animation
@ -52,11 +52,11 @@ namespace Avalonia.Animation
new Setter
{
Property = Visual.OpacityProperty,
Value = 0.0
Value = 0d
}
)
{
Cue = new Cue(0.0)
Cue = new Cue(0d)
}
};
_fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration;

14
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -86,11 +86,11 @@ namespace Avalonia.Animation
new Setter
{
Property = translateProperty,
Value = 0
Value = 0d
}
)
{
Cue = new Cue(0.0)
Cue = new Cue(0d)
},
new KeyFrame
(
@ -101,7 +101,7 @@ namespace Avalonia.Animation
}
)
{
Cue = new Cue(1.0)
Cue = new Cue(1d)
}
};
animation.Duration = Duration;
@ -119,22 +119,22 @@ namespace Avalonia.Animation
new Setter
{
Property = translateProperty,
Value = forward ? -distance : distance
Value = forward ? distance : -distance
}
)
{
Cue = new Cue(0.0)
Cue = new Cue(0d)
},
new KeyFrame
(
new Setter
{
Property = translateProperty,
Value = 0
Value = 0d
}
)
{
Cue = new Cue(1.0)
Cue = new Cue(1d)
},
};
animation.Duration = Duration;

36
src/Avalonia.Visuals/Media/Brush.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media
/// Describes how an area is painted.
/// </summary>
[TypeConverter(typeof(BrushConverter))]
public abstract class Brush : AvaloniaObject, IBrush
public abstract class Brush : AvaloniaObject, IMutableBrush
{
/// <summary>
/// Defines the <see cref="Opacity"/> property.
@ -18,6 +18,9 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<Brush, double>(nameof(Opacity), 1.0);
/// <inheritdoc/>
public event EventHandler Invalidated;
/// <summary>
/// Gets or sets the opacity of the brush.
/// </summary>
@ -50,5 +53,36 @@ namespace Avalonia.Media
throw new FormatException($"Invalid brush string: '{s}'.");
}
/// <inheritdoc/>
public abstract IBrush ToImmutable();
/// <summary>
/// Marks a property as affecting the brush's visual representation.
/// </summary>
/// <param name="properties">The properties.</param>
/// <remarks>
/// After a call to this method in a brush's static constructor, any change to the
/// property will cause the <see cref="Invalidated"/> event to be raised on the brush.
/// </remarks>
protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
where T : Brush
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.RaiseInvalidated(EventArgs.Empty);
}
foreach (var property in properties)
{
property.Changed.Subscribe(Invalidate);
}
}
/// <summary>
/// Raises the <see cref="Invalidated"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
}
}

65
src/Avalonia.Visuals/Media/GradientBrush.cs

@ -1,7 +1,11 @@
// 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.Collections.Specialized;
using System.ComponentModel;
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Media
@ -20,35 +24,74 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="GradientStops"/> property.
/// </summary>
public static readonly StyledProperty<IList<GradientStop>> GradientStopsProperty =
AvaloniaProperty.Register<GradientBrush, IList<GradientStop>>(nameof(GradientStops));
public static readonly StyledProperty<GradientStops> GradientStopsProperty =
AvaloniaProperty.Register<GradientBrush, GradientStops>(nameof(GradientStops));
private IDisposable _gradientStopsSubscription;
static GradientBrush()
{
GradientStopsProperty.Changed.Subscribe(GradientStopsChanged);
AffectsRender<LinearGradientBrush>(SpreadMethodProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="GradientBrush"/> class.
/// </summary>
public GradientBrush()
{
this.GradientStops = new List<GradientStop>();
this.GradientStops = new GradientStops();
}
/// <summary>
/// Gets or sets the brush's spread method that defines how to draw a gradient that
/// doesn't fill the bounds of the destination control.
/// </summary>
/// <inheritdoc/>
public GradientSpreadMethod SpreadMethod
{
get { return GetValue(SpreadMethodProperty); }
set { SetValue(SpreadMethodProperty, value); }
}
/// <summary>
/// Gets or sets the brush's gradient stops.
/// </summary>
/// <inheritdoc/>
[Content]
public IList<GradientStop> GradientStops
public GradientStops GradientStops
{
get { return GetValue(GradientStopsProperty); }
set { SetValue(GradientStopsProperty, value); }
}
/// <inheritdoc/>
IReadOnlyList<IGradientStop> IGradientBrush.GradientStops => GradientStops;
private static void GradientStopsChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is GradientBrush brush)
{
var oldValue = (GradientStops)e.OldValue;
var newValue = (GradientStops)e.NewValue;
if (oldValue != null)
{
oldValue.CollectionChanged -= brush.GradientStopsChanged;
brush._gradientStopsSubscription.Dispose();
}
if (newValue != null)
{
newValue.CollectionChanged += brush.GradientStopsChanged;
brush._gradientStopsSubscription = newValue.TrackItemPropertyChanged(brush.GradientStopChanged);
}
brush.RaiseInvalidated(EventArgs.Empty);
}
}
private void GradientStopsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaiseInvalidated(EventArgs.Empty);
}
private void GradientStopChanged(Tuple<object, PropertyChangedEventArgs> e)
{
RaiseInvalidated(EventArgs.Empty);
}
}
}

40
src/Avalonia.Visuals/Media/GradientStop.cs

@ -4,10 +4,22 @@
namespace Avalonia.Media
{
/// <summary>
/// GradientStop
/// Describes the location and color of a transition point in a gradient.
/// </summary>
public sealed class GradientStop
public sealed class GradientStop : AvaloniaObject, IGradientStop
{
/// <summary>
/// Describes the <see cref="Offset"/> property.
/// </summary>
public static StyledProperty<double> OffsetProperty =
AvaloniaProperty.Register<GradientStop, double>(nameof(Offset));
/// <summary>
/// Describes the <see cref="Color"/> property.
/// </summary>
public static StyledProperty<Color> ColorProperty =
AvaloniaProperty.Register<GradientStop, Color>(nameof(Color));
/// <summary>
/// Initializes a new instance of the <see cref="GradientStop"/> class.
/// </summary>
@ -24,16 +36,18 @@ namespace Avalonia.Media
Offset = offset;
}
// TODO: Make these dependency properties.
/// <summary>
/// The offset
/// </summary>
public double Offset { get; set; }
/// <inheritdoc/>
public double Offset
{
get => GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
/// <summary>
/// The color
/// </summary>
public Color Color { get; set; }
/// <inheritdoc/>
public Color Color
{
get => GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
}
}
}

23
src/Avalonia.Visuals/Media/GradientStops.cs

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// A collection of <see cref="GradientStop"/>s.
/// </summary>
public class GradientStops : AvaloniaList<GradientStop>
{
public GradientStops()
{
ResetBehavior = ResetBehavior.Remove;
}
public IReadOnlyList<ImmutableGradientStop> ToImmutable()
{
return this.Select(x => new ImmutableGradientStop(x.Offset, x.Color)).ToList();
}
}
}

16
src/Avalonia.Visuals/Media/IAffectsRender.cs

@ -0,0 +1,16 @@
using System;
namespace Avalonia.Media
{
/// <summary>
/// Signals to a self-rendering control that changes to the resource should invoke
/// <see cref="Visual.InvalidateVisual"/>.
/// </summary>
public interface IAffectsRender
{
/// <summary>
/// Raised when the resource changes visually.
/// </summary>
event EventHandler Invalidated;
}
}

4
src/Avalonia.Visuals/Media/IGradientBrush.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets the brush's gradient stops.
/// </summary>
IList<GradientStop> GradientStops { get; }
IReadOnlyList<IGradientStop> GradientStops { get; }
/// <summary>
/// Gets the brush's spread method that defines how to draw a gradient that doesn't fill
@ -18,4 +18,4 @@ namespace Avalonia.Media
/// </summary>
GradientSpreadMethod SpreadMethod { get; }
}
}
}

18
src/Avalonia.Visuals/Media/IGradientStop.cs

@ -0,0 +1,18 @@
namespace Avalonia.Media
{
/// <summary>
/// Describes the location and color of a transition point in a gradient.
/// </summary>
public interface IGradientStop
{
/// <summary>
/// Gets the gradient stop color.
/// </summary>
Color Color { get; }
/// <summary>
/// Gets the gradient stop offset.
/// </summary>
double Offset { get; }
}
}

6
src/Avalonia.Visuals/Media/IMutableBrush.cs

@ -1,9 +1,11 @@
namespace Avalonia.Media
using System;
namespace Avalonia.Media
{
/// <summary>
/// Represents a mutable brush which can return an immutable clone of itself.
/// </summary>
public interface IMutableBrush : IBrush
public interface IMutableBrush : IBrush, IAffectsRender
{
/// <summary>
/// Creates an immutable clone of the brush.

12
src/Avalonia.Visuals/Media/ImageBrush.cs

@ -2,13 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Paints an area with an <see cref="IBitmap"/>.
/// </summary>
public class ImageBrush : TileBrush, IImageBrush, IMutableBrush
public class ImageBrush : TileBrush, IImageBrush
{
/// <summary>
/// Defines the <see cref="Visual"/> property.
@ -16,6 +17,11 @@ namespace Avalonia.Media
public static readonly StyledProperty<IBitmap> SourceProperty =
AvaloniaProperty.Register<ImageBrush, IBitmap>(nameof(Source));
static ImageBrush()
{
AffectsRender<ImageBrush>(SourceProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush"/> class.
/// </summary>
@ -42,9 +48,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableImageBrush(this);
return new ImmutableImageBrush(this);
}
}
}

9
src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Media.Immutable
{
@ -15,7 +14,7 @@ namespace Avalonia.Media.Immutable
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
protected ImmutableGradientBrush(
IList<GradientStop> gradientStops,
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity,
GradientSpreadMethod spreadMethod)
{
@ -28,14 +27,14 @@ namespace Avalonia.Media.Immutable
/// Initializes a new instance of the <see cref="ImmutableGradientBrush"/> class.
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
protected ImmutableGradientBrush(IGradientBrush source)
: this(source.GradientStops.ToList(), source.Opacity, source.SpreadMethod)
protected ImmutableGradientBrush(GradientBrush source)
: this(source.GradientStops.ToImmutable(), source.Opacity, source.SpreadMethod)
{
}
/// <inheritdoc/>
public IList<GradientStop> GradientStops { get; }
public IReadOnlyList<IGradientStop> GradientStops { get; }
/// <inheritdoc/>
public double Opacity { get; }

20
src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs

@ -0,0 +1,20 @@
namespace Avalonia.Media.Immutable
{
/// <summary>
/// Describes the location and color of a transition point in a gradient.
/// </summary>
public class ImmutableGradientStop : IGradientStop
{
public ImmutableGradientStop(double offset, Color color)
{
Offset = offset;
Color = color;
}
/// <inheritdoc/>
public double Offset { get; }
/// <inheritdoc/>
public Color Color { get; }
}
}

4
src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs

@ -16,7 +16,7 @@ namespace Avalonia.Media.Immutable
/// <param name="startPoint">The start point for the gradient.</param>
/// <param name="endPoint">The end point for the gradient.</param>
public ImmutableLinearGradientBrush(
IList<GradientStop> gradientStops,
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? startPoint = null,
@ -31,7 +31,7 @@ namespace Avalonia.Media.Immutable
/// Initializes a new instance of the <see cref="ImmutableLinearGradientBrush"/> class.
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
public ImmutableLinearGradientBrush(ILinearGradientBrush source)
public ImmutableLinearGradientBrush(LinearGradientBrush source)
: base(source)
{
StartPoint = source.StartPoint;

4
src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs

@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable
/// The horizontal and vertical radius of the outermost circle of the radial gradient.
/// </param>
public ImmutableRadialGradientBrush(
IList<GradientStop> gradientStops,
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? center = null,
@ -38,7 +38,7 @@ namespace Avalonia.Media.Immutable
/// Initializes a new instance of the <see cref="ImmutableRadialGradientBrush"/> class.
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
public ImmutableRadialGradientBrush(IRadialGradientBrush source)
public ImmutableRadialGradientBrush(RadialGradientBrush source)
: base(source)
{
Center = source.Center;

13
src/Avalonia.Visuals/Media/LinearGradientBrush.cs

@ -1,12 +1,14 @@
// 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.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// A brush that draws with a linear gradient.
/// </summary>
public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush, IMutableBrush
public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush
{
/// <summary>
/// Defines the <see cref="StartPoint"/> property.
@ -24,6 +26,11 @@ namespace Avalonia.Media
nameof(EndPoint),
RelativePoint.BottomRight);
static LinearGradientBrush()
{
AffectsRender<LinearGradientBrush>(StartPointProperty, EndPointProperty);
}
/// <summary>
/// Gets or sets the start point for the gradient.
/// </summary>
@ -43,9 +50,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableLinearGradientBrush(this);
return new ImmutableLinearGradientBrush(this);
}
}
}

10
src/Avalonia.Visuals/Media/RadialGradientBrush.cs

@ -1,12 +1,14 @@
// 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.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Paints an area with a radial gradient.
/// </summary>
public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush, IMutableBrush
public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush
{
/// <summary>
/// Defines the <see cref="Center"/> property.
@ -63,9 +65,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableRadialGradientBrush(this);
return new ImmutableRadialGradientBrush(this);
}
}
}
}

13
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@ -1,12 +1,14 @@
// 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.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Fills an area with a solid color.
/// </summary>
public class SolidColorBrush : Brush, ISolidColorBrush, IMutableBrush
public class SolidColorBrush : Brush, ISolidColorBrush
{
/// <summary>
/// Defines the <see cref="Color"/> property.
@ -14,6 +16,11 @@ namespace Avalonia.Media
public static readonly StyledProperty<Color> ColorProperty =
AvaloniaProperty.Register<SolidColorBrush, Color>(nameof(Color));
static SolidColorBrush()
{
AffectsRender<SolidColorBrush>(ColorProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="SolidColorBrush"/> class.
/// </summary>
@ -75,9 +82,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableSolidColorBrush(this);
return new ImmutableSolidColorBrush(this);
}
}
}

7
src/Avalonia.Visuals/Media/TileBrush.cs

@ -79,6 +79,13 @@ namespace Avalonia.Media
static TileBrush()
{
AffectsRender<TileBrush>(
AlignmentXProperty,
AlignmentYProperty,
DestinationRectProperty,
SourceRectProperty,
StretchProperty,
TileModeProperty);
RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue<TileBrush>(BitmapInterpolationMode.Default);
}

12
src/Avalonia.Visuals/Media/VisualBrush.cs

@ -1,6 +1,7 @@
// 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.Immutable;
using Avalonia.VisualTree;
namespace Avalonia.Media
@ -8,7 +9,7 @@ namespace Avalonia.Media
/// <summary>
/// Paints an area with an <see cref="IVisual"/>.
/// </summary>
public class VisualBrush : TileBrush, IVisualBrush, IMutableBrush
public class VisualBrush : TileBrush, IVisualBrush
{
/// <summary>
/// Defines the <see cref="Visual"/> property.
@ -16,6 +17,11 @@ namespace Avalonia.Media
public static readonly StyledProperty<IVisual> VisualProperty =
AvaloniaProperty.Register<VisualBrush, IVisual>(nameof(Visual));
static VisualBrush()
{
AffectsRender<VisualBrush>(VisualProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="VisualBrush"/> class.
/// </summary>
@ -42,9 +48,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableVisualBrush(this);
return new ImmutableVisualBrush(this);
}
}
}

51
src/Avalonia.Visuals/Visual.cs

@ -100,7 +100,7 @@ namespace Avalonia
/// </summary>
static Visual()
{
AffectsRender(
AffectsRender<Visual>(
BoundsProperty,
ClipProperty,
ClipToBoundsProperty,
@ -320,11 +320,47 @@ namespace Avalonia
/// on the control which when changed should cause a redraw. This is similar to WPF's
/// FrameworkPropertyMetadata.AffectsRender flag.
/// </remarks>
[Obsolete("Use AffectsRender<T> and specify the control type.")]
protected static void AffectsRender(params AvaloniaProperty[] properties)
{
AffectsRender<Visual>(properties);
}
/// <summary>
/// Indicates that a property change should cause <see cref="InvalidateVisual"/> to be
/// called.
/// </summary>
/// <typeparam name="T">The control which the property affects.</typeparam>
/// <param name="properties">The properties.</param>
/// <remarks>
/// This method should be called in a control's static constructor with each property
/// on the control which when changed should cause a redraw. This is similar to WPF's
/// FrameworkPropertyMetadata.AffectsRender flag.
/// </remarks>
protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
where T : Visual
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is T sender)
{
if (e.OldValue is IAffectsRender oldValue)
{
oldValue.Invalidated -= sender.AffectsRenderInvalidated;
}
if (e.NewValue is IAffectsRender newValue)
{
newValue.Invalidated += sender.AffectsRenderInvalidated;
}
sender.InvalidateVisual();
}
}
foreach (var property in properties)
{
property.Changed.Subscribe(AffectsRenderInvalidate);
property.Changed.Subscribe(Invalidate);
}
}
@ -412,15 +448,6 @@ namespace Avalonia
RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
}
/// <summary>
/// Called when a property changes that should invalidate the visual.
/// </summary>
/// <param name="e">The event args.</param>
private static void AffectsRenderInvalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as Visual)?.InvalidateVisual();
}
/// <summary>
/// Gets the visual offset from the specified ancestor.
/// </summary>
@ -530,6 +557,8 @@ namespace Avalonia
OnVisualParentChanged(old, value);
}
private void AffectsRenderInvalidated(object sender, EventArgs e) => InvalidateVisual();
/// <summary>
/// Called when the <see cref="VisualChildren"/> collection changes.
/// </summary>

92
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -29,15 +29,18 @@ namespace Avalonia.Direct2D1
{
private static readonly Direct2D1Platform s_instance = new Direct2D1Platform();
private static SharpDX.Direct2D1.Factory s_d2D1Factory;
public static SharpDX.Direct3D11.Device Direct3D11Device { get; private set; }
private static SharpDX.DirectWrite.Factory s_dwfactory;
public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; }
private static SharpDX.WIC.ImagingFactory s_imagingFactory;
public static SharpDX.Direct2D1.Device Direct2D1Device { get; private set; }
private static SharpDX.DXGI.Device s_dxgiDevice;
public static SharpDX.DirectWrite.Factory1 DirectWriteFactory { get; private set; }
public static SharpDX.WIC.ImagingFactory ImagingFactory { get; private set; }
public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; }
private static SharpDX.Direct2D1.Device s_d2D1Device;
private static readonly object s_initLock = new object();
private static bool s_initialized = false;
@ -47,13 +50,14 @@ namespace Avalonia.Direct2D1
lock (s_initLock)
{
if (s_initialized)
{
return;
}
#if DEBUG
try
{
s_d2D1Factory =
new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded,
Direct2D1Factory = new SharpDX.Direct2D1.Factory1(
SharpDX.Direct2D1.FactoryType.MultiThreaded,
SharpDX.Direct2D1.DebugLevel.Error);
}
catch
@ -61,12 +65,19 @@ namespace Avalonia.Direct2D1
//
}
#endif
s_dwfactory = new SharpDX.DirectWrite.Factory();
s_imagingFactory = new SharpDX.WIC.ImagingFactory();
if (s_d2D1Factory == null)
s_d2D1Factory = new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded,
if (Direct2D1Factory == null)
{
Direct2D1Factory = new SharpDX.Direct2D1.Factory1(
SharpDX.Direct2D1.FactoryType.MultiThreaded,
SharpDX.Direct2D1.DebugLevel.None);
}
using (var factory = new SharpDX.DirectWrite.Factory())
{
DirectWriteFactory = factory.QueryInterface<SharpDX.DirectWrite.Factory1>();
}
ImagingFactory = new SharpDX.WIC.ImagingFactory();
var featureLevels = new[]
{
@ -79,19 +90,15 @@ namespace Avalonia.Direct2D1
SharpDX.Direct3D.FeatureLevel.Level_9_1,
};
using (var d3dDevice = new SharpDX.Direct3D11.Device(
Direct3D11Device = new SharpDX.Direct3D11.Device(
SharpDX.Direct3D.DriverType.Hardware,
SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport |
SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport,
featureLevels))
{
s_dxgiDevice = d3dDevice.QueryInterface<SharpDX.DXGI.Device>();
}
SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport,
featureLevels);
DxgiDevice = Direct3D11Device.QueryInterface<SharpDX.DXGI.Device1>();
Direct2D1Device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice);
using (var factory1 = s_d2D1Factory.QueryInterface<SharpDX.Direct2D1.Factory1>())
{
s_d2D1Device = new SharpDX.Direct2D1.Device(factory1, s_dxgiDevice);
}
s_initialized = true;
}
}
@ -99,19 +106,13 @@ namespace Avalonia.Direct2D1
public static void Initialize()
{
InitializeDirect2D();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(s_instance)
.BindToSelf(s_d2D1Factory)
.BindToSelf(s_dwfactory)
.BindToSelf(s_imagingFactory)
.BindToSelf(s_dxgiDevice)
.BindToSelf(s_d2D1Device);
AvaloniaLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(s_instance);
SharpDX.Configuration.EnableReleaseOnFinalizer = true;
}
public IBitmapImpl CreateBitmap(int width, int height)
{
return new WicBitmapImpl(s_imagingFactory, width, height);
return new WicBitmapImpl(width, height);
}
public IFormattedTextImpl CreateFormattedText(
@ -138,14 +139,22 @@ namespace Avalonia.Direct2D1
if (s is IPlatformHandle nativeWindow)
{
if (nativeWindow.HandleDescriptor != "HWND")
{
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " +
nativeWindow.HandleDescriptor);
}
return new HwndRenderTarget(nativeWindow);
}
if (s is IExternalDirect2DRenderTargetSurface external)
return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory);
{
return new ExternalRenderTarget(external);
}
if (s is IFramebufferPlatformSurface fb)
return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory);
{
return new FramebufferShimRenderTarget(fb);
}
}
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
}
@ -156,19 +165,12 @@ namespace Avalonia.Direct2D1
double dpiX,
double dpiY)
{
return new WicRenderTargetBitmapImpl(
s_imagingFactory,
s_d2D1Factory,
s_dwfactory,
width,
height,
dpiX,
dpiY);
return new WicRenderTargetBitmapImpl(width, height, dpiX, dpiY);
}
public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null)
{
return new WriteableWicBitmapImpl(s_imagingFactory, width, height, format);
return new WriteableWicBitmapImpl(width, height, format);
}
public IStreamGeometryImpl CreateStreamGeometry()
@ -178,17 +180,17 @@ namespace Avalonia.Direct2D1
public IBitmapImpl LoadBitmap(string fileName)
{
return new WicBitmapImpl(s_imagingFactory, fileName);
return new WicBitmapImpl(fileName);
}
public IBitmapImpl LoadBitmap(Stream stream)
{
return new WicBitmapImpl(s_imagingFactory, stream);
return new WicBitmapImpl(stream);
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
return new WicBitmapImpl(s_imagingFactory, format, data, width, height, stride);
return new WicBitmapImpl(format, data, width, height, stride);
}
}
}

19
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -3,24 +3,17 @@ using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1
{
class ExternalRenderTarget : IRenderTarget, ILayerFactory
{
private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
private readonly DirectWriteFactory _dwFactory;
private readonly SharpDX.WIC.ImagingFactory _wicFactory;
public ExternalRenderTarget(
IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory,
SharpDX.WIC.ImagingFactory wicFactory)
IExternalDirect2DRenderTargetSurface externalRenderTargetProvider)
{
_externalRenderTargetProvider = externalRenderTargetProvider;
_dwFactory = dwFactory;
_wicFactory = wicFactory;
}
public void Dispose()
@ -32,7 +25,7 @@ namespace Avalonia.Direct2D1
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () =>
return new DrawingContextImpl(visualBrushRenderer, null, target, null, () =>
{
try
{
@ -47,12 +40,8 @@ namespace Avalonia.Direct2D1
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(
_wicFactory,
_dwFactory,
target,
size);
var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size);
}
}
}

23
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -4,7 +4,6 @@ using Avalonia.Direct2D1.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Win32.Interop;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
@ -13,22 +12,14 @@ namespace Avalonia.Direct2D1
class FramebufferShimRenderTarget : IRenderTarget
{
private readonly IFramebufferPlatformSurface _surface;
private readonly ImagingFactory _imagingFactory;
private readonly Factory _d2DFactory;
private readonly SharpDX.DirectWrite.Factory _dwriteFactory;
public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface,
ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory)
public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface)
{
_surface = surface;
_imagingFactory = imagingFactory;
_d2DFactory = d2dFactory;
_dwriteFactory = dwriteFactory;
}
public void Dispose()
{
{
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
@ -40,7 +31,7 @@ namespace Avalonia.Direct2D1
throw new ArgumentException("Unsupported pixel format: " + locked.Format);
}
return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory)
return new FramebufferShim(locked)
.CreateDrawingContext(visualBrushRenderer);
}
@ -48,10 +39,8 @@ namespace Avalonia.Direct2D1
{
private readonly ILockedFramebuffer _target;
public FramebufferShim(ILockedFramebuffer target,
ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory
) : base(imagingFactory, d2dFactory, dwriteFactory,
target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format)
public FramebufferShim(ILockedFramebuffer target) :
base(target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format)
{
_target = target;
}
@ -72,10 +61,8 @@ namespace Avalonia.Direct2D1
}
Dispose();
_target.Dispose();
});
}
}
}
}

2
src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs

@ -16,7 +16,7 @@ namespace Avalonia.Direct2D1
protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc)
{
return new SwapChain1(dxgiFactory, DxgiDevice, _window.Handle, ref swapChainDesc);
return new SwapChain1(dxgiFactory, Direct2D1Platform.DxgiDevice, _window.Handle, ref swapChainDesc);
}
protected override Size2F GetWindowDpi()

40
src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs

@ -1,7 +1,6 @@
// 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 SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DirectWrite;
@ -9,7 +8,7 @@ using SharpDX.Mathematics.Interop;
namespace Avalonia.Direct2D1.Media
{
internal class AvaloniaTextRenderer : TextRenderer
internal class AvaloniaTextRenderer : TextRendererBase
{
private readonly DrawingContextImpl _context;
@ -27,18 +26,7 @@ namespace Avalonia.Direct2D1.Media
_foreground = foreground;
}
public IDisposable Shadow
{
get;
set;
}
public void Dispose()
{
Shadow?.Dispose();
}
public Result DrawGlyphRun(
public override Result DrawGlyphRun(
object clientDrawingContext,
float baselineOriginX,
float baselineOriginY,
@ -68,34 +56,14 @@ namespace Avalonia.Direct2D1.Media
return Result.Ok;
}
public Result DrawInlineObject(object clientDrawingContext, float originX, float originY, InlineObject inlineObject, bool isSideways, bool isRightToLeft, ComObject clientDrawingEffect)
{
throw new NotImplementedException();
}
public Result DrawStrikethrough(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref Strikethrough strikethrough, ComObject clientDrawingEffect)
{
throw new NotImplementedException();
}
public Result DrawUnderline(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref Underline underline, ComObject clientDrawingEffect)
{
throw new NotImplementedException();
}
public RawMatrix3x2 GetCurrentTransform(object clientDrawingContext)
public override RawMatrix3x2 GetCurrentTransform(object clientDrawingContext)
{
return _renderTarget.Transform;
}
public float GetPixelsPerDip(object clientDrawingContext)
public override float GetPixelsPerDip(object clientDrawingContext)
{
return _renderTarget.DotsPerInch.Width / 96;
}
public bool IsPixelSnappingDisabled(object clientDrawingContext)
{
return false;
}
}
}

13
src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs

@ -7,16 +7,13 @@ namespace Avalonia.Direct2D1.Media
internal static class Direct2D1FontCollectionCache
{
private static readonly ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection> s_cachedCollections;
private static readonly SharpDX.DirectWrite.Factory s_factory;
private static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection;
static Direct2D1FontCollectionCache()
{
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection>();
s_factory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
s_installedFontCollection = s_factory.GetSystemFontCollection(false);
s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false);
}
public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface)
@ -39,7 +36,7 @@ namespace Avalonia.Direct2D1.Media
}
return new SharpDX.DirectWrite.TextFormat(
s_factory,
Direct2D1Platform.DirectWriteFactory,
fontFamilyName,
fontCollection,
(SharpDX.DirectWrite.FontWeight)typeface.Weight,
@ -57,9 +54,9 @@ namespace Avalonia.Direct2D1.Media
{
var assets = FontFamilyLoader.LoadFontAssets(key);
var fontLoader = new DWriteResourceFontLoader(s_factory, assets);
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets);
return new SharpDX.DirectWrite.FontCollection(s_factory, fontLoader, fontLoader.Key);
return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key);
}
}
}
}

113
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -22,10 +22,9 @@ namespace Avalonia.Direct2D1.Media
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly DeviceContext _deviceContext;
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback;
private readonly SharpDX.WIC.ImagingFactory _imagingFactory;
private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContextImpl"/> class.
@ -36,16 +35,12 @@ namespace Avalonia.Direct2D1.Media
/// An object to use to create layers. May be null, in which case a
/// <see cref="WicRenderTargetBitmapImpl"/> will created when a new layer is requested.
/// </param>
/// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="imagingFactory">The WIC imaging factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
/// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer,
ILayerFactory layerFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.WIC.ImagingFactory imagingFactory,
SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null)
{
@ -54,9 +49,17 @@ namespace Avalonia.Direct2D1.Media
_renderTarget = renderTarget;
_swapChain = swapChain;
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_renderTarget.BeginDraw();
if (_renderTarget is DeviceContext deviceContext)
{
_deviceContext = deviceContext;
}
else
{
_deviceContext = _renderTarget.QueryInterface<DeviceContext>();
}
_deviceContext.BeginDraw();
}
/// <summary>
@ -64,14 +67,14 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public Matrix Transform
{
get { return _renderTarget.Transform.ToAvalonia(); }
set { _renderTarget.Transform = value.ToDirect2D(); }
get { return _deviceContext.Transform.ToAvalonia(); }
set { _deviceContext.Transform = value.ToDirect2D(); }
}
/// <inheritdoc/>
public void Clear(Color color)
{
_renderTarget.Clear(color.ToDirect2D());
_deviceContext.Clear(color.ToDirect2D());
}
/// <summary>
@ -80,10 +83,13 @@ namespace Avalonia.Direct2D1.Media
public void Dispose()
{
foreach (var layer in _layerPool)
{
layer.Dispose();
}
try
{
_renderTarget.EndDraw();
_deviceContext.EndDraw();
_swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None);
_finishedCallback?.Invoke();
@ -104,29 +110,32 @@ namespace Avalonia.Direct2D1.Media
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget))
using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
{
var interpolationMode = GetInterpolationMode(bitmapInterpolationMode);
_renderTarget.DrawBitmap(
_deviceContext.DrawBitmap(
d2d.Value,
destRect.ToSharpDX(),
(float)opacity,
interpolationMode,
sourceRect.ToSharpDX());
sourceRect.ToSharpDX(),
null);
}
}
private static SharpDX.Direct2D1.BitmapInterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode)
private static InterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode)
{
switch (interpolationMode)
{
case BitmapInterpolationMode.LowQuality:
return SharpDX.Direct2D1.BitmapInterpolationMode.NearestNeighbor;
return InterpolationMode.NearestNeighbor;
case BitmapInterpolationMode.MediumQuality:
return InterpolationMode.Linear;
case BitmapInterpolationMode.HighQuality:
return InterpolationMode.HighQualityCubic;
case BitmapInterpolationMode.Default:
return SharpDX.Direct2D1.BitmapInterpolationMode.Linear;
return InterpolationMode.Linear;
default:
throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null);
}
@ -141,17 +150,17 @@ namespace Avalonia.Direct2D1.Media
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget))
using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value))
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_deviceContext.Factory, destRect.ToDirect2D()))
{
if (d2dOpacityMask.PlatformBrush != null)
{
d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D();
}
_renderTarget.FillGeometry(
_deviceContext.FillGeometry(
geometry,
sourceBrush,
d2dOpacityMask.PlatformBrush);
@ -171,11 +180,11 @@ namespace Avalonia.Direct2D1.Media
var size = new Rect(p1, p2).Size;
using (var d2dBrush = CreateBrush(pen.Brush, size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
{
_renderTarget.DrawLine(
_deviceContext.DrawLine(
p1.ToSharpDX(),
p2.ToSharpDX(),
d2dBrush.PlatformBrush,
@ -201,7 +210,7 @@ namespace Avalonia.Direct2D1.Media
if (d2dBrush.PlatformBrush != null)
{
var impl = (GeometryImpl)geometry;
_renderTarget.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush);
_deviceContext.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush);
}
}
}
@ -209,12 +218,12 @@ namespace Avalonia.Direct2D1.Media
if (pen != null)
{
using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
{
var impl = (GeometryImpl)geometry;
_renderTarget.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke);
_deviceContext.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke);
}
}
}
@ -229,13 +238,13 @@ namespace Avalonia.Direct2D1.Media
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius)
{
using (var brush = CreateBrush(pen.Brush, rect.Size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (brush.PlatformBrush != null)
{
if (cornerRadius == 0)
{
_renderTarget.DrawRectangle(
_deviceContext.DrawRectangle(
rect.ToDirect2D(),
brush.PlatformBrush,
(float)pen.Thickness,
@ -243,7 +252,7 @@ namespace Avalonia.Direct2D1.Media
}
else
{
_renderTarget.DrawRoundedRectangle(
_deviceContext.DrawRoundedRectangle(
new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius },
brush.PlatformBrush,
(float)pen.Thickness,
@ -266,7 +275,7 @@ namespace Avalonia.Direct2D1.Media
var impl = (FormattedTextImpl)text;
using (var brush = CreateBrush(foreground, impl.Size))
using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush))
using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush))
{
if (brush.PlatformBrush != null)
{
@ -290,11 +299,11 @@ namespace Avalonia.Direct2D1.Media
{
if (cornerRadius == 0)
{
_renderTarget.FillRectangle(rect.ToDirect2D(), b.PlatformBrush);
_deviceContext.FillRectangle(rect.ToDirect2D(), b.PlatformBrush);
}
else
{
_renderTarget.FillRoundedRectangle(
_deviceContext.FillRoundedRectangle(
new RoundedRectangle
{
Rect = new RawRectangleF(
@ -320,7 +329,7 @@ namespace Avalonia.Direct2D1.Media
else
{
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height);
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
var pixelSize = size * (dpi / 96);
return platform.CreateRenderTargetBitmap(
(int)pixelSize.Width,
@ -337,12 +346,12 @@ namespace Avalonia.Direct2D1.Media
/// <returns>A disposable used to undo the clip rectangle.</returns>
public void PushClip(Rect clip)
{
_renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive);
_deviceContext.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive);
}
public void PopClip()
{
_renderTarget.PopAxisAlignedClip();
_deviceContext.PopAxisAlignedClip();
}
readonly Stack<Layer> _layers = new Stack<Layer>();
@ -363,8 +372,8 @@ namespace Avalonia.Direct2D1.Media
Opacity = (float)opacity,
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
_renderTarget.PushLayer(ref parameters, layer);
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);
_layers.Push(layer);
}
@ -382,7 +391,7 @@ namespace Avalonia.Direct2D1.Media
var layer = _layers.Pop();
if (layer != null)
{
_renderTarget.PopLayer();
_deviceContext.PopLayer();
_layerPool.Push(layer);
}
}
@ -403,21 +412,21 @@ namespace Avalonia.Direct2D1.Media
if (solidColorBrush != null)
{
return new SolidColorBrushImpl(solidColorBrush, _renderTarget);
return new SolidColorBrushImpl(solidColorBrush, _deviceContext);
}
else if (linearGradientBrush != null)
{
return new LinearGradientBrushImpl(linearGradientBrush, _renderTarget, destinationSize);
return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize);
}
else if (radialGradientBrush != null)
{
return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize);
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
}
else if (imageBrush?.Source != null)
{
return new ImageBrushImpl(
imageBrush,
_renderTarget,
_deviceContext,
(BitmapImpl)imageBrush.Source.PlatformImpl.Item,
destinationSize);
}
@ -430,7 +439,7 @@ namespace Avalonia.Direct2D1.Media
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
using (var intermediate = new BitmapRenderTarget(
_renderTarget,
_deviceContext,
CompatibleRenderTargetOptions.None,
intermediateSize.ToSharpDX()))
{
@ -442,8 +451,8 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl(
visualBrush,
_renderTarget,
new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap),
_deviceContext,
new D2DBitmapImpl(intermediate.Bitmap),
destinationSize);
}
}
@ -454,7 +463,7 @@ namespace Avalonia.Direct2D1.Media
}
}
return new SolidColorBrushImpl(null, _renderTarget);
return new SolidColorBrushImpl(null, _deviceContext);
}
public void PushGeometryClip(IGeometryImpl clip)
@ -466,8 +475,8 @@ namespace Avalonia.Direct2D1.Media
Opacity = 1,
GeometricMask = ((GeometryImpl)clip).Geometry
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
_renderTarget.PushLayer(ref parameters, layer);
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);
_layers.Push(layer);
@ -487,8 +496,8 @@ namespace Avalonia.Direct2D1.Media
Opacity = 1,
OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
_renderTarget.PushLayer(ref parameters, layer);
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);
_layers.Push(layer);
}

32
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@ -21,24 +21,21 @@ namespace Avalonia.Direct2D1.Media
{
Text = text;
var factory = AvaloniaLocator.Current.GetService<DWrite.Factory>();
var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface);
textFormat.WordWrapping =
wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap;
TextLayout = new DWrite.TextLayout(
factory,
Text ?? string.Empty,
textFormat,
(float)constraint.Width,
(float)constraint.Height)
using (var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface))
{
TextAlignment = textAlignment.ToDirect2D()
};
textFormat.Dispose();
textFormat.WordWrapping =
wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap;
TextLayout = new DWrite.TextLayout(
Direct2D1Platform.DirectWriteFactory,
Text ?? string.Empty,
textFormat,
(float)constraint.Width,
(float)constraint.Height)
{
TextAlignment = textAlignment.ToDirect2D()
};
}
if (spans != null)
{
@ -110,6 +107,7 @@ namespace Avalonia.Direct2D1.Media
private Size Measure()
{
var metrics = TextLayout.Metrics;
var width = metrics.WidthIncludingTrailingWhitespace;
if (float.IsNaN(width))

3
src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs

@ -53,10 +53,9 @@ namespace Avalonia.Direct2D1.Media
public ITransformedGeometryImpl WithTransform(Matrix transform)
{
var factory = AvaloniaLocator.Current.GetService<Factory>();
return new TransformedGeometryImpl(
new TransformedGeometry(
factory,
Direct2D1Platform.Direct2D1Factory,
GetSourceGeometry(),
transform.ToDirect2D()),
this);

7
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -1,19 +1,12 @@
using System;
using System.IO;
using Avalonia.Platform;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
public abstract class BitmapImpl : IBitmapImpl, IDisposable
{
public BitmapImpl(ImagingFactory imagingFactory)
{
WicImagingFactory = imagingFactory;
}
public ImagingFactory WicImagingFactory { get; }
public abstract int PixelWidth { get; }
public abstract int PixelHeight { get; }

47
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -1,10 +1,10 @@
using System;
// 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.IO;
using SharpDX.Direct2D1;
using ImageParameters = SharpDX.WIC.ImageParameters;
using ImagingFactory2 = SharpDX.WIC.ImagingFactory2;
using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder;
using WICFactory = SharpDX.WIC.ImagingFactory;
using SharpDX.WIC;
using Bitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
@ -13,53 +13,46 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class D2DBitmapImpl : BitmapImpl
{
private Bitmap _direct2D;
private readonly Bitmap _direct2DBitmap;
/// <summary>
/// Initialize a new instance of the <see cref="BitmapImpl"/> class
/// with a bitmap backed by GPU memory.
/// </summary>
/// <param name="imagingFactory">The image factory to use when saving out this bitmap.</param>
/// <param name="d2DBitmap">The GPU bitmap.</param>
/// <remarks>
/// This bitmap must be either from the same render target,
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable.
/// </remarks>
public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap)
: base(imagingFactory)
public D2DBitmapImpl(Bitmap d2DBitmap)
{
_direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
_direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
}
public override int PixelWidth => _direct2D.PixelSize.Width;
public override int PixelHeight => _direct2D.PixelSize.Height;
public override int PixelWidth => _direct2DBitmap.PixelSize.Width;
public override int PixelHeight => _direct2DBitmap.PixelSize.Height;
public override void Dispose()
{
base.Dispose();
_direct2D.Dispose();
_direct2DBitmap.Dispose();
}
public override OptionalDispose<Bitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<Bitmap>(_direct2D, false);
return new OptionalDispose<Bitmap>(_direct2DBitmap, false);
}
public override void Save(Stream stream)
{
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder))
using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null))
using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))
using (var bitmapSource = _direct2DBitmap.QueryInterface<BitmapSource>())
{
var parameters = new ImageParameters(
new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied),
_direct2D.DotsPerInch.Width,
_direct2D.DotsPerInch.Height,
0, 0, PixelWidth, PixelHeight);
imageEncoder.WriteFrame(_direct2D, frameEncode, parameters);
frameEncode.Commit();
frame.Initialize();
frame.WriteSource(bitmapSource);
frame.Commit();
encoder.Commit();
}
}

41
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -1,34 +1,28 @@
using Avalonia.Platform;
// 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.Platform;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media.Imaging
{
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly BitmapRenderTarget _target;
private readonly BitmapRenderTarget _renderTarget;
public D2DRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
BitmapRenderTarget target)
: base(imagingFactory, target.Bitmap)
public D2DRenderTargetBitmapImpl(BitmapRenderTarget renderTarget)
: base(renderTarget.Bitmap)
{
_dwriteFactory = dwriteFactory;
_target = target;
_renderTarget = renderTarget;
}
public override int PixelWidth => _target.PixelSize.Width;
public override int PixelHeight => _target.PixelSize.Height;
public override int PixelWidth => _renderTarget.PixelSize.Width;
public override int PixelHeight => _renderTarget.PixelSize.Height;
public static D2DRenderTargetBitmapImpl CreateCompatible(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
Size size)
{
@ -36,32 +30,27 @@ namespace Avalonia.Direct2D1.Media.Imaging
renderTarget,
CompatibleRenderTargetOptions.None,
new Size2F((float)size.Width, (float)size.Height));
return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget);
return new D2DRenderTargetBitmapImpl(bitmapRenderTarget);
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(
visualBrushRenderer,
this,
_target,
_dwriteFactory,
WicImagingFactory);
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size);
return CreateCompatible(_renderTarget, size);
}
public override void Dispose()
{
_target.Dispose();
_renderTarget.Dispose();
}
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<D2DBitmap>(_target.Bitmap, false);
return new OptionalDispose<D2DBitmap>(_renderTarget.Bitmap, false);
}
}
}

33
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -18,57 +18,52 @@ namespace Avalonia.Direct2D1.Media
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(ImagingFactory factory, string fileName)
: base(factory)
public WicBitmapImpl(string fileName)
{
using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand))
{
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="stream">The stream to read the bitmap from.</param>
public WicBitmapImpl(ImagingFactory factory, Stream stream)
: base(factory)
public WicBitmapImpl(Stream stream)
{
using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad))
using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad))
{
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="pixelFormat">Pixel format</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
: base(factory)
public WicBitmapImpl(int width, int height, APixelFormat? pixelFormat = null)
{
if (!pixelFormat.HasValue)
{
pixelFormat = APixelFormat.Bgra8888;
}
PixelFormat = pixelFormat;
WicImpl = new Bitmap(
factory,
Direct2D1Platform.ImagingFactory,
width,
height,
pixelFormat.Value.ToWic(),
BitmapCreateCacheOption.CacheOnLoad);
}
public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride)
: base(factory)
public WicBitmapImpl(APixelFormat format, IntPtr data, int width, int height, int stride)
{
WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
PixelFormat = format;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{
@ -111,14 +106,14 @@ namespace Avalonia.Direct2D1.Media
/// <returns>The Direct2D bitmap.</returns>
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
FormatConverter converter = new FormatConverter(WicImagingFactory);
FormatConverter converter = new FormatConverter(Direct2D1Platform.ImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}
public override void Save(Stream stream)
{
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))
{
frame.Initialize();

22
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -5,26 +5,20 @@ using System;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media
{
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target;
private readonly WicRenderTarget _renderTarget;
public WicRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
Factory d2dFactory,
DirectWriteFactory dwriteFactory,
int width,
int height,
double dpiX,
double dpiY,
Platform.PixelFormat? pixelFormat = null)
: base(imagingFactory, width, height, pixelFormat)
: base(width, height, pixelFormat)
{
var props = new RenderTargetProperties
{
@ -32,17 +26,16 @@ namespace Avalonia.Direct2D1.Media
DpiY = (float)dpiY,
};
_target = new WicRenderTarget(
d2dFactory,
_renderTarget = new WicRenderTarget(
Direct2D1Platform.Direct2D1Factory,
WicImpl,
props);
_dwriteFactory = dwriteFactory;
}
public override void Dispose()
{
_target.Dispose();
_renderTarget.Dispose();
base.Dispose();
}
@ -51,8 +44,7 @@ namespace Avalonia.Direct2D1.Media
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
{
return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory,
finishedCallback: finishedCallback);
return new DrawingContextImpl(visualBrushRenderer, null, _renderTarget, finishedCallback: finishedCallback);
}
}
}

9
src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs

@ -1,4 +1,7 @@
using System;
// 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 SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
@ -7,8 +10,8 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl
{
public WriteableWicBitmapImpl(ImagingFactory factory, int width, int height, PixelFormat? pixelFormat)
: base(factory, width, height, pixelFormat)
public WriteableWicBitmapImpl(int width, int height, PixelFormat? pixelFormat)
: base(width, height, pixelFormat)
{
}

6
src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs

@ -31,8 +31,7 @@ namespace Avalonia.Direct2D1.Media
/// <inheritdoc/>
public IStreamGeometryImpl Clone()
{
Factory factory = AvaloniaLocator.Current.GetService<Factory>();
var result = new PathGeometry(factory);
var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory);
var sink = result.Open();
((PathGeometry)Geometry).Stream(sink);
sink.Close();
@ -47,8 +46,7 @@ namespace Avalonia.Direct2D1.Media
private static Geometry CreateGeometry()
{
Factory factory = AvaloniaLocator.Current.GetService<Factory>();
return new PathGeometry(factory);
return new PathGeometry(Direct2D1Platform.Direct2D1Factory);
}
}
}

18
src/Windows/Avalonia.Direct2D1/RenderTarget.cs

@ -5,9 +5,6 @@ using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using DwFactory = SharpDX.DirectWrite.Factory;
using WicFactory = SharpDX.WIC.ImagingFactory;
namespace Avalonia.Direct2D1
{
@ -24,32 +21,21 @@ namespace Avalonia.Direct2D1
/// <param name="renderTarget">The render target.</param>
public RenderTarget(SharpDX.Direct2D1.RenderTarget renderTarget)
{
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>();
WicFactory = AvaloniaLocator.Current.GetService<WicFactory>();
_renderTarget = renderTarget;
}
public Factory Direct2DFactory { get; }
public DwFactory DirectWriteFactory { get; }
public WicFactory WicFactory { get; }
/// <summary>
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory);
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicFactory,
DirectWriteFactory,
_renderTarget,
size);
return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size);
}
public void Dispose()

142
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -1,18 +1,16 @@
using Avalonia.Direct2D1.Media;
// 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.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DXGI;
using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Device = SharpDX.Direct2D1.Device;
using Factory = SharpDX.Direct2D1.Factory;
using Factory2 = SharpDX.DXGI.Factory2;
using PixelFormat = SharpDX.Direct2D1.PixelFormat;
namespace Avalonia.Direct2D1
{
{
public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory
{
private Size2 _savedSize;
@ -20,23 +18,6 @@ namespace Avalonia.Direct2D1
private DeviceContext _deviceContext;
private SwapChain1 _swapChain;
protected SwapChainRenderTarget()
{
DxgiDevice = AvaloniaLocator.Current.GetService<SharpDX.DXGI.Device>();
D2DDevice = AvaloniaLocator.Current.GetService<Device>();
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
WicImagingFactory = AvaloniaLocator.Current.GetService<SharpDX.WIC.ImagingFactory>();
}
public Factory Direct2DFactory { get; }
public SharpDX.DirectWrite.Factory DirectWriteFactory { get; }
public SharpDX.WIC.ImagingFactory WicImagingFactory { get; }
protected SharpDX.DXGI.Device DxgiDevice { get; }
public Device D2DDevice { get; }
/// <summary>
/// Creates a drawing context for a rendering session.
/// </summary>
@ -50,87 +31,92 @@ namespace Avalonia.Direct2D1
{
_savedSize = size;
_savedDpi = dpi;
CreateSwapChain();
Resize();
}
return new DrawingContextImpl(
visualBrushRenderer,
this,
_deviceContext,
DirectWriteFactory,
WicImagingFactory,
_swapChain);
return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_deviceContext == null)
{
CreateSwapChain();
CreateDeviceContext();
}
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicImagingFactory,
DirectWriteFactory,
_deviceContext,
size);
return D2DRenderTargetBitmapImpl.CreateCompatible(_deviceContext, size);
}
public void Dispose()
{
_deviceContext?.Dispose();
_swapChain?.Dispose();
}
private void Resize()
{
_deviceContext?.Dispose();
_deviceContext = null;
_swapChain?.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None);
CreateDeviceContext();
}
private void CreateSwapChain()
{
using (var dxgiAdaptor = DxgiDevice.Adapter)
using (var dxgiFactory = dxgiAdaptor.GetParent<Factory2>())
var swapChainDescription = new SwapChainDescription1
{
_deviceContext?.Dispose();
_deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi};
var swapChainDesc = new SwapChainDescription1
Width = _savedSize.Width,
Height = _savedSize.Height,
Format = Format.B8G8R8A8_UNorm,
SampleDescription = new SampleDescription
{
Width = _savedSize.Width,
Height = _savedSize.Height,
Format = Format.B8G8R8A8_UNorm,
Stereo = false,
SampleDescription = new SampleDescription
Count = 1,
Quality = 0,
},
Usage = Usage.RenderTargetOutput,
BufferCount = 1,
SwapEffect = SwapEffect.Discard,
};
using (var dxgiAdapter = Direct2D1Platform.DxgiDevice.Adapter)
using (var dxgiFactory = dxgiAdapter.GetParent<SharpDX.DXGI.Factory2>())
{
_swapChain = CreateSwapChain(dxgiFactory, swapChainDescription);
}
}
private void CreateDeviceContext()
{
_deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi };
if (_swapChain == null)
{
CreateSwapChain();
}
using (var dxgiBackBuffer = _swapChain.GetBackBuffer<Surface>(0))
using (var d2dBackBuffer = new Bitmap1(
_deviceContext,
dxgiBackBuffer,
new BitmapProperties1(
new SharpDX.Direct2D1.PixelFormat
{
Count = 1,
Quality = 0,
AlphaMode = SharpDX.Direct2D1.AlphaMode.Premultiplied,
Format = Format.B8G8R8A8_UNorm
},
Usage = Usage.RenderTargetOutput,
BufferCount = 1,
Scaling = Scaling.Stretch,
SwapEffect = SwapEffect.Discard,
Flags = 0,
};
_swapChain?.Dispose();
_swapChain = CreateSwapChain(dxgiFactory, swapChainDesc);
using (var dxgiBackBuffer = _swapChain.GetBackBuffer<Surface>(0))
using (var d2dBackBuffer = new Bitmap1(
_deviceContext,
dxgiBackBuffer,
new BitmapProperties1(
new PixelFormat
{
AlphaMode = AlphaMode.Premultiplied,
Format = Format.B8G8R8A8_UNorm
},
_savedDpi.Width,
_savedDpi.Height,
BitmapOptions.Target | BitmapOptions.CannotDraw)))
{
_deviceContext.Target = d2dBackBuffer;
}
_savedSize.Width,
_savedSize.Height,
BitmapOptions.Target | BitmapOptions.CannotDraw)))
{
_deviceContext.Target = d2dBackBuffer;
}
}
protected abstract SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc);
protected abstract SwapChain1 CreateSwapChain(SharpDX.DXGI.Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc);
protected abstract Size2F GetWindowDpi();

14
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@ -479,6 +479,18 @@ namespace Avalonia.Base.UnitTests
Assert.False(source.SetterCalled);
}
[Fact]
public void Disposing_Completed_Binding_Does_Not_Throw()
{
var target = new Class1();
var source = new Subject<string>();
var subscription = target.Bind(Class1.FooProperty, source);
source.OnCompleted();
subscription.Dispose();
}
/// <summary>
/// Returns an observable that returns a single value but does not complete.
/// </summary>
@ -595,4 +607,4 @@ namespace Avalonia.Base.UnitTests
public bool SetterCalled { get; private set; }
}
}
}
}

21
tests/Avalonia.Controls.UnitTests/BorderTests.cs

@ -1,6 +1,10 @@
// 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;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -42,5 +46,22 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(6, 6, 0, 0), content.Bounds);
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new Border()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

26
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class MenuItemTests
{
[Fact]
public void Header_Of_Minus_Should_Apply_Separator_Pseudoclass()
{
var target = new MenuItem { Header = "-" };
Assert.True(target.Classes.Contains(":separator"));
}
[Fact]
public void Separator_Item_Should_Set_Focusable_False()
{
var target = new MenuItem { Header = "-" };
Assert.False(target.Focusable);
}
}
}

21
tests/Avalonia.Controls.UnitTests/PanelTests.cs

@ -3,7 +3,11 @@
using System.Linq;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -115,5 +119,22 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new Panel()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

20
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -13,6 +13,7 @@ using System;
using System.Linq;
using Xunit;
using Avalonia.Rendering;
using Avalonia.Media;
namespace Avalonia.Controls.UnitTests.Presenters
{
@ -203,5 +204,22 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.NotEqual(foo, logicalChildren.First());
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new ContentPresenter()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}
}

48
tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests.Shapes
{
public class RectangleTests
{
[Fact]
public void Changing_Fill_Brush_Color_Should_Invalidate_Visual()
{
var target = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Fill).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
[Fact]
public void Changing_Stroke_Brush_Color_Should_Invalidate_Visual()
{
var target = new Rectangle()
{
Stroke = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Stroke).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

38
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@ -2,6 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -25,5 +29,39 @@ namespace Avalonia.Controls.UnitTests
"",
textBlock.Text);
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new TextBlock()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
[Fact]
public void Changing_Foreground_Brush_Color_Should_Invalidate_Visual()
{
var target = new TextBlock()
{
Foreground = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Foreground).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

2
tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs

@ -124,7 +124,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops = new[]
GradientStops =
{
new GradientStop(Color.FromUInt32(0xffffffff), 0),
new GradientStop(Color.FromUInt32(0x00ffffff), 1)

4
tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs

@ -36,7 +36,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
GradientStops = new[]
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
@ -63,7 +63,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
GradientStops = new[]
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }

2
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@ -34,7 +34,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
Background = new RadialGradientBrush
{
GradientStops = new[]
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }

4
tests/Avalonia.RenderTests/OpacityMaskTests.cs

@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops = new List<GradientStop>
GradientStops =
{
new GradientStop(Color.FromUInt32(0xffffffff), 0),
new GradientStop(Color.FromUInt32(0x00ffffff), 1)
@ -65,7 +65,7 @@ namespace Avalonia.Direct2D1.RenderTests
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops = new List<GradientStop>
GradientStops =
{
new GradientStop(Color.FromUInt32(0xffffffff), 0),
new GradientStop(Color.FromUInt32(0x00ffffff), 1)

7
tests/Avalonia.UnitTests/TestRoot.cs

@ -19,6 +19,13 @@ namespace Avalonia.UnitTests
public TestRoot()
{
Renderer = Mock.Of<IRenderer>();
}
public TestRoot(IControl child)
: this()
{
Child = child;
}
event EventHandler<NameScopeEventArgs> INameScope.Registered

27
tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class ImageBrushTests
{
[Fact]
public void Changing_Source_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new ImageBrush(bitmap1);
var raised = false;
target.Invalidated += (s, e) => raised = true;
target.Source = bitmap2;
Assert.True(raised);
}
}
}

86
tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs

@ -0,0 +1,86 @@
using System;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class LinearGradientBrushTests
{
[Fact]
public void Changing_StartPoint_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.StartPoint = new RelativePoint();
target.Invalidated += (s, e) => raised = true;
target.StartPoint = new RelativePoint(10, 10, RelativeUnit.Absolute);
Assert.True(raised);
}
[Fact]
public void Changing_EndPoint_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.EndPoint = new RelativePoint();
target.Invalidated += (s, e) => raised = true;
target.EndPoint = new RelativePoint(10, 10, RelativeUnit.Absolute);
Assert.True(raised);
}
[Fact]
public void Changing_GradientStops_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) };
target.Invalidated += (s, e) => raised = true;
target.GradientStops = new GradientStops { new GradientStop(Colors.Green, 0) };
Assert.True(raised);
}
[Fact]
public void Adding_GradientStop_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) };
target.Invalidated += (s, e) => raised = true;
target.GradientStops.Add(new GradientStop(Colors.Green, 1));
Assert.True(raised);
}
[Fact]
public void Changing_GradientStop_Offset_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) };
target.Invalidated += (s, e) => raised = true;
target.GradientStops[0].Offset = 0.5;
Assert.True(raised);
}
}
}

21
tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs

@ -0,0 +1,21 @@
using System;
using Avalonia.Media;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class SolidColorBrushTests
{
[Fact]
public void Changing_Color_Raises_Invalidated()
{
var target = new SolidColorBrush(Colors.Red);
var raised = false;
target.Invalidated += (s, e) => raised = true;
target.Color = Colors.Green;
Assert.True(raised);
}
}
}
Loading…
Cancel
Save