Browse Source

Merge branch 'master' into DisableButtonOnNullCommandBinding

pull/894/head
Jeremy Koritzinsky 9 years ago
committed by GitHub
parent
commit
dd3923bfb3
  1. 3
      .gitmodules
  2. 5
      .ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject
  3. 5
      .ncrunch/Avalonia.Visuals.UnitTests.net461.v3.ncrunchproject
  4. 5
      .ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject
  5. 3
      Avalonia.sln
  6. 2
      build.cake
  7. 5
      build/ReactiveUI.props
  8. 13
      build/Rx.props
  9. 10
      build/SharpDX.props
  10. 2
      docs/tutorial/from-wpf.md
  11. 26
      packages.cake
  12. 16
      samples/BindingTest/App.config
  13. 1
      samples/BindingTest/App.xaml.cs
  14. 2
      samples/BindingTest/BindingTest.csproj
  15. 10
      samples/BindingTest/ViewModels/MainWindowViewModel.cs
  16. 16
      samples/ControlCatalog.Desktop/App.config
  17. 1
      samples/ControlCatalog/MainWindow.xaml.cs
  18. 16
      samples/RenderTest/App.config
  19. 1
      samples/RenderTest/Program.cs
  20. 1
      samples/RenderTest/RenderTest.csproj
  21. 10
      samples/RenderTest/ViewModels/MainWindowViewModel.cs
  22. 16
      samples/VirtualizationTest/App.config
  23. 1
      samples/VirtualizationTest/Program.cs
  24. 25
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  25. 1
      samples/VirtualizationTest/VirtualizationTest.csproj
  26. 4
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  27. 2
      samples/interop/Direct3DInteropSample/Program.cs
  28. 2
      src/Android/Avalonia.Android/AndroidPlatform.cs
  29. 8
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  30. 2
      src/Avalonia.Base/Collections/AvaloniaList.cs
  31. 5
      src/Avalonia.Base/Utilities/WeakObservable.cs
  32. 24
      src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
  33. 14
      src/Avalonia.Controls/Classes.cs
  34. 7
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  35. 6
      src/Avalonia.Controls/TopLevel.cs
  36. 9
      src/Avalonia.Controls/Window.cs
  37. 2
      src/Avalonia.Controls/WindowBase.cs
  38. 24
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  39. 73
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  40. 27
      src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs
  41. 23
      src/Avalonia.ReactiveUI/Registrations.cs
  42. 34
      src/Avalonia.ReactiveUI/Shims.cs
  43. 1
      src/Avalonia.ReactiveUI/src
  44. 3
      src/Avalonia.Themes.Default/CheckBox.xaml
  45. 1
      src/Avalonia.Themes.Default/DropDownItem.xaml
  46. 6
      src/Avalonia.Themes.Default/ListBox.xaml
  47. 1
      src/Avalonia.Themes.Default/ListBoxItem.xaml
  48. 1
      src/Avalonia.Themes.Default/MenuItem.xaml
  49. 3
      src/Avalonia.Themes.Default/RadioButton.xaml
  50. 3
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  51. 1
      src/Avalonia.Themes.Default/TabStripItem.xaml
  52. 6
      src/Avalonia.Themes.Default/TreeView.xaml
  53. 3
      src/Avalonia.Themes.Default/TreeViewItem.xaml
  54. 51
      src/Avalonia.Visuals/Media/BrushExtensions.cs
  55. 0
      src/Avalonia.Visuals/Media/IImageBrush.cs
  56. 12
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  57. 8
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  58. 34
      src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs
  59. 430
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  60. 88
      src/Avalonia.Visuals/Rendering/DirtyRects.cs
  61. 107
      src/Avalonia.Visuals/Rendering/DirtyVisuals.cs
  62. 51
      src/Avalonia.Visuals/Rendering/DisplayDirtyRect.cs
  63. 62
      src/Avalonia.Visuals/Rendering/DisplayDirtyRects.cs
  64. 11
      src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs
  65. 5
      src/Avalonia.Visuals/Rendering/IRenderRoot.cs
  66. 10
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  67. 18
      src/Avalonia.Visuals/Rendering/IRendererFactory.cs
  68. 17
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  69. 47
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  70. 63
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  71. 31
      src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
  72. 64
      src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
  73. 392
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  74. 64
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs
  75. 101
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  76. 36
      src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs
  77. 24
      src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs
  78. 94
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  79. 94
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  80. 95
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  81. 80
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs
  82. 64
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs
  83. 116
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  84. 184
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  85. 384
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  86. 75
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs
  87. 199
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs
  88. 96
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  89. 285
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  90. 11
      src/Avalonia.Visuals/Vector.cs
  91. 24
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  92. 26
      src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject
  93. 1
      src/Gtk/Avalonia.Cairo/CairoPlatform.cs
  94. 5
      src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs
  95. 8
      src/Gtk/Avalonia.Gtk/GtkPlatform.cs
  96. 6
      src/Gtk/Avalonia.Gtk/TopLevelImpl.cs
  97. 8
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  98. 6
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  99. 6
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  100. 1
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

3
.gitmodules

@ -1,6 +1,3 @@
[submodule "src/Avalonia.ReactiveUI/src"]
path = src/Avalonia.ReactiveUI/src
url = https://github.com/reactiveui/ReactiveUI.git
[submodule "src/Avalonia.HtmlRenderer/external"]
path = src/Avalonia.HtmlRenderer/external
url = https://github.com/AvaloniaUI/HTML-Renderer.git

5
.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Visuals.UnitTests.net461.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
</Settings>
</ProjectConfiguration>

3
Avalonia.sln

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.4
VisualStudioVersion = 15.0.26430.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@ -169,6 +169,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props
build\NetCore.props = build\NetCore.props
build\ReactiveUI.props = build\ReactiveUI.props
build\Rx.props = build\Rx.props
build\Serilog.props = build\Serilog.props
build\Serilog.Sinks.Trace.props = build\Serilog.Sinks.Trace.props

2
build.cake

@ -193,7 +193,7 @@ Task("Run-Net-Core-Unit-Tests")
Task("Run-Unit-Tests")
.IsDependentOn("Run-Net-Core-Unit-Tests")
.IsDependentOn("Build")
.IsDependentOn("Run-Leak-Tests")
//.IsDependentOn("Run-Leak-Tests")
.WithCriteria(() => !parameters.SkipTests)
.Does(() =>
{

5
build/ReactiveUI.props

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

13
build/Rx.props

@ -1,11 +1,10 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Reactive" Version="3.0.0" />
<PackageReference Include="System.Reactive.Core" Version="3.0.0" />
<PackageReference Include="System.Reactive.Interfaces" Version="3.0.0" />
<PackageReference Include="System.Reactive.Linq" Version="3.0.0" />
<PackageReference Include="System.Reactive.PlatformServices" Version="3.0.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net45'" Include="System.Reactive.Windows.Threading" Version="3.0.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="System.Reactive.Windows.Threading" Version="3.0.0" />
<PackageReference Include="System.Reactive" Version="3.1.0" />
<PackageReference Include="System.Reactive.Core" Version="3.1.0" />
<PackageReference Include="System.Reactive.Interfaces" Version="3.1.0" />
<PackageReference Include="System.Reactive.Linq" Version="3.1.0" />
<PackageReference Include="System.Reactive.PlatformServices" Version="3.1.0" />
<PackageReference Condition="$(TargetFramework.StartsWith('net4'))" Include="System.Reactive.Windows.Threading" Version="3.1.0" />
</ItemGroup>
</Project>

10
build/SharpDX.props

@ -1,9 +1,9 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SharpDX" Version="3.1.1" />
<PackageReference Include="SharpDX.Direct2D1" Version="3.1.1" />
<PackageReference Include="SharpDX.Direct3D11" Version="3.1.1" />
<PackageReference Include="SharpDX.Direct3D9" Version="3.1.1" Condition="'$(UseDirect3D9)' == 'true'" />
<PackageReference Include="SharpDX.DXGI" Version="3.1.1" />
<PackageReference Include="SharpDX" Version="4.0.1" />
<PackageReference Include="SharpDX.Direct2D1" Version="4.0.1" />
<PackageReference Include="SharpDX.Direct3D11" Version="4.0.1" />
<PackageReference Include="SharpDX.DXGI" Version="4.0.1" />
<PackageReference Include="SharpDX.Direct3D9" Version="4.0.1" Condition="'$(UseDirect3D9)' == 'true'" />
</ItemGroup>
</Project>

2
docs/tutorial/from-wpf.md

@ -161,7 +161,7 @@ the same way that event class listeners are added](../spec/working-with-properti
## RenderTransforms and RenderTransformOrigin
RenderTransformOrigins are different in WPF and Avalonia: If you apply a `RenderTransform`, keep in mind that our default value for the RenderTransformOrigin is `RelativePoint.Middle`. In WPF the default value is `RelativePoint.TopLeft` (0, 0). In controls like Viewbox (currently being developed) the same code will lead to a different rendering behavior:
RenderTransformOrigins are different in WPF and Avalonia: If you apply a `RenderTransform`, keep in mind that our default value for the RenderTransformOrigin is `RelativePoint.Center`. In WPF the default value is `RelativePoint.TopLeft` (0, 0). In controls like Viewbox (currently being developed) the same code will lead to a different rendering behavior:
In WPF:
![WPF](https://files.gitter.im/AvaloniaUI/Avalonia/cDrM/image.png)

26
packages.cake

@ -111,6 +111,7 @@ public class Packages
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
@ -124,6 +125,7 @@ public class Packages
context.Information("Package: Splat, version: {0}", SplatVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion);
context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
@ -176,7 +178,6 @@ public class Packages
new [] { "./src/", "Avalonia.Visuals", ".xml" },
new [] { "./src/", "Avalonia.Styling", ".dll" },
new [] { "./src/", "Avalonia.Styling", ".xml" },
new [] { "./src/", "Avalonia.ReactiveUI", ".dll" },
new [] { "./src/", "Avalonia.Themes.Default", ".dll" },
new [] { "./src/", "Avalonia.Themes.Default", ".xml" },
new [] { "./src/Markup/", "Avalonia.Markup", ".dll" },
@ -273,7 +274,24 @@ public class Packages
},
BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard1.3"),
OutputDirectory = parameters.NugetRoot
}
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.ReactiveUI
///////////////////////////////////////////////////////////////////////////////
new NuGetPackSettings()
{
Id = "Avalonia.ReactiveUI",
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
}.Deps(new string[] {null}, "reactiveui"),
Files = new []
{
new NuSpecContent { Source = "Avalonia.ReactiveUI.dll", Target = "lib/netstandard1.3" }
},
BasePath = context.Directory("./src/Avalonia.ReactiveUI/bin/" + parameters.DirSuffix + "/netstandard1.3"),
OutputDirectory = parameters.NugetRoot
},
};
var nuspecNuGetSettingsMobile = new []
@ -406,9 +424,9 @@ public class Packages
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Direct2D1.dll", Target = "lib/net45" }
new NuSpecContent { Source = "Avalonia.Direct2D1.dll", Target = "lib/netstandard1.3" }
},
BasePath = context.Directory("./src/Windows/Avalonia.Direct2D1/bin/" + parameters.DirSuffix),
BasePath = context.Directory("./src/Windows/Avalonia.Direct2D1/bin/" + parameters.DirSuffix + "/netstandard1.3"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////

16
samples/BindingTest/App.config

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>

1
samples/BindingTest/App.xaml.cs

@ -20,6 +20,7 @@ namespace BindingTest
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.Start<MainWindow>();
}

2
samples/BindingTest/BindingTest.csproj

@ -45,7 +45,6 @@
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
@ -163,4 +162,5 @@
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

10
samples/BindingTest/ViewModels/MainWindowViewModel.cs

@ -28,15 +28,13 @@ namespace BindingTest.ViewModels
SelectedItems = new ObservableCollection<TestItem>();
ShuffleItems = ReactiveCommand.Create();
ShuffleItems.Subscribe(_ =>
ShuffleItems = ReactiveCommand.Create(() =>
{
var r = new Random();
Items.Move(r.Next(Items.Count), 1);
});
StringValueCommand = ReactiveCommand.Create();
StringValueCommand.Subscribe(param =>
StringValueCommand = ReactiveCommand.Create<object>(param =>
{
BooleanFlag = !BooleanFlag;
StringValue = param.ToString();
@ -58,7 +56,7 @@ namespace BindingTest.ViewModels
public ObservableCollection<TestItem> Items { get; }
public ObservableCollection<TestItem> SelectedItems { get; }
public ReactiveCommand<object> ShuffleItems { get; }
public ReactiveCommand ShuffleItems { get; }
public string BooleanString
{
@ -91,7 +89,7 @@ namespace BindingTest.ViewModels
}
public IObservable<string> CurrentTimeObservable { get; }
public ReactiveCommand<object> StringValueCommand { get; }
public ReactiveCommand StringValueCommand { get; }
public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();
public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel();

16
samples/ControlCatalog.Desktop/App.config

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>

1
samples/ControlCatalog/MainWindow.xaml.cs

@ -10,6 +10,7 @@ namespace ControlCatalog
{
this.InitializeComponent();
this.AttachDevTools();
Renderer.DrawDirtyRects = Renderer.DrawFps = true;
}
private void InitializeComponent()

16
samples/RenderTest/App.config

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>

1
samples/RenderTest/Program.cs

@ -18,6 +18,7 @@ namespace RenderTest
// again.
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.Start<MainWindow>();
}

1
samples/RenderTest/RenderTest.csproj

@ -183,4 +183,5 @@
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

10
samples/RenderTest/ViewModels/MainWindowViewModel.cs

@ -10,10 +10,8 @@ namespace RenderTest.ViewModels
public MainWindowViewModel()
{
ToggleDrawDirtyRects = ReactiveCommand.Create();
ToggleDrawDirtyRects.Subscribe(_ => DrawDirtyRects = !DrawDirtyRects);
ToggleDrawFps = ReactiveCommand.Create();
ToggleDrawFps.Subscribe(_ => DrawFps = !DrawFps);
ToggleDrawDirtyRects = ReactiveCommand.Create(() => DrawDirtyRects = !DrawDirtyRects);
ToggleDrawFps = ReactiveCommand.Create(() => DrawFps = !DrawFps);
}
public bool DrawDirtyRects
@ -28,7 +26,7 @@ namespace RenderTest.ViewModels
set { this.RaiseAndSetIfChanged(ref drawFps, value); }
}
public ReactiveCommand<object> ToggleDrawDirtyRects { get; }
public ReactiveCommand<object> ToggleDrawFps { get; }
public ReactiveCommand ToggleDrawDirtyRects { get; }
public ReactiveCommand ToggleDrawFps { get; }
}
}

16
samples/VirtualizationTest/App.config

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
<assemblyIdentity name="Mono.Cairo" publicKeyToken="0738eb9f132ed756" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0" />
<assemblyIdentity name="SharpDX.DXGI" publicKeyToken="b4dcf0f35e5521f1" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-3.1.1.0" newVersion="3.1.1.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>

1
samples/VirtualizationTest/Program.cs

@ -17,6 +17,7 @@ namespace VirtualizationTest
AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.Start<MainWindow>();
}

25
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs

@ -23,20 +23,15 @@ namespace VirtualizationTest.ViewModels
public MainWindowViewModel()
{
this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems);
RecreateCommand = ReactiveCommand.Create();
RecreateCommand.Subscribe(_ => Recreate());
RecreateCommand = ReactiveCommand.Create(() => Recreate());
AddItemCommand = ReactiveCommand.Create();
AddItemCommand.Subscribe(_ => AddItem());
AddItemCommand = ReactiveCommand.Create(() => AddItem());
RemoveItemCommand = ReactiveCommand.Create();
RemoveItemCommand.Subscribe(_ => Remove());
RemoveItemCommand = ReactiveCommand.Create(() => Remove());
SelectFirstCommand = ReactiveCommand.Create();
SelectFirstCommand.Subscribe(_ => SelectItem(0));
SelectFirstCommand = ReactiveCommand.Create(() => SelectItem(0));
SelectLastCommand = ReactiveCommand.Create();
SelectLastCommand.Subscribe(_ => SelectItem(Items.Count - 1));
SelectLastCommand = ReactiveCommand.Create(() => SelectItem(Items.Count - 1));
}
public string NewItemString
@ -78,11 +73,11 @@ namespace VirtualizationTest.ViewModels
public IEnumerable<ItemVirtualizationMode> VirtualizationModes =>
Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
public ReactiveCommand<object> AddItemCommand { get; private set; }
public ReactiveCommand<object> RecreateCommand { get; private set; }
public ReactiveCommand<object> RemoveItemCommand { get; private set; }
public ReactiveCommand<object> SelectFirstCommand { get; private set; }
public ReactiveCommand<object> SelectLastCommand { get; private set; }
public ReactiveCommand AddItemCommand { get; private set; }
public ReactiveCommand RecreateCommand { get; private set; }
public ReactiveCommand RemoveItemCommand { get; private set; }
public ReactiveCommand SelectFirstCommand { get; private set; }
public ReactiveCommand SelectLastCommand { get; private set; }
private void ResizeItems(int count)
{

1
samples/VirtualizationTest/VirtualizationTest.csproj

@ -158,4 +158,5 @@
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

4
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -4,8 +4,8 @@
<TargetFramework>net461</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpDX.Mathematics" Version="3.1.1" />
<PackageReference Include="SharpDX.D3DCompiler" Version="3.1.1" />
<PackageReference Include="SharpDX.Mathematics" Version="4.0.1" />
<PackageReference Include="SharpDX.D3DCompiler" Version="4.0.1" />
<Compile Update="**\*.paml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>

2
samples/interop/Direct3DInteropSample/Program.cs

@ -11,7 +11,7 @@ namespace Direct3DInteropSample
{
static void Main(string[] args)
{
AppBuilder.Configure<App>().UseWin32().UseDirect2D1().Start<MainWindow>();
AppBuilder.Configure<App>().UseWin32(deferredRendering: false).UseDirect2D1().Start<MainWindow>();
}
}
}

2
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -52,13 +52,11 @@ namespace Avalonia.Android
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IWindowingPlatform>().ToConstant(Instance)
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
.Bind<IRenderLoop>().ToConstant(new DefaultRenderLoop(60))
.Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly));
SkiaPlatform.Initialize();

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

@ -13,6 +13,7 @@ using System.Reactive.Disposables;
using Avalonia.Android.Platform.Input;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -85,7 +86,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces => new object[] {this};
public IRenderer CreateRenderer(IRenderRoot root)
{
return new ImmediateRenderer(root);
}
public virtual void Hide()
{
_view.Visibility = ViewStates.Invisible;

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

@ -220,7 +220,7 @@ namespace Avalonia.Collections
/// <summary>
/// Removes all items from the collection.
/// </summary>
public void Clear()
public virtual void Clear()
{
if (this.Count > 0)
{

5
src/Avalonia.Base/Utilities/WeakObservable.cs

@ -16,12 +16,13 @@ namespace Avalonia.Utilities
/// Converts a .NET event conforming to the standard .NET event pattern into an observable
/// sequence, subscribing weakly.
/// </summary>
/// <typeparam name="TTarget">The type of target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event args.</typeparam>
/// <param name="target">Object instance that exposes the event to convert.</param>
/// <param name="eventName">Name of the event to convert.</param>
/// <returns></returns>
public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TEventArgs>(
object target,
public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TTarget, TEventArgs>(
TTarget target,
string eventName)
where TEventArgs : EventArgs
{

24
src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs

@ -17,22 +17,23 @@ namespace Avalonia.Utilities
/// <summary>
/// Subscribes to an event on an object using a weak subscription.
/// </summary>
/// <typeparam name="T">The type of the event arguments.</typeparam>
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
/// <param name="target">The event source.</param>
/// <param name="eventName">The name of the event.</param>
/// <param name="subscriber">The subscriber.</param>
public static void Subscribe<T>(object target, string eventName, IWeakSubscriber<T> subscriber)
where T : EventArgs
public static void Subscribe<TTarget, TEventArgs>(TTarget target, string eventName, IWeakSubscriber<TEventArgs> subscriber)
where TEventArgs : EventArgs
{
var dic = SubscriptionTypeStorage<T>.Subscribers.GetOrCreateValue(target);
Subscription<T> sub;
var dic = SubscriptionTypeStorage<TEventArgs>.Subscribers.GetOrCreateValue(target);
Subscription<TEventArgs> sub;
if (!dic.TryGetValue(eventName, out sub))
{
dic[eventName] = sub = new Subscription<T>(dic, target, eventName);
dic[eventName] = sub = new Subscription<TEventArgs>(dic, typeof(TTarget), target, eventName);
}
sub.Add(new WeakReference<IWeakSubscriber<T>>(subscriber));
sub.Add(new WeakReference<IWeakSubscriber<TEventArgs>>(subscriber));
}
/// <summary>
@ -84,19 +85,18 @@ namespace Avalonia.Utilities
private WeakReference<IWeakSubscriber<T>>[] _data = new WeakReference<IWeakSubscriber<T>>[16];
private int _count = 0;
public Subscription(SubscriptionDic<T> sdic, object target, string eventName)
public Subscription(SubscriptionDic<T> sdic, Type targetType, object target, string eventName)
{
_sdic = sdic;
_target = target;
_eventName = eventName;
var t = target.GetType();
Dictionary<string, EventInfo> evDic;
if (!Accessors.TryGetValue(t, out evDic))
Accessors[t] = evDic = new Dictionary<string, EventInfo>();
if (!Accessors.TryGetValue(targetType, out evDic))
Accessors[targetType] = evDic = new Dictionary<string, EventInfo>();
if (!evDic.TryGetValue(eventName, out _info))
{
var ev = t.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName);
var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName);
if (ev == null)
{

14
src/Avalonia.Controls/Classes.cs

@ -86,6 +86,20 @@ namespace Avalonia.Controls
base.AddRange(c);
}
/// <summary>
/// Remvoes all non-pseudoclasses from the collection.
/// </summary>
public override void Clear()
{
for (var i = Count - 1; i >= 0; --i)
{
if (!this[i].StartsWith(":"))
{
RemoveAt(i);
}
}
}
/// <summary>
/// Inserts a style class into the collection.
/// </summary>

7
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Rendering;
using JetBrains.Annotations;
namespace Avalonia.Platform
@ -60,6 +61,12 @@ namespace Avalonia.Platform
/// </summary>
Action<double> ScalingChanged { get; set; }
/// <summary>
/// Creates a new renderer for the toplevel.
/// </summary>
/// <param name="root">The toplevel.</param>
IRenderer CreateRenderer(IRenderRoot root);
/// <summary>
/// Invalidates a rect on the toplevel.
/// </summary>

6
src/Avalonia.Controls/TopLevel.cs

@ -90,8 +90,7 @@ namespace Avalonia.Controls
_renderInterface = TryGetService<IPlatformRenderInterface>(dependencyResolver);
var renderLoop = TryGetService<IRenderLoop>(dependencyResolver);
var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver);
Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
Renderer = impl.CreateRenderer(this);
impl.SetInputRoot(this);
@ -181,6 +180,9 @@ namespace Avalonia.Controls
/// <inheritdoc/>
double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
/// <inheritdoc/>
double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1;
IStyleHost IStyleHost.StylingParent
{
get { return AvaloniaLocator.Current.GetService<IGlobalStyles>(); }

9
src/Avalonia.Controls/Window.cs

@ -225,8 +225,14 @@ namespace Avalonia.Controls
/// </summary>
public override void Hide()
{
if (!IsVisible)
{
return;
}
using (BeginAutoSizing())
{
Renderer?.Stop();
PlatformImpl?.Hide();
}
@ -252,6 +258,7 @@ namespace Avalonia.Controls
using (BeginAutoSizing())
{
PlatformImpl?.Show();
Renderer?.Start();
}
}
@ -297,6 +304,8 @@ namespace Avalonia.Controls
var modal = PlatformImpl?.ShowDialog();
var result = new TaskCompletionSource<TResult>();
Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>(
x => this.Closed += x,
x => this.Closed -= x)

2
src/Avalonia.Controls/WindowBase.cs

@ -117,6 +117,7 @@ namespace Avalonia.Controls
try
{
Renderer?.Stop();
PlatformImpl?.Hide();
IsVisible = false;
}
@ -145,6 +146,7 @@ namespace Avalonia.Controls
}
PlatformImpl?.Show();
Renderer?.Start();
}
finally
{

24
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Threading;
using ReactiveUI;
using System;
using System.Reactive.Concurrency;
using System.Threading;
namespace Avalonia
{
public static class AppBuilderExtensions
{
public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{
return builder.AfterSetup(_ =>
{
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
});
}
}
}

73
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -1,80 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;MONO PORTABLE;NETSTANDARD1_1</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;MONO PORTABLE;RELEASE;NETSTANDARD1_1</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<None Remove="Shims.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Registrations.cs" />
<Compile Include="Shims.cs" />
<Compile Include="src\ReactiveUI\ExpressionMixins.cs" />
<Compile Include="src\ReactiveUI\ExpressionRewriter.cs" />
<Compile Include="src\ReactiveUI\Activation.cs" />
<Compile Include="src\ReactiveUI\ReactiveBinding.cs" />
<Compile Include="src\ReactiveUI\AutoPersistHelper.cs" />
<Compile Include="src\ReactiveUI\BindingTypeConverters.cs" />
<Compile Include="src\ReactiveUI\CollectionDebugView.cs" />
<Compile Include="src\ReactiveUI\CommandBinding.cs" />
<Compile Include="src\ReactiveUI\CompatMixins.cs" />
<Compile Include="src\ReactiveUI\ContractStubs.cs" />
<Compile Include="src\ReactiveUI\CreatesCommandBinding.cs" />
<Compile Include="src\ReactiveUI\DefaultPropertyBinding.cs" />
<Compile Include="src\ReactiveUI\Errors.cs" />
<Compile Include="src\ReactiveUI\IDependencyResolver.cs" />
<Compile Include="src\ReactiveUI\INPCObservableForProperty.cs" />
<Compile Include="src\ReactiveUI\Interfaces.cs" />
<Compile Include="src\ReactiveUI\IROObservableForProperty.cs" />
<Compile Include="src\ReactiveUI\MessageBus.cs" />
<Compile Include="src\ReactiveUI\MobileLifecycle.cs" />
<Compile Include="src\ReactiveUI\NullDefaultPropertyBindingProvider.cs" />
<Compile Include="src\ReactiveUI\ObservableAsPropertyHelper.cs" />
<Compile Include="src\ReactiveUI\ObservedChangedMixin.cs" />
<Compile Include="src\ReactiveUI\OrderedComparer.cs" />
<Compile Include="src\ReactiveUI\POCOObservableForProperty.cs" />
<Compile Include="src\ReactiveUI\PropertyBinding.cs" />
<Compile Include="src\ReactiveUI\ReactiveList.cs" />
<Compile Include="src\ReactiveUI\ReactiveCollectionMixins.cs" />
<Compile Include="src\ReactiveUI\ReactiveCommand.cs" />
<Compile Include="src\ReactiveUI\ReactiveNotifyPropertyChangedMixin.cs" />
<Compile Include="src\ReactiveUI\ReactiveObject.cs" />
<Compile Include="src\ReactiveUI\RefcountDisposeWrapper.cs" />
<Compile Include="src\ReactiveUI\Reflection.cs" />
<Compile Include="src\ReactiveUI\RegisterableInterfaces.cs" />
<Compile Include="src\ReactiveUI\Registrations.cs" />
<Compile Include="src\ReactiveUI\RoutableViewModelMixin.cs" />
<Compile Include="src\ReactiveUI\RoutingState.cs" />
<Compile Include="src\ReactiveUI\RxApp.cs" />
<Compile Include="src\ReactiveUI\ViewLocator.cs" />
<Compile Include="src\ReactiveUI\ScheduledSubject.cs" />
<Compile Include="src\ReactiveUI\VariadicTemplates.cs" />
<Compile Include="src\ReactiveUI\WaitForDispatcherScheduler.cs" />
<Compile Include="src\ReactiveUI\LoggingMixins.cs" />
<Compile Include="src\ReactiveUI\IReactiveObject.cs" />
<Compile Include="src\ReactiveUI\WeakEventManager.cs" />
<Compile Include="src\ReactiveUI\Legacy\ReactiveCommand.cs" />
<Compile Include="..\Shared\SharedAssemblyInfo.cs" Link="Properties\SharedAssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
@ -87,5 +17,6 @@
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
<Import Project="..\..\build\Splat.props" />
</Project>

27
src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs

@ -1,33 +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.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Avalonia.ReactiveUI")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Avalonia.ReactiveUI")]
[assembly: AssemblyCopyright("Copyright \u00A9 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

23
src/Avalonia.ReactiveUI/Registrations.cs

@ -1,23 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Concurrency;
using System.Threading;
namespace ReactiveUI
{
/// <summary>
/// Ignore me. This class is a secret handshake between RxUI and RxUI.Xaml
/// in order to register certain classes on startup that would be difficult
/// to register otherwise.
/// </summary>
public class PlatformRegistrations : IWantsToRegisterStuff
{
public void Register(Action<Func<object>, Type> registerFunction)
{
RxApp.MainThreadScheduler = new SynchronizationContextScheduler(SynchronizationContext.Current);
}
}
}

34
src/Avalonia.ReactiveUI/Shims.cs

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace System.Runtime.Serialization
{
class IgnoreDataMemberAttribute : Attribute
{
}
class DataMemberAttribute : Attribute
{
}
class OnDeserializedAttribute : Attribute
{
}
class DataContractAttribute : Attribute
{
}
class StreamingContext { }
}
namespace System.Diagnostics.Contracts
{
static class Contract
{
public static void Requires(bool condition)
{
}
}
}

1
src/Avalonia.ReactiveUI/src

@ -1 +0,0 @@
Subproject commit 3f725c808b1d4c8457f0d3204e0a071aa462cd75

3
src/Avalonia.Themes.Default/CheckBox.xaml

@ -1,10 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="CheckBox">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StyleResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{StyleResource ThemeBorderThickness}"/>
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="Auto,*">
<Grid ColumnDefinitions="Auto,*" Background="{TemplateBinding Background}">
<Border Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"

1
src/Avalonia.Themes.Default/DropDownItem.xaml

@ -1,5 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="DropDownItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>

6
src/Avalonia.Themes.Default/ListBox.xaml

@ -1,13 +1,13 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ListBox">
<Setter Property="Background" Value="{StyleResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{StyleResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{StyleResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Name="PART_ScrollViewer">
<ScrollViewer Name="PART_ScrollViewer" Background="{TemplateBinding Background}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

1
src/Avalonia.Themes.Default/ListBoxItem.xaml

@ -1,5 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"

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

@ -2,6 +2,7 @@
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style Selector="MenuItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="6,0"/>
<Setter Property="Template">

3
src/Avalonia.Themes.Default/RadioButton.xaml

@ -1,10 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="RadioButton">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StyleResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{StyleResource ThemeBorderThickness}"/>
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="Auto,*">
<Grid ColumnDefinitions="Auto,*" Background="{TemplateBinding Background}">
<Ellipse Name="border"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"

3
src/Avalonia.Themes.Default/ScrollViewer.xaml

@ -1,8 +1,9 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ScrollViewer">
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto" Background="{TemplateBinding Background}">
<Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
<ScrollContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}"
Extent="{TemplateBinding Path=Extent, Mode=TwoWay}"
Margin="{TemplateBinding Padding}"

1
src/Avalonia.Themes.Default/TabStripItem.xaml

@ -1,5 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="TabStripItem">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="FontSize" Value="{StyleResource FontSizeLarge}"/>
<Setter Property="Foreground" Value="{StyleResource ThemeForegroundLightBrush}"/>
<Setter Property="Template">

6
src/Avalonia.Themes.Default/TreeView.xaml

@ -1,13 +1,13 @@
<Style xmlns="https://github.com/avaloniaui" Selector="TreeView">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StyleResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{StyleResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer CanScrollHorizontally="True">
<ScrollViewer CanScrollHorizontally="True" Background="{TemplateBinding Background}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

3
src/Avalonia.Themes.Default/TreeViewItem.xaml

@ -31,7 +31,8 @@
<Style Selector="TreeViewItem /template/ ToggleButton#expander">
<Setter Property="Template">
<ControlTemplate>
<Border Width="14"
<Border Background="Transparent"
Width="14"
Height="12"
HorizontalAlignment="Center"
VerticalAlignment="Center">

51
src/Avalonia.Visuals/Media/BrushExtensions.cs

@ -0,0 +1,51 @@
using System;
namespace Avalonia.Media
{
/// <summary>
/// Extension methods for brush classes.
/// </summary>
public static class BrushExtensions
{
/// <summary>
/// Converts a brush to an immutable brush.
/// </summary>
/// <param name="brush">The brush.</param>
/// <returns>
/// The result of calling <see cref="IMutableBrush.ToImmutable"/> if the brush is mutable,
/// otherwise <paramref name="brush"/>.
/// </returns>
public static IBrush ToImmutable(this IBrush brush)
{
Contract.Requires<ArgumentNullException>(brush != null);
return (brush as IMutableBrush)?.ToImmutable() ?? brush;
}
/// <summary>
/// Converts a pen to a pen with an immutable brush
/// </summary>
/// <param name="pen">The pen.</param>
/// <returns>
/// A copy of the pen with an immutable brush, or <paramref name="pen"/> if the pen's brush
/// is already immutable or null.
/// </returns>
public static Pen ToImmutable(this Pen pen)
{
Contract.Requires<ArgumentNullException>(pen != null);
var brush = pen?.Brush?.ToImmutable();
return pen == null || ReferenceEquals(pen?.Brush, brush) ?
pen :
new Pen(
brush,
thickness: pen.Thickness,
dashStyle: pen.DashStyle,
dashCap: pen.DashCap,
startLineCap: pen.StartLineCap,
endLineCap: pen.EndLineCap,
lineJoin: pen.LineJoin,
miterLimit: pen.MiterLimit);
}
}
}

0
src/Avalonia.Visuals/Media/Imaging/IImageBrush.cs → src/Avalonia.Visuals/Media/IImageBrush.cs

12
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -3,7 +3,6 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Platform
{
@ -32,6 +31,15 @@ namespace Avalonia.Platform
/// <param name="destRect">The rect in the output to draw to.</param>
void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect);
/// <summary>
/// Draws a bitmap image.
/// </summary>
/// <param name="source">The bitmap image.</param>
/// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
/// <summary>
/// Draws a line.
/// </summary>
@ -100,4 +108,4 @@ namespace Avalonia.Platform
void PopGeometryClip();
}
}
}

8
src/Avalonia.Visuals/Properties/AssemblyInfo.cs

@ -2,8 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: AssemblyTitle("Avalonia.Visuals")]
[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
[assembly: InternalsVisibleTo("Avalonia.Cairo.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")]

34
src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs

@ -0,0 +1,34 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class DefaultRenderLayerFactory : IRenderLayerFactory
{
private IPlatformRenderInterface _renderInterface;
public DefaultRenderLayerFactory()
: this(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>())
{
}
public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface)
{
_renderInterface = renderInterface;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _renderInterface.CreateRenderTargetBitmap(
(int)Math.Ceiling(size.Width),
(int)Math.Ceiling(size.Height),
dpiX,
dpiY);
}
}
}

430
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -0,0 +1,430 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.VisualTree;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media.Immutable;
using System.Threading;
namespace Avalonia.Rendering
{
/// <summary>
/// A renderer which renders the state of the visual tree to an intermediate scene graph
/// representation which is then rendered on a rendering thread.
/// </summary>
public class DeferredRenderer : RendererBase, IRenderer, IVisualBrushRenderer
{
private readonly IDispatcher _dispatcher;
private readonly IRenderLoop _renderLoop;
private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers;
private readonly IRenderLayerFactory _layerFactory;
private bool _running;
private Scene _scene;
private IRenderTarget _renderTarget;
private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay;
private bool _updateQueued;
private object _rendering = new object();
private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IDrawOperation _currentDraw;
/// <summary>
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
/// </summary>
/// <param name="root">The control to render.</param>
/// <param name="renderLoop">The render loop.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <param name="dispatcher">The dispatcher to use. Optional.</param>
public DeferredRenderer(
IRenderRoot root,
IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null,
IDispatcher dispatcher = null)
{
Contract.Requires<ArgumentNullException>(root != null);
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_scene = new Scene(root);
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_renderLoop = renderLoop;
}
/// <summary>
/// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
/// </summary>
/// <param name="root">The control to render.</param>
/// <param name="renderTarget">The render target.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <remarks>
/// This constructor is intended to be used for unit testing.
/// </remarks>
public DeferredRenderer(
IVisual root,
IRenderTarget renderTarget,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null)
{
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderTarget != null);
_root = root;
_renderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_scene = new Scene(root);
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
}
/// <inheritdoc/>
public bool DrawFps { get; set; }
/// <inheritdoc/>
public bool DrawDirtyRects { get; set; }
/// <summary>
/// Gets or sets a path to which rendered frame should be rendered for debugging.
/// </summary>
public string DebugFramesPath { get; set; }
/// <inheritdoc/>
public void AddDirty(IVisual visual)
{
_dirty?.Add(visual);
}
/// <summary>
/// Disposes of the renderer and detaches from the render loop.
/// </summary>
public void Dispose() => Stop();
/// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
{
if (_renderLoop == null && (_dirty == null || _dirty.Count > 0))
{
// When unit testing the renderLoop may be null, so update the scene manually.
UpdateScene();
}
return _scene.HitTest(p, filter);
}
/// <inheritdoc/>
public void Paint(Rect rect)
{
}
/// <inheritdoc/>
public void Resized(Size size)
{
}
/// <inheritdoc/>
public void Start()
{
if (!_running && _renderLoop != null)
{
_renderLoop.Tick += OnRenderLoopTick;
_running = true;
}
}
/// <inheritdoc/>
public void Stop()
{
if (_running && _renderLoop != null)
{
_renderLoop.Tick -= OnRenderLoopTick;
_running = false;
}
}
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{
return (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
}
/// <inheritdoc/>
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{
var childScene = (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual];
if (childScene != null)
{
Render(context, (VisualNode)childScene.Root, null, new Rect(childScene.Size));
}
}
internal void UnitTestUpdateScene() => UpdateScene();
internal void UnitTestRender() => Render(_scene);
private void Render(Scene scene)
{
_dirtyRectsDisplay.Tick();
if (scene.Size != Size.Empty)
{
if (scene.Generation != _lastSceneId)
{
_layers.Update(scene);
RenderToLayers(scene);
if (DebugFramesPath != null)
{
SaveDebugFrames(scene.Generation);
}
_lastSceneId = scene.Generation;
}
RenderOverlay(scene);
RenderComposite(scene);
}
}
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
{
if (layer == null || node.LayerRoot == layer)
{
clipBounds = node.ClipBounds.Intersect(clipBounds);
if (!clipBounds.IsEmpty)
{
node.BeginRender(context);
foreach (var operation in node.DrawOperations)
{
_currentDraw = operation;
operation.Render(context);
_currentDraw = null;
}
foreach (var child in node.Children)
{
Render(context, (VisualNode)child, layer, clipBounds);
}
node.EndRender(context);
}
}
}
private void RenderToLayers(Scene scene)
{
if (scene.Layers.HasDirty)
{
foreach (var layer in scene.Layers)
{
var renderTarget = _layers[layer.LayerRoot].Bitmap;
var node = (VisualNode)scene.FindNode(layer.LayerRoot);
if (node != null)
{
using (var context = renderTarget.CreateDrawingContext(this))
{
foreach (var rect in layer.Dirty)
{
context.Transform = Matrix.Identity;
context.PushClip(rect);
context.Clear(Colors.Transparent);
Render(context, node, layer.LayerRoot, rect);
context.PopClip();
if (DrawDirtyRects)
{
_dirtyRectsDisplay.Add(rect);
}
}
}
}
}
}
}
private void RenderOverlay(Scene scene)
{
if (DrawDirtyRects)
{
var overlay = GetOverlay(scene.Size, scene.Scaling);
using (var context = overlay.CreateDrawingContext(this))
{
context.Clear(Colors.Transparent);
RenderDirtyRects(context);
}
}
else
{
_overlay?.Dispose();
_overlay = null;
}
}
private void RenderDirtyRects(IDrawingContextImpl context)
{
foreach (var r in _dirtyRectsDisplay)
{
var brush = new ImmutableSolidColorBrush(Colors.Magenta, r.Opacity);
context.FillRectangle(brush, r.Rect);
}
}
private void RenderComposite(Scene scene)
{
try
{
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
using (var context = _renderTarget.CreateDrawingContext(this))
{
var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)
{
var bitmap = _layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
{
context.PushGeometryClip(layer.GeometryClip);
}
if (layer.OpacityMask == null)
{
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (layer.GeometryClip != null)
{
context.PopGeometryClip();
}
}
if (_overlay != null)
{
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
}
if (DrawFps)
{
RenderFps(context, clientRect, true);
}
}
}
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
}
}
private void UpdateScene()
{
Dispatcher.UIThread.VerifyAccess();
try
{
var scene = _scene.Clone();
if (_dirty == null)
{
_dirty = new DirtyVisuals();
_sceneBuilder.UpdateAll(scene);
}
else if (_dirty.Count > 0)
{
foreach (var visual in _dirty)
{
_sceneBuilder.Update(scene, visual);
}
}
Interlocked.Exchange(ref _scene, scene);
_dirty.Clear();
(_root as IRenderRoot)?.Invalidate(new Rect(scene.Size));
}
finally
{
_updateQueued = false;
}
}
private void OnRenderLoopTick(object sender, EventArgs e)
{
if (Monitor.TryEnter(_rendering))
{
try
{
if (!_updateQueued && (_dirty == null || _dirty.Count > 0))
{
_updateQueued = true;
_dispatcher.InvokeAsync(UpdateScene, DispatcherPriority.Render);
}
Scene scene = null;
Interlocked.Exchange(ref scene, _scene);
Render(scene);
}
catch { }
finally
{
Monitor.Exit(_rendering);
}
}
}
private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling)
{
size = new Size(size.Width * scaling, size.Height * scaling);
if (_overlay == null ||
_overlay.PixelWidth != size.Width ||
_overlay.PixelHeight != size.Height)
{
_overlay?.Dispose();
_overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling);
}
return _overlay;
}
private void SaveDebugFrames(int id)
{
var index = 0;
foreach (var layer in _layers)
{
var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
layer.Bitmap.Save(fileName);
}
}
}
}

88
src/Avalonia.Visuals/Rendering/DirtyRects.cs

@ -0,0 +1,88 @@
// 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;
using System.Collections.Generic;
namespace Avalonia.Rendering
{
/// <summary>
/// Tracks dirty rectangles.
/// </summary>
internal class DirtyRects : IEnumerable<Rect>
{
private List<Rect> _rects = new List<Rect>();
public bool IsEmpty => _rects.Count == 0;
/// <summary>
/// Adds a dirty rectangle, extending an existing dirty rectangle if it intersects.
/// </summary>
/// <param name="rect">The dirt rectangle.</param>
/// <remarks>
/// We probably want to do this more intellegently because:
/// - Adding e.g. the top left quarter of a scene and the bottom left quarter of a scene
/// will cause the whole scene to be invalidated if they overlap by a single pixel
/// - Adding two adjacent rectangles that don't overlap will not cause them to be
/// coalesced
/// - It only coaleces the first intersecting rectangle found - one needs to
/// call <see cref="Coalesce"/> at the end of the draw cycle to coalesce the rest.
/// </remarks>
public void Add(Rect rect)
{
if (!rect.IsEmpty)
{
for (var i = 0; i < _rects.Count; ++i)
{
var r = _rects[i];
if (r.Intersects(rect))
{
_rects[i] = r.Union(rect);
return;
}
}
_rects.Add(rect);
}
}
/// <summary>
/// Works around our flimsy dirt-rect coalescing algorithm.
/// </summary>
/// <remarks>
/// See the comments in <see cref="Add(Rect)"/>.
/// </remarks>
public void Coalesce()
{
for (var i = _rects.Count - 1; i >= 0; --i)
{
var a = _rects[i];
for (var j = 0; j < i; ++j)
{
var b = _rects[j];
if (i < _rects.Count && a.Intersects(b))
{
_rects[i] = _rects[i].Union(b);
_rects.RemoveAt(i);
}
}
}
}
/// <summary>
/// Gets the dirty rectangles.
/// </summary>
/// <returns>A collection of dirty rectangles</returns>
public IEnumerator<Rect> GetEnumerator() => _rects.GetEnumerator();
/// <summary>
/// Gets the dirty rectangles.
/// </summary>
/// <returns>A collection of dirty rectangles</returns>
IEnumerator IEnumerable.GetEnumerator() => _rects.GetEnumerator();
}
}

107
src/Avalonia.Visuals/Rendering/DirtyVisuals.cs

@ -0,0 +1,107 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
/// <summary>
/// Stores a list of dirty visuals for an <see cref="IRenderer"/>.
/// </summary>
/// <remarks>
/// This class stores the dirty visuals for a scene, ordered by their distance to the root
/// visual. TODO: We probably want to put an upper limit on the number of visuals that can be
/// stored and if we reach that limit, assume all visuals are dirty.
/// </remarks>
internal class DirtyVisuals : IEnumerable<IVisual>
{
private SortedDictionary<int, List<IVisual>> _inner = new SortedDictionary<int, List<IVisual>>();
private Dictionary<IVisual, int> _index = new Dictionary<IVisual, int>();
/// <summary>
/// Gets the number of dirty visuals.
/// </summary>
public int Count => _index.Count;
/// <summary>
/// Adds a visual to the dirty list.
/// </summary>
/// <param name="visual">The dirty visual.</param>
public void Add(IVisual visual)
{
var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot);
int existingDistance;
if (_index.TryGetValue(visual, out existingDistance))
{
if (distance == existingDistance)
{
return;
}
_inner[existingDistance].Remove(visual);
_index.Remove(visual);
}
List<IVisual> list;
if (!_inner.TryGetValue(distance, out list))
{
list = new List<IVisual>();
_inner.Add(distance, list);
}
list.Add(visual);
_index.Add(visual, distance);
}
/// <summary>
/// Clears the list.
/// </summary>
public void Clear()
{
_inner.Clear();
_index.Clear();
}
/// <summary>
/// Removes a visual from the dirty list.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>True if the visual was present in the list; otherwise false.</returns>
public bool Remove(IVisual visual)
{
int distance;
if (_index.TryGetValue(visual, out distance))
{
_inner[distance].Remove(visual);
_index.Remove(visual);
return true;
}
return false;
}
/// <summary>
/// Gets the dirty visuals, in ascending order of distance to their root.
/// </summary>
/// <returns>A collection of visuals.</returns>
public IEnumerator<IVisual> GetEnumerator()
{
foreach (var i in _inner)
{
foreach (var j in i.Value)
{
yield return j;
}
}
}
/// <summary>
/// Gets the dirty visuals, in ascending order of distance to their root.
/// </summary>
/// <returns>A collection of visuals.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

51
src/Avalonia.Visuals/Rendering/DisplayDirtyRect.cs

@ -0,0 +1,51 @@
using System;
namespace Avalonia.Rendering
{
/// <summary>
/// Holds the state for a dirty rect rendered when <see cref="IRenderer.DrawDirtyRects"/> is set.
/// </summary>
internal class DisplayDirtyRect
{
public static readonly TimeSpan TimeToLive = TimeSpan.FromMilliseconds(250);
/// <summary>
/// Initializes a new instance of the <see cref="DisplayDirtyRect"/> class.
/// </summary>
/// <param name="rect">The dirt rect.</param>
public DisplayDirtyRect(Rect rect)
{
Rect = rect;
ResetLifetime();
}
/// <summary>
/// Gets the bounds of the dirty rectangle.
/// </summary>
public Rect Rect { get; }
/// <summary>
/// Gets the time at which the rectangle was made dirty.
/// </summary>
public DateTimeOffset Born { get; private set; }
/// <summary>
/// Gets the time at which the rectagle should no longer be displayed.
/// </summary>
public DateTimeOffset Dies { get; private set; }
/// <summary>
/// Gets the opacity at which to display the dirty rectangle.
/// </summary>
public double Opacity => (Dies - DateTimeOffset.UtcNow).TotalMilliseconds / TimeToLive.TotalMilliseconds;
/// <summary>
/// Resets the rectangle's lifetime.
/// </summary>
public void ResetLifetime()
{
Born = DateTimeOffset.UtcNow;
Dies = Born + TimeToLive;
}
}
}

62
src/Avalonia.Visuals/Rendering/DisplayDirtyRects.cs

@ -0,0 +1,62 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Avalonia.Rendering
{
/// <summary>
/// Holds a collection of <see cref="DisplayDirtyRect"/> objects and manages their aging.
/// </summary>
internal class DisplayDirtyRects : IEnumerable<DisplayDirtyRect>
{
private List<DisplayDirtyRect> _inner = new List<DisplayDirtyRect>();
/// <summary>
/// Adds new new dirty rect to the collection.
/// </summary>
/// <param name="rect"></param>
public void Add(Rect rect)
{
foreach (var r in _inner)
{
if (r.Rect == rect)
{
r.ResetLifetime();
return;
}
}
_inner.Add(new DisplayDirtyRect(rect));
}
/// <summary>
/// Removes dirty rects one they are no longer active.
/// </summary>
public void Tick()
{
var now = DateTimeOffset.UtcNow;
for (var i = _inner.Count - 1; i >= 0; --i)
{
var r = _inner[i];
if (now > r.Dies)
{
_inner.RemoveAt(i);
}
}
}
/// <summary>
/// Gets the dirty rects.
/// </summary>
/// <returns>A collection of <see cref="DisplayDirtyRect"/> objects.</returns>
public IEnumerator<DisplayDirtyRect> GetEnumerator() => _inner.GetEnumerator();
/// <summary>
/// Gets the dirty rects.
/// </summary>
/// <returns>A collection of <see cref="DisplayDirtyRect"/> objects.</returns>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

11
src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs

@ -0,0 +1,11 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public interface IRenderLayerFactory
{
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY);
}
}

5
src/Avalonia.Visuals/Rendering/IRenderRoot.cs

@ -22,6 +22,11 @@ namespace Avalonia.Rendering
/// </summary>
IRenderer Renderer { get; }
/// <summary>
/// The scaling factor to use in rendering.
/// </summary>
double RenderScaling { get; }
/// <summary>
/// Creates a render target for the window.
/// </summary>

10
src/Avalonia.Visuals/Rendering/IRenderer.cs

@ -48,5 +48,15 @@ namespace Avalonia.Rendering
/// </summary>
/// <param name="rect">The dirty rectangle.</param>
void Paint(Rect rect);
/// <summary>
/// Starts the renderer.
/// </summary>
void Start();
/// <summary>
/// Stops the renderer.
/// </summary>
void Stop();
}
}

18
src/Avalonia.Visuals/Rendering/IRendererFactory.cs

@ -1,18 +0,0 @@
using System;
namespace Avalonia.Rendering
{
/// <summary>
/// Defines a factory for creating <see cref="IRenderer"/> instances.
/// </summary>
public interface IRendererFactory
{
/// <summary>
/// Creates a new renderer for the specified render root.
/// </summary>
/// <param name="root">The render root.</param>
/// <param name="renderLoop">The render loop.</param>
/// <returns>An instance of an <see cref="IRenderer"/>.</returns>
IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop);
}
}

17
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -20,11 +20,6 @@ namespace Avalonia.Rendering
/// </remarks>
public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer
{
class ImmediateRendererFactory : IRendererFactory
{
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) => new ImmediateRenderer(root);
}
private readonly IVisual _root;
private readonly IRenderRoot _renderRoot;
private IRenderTarget _renderTarget;
@ -41,8 +36,6 @@ namespace Avalonia.Rendering
_renderRoot = root as IRenderRoot;
}
public static IRendererFactory Factory { get; } = new ImmediateRendererFactory();
/// <inheritdoc/>
public bool DrawFps { get; set; }
@ -148,6 +141,16 @@ namespace Avalonia.Rendering
return HitTest(_root, p, filter);
}
/// <inheritdoc/>
public void Start()
{
}
/// <inheritdoc/>
public void Stop()
{
}
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{

47
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -0,0 +1,47 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class RenderLayer
{
private readonly IRenderLayerFactory _factory;
public RenderLayer(
IRenderLayerFactory factory,
Size size,
double scaling,
IVisual layerRoot)
{
_factory = factory;
Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling);
Size = size;
Scaling = scaling;
LayerRoot = layerRoot;
}
public IRenderTargetBitmapImpl Bitmap { get; private set; }
public double Scaling { get; private set; }
public Size Size { get; private set; }
public IVisual LayerRoot { get; }
public void ResizeBitmap(Size size, double scaling)
{
if (Size != size || Scaling != scaling)
{
var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling);
using (var context = resized.CreateDrawingContext(null))
{
context.Clear(Colors.Transparent);
context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size));
Bitmap.Dispose();
Bitmap = resized;
Size = size;
}
}
}
}
}

63
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -0,0 +1,63 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class RenderLayers : IEnumerable<RenderLayer>
{
private readonly IRenderLayerFactory _factory;
private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public RenderLayers(IRenderLayerFactory factory)
{
_factory = factory;
}
public int Count => _inner.Count;
public RenderLayer this[IVisual layerRoot] => _index[layerRoot];
public void Update(Scene scene)
{
for (var i = scene.Layers.Count - 1; i >= 0; --i)
{
var src = scene.Layers[i];
RenderLayer layer;
if (!_index.TryGetValue(src.LayerRoot, out layer))
{
layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot);
_inner.Add(layer);
_index.Add(src.LayerRoot, layer);
}
else
{
layer.ResizeBitmap(scene.Size, scene.Scaling);
}
}
for (var i = _inner.Count - 1; i >= 0; --i)
{
var layer = _inner[i];
if (!scene.Layers.Exists(layer.LayerRoot))
{
layer.Bitmap.Dispose();
_inner.RemoveAt(i);
_index.Remove(layer.LayerRoot);
}
}
}
public bool TryGetValue(IVisual layerRoot, out RenderLayer value)
{
return _index.TryGetValue(layerRoot, out value);
}
public IEnumerator<RenderLayer> GetEnumerator() => _inner.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

31
src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs

@ -0,0 +1,31 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Base class for draw operations that can use a brush.
/// </summary>
internal abstract class BrushDrawOperation : IDrawOperation
{
/// <inheritdoc/>
public abstract Rect Bounds { get; }
/// <inheritdoc/>
public abstract bool HitTest(Point p);
/// <summary>
/// Gets a collection of child scenes that are needed to draw visual brushes.
/// </summary>
public abstract IDictionary<IVisual, Scene> ChildScenes { get; }
/// <inheritdoc/>
public abstract void Render(IDrawingContextImpl context);
}
}

64
src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs

@ -0,0 +1,64 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents a clip push or pop.
/// </summary>
internal class ClipNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
/// clip push.
/// </summary>
/// <param name="clip">The clip to push.</param>
public ClipNode(Rect clip)
{
Clip = clip;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClipNode"/> class that represents a
/// clip pop.
/// </summary>
public ClipNode()
{
}
/// <inheritdoc/>
public Rect Bounds => Rect.Empty;
/// <summary>
/// Gets the clip to be pushed or null if the operation represents a pop.
/// </summary>
public Rect? Clip { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="clip">The clip of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(Rect? clip) => Clip == clip;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
if (Clip.HasValue)
{
context.PushClip(Clip.Value);
}
else
{
context.PopClip();
}
}
}
}

392
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -0,0 +1,392 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A drawing context which builds a scene graph.
/// </summary>
internal class DeferredDrawingContextImpl : IDrawingContextImpl
{
private readonly ISceneBuilder _sceneBuilder;
private VisualNode _node;
private int _childIndex;
private int _drawOperationindex;
/// <summary>
/// Initializes a new instance of the <see cref="DeferredDrawingContextImpl"/> class.
/// </summary>
/// <param name="sceneBuilder">
/// A scene builder used for constructing child scenes for visual brushes.
/// </param>
/// <param name="layers">The scene layers.</param>
public DeferredDrawingContextImpl(ISceneBuilder sceneBuilder, SceneLayers layers)
{
_sceneBuilder = sceneBuilder;
Layers = layers;
}
/// <inheritdoc/>
public Matrix Transform { get; set; } = Matrix.Identity;
/// <summary>
/// Gets the layers in the scene being built.
/// </summary>
public SceneLayers Layers { get; }
/// <summary>
/// Informs the drawing context of the visual node that is about to be rendered.
/// </summary>
/// <param name="node">The visual node.</param>
/// <returns>
/// An object which when disposed will commit the changes to visual node.
/// </returns>
public UpdateState BeginUpdate(VisualNode node)
{
Contract.Requires<ArgumentNullException>(node != null);
if (_node != null)
{
if (_childIndex < _node.Children.Count)
{
_node.ReplaceChild(_childIndex, node);
}
else
{
_node.AddChild(node);
}
++_childIndex;
}
var state = new UpdateState(this, _node, _childIndex, _drawOperationindex);
_node = node;
_childIndex = _drawOperationindex = 0;
return state;
}
/// <inheritdoc/>
public void Clear(Color color)
{
// Cannot clear a deferred scene.
}
/// <inheritdoc/>
public void Dispose()
{
// Nothing to do here as we allocate no unmanaged resources.
}
/// <summary>
/// Removes any remaining drawing operations from the visual node.
/// </summary>
/// <remarks>
/// Drawing operations are updated in place, overwriting existing drawing operations if
/// they are different. Once drawing has completed for the current visual node, it is
/// possible that there are stale drawing operations at the end of the list. This method
/// trims these stale drawing operations.
/// </remarks>
public void TrimChildren()
{
_node.TrimChildren(_childIndex);
}
/// <inheritdoc/>
public void DrawGeometry(IBrush brush, Pen pen, IGeometryImpl geometry)
{
var next = NextDrawAs<GeometryNode>();
if (next == null || !next.Equals(Transform, brush, pen, geometry))
{
Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
{
var next = NextDrawAs<ImageNode>();
if (next == null || !next.Equals(Transform, source, opacity, sourceRect, destRect))
{
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
{
// This method is currently only used to composite layers so shouldn't be called here.
throw new NotSupportedException();
}
/// <inheritdoc/>
public void DrawLine(Pen pen, Point p1, Point p2)
{
var next = NextDrawAs<LineNode>();
if (next == null || !next.Equals(Transform, pen, p1, p2))
{
Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
{
var next = NextDrawAs<RectangleNode>();
if (next == null || !next.Equals(Transform, null, pen, rect, cornerRadius))
{
Add(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
var next = NextDrawAs<TextNode>();
if (next == null || !next.Equals(Transform, foreground, origin, text))
{
Add(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0)
{
var next = NextDrawAs<RectangleNode>();
if (next == null || !next.Equals(Transform, brush, null, rect, cornerRadius))
{
Add(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopClip()
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Equals(null))
{
Add(new ClipNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopGeometryClip()
{
var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Equals(null))
{
Add(new GeometryClipNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopOpacity()
{
var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Equals(null))
{
Add(new OpacityNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopOpacityMask()
{
var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Equals(null, null))
{
Add(new OpacityMaskNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushClip(Rect clip)
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Equals(clip))
{
Add(new ClipNode(clip));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushGeometryClip(IGeometryImpl clip)
{
var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Equals(clip))
{
Add(new GeometryClipNode(clip));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushOpacity(double opacity)
{
var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Equals(opacity))
{
Add(new OpacityNode(opacity));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushOpacityMask(IBrush mask, Rect bounds)
{
var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Equals(mask, bounds))
{
Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
}
else
{
++_drawOperationindex;
}
}
public struct UpdateState : IDisposable
{
public UpdateState(
DeferredDrawingContextImpl owner,
VisualNode node,
int childIndex,
int drawOperationIndex)
{
Owner = owner;
Node = node;
ChildIndex = childIndex;
DrawOperationIndex = drawOperationIndex;
}
public void Dispose()
{
Owner._node.TrimDrawOperations(Owner._drawOperationindex);
var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot).Dirty;
foreach (var operation in Owner._node.DrawOperations)
{
dirty.Add(operation.Bounds);
}
Owner._node = Node;
Owner._childIndex = ChildIndex;
Owner._drawOperationindex = DrawOperationIndex;
}
public DeferredDrawingContextImpl Owner { get; }
public VisualNode Node { get; }
public int ChildIndex { get; }
public int DrawOperationIndex { get; }
}
private void Add(IDrawOperation node)
{
if (_drawOperationindex < _node.DrawOperations.Count)
{
_node.ReplaceDrawOperation(_drawOperationindex, node);
}
else
{
_node.AddDrawOperation(node);
}
++_drawOperationindex;
}
private T NextDrawAs<T>() where T : class, IDrawOperation
{
return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as T : null;
}
private IDictionary<IVisual, Scene> CreateChildScene(IBrush brush)
{
var visualBrush = brush as VisualBrush;
if (visualBrush != null)
{
var visual = visualBrush.Visual;
if (visual != null)
{
(visual as IVisualBrushInitialize)?.EnsureInitialized();
var scene = new Scene(visual);
_sceneBuilder.UpdateAll(scene);
return new Dictionary<IVisual, Scene> { { visualBrush.Visual, scene } };
}
}
return null;
}
}
}

64
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs

@ -0,0 +1,64 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents a geometry clip push or pop.
/// </summary>
internal class GeometryClipNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="GeometryClipNode"/> class that represents a
/// geometry clip push.
/// </summary>
/// <param name="clip">The clip to push.</param>
public GeometryClipNode(IGeometryImpl clip)
{
Clip = clip;
}
/// <summary>
/// Initializes a new instance of the <see cref="GeometryClipNode"/> class that represents a
/// geometry clip pop.
/// </summary>
public GeometryClipNode()
{
}
/// <inheritdoc/>
public Rect Bounds => Rect.Empty;
/// <summary>
/// Gets the clip to be pushed or null if the operation represents a pop.
/// </summary>
public IGeometryImpl Clip { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="clip">The clip of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(IGeometryImpl clip) => Clip == clip;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
if (Clip != null)
{
context.PushGeometryClip(Clip);
}
else
{
context.PopGeometryClip();
}
}
}
}

101
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@ -0,0 +1,101 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents a geometry draw.
/// </summary>
internal class GeometryNode : BrushDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="GeometryNode"/> class.
/// </summary>
/// <param name="transform">The transform.</param>
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public GeometryNode(
Matrix transform,
IBrush brush,
Pen pen,
IGeometryImpl geometry,
IDictionary<IVisual, Scene> childScenes = null)
{
Bounds = geometry.GetRenderBounds(pen?.Thickness ?? 0).TransformToAABB(transform);
Transform = transform;
Brush = brush?.ToImmutable();
Pen = pen?.ToImmutable();
Geometry = geometry;
ChildScenes = childScenes;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <summary>
/// Gets the fill brush.
/// </summary>
public IBrush Brush { get; }
/// <summary>
/// Gets the stroke pen.
/// </summary>
public Pen Pen { get; }
/// <summary>
/// Gets the geometry to draw.
/// </summary>
public IGeometryImpl Geometry { get; }
/// <inheritdoc/>
public override IDictionary<IVisual, Scene> ChildScenes { get; }
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="transform">The transform of the other draw operation.</param>
/// <param name="brush">The fill of the other draw operation.</param>
/// <param name="pen">The stroke of the other draw operation.</param>
/// <param name="geometry">The geometry of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(Matrix transform, IBrush brush, Pen pen, IGeometryImpl geometry)
{
return transform == Transform &&
Equals(brush, Brush) &&
pen == Pen &&
Equals(geometry, Geometry);
}
/// <inheritdoc/>
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
context.DrawGeometry(Brush, Pen, Geometry);
}
/// <inheritdoc/>
public override bool HitTest(Point p)
{
p *= Transform.Invert();
return (Brush != null && Geometry.FillContains(p)) ||
(Pen != null && Geometry.StrokeContains(Pen, p));
}
}
}

36
src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs

@ -0,0 +1,36 @@
// 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;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Represents a node in the low-level scene graph that represents geometry.
/// </summary>
public interface IDrawOperation
{
/// <summary>
/// Gets the bounds of the visible content in the node.
/// </summary>
Rect Bounds { get; }
/// <summary>
/// Hit test the geometry in this node.
/// </summary>
/// <param name="p">The point in global coordinates.</param>
/// <returns>True if the point hits the node's geometry; otherwise false.</returns>
/// <remarks>
/// This method does not recurse to child <see cref="IVisualNode"/>s, if you want
/// to hit test children they must be hit tested manually.
/// </remarks>
bool HitTest(Point p);
/// <summary>
/// Renders the node to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
void Render(IDrawingContextImpl context);
}
}

24
src/Avalonia.Visuals/Rendering/SceneGraph/ISceneBuilder.cs

@ -0,0 +1,24 @@
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Builds a scene graph from a visual tree.
/// </summary>
public interface ISceneBuilder
{
/// <summary>
/// Builds the initial scene graph for a visual tree.
/// </summary>
/// <param name="scene">The scene to build.</param>
void UpdateAll(Scene scene);
/// <summary>
/// Updates the visual (and potentially its children) in a scene.
/// </summary>
/// <param name="scene">The scene.</param>
/// <param name="visual">The visual to update.</param>
/// <returns>True if changes were made, otherwise false.</returns>
bool Update(Scene scene, IVisual visual);
}
}

94
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@ -0,0 +1,94 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Represents a node in the low-level scene graph representing an <see cref="IVisual"/>.
/// </summary>
public interface IVisualNode
{
/// <summary>
/// Gets the visual to which the node relates.
/// </summary>
IVisual Visual { get; }
/// <summary>
/// Gets the parent scene graph node.
/// </summary>
IVisualNode Parent { get; }
/// <summary>
/// Gets the transform for the node from global to control coordinates.
/// </summary>
Matrix Transform { get; }
/// <summary>
/// Gets the bounds for the node's geometry in global coordinates.
/// </summary>
Rect Bounds { get; }
/// <summary>
/// Gets the clip bounds for the node in global coordinates.
/// </summary>
/// <remarks>
/// This clip does not take into account parent clips, to find the absolute clip bounds
/// it is necessary to traverse the tree.
/// </remarks>
Rect ClipBounds { get; }
/// <summary>
/// Whether the node is clipped to <see cref="ClipBounds"/>.
/// </summary>
bool ClipToBounds { get; }
/// <summary>
/// Gets the node's clip geometry, if any.
/// </summary>
IGeometryImpl GeometryClip { get; set; }
/// <summary>
/// Gets a value indicating whether one of the node's ancestors has a geometry clip.
/// </summary>
bool HasAncestorGeometryClip { get; }
/// <summary>
/// Gets the child scene graph nodes.
/// </summary>
IReadOnlyList<IVisualNode> Children { get; }
/// <summary>
/// Gets the drawing operations for the visual.
/// </summary>
IReadOnlyList<IDrawOperation> DrawOperations { get; }
/// <summary>
/// Sets up the drawing context for rendering the node's geometry.
/// </summary>
/// <param name="context">The drawing context.</param>
void BeginRender(IDrawingContextImpl context);
/// <summary>
/// Resets the drawing context after rendering the node's geometry.
/// </summary>
/// <param name="context">The drawing context.</param>
void EndRender(IDrawingContextImpl context);
/// <summary>
/// Hit test the geometry in this node.
/// </summary>
/// <param name="p">The point in global coordinates.</param>
/// <returns>True if the point hits the node's geometry; otherwise false.</returns>
/// <remarks>
/// This method does not recurse to child <see cref="IVisualNode"/>s, if you want
/// to hit test children they must be hit tested manually.
/// </remarks>
bool HitTest(Point p);
}
}

94
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -0,0 +1,94 @@
// 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;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an image draw.
/// </summary>
internal class ImageNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageNode"/> class.
/// </summary>
/// <param name="transform">The transform.</param>
/// <param name="source">The image to draw.</param>
/// <param name="opacity">The draw opacity.</param>
/// <param name="sourceRect">The source rect.</param>
/// <param name="destRect">The destination rect.</param>
public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
{
Bounds = destRect.TransformToAABB(transform);
Transform = transform;
Source = source;
Opacity = opacity;
SourceRect = sourceRect;
DestRect = destRect;
}
/// <inheritdoc/>
public Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <summary>
/// Gets the image to draw.
/// </summary>
public IBitmapImpl Source { get; }
/// <summary>
/// Gets the draw opacity.
/// </summary>
public double Opacity { get; }
/// <summary>
/// Gets the source rect.
/// </summary>
public Rect SourceRect { get; }
/// <summary>
/// Gets the destination rect.
/// </summary>
public Rect DestRect { get; }
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="transform">The transform of the other draw operation.</param>
/// <param name="source">The image of the other draw operation.</param>
/// <param name="opacity">The opacity of the other draw operation.</param>
/// <param name="sourceRect">The source rect of the other draw operation.</param>
/// <param name="destRect">The dest rect of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
{
return transform == Transform &&
Equals(source, Source) &&
opacity == Opacity &&
sourceRect == SourceRect &&
destRect == DestRect;
}
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
// TODO: Probably need to introduce some kind of locking mechanism in the case of
// WriteableBitmap.
context.Transform = Transform;
context.DrawImage(Source, Opacity, SourceRect, DestRect);
}
/// <inheritdoc/>
public bool HitTest(Point p) => Bounds.Contains(p);
}
}

95
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@ -0,0 +1,95 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents a line draw.
/// </summary>
internal class LineNode : BrushDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="GeometryNode"/> class.
/// </summary>
/// <param name="transform">The transform.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="p1">The start point of the line.</param>
/// <param name="p2">The end point of the line.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public LineNode(
Matrix transform,
Pen pen,
Point p1,
Point p2,
IDictionary<IVisual, Scene> childScenes = null)
{
Bounds = new Rect(p1, p2).TransformToAABB(transform).Inflate(pen?.Thickness ?? 0);
Transform = transform;
Pen = pen?.ToImmutable();
P1 = p1;
P2 = p2;
ChildScenes = childScenes;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <summary>
/// Gets the stroke pen.
/// </summary>
public Pen Pen { get; }
/// <summary>
/// Gets the start point of the line.
/// </summary>
public Point P1 { get; }
/// <summary>
/// Gets the end point of the line.
/// </summary>
public Point P2 { get; }
/// <inheritdoc/>
public override IDictionary<IVisual, Scene> ChildScenes { get; }
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="transform">The transform of the other draw operation.</param>
/// <param name="pen">The stroke of the other draw operation.</param>
/// <param name="p1">The start point of the other draw operation.</param>
/// <param name="p2">The end point of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(Matrix transform, Pen pen, Point p1, Point p2)
{
return transform == Transform && pen == Pen && p1 == P1 && p2 == P2;
}
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
context.DrawLine(Pen, P1, P2);
}
public override bool HitTest(Point p)
{
// TODO: Implement line hit testing.
return false;
}
}
}

80
src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an opacity mask push or pop.
/// </summary>
internal class OpacityMaskNode : BrushDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="OpacityMaskNode"/> class that represents an
/// opacity mask push.
/// </summary>
/// <param name="mask">The opacity mask to push.</param>
/// <param name="bounds">The bounds of the mask.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary<IVisual, Scene> childScenes = null)
{
Mask = mask?.ToImmutable();
MaskBounds = bounds;
ChildScenes = childScenes;
}
/// <summary>
/// Initializes a new instance of the <see cref="OpacityMaskNode"/> class that represents an
/// opacity mask pop.
/// </summary>
public OpacityMaskNode()
{
}
/// <inheritdoc/>
public override Rect Bounds => Rect.Empty;
/// <summary>
/// Gets the mask to be pushed or null if the operation represents a pop.
/// </summary>
public IBrush Mask { get; }
/// <summary>
/// Gets the bounds of the opacity mask or null if the operation represents a pop.
/// </summary>
public Rect? MaskBounds { get; }
/// <inheritdoc/>
public override IDictionary<IVisual, Scene> ChildScenes { get; }
/// <inheritdoc/>
public override bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="mask">The opacity mask of the other draw operation.</param>
/// <param name="bounds">The opacity mask bounds of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(IBrush mask, Rect? bounds) => Mask == mask && MaskBounds == bounds;
/// <inheritdoc/>
public override void Render(IDrawingContextImpl context)
{
if (Mask != null)
{
context.PushOpacityMask(Mask, MaskBounds.Value);
}
else
{
context.PopOpacityMask();
}
}
}
}

64
src/Avalonia.Visuals/Rendering/SceneGraph/OpacityNode.cs

@ -0,0 +1,64 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an opacity push or pop.
/// </summary>
internal class OpacityNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="OpacityNode"/> class that represents an
/// opacity push.
/// </summary>
/// <param name="opacity">The opacity to push.</param>
public OpacityNode(double opacity)
{
Opacity = opacity;
}
/// <summary>
/// Initializes a new instance of the <see cref="OpacityNode"/> class that represents an
/// opacity pop.
/// </summary>
public OpacityNode()
{
}
/// <inheritdoc/>
public Rect Bounds => Rect.Empty;
/// <summary>
/// Gets the opacity to be pushed or null if the operation represents a pop.
/// </summary>
public double? Opacity { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="opacity">The opacity of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(double? opacity) => Opacity == opacity;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
if (Opacity.HasValue)
{
context.PushOpacity(Opacity.Value);
}
else
{
context.PopOpacity();
}
}
}
}

116
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@ -0,0 +1,116 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents a rectangle draw.
/// </summary>
internal class RectangleNode : BrushDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="RectangleNode"/> class.
/// </summary>
/// <param name="transform">The transform.</param>
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="rect">The rectanle to draw.</param>
/// <param name="cornerRadius">The rectangle corner radius.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public RectangleNode(
Matrix transform,
IBrush brush,
Pen pen,
Rect rect,
float cornerRadius,
IDictionary<IVisual, Scene> childScenes = null)
{
Bounds = rect.TransformToAABB(transform).Inflate(pen?.Thickness ?? 0);
Transform = transform;
Brush = brush?.ToImmutable();
Pen = pen?.ToImmutable();
Rect = rect;
CornerRadius = cornerRadius;
ChildScenes = childScenes;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <summary>
/// Gets the fill brush.
/// </summary>
public IBrush Brush { get; }
/// <summary>
/// Gets the stroke pen.
/// </summary>
public Pen Pen { get; }
/// <summary>
/// Gets the rectangle to draw.
/// </summary>
public Rect Rect { get; }
/// <summary>
/// Gets the rectangle corner radius.
/// </summary>
public float CornerRadius { get; }
/// <inheritdoc/>
public override IDictionary<IVisual, Scene> ChildScenes { get; }
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="transform">The transform of the other draw operation.</param>
/// <param name="brush">The fill of the other draw operation.</param>
/// <param name="pen">The stroke of the other draw operation.</param>
/// <param name="rect">The rectangle of the other draw operation.</param>
/// <param name="cornerRadius">The rectangle corner radius of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(Matrix transform, IBrush brush, Pen pen, Rect rect, float cornerRadius)
{
return transform == Transform &&
Equals(brush, Brush) &&
pen == Pen &&
rect == Rect &&
cornerRadius == CornerRadius;
}
/// <inheritdoc/>
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
if (Brush != null)
{
context.FillRectangle(Brush, Rect, CornerRadius);
}
if (Pen != null)
{
context.DrawRectangle(Pen, Rect, CornerRadius);
}
}
// TODO: This doesn't respect CornerRadius yet.
/// <inheritdoc/>
public override bool HitTest(Point p) => Bounds.Contains(p);
}
}

184
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -0,0 +1,184 @@
// 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 Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Represents a scene graph used by the <see cref="DeferredRenderer"/>.
/// </summary>
public class Scene
{
private Dictionary<IVisual, IVisualNode> _index;
/// <summary>
/// Initializes a new instance of the <see cref="Scene"/> class.
/// </summary>
/// <param name="rootVisual">The root visual to draw.</param>
public Scene(IVisual rootVisual)
: this(
new VisualNode(rootVisual, null),
new Dictionary<IVisual, IVisualNode>(),
new SceneLayers(rootVisual),
0)
{
_index.Add(rootVisual, Root);
}
private Scene(VisualNode root, Dictionary<IVisual, IVisualNode> index, SceneLayers layers, int generation)
{
Contract.Requires<ArgumentNullException>(root != null);
var renderRoot = root.Visual as IRenderRoot;
_index = index;
Root = root;
Layers = layers;
Generation = generation;
root.LayerRoot = root.Visual;
}
/// <summary>
/// Gets a value identifying the scene's generation. This is incremented each time the scene is cloned.
/// </summary>
public int Generation { get; }
/// <summary>
/// Gets the layers for the scene.
/// </summary>
public SceneLayers Layers { get; }
/// <summary>
/// Gets the root node of the scene graph.
/// </summary>
public IVisualNode Root { get; }
/// <summary>
/// Gets or sets the size of the scene in device independent pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets the scene scaling.
/// </summary>
public double Scaling { get; set; } = 1;
/// <summary>
/// Adds a node to the scene index.
/// </summary>
/// <param name="node">The node.</param>
public void Add(IVisualNode node)
{
Contract.Requires<ArgumentNullException>(node != null);
_index.Add(node.Visual, node);
}
/// <summary>
/// Clones the scene.
/// </summary>
/// <returns>The cloned scene.</returns>
public Scene Clone()
{
var index = new Dictionary<IVisual, IVisualNode>();
var root = Clone((VisualNode)Root, null, index);
var result = new Scene(root, index, Layers.Clone(), Generation + 1)
{
Size = Size,
Scaling = Scaling,
};
return result;
}
/// <summary>
/// Tries to find a node in the scene graph representing the specified visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>
/// The node representing the visual or null if it could not be found.
/// </returns>
public IVisualNode FindNode(IVisual visual)
{
IVisualNode node;
_index.TryGetValue(visual, out node);
return node;
}
/// <summary>
/// Gets the visuals at a point in the scene.
/// </summary>
/// <param name="p">The point.</param>
/// <param name="filter">A filter. May be null.</param>
/// <returns>The visuals at the specified point.</returns>
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
{
return HitTest(Root, p, null, filter);
}
/// <summary>
/// Removes a node from the scene index.
/// </summary>
/// <param name="node">The node.</param>
public void Remove(IVisualNode node)
{
Contract.Requires<ArgumentNullException>(node != null);
_index.Remove(node.Visual);
}
private VisualNode Clone(VisualNode source, IVisualNode parent, Dictionary<IVisual, IVisualNode> index)
{
var result = source.Clone(parent);
index.Add(result.Visual, result);
foreach (var child in source.Children)
{
result.AddChild(Clone((VisualNode)child, result, index));
}
return result;
}
private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> filter)
{
if (filter?.Invoke(node.Visual) != false)
{
var clipped = false;
if (node.ClipToBounds)
{
clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
clipped = !clip.Value.Contains(p);
}
if (node.GeometryClip != null)
{
var controlPoint = Root.Visual.TranslatePoint(p, node.Visual);
clipped = !node.GeometryClip.FillContains(controlPoint);
}
if (!clipped)
{
for (var i = node.Children.Count - 1; i >= 0; --i)
{
foreach (var h in HitTest(node.Children[i], p, clip, filter))
{
yield return h;
}
}
if (node.HitTest(p))
{
yield return node.Visual;
}
}
}
}
}
}

384
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -0,0 +1,384 @@
// 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.Linq;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Builds a scene graph from a visual tree.
/// </summary>
public class SceneBuilder : ISceneBuilder
{
/// <inheritdoc/>
public void UpdateAll(Scene scene)
{
Contract.Requires<ArgumentNullException>(scene != null);
Dispatcher.UIThread.VerifyAccess();
UpdateSize(scene);
scene.Layers.GetOrAdd(scene.Root.Visual);
using (var impl = new DeferredDrawingContextImpl(this, scene.Layers))
using (var context = new DrawingContext(impl))
{
Update(context, scene, (VisualNode)scene.Root, scene.Root.Visual.Bounds, true);
}
}
/// <inheritdoc/>
public bool Update(Scene scene, IVisual visual)
{
Contract.Requires<ArgumentNullException>(scene != null);
Contract.Requires<ArgumentNullException>(visual != null);
Dispatcher.UIThread.VerifyAccess();
var node = (VisualNode)scene.FindNode(visual);
if (visual == scene.Root.Visual)
{
UpdateSize(scene);
}
if (visual.VisualRoot != null)
{
if (visual.IsVisible)
{
// If the node isn't yet part of the scene, find the nearest ancestor that is.
node = node ?? FindExistingAncestor(scene, visual);
// We don't need to do anything if this part of the tree has already been fully
// updated.
if (node != null && !node.SubTreeUpdated)
{
// If the control we've been asked to update isn't part of the scene then
// we're carrying out an add operation, so recurse and add all the
// descendents too.
var recurse = node.Visual != visual;
using (var impl = new DeferredDrawingContextImpl(this, scene.Layers))
using (var context = new DrawingContext(impl))
{
var clip = scene.Root.Visual.Bounds;
if (node.Parent != null)
{
context.PushPostTransform(node.Parent.Transform);
clip = node.Parent.ClipBounds;
}
using (context.PushTransformContainer())
{
Update(context, scene, node, clip, recurse);
}
}
return true;
}
}
else
{
if (node != null)
{
// The control has been hidden so remove it from its parent and deindex the
// node and its descendents.
((VisualNode)node.Parent)?.RemoveChild(node);
Deindex(scene, node);
return true;
}
}
}
else if (node != null)
{
// The control has been removed so remove it from its parent and deindex the
// node and its descendents.
var trim = FindFirstDeadAncestor(scene, node);
((VisualNode)trim.Parent).RemoveChild(trim);
Deindex(scene, trim);
return true;
}
return false;
}
private static VisualNode FindExistingAncestor(Scene scene, IVisual visual)
{
var node = scene.FindNode(visual);
while (node == null && visual.IsVisible)
{
visual = visual.VisualParent;
node = scene.FindNode(visual);
}
return visual.IsVisible ? (VisualNode)node : null;
}
private static VisualNode FindFirstDeadAncestor(Scene scene, IVisualNode node)
{
var parent = node.Parent;
while (parent.Visual.VisualRoot == null)
{
node = parent;
parent = node.Parent;
}
return (VisualNode)node;
}
private static void Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, bool forceRecurse)
{
var visual = node.Visual;
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl;
contextImpl.Layers.Find(node.LayerRoot)?.Dirty.Add(node.Bounds);
if (visual.IsVisible)
{
var m = Matrix.CreateTranslation(visual.Bounds.Position);
var renderTransform = Matrix.Identity;
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
m = renderTransform * m;
using (contextImpl.BeginUpdate(node))
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
var startLayer = opacity < 1 || visual.OpacityMask != null;
forceRecurse = forceRecurse || node.Transform != contextImpl.Transform;
node.Transform = contextImpl.Transform;
node.ClipBounds = bounds.TransformToAABB(node.Transform).Intersect(clip);
node.ClipToBounds = clipToBounds;
node.GeometryClip = visual.Clip?.PlatformImpl;
node.Opacity = opacity;
node.OpacityMask = visual.OpacityMask;
if (startLayer)
{
if (node.LayerRoot != visual)
{
MakeLayer(scene, node);
}
else
{
UpdateLayer(node, scene.Layers[node.LayerRoot]);
}
}
else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
{
ClearLayer(scene, node);
}
if (node.ClipToBounds)
{
clip = clip.Intersect(node.ClipBounds);
}
try
{
visual.Render(context);
}
catch { }
if (visual is Visual)
{
var transformed = new TransformedBounds(new Rect(visual.Bounds.Size), clip, node.Transform);
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
if (forceRecurse)
{
foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
{
var childNode = scene.FindNode(child) ?? CreateNode(scene, child, node);
Update(context, scene, (VisualNode)childNode, clip, forceRecurse);
}
node.SubTreeUpdated = true;
contextImpl.TrimChildren();
}
}
}
}
private void UpdateSize(Scene scene)
{
var renderRoot = scene.Root.Visual as IRenderRoot;
var newSize = renderRoot?.ClientSize ?? scene.Root.Visual.Bounds.Size;
scene.Scaling = renderRoot?.RenderScaling ?? 1;
if (scene.Size != newSize)
{
var oldSize = scene.Size;
scene.Size = newSize;
Rect horizontalDirtyRect = Rect.Empty;
Rect verticalDirtyRect = Rect.Empty;
if (newSize.Width > oldSize.Width)
{
horizontalDirtyRect = new Rect(oldSize.Width, 0, newSize.Width - oldSize.Width, oldSize.Height);
}
if (newSize.Height > oldSize.Height)
{
verticalDirtyRect = new Rect(0, oldSize.Height, newSize.Width, newSize.Height - oldSize.Height);
}
foreach (var layer in scene.Layers)
{
layer.Dirty.Add(horizontalDirtyRect);
layer.Dirty.Add(verticalDirtyRect);
}
}
}
private static VisualNode CreateNode(Scene scene, IVisual visual, VisualNode parent)
{
var node = new VisualNode(visual, parent);
node.LayerRoot = parent.LayerRoot;
scene.Add(node);
return node;
}
private static void Deindex(Scene scene, VisualNode node)
{
scene.Remove(node);
node.SubTreeUpdated = true;
scene.Layers[node.LayerRoot].Dirty.Add(node.Bounds);
if (node.Visual is Visual v)
{
BoundsTracker.SetTransformedBounds(v, null);
}
foreach (VisualNode child in node.Children)
{
var geometry = child as IDrawOperation;
if (child is VisualNode visual)
{
Deindex(scene, visual);
}
}
if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual)
{
scene.Layers.Remove(node.LayerRoot);
}
}
private static void ClearLayer(Scene scene, VisualNode node)
{
var parent = (VisualNode)node.Parent;
var oldLayerRoot = node.LayerRoot;
var newLayerRoot = parent.LayerRoot;
var existingDirtyRects = scene.Layers[node.LayerRoot].Dirty;
var newDirtyRects = scene.Layers[newLayerRoot].Dirty;
existingDirtyRects.Coalesce();
foreach (var r in existingDirtyRects)
{
newDirtyRects.Add(r);
}
var oldLayer = scene.Layers[oldLayerRoot];
PropagateLayer(node, scene.Layers[newLayerRoot], oldLayer);
scene.Layers.Remove(oldLayer);
}
private static void MakeLayer(Scene scene, VisualNode node)
{
var oldLayerRoot = node.LayerRoot;
var layer = scene.Layers.Add(node.Visual);
var oldLayer = scene.Layers[oldLayerRoot];
UpdateLayer(node, layer);
PropagateLayer(node, layer, scene.Layers[oldLayerRoot]);
}
private static void UpdateLayer(VisualNode node, SceneLayer layer)
{
layer.Opacity = node.Visual.Opacity;
if (node.Visual.OpacityMask != null)
{
layer.OpacityMask = node.Visual.OpacityMask?.ToImmutable();
layer.OpacityMaskRect = node.ClipBounds;
}
else
{
layer.OpacityMask = null;
layer.OpacityMaskRect = Rect.Empty;
}
layer.GeometryClip = node.HasAncestorGeometryClip ?
CreateLayerGeometryClip(node) :
null;
}
private static void PropagateLayer(VisualNode node, SceneLayer layer, SceneLayer oldLayer)
{
node.LayerRoot = layer.LayerRoot;
layer.Dirty.Add(node.Bounds);
oldLayer.Dirty.Add(node.Bounds);
foreach (VisualNode child in node.Children)
{
// If the child is not the start of a new layer, recurse.
if (child.LayerRoot != child.Visual)
{
PropagateLayer(child, layer, oldLayer);
}
}
}
private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
{
IGeometryImpl result = null;
for (;;)
{
node = (VisualNode)node.Parent;
if (node == null || (node.GeometryClip == null && !node.HasAncestorGeometryClip))
{
break;
}
if (node?.GeometryClip != null)
{
var transformed = node.GeometryClip.WithTransform(node.Transform);
result = result == null ? transformed : result.Intersect(transformed);
}
}
return result;
}
}
}

75
src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs

@ -0,0 +1,75 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Represents a layer in a <see cref="Scene"/>.
/// </summary>
public class SceneLayer
{
/// <summary>
/// Initializes a new instance of the <see cref="SceneLayer"/> class.
/// </summary>
/// <param name="layerRoot">The visual at the root of the layer.</param>
/// <param name="distanceFromRoot">The distance from the scene root.</param>
public SceneLayer(IVisual layerRoot, int distanceFromRoot)
{
LayerRoot = layerRoot;
Dirty = new DirtyRects();
DistanceFromRoot = distanceFromRoot;
}
/// <summary>
/// Clones the layer.
/// </summary>
/// <returns>The cloned layer.</returns>
public SceneLayer Clone()
{
return new SceneLayer(LayerRoot, DistanceFromRoot)
{
Opacity = Opacity,
OpacityMask = OpacityMask,
OpacityMaskRect = OpacityMaskRect,
GeometryClip = GeometryClip,
};
}
/// <summary>
/// Gets the visual at the root of the layer.
/// </summary>
public IVisual LayerRoot { get; }
/// <summary>
/// Gets the distance of the layer root from the root of the scene.
/// </summary>
public int DistanceFromRoot { get; }
/// <summary>
/// Gets or sets the opacity of the layer.
/// </summary>
public double Opacity { get; set; } = 1;
/// <summary>
/// Gets or sets the opacity mask for the layer.
/// </summary>
public IBrush OpacityMask { get; set; }
/// <summary>
/// Gets or sets the target rectangle for the layer opacity mask.
/// </summary>
public Rect OpacityMaskRect { get; set; }
/// <summary>
/// Gets the layer's geometry clip.
/// </summary>
public IGeometryImpl GeometryClip { get; set; }
/// <summary>
/// Gets the dirty rectangles for the layer.
/// </summary>
internal DirtyRects Dirty { get; }
}
}

199
src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs

@ -0,0 +1,199 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Holds a collection of layers for a <see cref="Scene"/>.
/// </summary>
public class SceneLayers : IEnumerable<SceneLayer>
{
private readonly IVisual _root;
private readonly List<SceneLayer> _inner = new List<SceneLayer>();
private readonly Dictionary<IVisual, SceneLayer> _index = new Dictionary<IVisual, SceneLayer>();
/// <summary>
/// Initializes a new instance of the <see cref="SceneLayers"/> class.
/// </summary>
/// <param name="root">The scene's root visual.</param>
public SceneLayers(IVisual root)
{
_root = root;
}
/// <summary>
/// Gets the number of layers in the scene.
/// </summary>
public int Count => _inner.Count;
/// <summary>
/// Gets a value indicating whether any of the layers have a dirty region.
/// </summary>
public bool HasDirty
{
get
{
foreach (var layer in _inner)
{
if (!layer.Dirty.IsEmpty)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Gets a layer by index.
/// </summary>
/// <param name="index">The index of the layer.</param>
/// <returns>The layer.</returns>
public SceneLayer this[int index] => _inner[index];
/// <summary>
/// Gets a layer by its root visual.
/// </summary>
/// <param name="visual">The layer's root visual.</param>
/// <returns>The layer.</returns>
public SceneLayer this[IVisual visual] => _index[visual];
/// <summary>
/// Adds a layer to the scene.
/// </summary>
/// <param name="layerRoot">The root visual of the layer.</param>
/// <returns>The created layer.</returns>
public SceneLayer Add(IVisual layerRoot)
{
Contract.Requires<ArgumentNullException>(layerRoot != null);
var distance = layerRoot.CalculateDistanceFromAncestor(_root);
var layer = new SceneLayer(layerRoot, distance);
var insert = FindInsertIndex(layer);
_index.Add(layerRoot, layer);
_inner.Insert(insert, layer);
return layer;
}
/// <summary>
/// Makes a deep clone of the layers.
/// </summary>
/// <returns>The cloned layers.</returns>
public SceneLayers Clone()
{
var result = new SceneLayers(_root);
foreach (var src in _inner)
{
var dest = src.Clone();
result._index.Add(dest.LayerRoot, dest);
result._inner.Add(dest);
}
return result;
}
/// <summary>
/// Tests whether a layer exists with the specified root visual.
/// </summary>
/// <param name="layerRoot">The root visual.</param>
/// <returns>
/// True if a layer exists with the specified root visual, otherwise false.
/// </returns>
public bool Exists(IVisual layerRoot)
{
Contract.Requires<ArgumentNullException>(layerRoot != null);
return _index.ContainsKey(layerRoot);
}
/// <summary>
/// Tries to find a layer with the specified root visual.
/// </summary>
/// <param name="layerRoot">The root visual.</param>
/// <returns>The layer if found, otherwise null.</returns>
public SceneLayer Find(IVisual layerRoot)
{
SceneLayer result;
_index.TryGetValue(layerRoot, out result);
return result;
}
/// <summary>
/// Gets an existing layer or creates a new one if no existing layer is found.
/// </summary>
/// <param name="layerRoot">The root visual.</param>
/// <returns>The layer.</returns>
public SceneLayer GetOrAdd(IVisual layerRoot)
{
Contract.Requires<ArgumentNullException>(layerRoot != null);
SceneLayer result;
if (!_index.TryGetValue(layerRoot, out result))
{
result = Add(layerRoot);
}
return result;
}
/// <summary>
/// Removes a layer from the scene.
/// </summary>
/// <param name="layerRoot">The root visual.</param>
/// <returns>True if a matching layer was removed, otherwise false.</returns>
public bool Remove(IVisual layerRoot)
{
Contract.Requires<ArgumentNullException>(layerRoot != null);
SceneLayer layer;
if (_index.TryGetValue(layerRoot, out layer))
{
Remove(layer);
}
return layer != null;
}
/// <summary>
/// Removes a layer from the scene.
/// </summary>
/// <param name="layer">The layer.</param>
/// <returns>True if the layer was part of the scene, otherwise false.</returns>
public bool Remove(SceneLayer layer)
{
Contract.Requires<ArgumentNullException>(layer != null);
_index.Remove(layer.LayerRoot);
return _inner.Remove(layer);
}
/// <inheritdoc/>
public IEnumerator<SceneLayer> GetEnumerator() => _inner.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private int FindInsertIndex(SceneLayer insert)
{
var index = 0;
foreach (var layer in _inner)
{
if (layer.DistanceFromRoot > insert.DistanceFromRoot)
{
break;
}
++index;
}
return index;
}
}
}

96
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@ -0,0 +1,96 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents a text draw.
/// </summary>
internal class TextNode : BrushDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="TextNode"/> class.
/// </summary>
/// <param name="transform">The transform.</param>
/// <param name="foreground">The foreground brush.</param>
/// <param name="origin">The draw origin.</param>
/// <param name="text">The text to draw.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public TextNode(
Matrix transform,
IBrush foreground,
Point origin,
IFormattedTextImpl text,
IDictionary<IVisual, Scene> childScenes = null)
{
Bounds = new Rect(origin, text.Size).TransformToAABB(transform);
Transform = transform;
Foreground = foreground?.ToImmutable();
Origin = origin;
Text = text;
ChildScenes = childScenes;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
public Matrix Transform { get; }
/// <summary>
/// Gets the foreground brush.
/// </summary>
public IBrush Foreground { get; }
/// <summary>
/// Gets the draw origin.
/// </summary>
public Point Origin { get; }
/// <summary>
/// Gets the text to draw.
/// </summary>
public IFormattedTextImpl Text { get; }
/// <inheritdoc/>
public override IDictionary<IVisual, Scene> ChildScenes { get; }
/// <inheritdoc/>
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
context.DrawText(Foreground, Origin, Text);
}
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="transform">The transform of the other draw operation.</param>
/// <param name="foreground">The foregroundof the other draw operation.</param>
/// <param name="origin">The draw origin of the other draw operation.</param>
/// <param name="text">The text of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
internal bool Equals(Matrix transform, IBrush foreground, Point origin, IFormattedTextImpl text)
{
return transform == Transform &&
Equals(foreground, Foreground) &&
origin == Origin &&
Equals(text, Text);
}
/// <inheritdoc/>
public override bool HitTest(Point p) => Bounds.Contains(p);
}
}

285
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -0,0 +1,285 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the low-level scene graph representing an <see cref="IVisual"/>.
/// </summary>
internal class VisualNode : IVisualNode
{
private static readonly IReadOnlyList<IVisualNode> EmptyChildren = new IVisualNode[0];
private static readonly IReadOnlyList<IDrawOperation> EmptyDrawOperations = new IDrawOperation[0];
private Rect? _bounds;
private double _opacity;
private List<IVisualNode> _children;
private List<IDrawOperation> _drawOperations;
private bool _drawOperationsCloned;
/// <summary>
/// Initializes a new instance of the <see cref="VisualNode"/> class.
/// </summary>
/// <param name="visual">The visual that this node represents.</param>
/// <param name="parent">The parent scene graph node, if any.</param>
public VisualNode(IVisual visual, IVisualNode parent)
{
Contract.Requires<ArgumentNullException>(visual != null);
Visual = visual;
Parent = parent;
HasAncestorGeometryClip = parent != null &&
(parent.HasAncestorGeometryClip || parent.GeometryClip != null);
}
/// <inheritdoc/>
public IVisual Visual { get; }
/// <inheritdoc/>
public IVisualNode Parent { get; }
/// <inheritdoc/>
public Matrix Transform { get; set; }
/// <inheritdoc/>
public Rect Bounds => _bounds ?? CalculateBounds();
/// <inheritdoc/>
public Rect ClipBounds { get; set; }
/// <inheritdoc/>
public bool ClipToBounds { get; set; }
/// <inheritdoc/>
public IGeometryImpl GeometryClip { get; set; }
/// <inheritdoc/>
public bool HasAncestorGeometryClip { get; }
/// <summary>
/// Gets or sets the opacity of the scene graph node.
/// </summary>
public double Opacity
{
get { return _opacity; }
set
{
if (_opacity != value)
{
_opacity = value;
OpacityChanged = true;
}
}
}
/// <summary>
/// Gets or sets the opacity mask for the scnee graph node.
/// </summary>
public IBrush OpacityMask { get; set; }
/// <summary>
/// Gets a value indicating whether this node in the scene graph has already
/// been updated in the current update pass.
/// </summary>
public bool SubTreeUpdated { get; set; }
/// <summary>
/// Gets a value indicating whether the <see cref="Opacity"/> property has changed.
/// </summary>
public bool OpacityChanged { get; private set; }
public IVisual LayerRoot { get; set; }
/// <inheritdoc/>
public IReadOnlyList<IVisualNode> Children => _children ?? EmptyChildren;
/// <inheritdoc/>
public IReadOnlyList<IDrawOperation> DrawOperations => _drawOperations ?? EmptyDrawOperations;
/// <summary>
/// Adds a child to the <see cref="Children"/> collection.
/// </summary>
/// <param name="child">The child to add.</param>
public void AddChild(IVisualNode child)
{
EnsureChildrenCreated();
_children.Add(child);
}
/// <summary>
/// Adds an operation to the <see cref="DrawOperations"/> collection.
/// </summary>
/// <param name="operation">The operation to add.</param>
public void AddDrawOperation(IDrawOperation operation)
{
EnsureDrawOperationsCreated();
_drawOperations.Add(operation);
}
/// <summary>
/// Removes a child from the <see cref="Children"/> collection.
/// </summary>
/// <param name="child">The child to remove.</param>
public void RemoveChild(IVisualNode child)
{
EnsureChildrenCreated();
_children.Remove(child);
}
/// <summary>
/// Replaces a child in the <see cref="Children"/> collection.
/// </summary>
/// <param name="index">The child to be replaced.</param>
/// <param name="node">The child to add.</param>
public void ReplaceChild(int index, IVisualNode node)
{
EnsureChildrenCreated();
_children[index] = node;
}
/// <summary>
/// Replaces an item in the <see cref="DrawOperations"/> collection.
/// </summary>
/// <param name="index">The opeation to be replaced.</param>
/// <param name="operation">The operation to add.</param>
public void ReplaceDrawOperation(int index, IDrawOperation operation)
{
EnsureDrawOperationsCreated();
_drawOperations[index] = operation;
}
/// <summary>
/// Removes items in the <see cref="Children"/> collection from the specified index
/// to the end.
/// </summary>
/// <param name="first">The index of the first child to be removed.</param>
public void TrimChildren(int first)
{
if (first < _children?.Count)
{
EnsureChildrenCreated();
_children.RemoveRange(first, _children.Count - first);
}
}
/// <summary>
/// Removes items in the <see cref="DrawOperations"/> collection from the specified index
/// to the end.
/// </summary>
/// <param name="first">The index of the first operation to be removed.</param>
public void TrimDrawOperations(int first)
{
if (first < _drawOperations?.Count)
{
EnsureDrawOperationsCreated();
_drawOperations.RemoveRange(first, _drawOperations.Count - first);
}
}
/// <summary>
/// Makes a copy of the node
/// </summary>
/// <param name="parent">The new parent node.</param>
/// <returns>A cloned node.</returns>
public VisualNode Clone(IVisualNode parent)
{
return new VisualNode(Visual, parent)
{
Transform = Transform,
ClipBounds = ClipBounds,
ClipToBounds = ClipToBounds,
GeometryClip = GeometryClip,
_opacity = Opacity,
OpacityMask = OpacityMask,
_drawOperations = _drawOperations,
_drawOperationsCloned = true,
LayerRoot= LayerRoot,
};
}
/// <inheritdoc/>
public bool HitTest(Point p)
{
foreach (var operation in DrawOperations)
{
if (operation.HitTest(p) == true)
{
return true;
}
}
return false;
}
/// <inheritdoc/>
public void BeginRender(IDrawingContextImpl context)
{
if (ClipToBounds)
{
context.Transform = Matrix.Identity;
context.PushClip(ClipBounds);
}
context.Transform = Transform;
if (GeometryClip != null)
{
context.PushGeometryClip(GeometryClip);
}
}
/// <inheritdoc/>
public void EndRender(IDrawingContextImpl context)
{
if (GeometryClip != null)
{
context.PopGeometryClip();
}
if (ClipToBounds)
{
context.PopClip();
}
}
private Rect CalculateBounds()
{
var result = new Rect();
foreach (var operation in DrawOperations)
{
result = result.Union(operation.Bounds);
}
_bounds = result;
return result;
}
private void EnsureChildrenCreated()
{
if (_children == null)
{
_children = new List<IVisualNode>();
}
}
private void EnsureDrawOperationsCreated()
{
if (_drawOperations == null)
{
_drawOperations = new List<IDrawOperation>();
}
else if (_drawOperationsCloned)
{
_drawOperations = new List<IDrawOperation>(_drawOperations);
_drawOperationsCloned = false;
}
}
}
}

11
src/Avalonia.Visuals/Vector.cs

@ -74,6 +74,17 @@ namespace Avalonia
return new Vector(vector._x * scale, vector._y * scale);
}
/// <summary>
/// Scales a vector.
/// </summary>
/// <param name="vector">The vector</param>
/// <param name="scale">The divisor.</param>
/// <returns>The scaled vector.</returns>
public static Vector operator /(Vector vector, double scale)
{
return new Vector(vector._x / scale, vector._y / scale);
}
/// <summary>
/// Length of the vector
/// </summary>

24
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@ -13,6 +13,30 @@ namespace Avalonia.VisualTree
/// </summary>
public static class VisualExtensions
{
/// <summary>
/// Calculates the distance from a visual's <see cref="IRenderRoot"/>.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="ancestor">The ancestor visual.</param>
/// <returns>
/// The number of steps from the visual to the ancestor or -1 if
/// <paramref name="visual"/> is not a descendent of <paramref name="ancestor"/>.
/// </returns>
public static int CalculateDistanceFromAncestor(this IVisual visual, IVisual ancestor)
{
Contract.Requires<ArgumentNullException>(visual != null);
var result = 0;
while (visual != null && visual != ancestor)
{
++result;
visual = visual.VisualParent;
}
return visual != null ? result : -1;
}
/// <summary>
/// Tries to get the first common ancestor of two visuals.
/// </summary>

26
src/Gtk/Avalonia.Cairo/Avalonia.Cairo.v2.ncrunchproject

@ -1,26 +0,0 @@
<ProjectConfiguration>
<AutoDetectNugetBuildDependencies>true</AutoDetectNugetBuildDependencies>
<BuildPriority>1000</BuildPriority>
<CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
<ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
<PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
<AllowCodeAnalysis>false</AllowCodeAnalysis>
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
<RunPreBuildEvents>false</RunPreBuildEvents>
<RunPostBuildEvents>false</RunPostBuildEvents>
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
<InstrumentAssembly>true</InstrumentAssembly>
<PreventSigningOfAssembly>false</PreventSigningOfAssembly>
<AnalyseExecutionTimes>true</AnalyseExecutionTimes>
<DetectStackOverflow>true</DetectStackOverflow>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration />
<UseBuildPlatform />
<ProxyProcessPath />
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture>
</ProjectConfiguration>

1
src/Gtk/Avalonia.Cairo/CairoPlatform.cs

@ -26,6 +26,7 @@ namespace Avalonia.Cairo
{
using System.IO;
using global::Cairo;
using Rendering;
public class CairoPlatform : IPlatformRenderInterface
{

5
src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs

@ -10,9 +10,10 @@ namespace Avalonia.Cairo
{
var center = brush.Center.ToPixels(destinationSize);
var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize);
var radius = brush.Radius;
var radius = brush.Radius * Math.Min(destinationSize.Width, destinationSize.Height);
this.PlatformBrush = new RadialGradient(center.X, center.Y, radius, gradientOrigin.X, gradientOrigin.Y, radius);
this.PlatformBrush = new RadialGradient(center.X, center.Y, 1, gradientOrigin.X, gradientOrigin.Y, radius);
this.PlatformBrush.Matrix = Matrix.Identity.ToCairo();
foreach (var stop in brush.GradientStops)
{

8
src/Gtk/Avalonia.Gtk/GtkPlatform.cs

@ -28,7 +28,7 @@ namespace Avalonia.Gtk
using Rendering;
using Gtk = global::Gtk;
public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IRendererFactory
public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader
{
private static readonly GtkPlatform s_instance = new GtkPlatform();
private static Thread _uiThread;
@ -53,7 +53,6 @@ namespace Avalonia.Gtk
.Bind<IKeyboardDevice>().ToConstant(GtkKeyboardDevice.Instance)
.Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRendererFactory>().ToConstant(s_instance)
.Bind<IRenderLoop>().ToConstant(new DefaultRenderLoop(60))
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>()
.Bind<IPlatformIconLoader>().ToConstant(s_instance);
@ -112,11 +111,6 @@ namespace Avalonia.Gtk
return new PopupImpl();
}
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new ImmediateRenderer(root);
}
public IWindowIconImpl LoadIcon(string fileName)
{
return new IconImpl(new Gdk.Pixbuf(fileName));

6
src/Gtk/Avalonia.Gtk/TopLevelImpl.cs

@ -10,6 +10,7 @@ using Gdk;
using Action = System.Action;
using WindowEdge = Avalonia.Controls.WindowEdge;
using GLib;
using Avalonia.Rendering;
namespace Avalonia.Gtk
{
@ -141,6 +142,11 @@ namespace Avalonia.Gtk
return new PopupImpl();
}
public IRenderer CreateRenderer(IRenderRoot root)
{
return new ImmediateRenderer(root);
}
public void Invalidate(Rect rect)
{
if (_widget?.GdkWindow != null)

8
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -15,7 +15,7 @@ using Avalonia.Gtk3;
namespace Avalonia.Gtk3
{
public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface, IRendererFactory
public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
{
internal static readonly Gtk3Platform Instance = new Gtk3Platform();
internal static readonly MouseDevice Mouse = new MouseDevice();
@ -38,7 +38,6 @@ namespace Avalonia.Gtk3
.Bind<IPlatformThreadingInterface>().ToConstant(Instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>()
.Bind<IRenderLoop>().ToConstant(new DefaultRenderLoop(60))
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoader());
}
@ -52,11 +51,6 @@ namespace Avalonia.Gtk3
public IPopupImpl CreatePopup() => new PopupImpl();
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new ImmediateRenderer(root);
}
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(100); //STUB

6
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -9,6 +9,7 @@ using Avalonia.Gtk3.Interop;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Gtk3
{
@ -354,5 +355,10 @@ namespace Avalonia.Gtk3
IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget));
public IEnumerable<object> Surfaces => new object[] {Handle, _framebuffer};
public IRenderer CreateRenderer(IRenderRoot root)
{
return new ImmediateRenderer(root);
}
}
}

6
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -5,6 +5,7 @@ using System.Text;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer
@ -24,6 +25,11 @@ namespace Avalonia.LinuxFramebuffer
mice.Event += e => Input?.Invoke(e);
}
public IRenderer CreateRenderer(IRenderRoot root)
{
return new ImmediateRenderer(root);
}
public void Dispose()
{
throw new NotSupportedException();

1
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -34,7 +34,6 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
.Bind<IRenderLoop>().ToConstant(PlatformThreadingInterface.Instance);
}

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

Loading…
Cancel
Save