committed by
GitHub
316 changed files with 10388 additions and 3655 deletions
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -1,9 +1,9 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="SharpDX" Version="3.1.1" /> |
<PackageReference Include="SharpDX" Version="4.0.1" /> |
||||
<PackageReference Include="SharpDX.Direct2D1" Version="3.1.1" /> |
<PackageReference Include="SharpDX.Direct2D1" Version="4.0.1" /> |
||||
<PackageReference Include="SharpDX.Direct3D11" Version="3.1.1" /> |
<PackageReference Include="SharpDX.Direct3D11" Version="4.0.1" /> |
||||
<PackageReference Include="SharpDX.Direct3D9" Version="3.1.1" Condition="'$(UseDirect3D9)' == 'true'" /> |
<PackageReference Include="SharpDX.DXGI" Version="4.0.1" /> |
||||
<PackageReference Include="SharpDX.DXGI" Version="3.1.1" /> |
<PackageReference Include="SharpDX.Direct3D9" Version="4.0.1" Condition="'$(UseDirect3D9)' == 'true'" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,15 @@ |
|||||
|
// 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; |
||||
|
|
||||
|
namespace Avalonia.Metadata |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the ambient class/property
|
||||
|
/// </summary>
|
||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = true)] |
||||
|
public class AmbientAttribute : Attribute |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
// 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; |
||||
|
|
||||
|
namespace Avalonia.Metadata |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the property that contains the object's content in markup.
|
||||
|
/// </summary>
|
||||
|
[AttributeUsage(AttributeTargets.Property)] |
||||
|
public class TemplateContentAttribute : Attribute |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Styling |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Holds resources for a <see cref="Style"/>.
|
||||
|
/// </summary>
|
||||
|
public class StyleResources : IDictionary<string, object>, IDictionary |
||||
|
{ |
||||
|
private Dictionary<string, object> _inner = new Dictionary<string, object>(); |
||||
|
|
||||
|
public object this[string key] |
||||
|
{ |
||||
|
get { return _inner[key]; } |
||||
|
set { _inner[key] = value; } |
||||
|
} |
||||
|
|
||||
|
public int Count => _inner.Count; |
||||
|
|
||||
|
ICollection<string> IDictionary<string, object>.Keys => _inner.Keys; |
||||
|
|
||||
|
ICollection<object> IDictionary<string, object>.Values => _inner.Values; |
||||
|
|
||||
|
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false; |
||||
|
|
||||
|
object IDictionary.this[object key] |
||||
|
{ |
||||
|
get { return ((IDictionary)_inner)[key]; } |
||||
|
set { ((IDictionary)_inner)[key] = value; } |
||||
|
} |
||||
|
|
||||
|
ICollection IDictionary.Keys => _inner.Keys; |
||||
|
|
||||
|
ICollection IDictionary.Values => _inner.Values; |
||||
|
|
||||
|
bool ICollection.IsSynchronized => false; |
||||
|
|
||||
|
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot; |
||||
|
|
||||
|
bool IDictionary.IsFixedSize => false; |
||||
|
|
||||
|
bool IDictionary.IsReadOnly => false; |
||||
|
|
||||
|
public void Add(string key, object value) => _inner.Add(key, value); |
||||
|
|
||||
|
public void Clear() => _inner.Clear(); |
||||
|
|
||||
|
public bool ContainsKey(string key) => _inner.ContainsKey(key); |
||||
|
|
||||
|
public bool Remove(string key) => _inner.Remove(key); |
||||
|
|
||||
|
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _inner.GetEnumerator(); |
||||
|
|
||||
|
public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value); |
||||
|
|
||||
|
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) |
||||
|
{ |
||||
|
return ((IDictionary<string, object>)_inner).Contains(item); |
||||
|
} |
||||
|
|
||||
|
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) |
||||
|
{ |
||||
|
((IDictionary<string, object>)_inner).Add(item); |
||||
|
} |
||||
|
|
||||
|
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) |
||||
|
{ |
||||
|
((IDictionary<string, object>)_inner).CopyTo(array, arrayIndex); |
||||
|
} |
||||
|
|
||||
|
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) |
||||
|
{ |
||||
|
return ((IDictionary<string, object>)_inner).Remove(item); |
||||
|
} |
||||
|
|
||||
|
void ICollection.CopyTo(Array array, int index) => ((IDictionary)_inner).CopyTo(array, index); |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); |
||||
|
|
||||
|
IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator(); |
||||
|
|
||||
|
void IDictionary.Add(object key, object value) => ((IDictionary)_inner).Add(key, value); |
||||
|
|
||||
|
bool IDictionary.Contains(object key) => ((IDictionary)_inner).Contains(key); |
||||
|
|
||||
|
void IDictionary.Remove(object key) => ((IDictionary)_inner).Remove(key); |
||||
|
} |
||||
|
} |
||||
@ -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,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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue