committed by
GitHub
150 changed files with 6750 additions and 717 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> |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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,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,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>true</IgnoreThisComponentCompletely> |
|||
<RunPreBuildEvents>false</RunPreBuildEvents> |
|||
<RunPostBuildEvents>false</RunPostBuildEvents> |
|||
<PreviouslyBuiltSuccessfully>false</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,172 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using SkiaSharp; |
|||
#if WIN32
|
|||
using Avalonia.Win32.Interop; |
|||
#endif
|
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
internal partial class RenderTarget : IRenderTarget |
|||
{ |
|||
public SKSurface Surface { get; protected set; } |
|||
|
|||
public virtual DrawingContext CreateDrawingContext() |
|||
{ |
|||
return |
|||
new DrawingContext( |
|||
new DrawingContextImpl(Surface.Canvas)); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
// Nothing to do here.
|
|||
} |
|||
} |
|||
|
|||
internal class WindowRenderTarget : RenderTarget |
|||
{ |
|||
private readonly IPlatformHandle _hwnd; |
|||
SKBitmap _bitmap; |
|||
int Width { get; set; } |
|||
int Height { get; set; } |
|||
|
|||
public WindowRenderTarget(IPlatformHandle hwnd) |
|||
{ |
|||
_hwnd = hwnd; |
|||
FixSize(); |
|||
} |
|||
|
|||
private void FixSize() |
|||
{ |
|||
int width, height; |
|||
GetPlatformWindowSize(out width, out height); |
|||
if (Width == width && Height == height) |
|||
return; |
|||
|
|||
Width = width; |
|||
Height = height; |
|||
|
|||
if (Surface != null) |
|||
{ |
|||
Surface.Dispose(); |
|||
} |
|||
|
|||
if (_bitmap != null) |
|||
{ |
|||
_bitmap.Dispose(); |
|||
} |
|||
|
|||
_bitmap = new SKBitmap(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul); |
|||
|
|||
IntPtr length; |
|||
var pixels = _bitmap.GetPixels(out length); |
|||
|
|||
// Wrap the bitmap in a Surface and keep it cached
|
|||
Surface = SKSurface.Create(_bitmap.Info, pixels, _bitmap.RowBytes); |
|||
} |
|||
|
|||
private void GetPlatformWindowSize(out int w, out int h) |
|||
{ |
|||
#if WIN32
|
|||
UnmanagedMethods.RECT rc; |
|||
UnmanagedMethods.GetClientRect(_hwnd.Handle, out rc); |
|||
w = rc.right - rc.left; |
|||
h = rc.bottom - rc.top; |
|||
#else
|
|||
throw new NotImplementedException(); |
|||
#endif
|
|||
} |
|||
|
|||
private Size GetWindowDpiWin32() |
|||
{ |
|||
if (UnmanagedMethods.ShCoreAvailable) |
|||
{ |
|||
uint dpix, dpiy; |
|||
|
|||
var monitor = UnmanagedMethods.MonitorFromWindow( |
|||
_hwnd.Handle, |
|||
UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); |
|||
|
|||
if (UnmanagedMethods.GetDpiForMonitor( |
|||
monitor, |
|||
UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, |
|||
out dpix, |
|||
out dpiy) == 0) |
|||
{ |
|||
return new Size(dpix, dpiy); |
|||
} |
|||
} |
|||
|
|||
return new Size(96, 96); |
|||
} |
|||
|
|||
public override DrawingContext CreateDrawingContext() |
|||
{ |
|||
FixSize(); |
|||
|
|||
var canvas = Surface.Canvas; |
|||
canvas.RestoreToCount(0); |
|||
canvas.Save(); |
|||
canvas.Clear(SKColors.Red); |
|||
canvas.ResetMatrix(); |
|||
|
|||
double scale = 1.0; |
|||
|
|||
var runtimeService = AvaloniaLocator.Current.GetService<IRuntimePlatform>(); |
|||
|
|||
if (runtimeService != null) |
|||
{ |
|||
switch (runtimeService.GetRuntimeInfo().OperatingSystem) |
|||
{ |
|||
case OperatingSystemType.WinNT: |
|||
var dpi = GetWindowDpiWin32(); |
|||
scale = dpi.Width / 96.0; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
var result = |
|||
new DrawingContext( |
|||
new WindowDrawingContextImpl(this), Matrix.CreateScale(scale, scale)); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public void Present() |
|||
{ |
|||
_bitmap.LockPixels(); |
|||
IntPtr length; |
|||
var pixels = _bitmap.GetPixels(out length); |
|||
|
|||
#if WIN32
|
|||
UnmanagedMethods.BITMAPINFO bmi = new UnmanagedMethods.BITMAPINFO(); |
|||
bmi.biSize = UnmanagedMethods.SizeOf_BITMAPINFOHEADER; |
|||
bmi.biWidth = _bitmap.Width; |
|||
bmi.biHeight = -_bitmap.Height; // top-down image
|
|||
bmi.biPlanes = 1; |
|||
bmi.biBitCount = 32; |
|||
bmi.biCompression = (uint)UnmanagedMethods.BitmapCompressionMode.BI_RGB; |
|||
bmi.biSizeImage = 0; |
|||
|
|||
IntPtr hdc = UnmanagedMethods.GetDC(_hwnd.Handle); |
|||
|
|||
int ret = UnmanagedMethods.SetDIBitsToDevice(hdc, |
|||
0, 0, |
|||
(uint)_bitmap.Width, (uint)_bitmap.Height, |
|||
0, 0, |
|||
0, (uint)_bitmap.Height, |
|||
pixels, |
|||
ref bmi, |
|||
(uint)UnmanagedMethods.DIBColorTable.DIB_RGB_COLORS); |
|||
|
|||
UnmanagedMethods.ReleaseDC(_hwnd.Handle, hdc); |
|||
#else
|
|||
throw new NotImplementedException(); |
|||
#endif
|
|||
|
|||
_bitmap.UnlockPixels(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Moq; |
|||
using System; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Input.UnitTests |
|||
{ |
|||
public class MouseDeviceTests |
|||
{ |
|||
[Fact] |
|||
public void MouseMove_Should_Update_PointerOver() |
|||
{ |
|||
var renderer = new Mock<IRenderer>(); |
|||
|
|||
using (TestApplication(renderer.Object)) |
|||
{ |
|||
var inputManager = InputManager.Instance; |
|||
|
|||
Canvas canvas; |
|||
Border border; |
|||
Decorator decorator; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
MouseDevice = new MouseDevice(), |
|||
Renderer = renderer.Object, |
|||
Child = new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()), |
|||
(border = new Border |
|||
{ |
|||
Child = decorator = new Decorator(), |
|||
}) |
|||
} |
|||
} |
|||
}; |
|||
|
|||
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<Func<IVisual, bool>>())) |
|||
.Returns(new[] { decorator }); |
|||
|
|||
inputManager.ProcessInput(new RawMouseEventArgs( |
|||
root.MouseDevice, |
|||
0, |
|||
root, |
|||
RawMouseEventType.Move, |
|||
new Point(), |
|||
InputModifiers.None)); |
|||
|
|||
Assert.True(decorator.IsPointerOver); |
|||
Assert.True(border.IsPointerOver); |
|||
Assert.False(canvas.IsPointerOver); |
|||
Assert.True(root.IsPointerOver); |
|||
|
|||
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<Func<IVisual, bool>>())) |
|||
.Returns(new[] { canvas }); |
|||
|
|||
inputManager.ProcessInput(new RawMouseEventArgs( |
|||
root.MouseDevice, |
|||
0, |
|||
root, |
|||
RawMouseEventType.Move, |
|||
new Point(), |
|||
InputModifiers.None)); |
|||
|
|||
Assert.False(decorator.IsPointerOver); |
|||
Assert.False(border.IsPointerOver); |
|||
Assert.True(canvas.IsPointerOver); |
|||
Assert.True(root.IsPointerOver); |
|||
} |
|||
} |
|||
|
|||
private IDisposable TestApplication(IRenderer renderer) |
|||
{ |
|||
return UnitTestApplication.Start( |
|||
new TestServices(inputManager: new InputManager())); |
|||
} |
|||
} |
|||
} |
|||
@ -1,27 +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> |
|||
<HiddenWarnings>AbnormalReferenceResolution</HiddenWarnings> |
|||
</ProjectConfiguration> |
|||
@ -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,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>true</IgnoreThisComponentCompletely> |
|||
<RunPreBuildEvents>false</RunPreBuildEvents> |
|||
<RunPostBuildEvents>false</RunPostBuildEvents> |
|||
<PreviouslyBuiltSuccessfully>false</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> |
|||
@ -0,0 +1,162 @@ |
|||
// 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.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Xunit; |
|||
|
|||
#if AVALONIA_CAIRO
|
|||
namespace Avalonia.Cairo.RenderTests.Controls |
|||
#elif AVALONIA_SKIA
|
|||
namespace Avalonia.Skia.RenderTests |
|||
#else
|
|||
namespace Avalonia.Direct2D1.RenderTests.Controls |
|||
#endif
|
|||
{ |
|||
public class CustomRenderTests : TestBase |
|||
{ |
|||
public CustomRenderTests() |
|||
: base(@"Controls\CustomRender") |
|||
{ |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Clip() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new CustomRenderer((control, context) => |
|||
{ |
|||
context.FillRectangle( |
|||
Brushes.Red, |
|||
new Rect(control.Bounds.Size), |
|||
4); |
|||
|
|||
using (context.PushClip(new Rect(control.Bounds.Size).Deflate(20))) |
|||
{ |
|||
context.FillRectangle( |
|||
Brushes.Blue, |
|||
new Rect(control.Bounds.Size), |
|||
4); |
|||
} |
|||
}), |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task GeometryClip() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new CustomRenderer((control, context) => |
|||
{ |
|||
var clip = new EllipseGeometry(new Rect(control.Bounds.Size)); |
|||
|
|||
context.FillRectangle( |
|||
Brushes.Red, |
|||
new Rect(control.Bounds.Size), |
|||
4); |
|||
|
|||
using (context.PushGeometryClip(clip)) |
|||
{ |
|||
context.FillRectangle( |
|||
Brushes.Blue, |
|||
new Rect(control.Bounds.Size), |
|||
4); |
|||
} |
|||
}), |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Opacity() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new CustomRenderer((control, context) => |
|||
{ |
|||
context.FillRectangle( |
|||
Brushes.Red, |
|||
new Rect(control.Bounds.Size), |
|||
4); |
|||
|
|||
using (context.PushOpacity(0.5)) |
|||
{ |
|||
context.FillRectangle( |
|||
Brushes.Blue, |
|||
new Rect(control.Bounds.Size).Deflate(20), |
|||
4); |
|||
} |
|||
}), |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task OpacityMask() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new CustomRenderer((control, context) => |
|||
{ |
|||
var mask = new LinearGradientBrush |
|||
{ |
|||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), |
|||
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), |
|||
GradientStops = new[] |
|||
{ |
|||
new GradientStop(Color.FromUInt32(0xffffffff), 0), |
|||
new GradientStop(Color.FromUInt32(0x00ffffff), 1) |
|||
}, |
|||
}; |
|||
|
|||
context.FillRectangle( |
|||
Brushes.Red, |
|||
new Rect(control.Bounds.Size), |
|||
4); |
|||
|
|||
using (context.PushOpacityMask(mask, new Rect(control.Bounds.Size))) |
|||
{ |
|||
context.FillRectangle( |
|||
Brushes.Blue, |
|||
new Rect(control.Bounds.Size).Deflate(20), |
|||
4); |
|||
} |
|||
}), |
|||
}; |
|||
|
|||
await RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
class CustomRenderer : Control |
|||
{ |
|||
private Action<CustomRenderer, DrawingContext> _render; |
|||
public CustomRenderer(Action<CustomRenderer, DrawingContext> render) => _render = render; |
|||
public override void Render(DrawingContext context) => _render(this, context); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue