A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

108 lines
4.5 KiB

using System;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Platform;
// Special license applies <see href="https://raw.githubusercontent.com/AvaloniaUI/Avalonia/master/src/Avalonia.Base/Rendering/Composition/License.md">License.md</see>
namespace Avalonia.Rendering.Composition.Server
{
/// <summary>
/// Server-side counterpart of <see cref="CompositionContainerVisual"/>.
/// Mostly propagates update and render calls, but is also responsible
/// for updating adorners in deferred manner
/// </summary>
internal partial class ServerCompositionContainerVisual : ServerCompositionVisual
{
public ServerCompositionVisualCollection Children { get; private set; } = null!;
private Rect? _transformedContentBounds;
private IImmutableEffect? _oldEffect;
protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
base.RenderCore(canvas, currentTransformedClip);
foreach (var ch in Children)
{
ch.Render(canvas, currentTransformedClip);
}
}
public override UpdateResult Update(ServerCompositionTarget root)
{
var (combinedBounds, oldInvalidated, newInvalidated) = base.Update(root);
foreach (var child in Children)
{
if (child.AdornedVisual != null)
root.EnqueueAdornerUpdate(child);
else
{
var res = child.Update(root);
oldInvalidated |= res.InvalidatedOld;
newInvalidated |= res.InvalidatedNew;
combinedBounds = Rect.Union(combinedBounds, res.Bounds);
}
}
// If effect is changed, we need to clean both old and new bounds
var effectChanged = !Effect.EffectEquals(_oldEffect);
if (effectChanged)
oldInvalidated = newInvalidated = true;
// Expand invalidated bounds to the whole content area since we don't actually know what is being sampled
// We also ignore clip for now since we don't have means to reset it?
if (_oldEffect != null && oldInvalidated && _transformedContentBounds.HasValue)
AddEffectPaddedDirtyRect(_oldEffect, _transformedContentBounds.Value);
if (Effect != null && newInvalidated && combinedBounds.HasValue)
AddEffectPaddedDirtyRect(Effect, combinedBounds.Value);
_oldEffect = Effect;
_transformedContentBounds = combinedBounds;
IsDirtyComposition = false;
return new(_transformedContentBounds, oldInvalidated, newInvalidated);
}
void AddEffectPaddedDirtyRect(IImmutableEffect effect, Rect transformedBounds)
{
var padding = effect.GetEffectOutputPadding();
if (padding == default)
{
AddDirtyRect(transformedBounds);
return;
}
// We are in a weird position here: bounds are in global coordinates while padding gets applied in local ones
// Since we have optimizations to AVOID recomputing transformed bounds and since visuals with effects are relatively rare
// we instead apply the transformation matrix to rescale the bounds
// If we only have translation and scale, just scale the padding
if (CombinedTransformMatrix is
{
M12: 0, M13: 0, M14: 0,
M21: 0, M23: 0, M24: 0,
M31: 0, M32: 0, M34: 0,
M43: 0, M44: 1
})
padding = new Thickness(padding.Left * CombinedTransformMatrix.M11,
padding.Top * CombinedTransformMatrix.M22,
padding.Right * CombinedTransformMatrix.M11,
padding.Bottom * CombinedTransformMatrix.M22);
else
{
// Conservatively use the transformed rect size
var transformedPaddingRect = new Rect().Inflate(padding).TransformToAABB(CombinedTransformMatrix);
padding = new(Math.Max(transformedPaddingRect.Width, transformedPaddingRect.Height));
}
AddDirtyRect(transformedBounds.Inflate(padding));
}
partial void Initialize()
{
Children = new ServerCompositionVisualCollection(Compositor);
}
}
}