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.
 
 
 

301 lines
11 KiB

using System;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Transport;
using Avalonia.Utilities;
// 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 <see cref="CompositionVisual"/> counterpart.
/// Is responsible for computing the transformation matrix, for applying various visual
/// properties before calling visual-specific drawing code and for notifying the
/// <see cref="ServerCompositionTarget"/> for new dirty rects
/// </summary>
partial class ServerCompositionVisual : ServerObject
{
private bool _isDirtyForUpdate;
private Rect _oldOwnContentBounds;
private bool _isBackface;
private Rect? _transformedClipBounds;
private Rect _combinedTransformedClipBounds;
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
}
public void Render(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
{
if (Visible == false || IsVisibleInFrame == false)
return;
if (Opacity == 0)
return;
currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds);
if (currentTransformedClip.Width == 0 && currentTransformedClip.Height == 0)
return;
Root!.RenderedVisuals++;
var boundsRect = new Rect(new Size(Size.X, Size.Y));
if (AdornedVisual != null)
{
canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity;
if (AdornerIsClipped)
canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
}
var transform = GlobalTransformMatrix;
canvas.PostTransform = MatrixUtils.ToMatrix(transform);
canvas.Transform = Matrix.Identity;
if (Effect != null)
canvas.PushEffect(Effect);
if (Opacity != 1)
canvas.PushOpacity(Opacity, boundsRect);
if (ClipToBounds && !HandlesClipToBounds)
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
if (Clip != null)
canvas.PushGeometryClip(Clip);
if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
canvas.RenderOptions = RenderOptions;
RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix
canvas.PostTransform = MatrixUtils.ToMatrix(transform);
canvas.Transform = Matrix.Identity;
if (OpacityMaskBrush != null)
canvas.PopOpacityMask();
if (Clip != null)
canvas.PopGeometryClip();
if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip();
if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip();
if (Opacity != 1)
canvas.PopOpacity();
if (Effect != null)
canvas.PopEffect();
}
protected virtual bool HandlesClipToBounds => false;
private ReadbackData _readback0, _readback1, _readback2;
/// <summary>
/// Obtains "readback" data - the data that is sent from the render thread to the UI thread
/// in non-blocking manner. Used mostly by hit-testing
/// </summary>
public ref ReadbackData GetReadback(int idx)
{
if (idx == 0)
return ref _readback0;
if (idx == 1)
return ref _readback1;
return ref _readback2;
}
public Matrix4x4 CombinedTransformMatrix { get; private set; } = Matrix4x4.Identity;
public Matrix4x4 GlobalTransformMatrix { get; private set; }
public record struct UpdateResult(Rect? Bounds, bool InvalidatedOld, bool InvalidatedNew)
{
public UpdateResult() : this(null, false, false)
{
}
}
public virtual UpdateResult Update(ServerCompositionTarget root)
{
if (Parent == null && Root == null)
return default;
var wasVisible = IsVisibleInFrame;
if(Parent != null)
{
RenderOptions = RenderOptions.MergeWith(Parent.RenderOptions);
}
// Calculate new parent-relative transform
if (_combinedTransformDirty)
{
CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint,
// HACK: Ignore RenderTransform set by the adorner layer
AdornedVisual != null ? Matrix4x4.Identity : TransformMatrix,
Scale, RotationAngle, Orientation, Offset);
_combinedTransformDirty = false;
}
var parentTransform = (AdornedVisual ?? Parent)?.GlobalTransformMatrix ?? Matrix4x4.Identity;
var newTransform = CombinedTransformMatrix * parentTransform;
// Check if visual was moved and recalculate face orientation
var positionChanged = false;
if (GlobalTransformMatrix != newTransform)
{
_isBackface = Vector3.Transform(
new Vector3(0, 0, float.PositiveInfinity), GlobalTransformMatrix).Z <= 0;
positionChanged = true;
}
var oldTransformedContentBounds = TransformedOwnContentBounds;
var oldCombinedTransformedClipBounds = _combinedTransformedClipBounds;
if (_parent?.IsDirtyComposition == true)
{
IsDirtyComposition = true;
_isDirtyForUpdate = true;
}
var invalidateOldBounds = _isDirtyForUpdate;
var invalidateNewBounds = _isDirtyForUpdate;
GlobalTransformMatrix = newTransform;
var ownBounds = OwnContentBounds;
// Since padding is applied in the current visual's coordinate space we expand bounds before transforming them
if (Effect != null)
ownBounds = ownBounds.Inflate(Effect.GetEffectOutputPadding());
if (ownBounds != _oldOwnContentBounds || positionChanged)
{
_oldOwnContentBounds = ownBounds;
if (ownBounds.Width == 0 && ownBounds.Height == 0)
TransformedOwnContentBounds = default;
else
TransformedOwnContentBounds =
ownBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix));
}
if (_clipSizeDirty || positionChanged)
{
_transformedClipBounds = ClipToBounds
? new Rect(new Size(Size.X, Size.Y))
.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix))
: null;
_clipSizeDirty = false;
}
_combinedTransformedClipBounds =
AdornedVisual?._combinedTransformedClipBounds
?? (Parent?.Effect == null ? Parent?._combinedTransformedClipBounds : null)
?? new Rect(Root!.Size);
if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1);
IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false
&& Visible
&& !_isBackface
&& (_combinedTransformedClipBounds.Width != 0 || _combinedTransformedClipBounds.Height != 0);
IsVisibleInFrame = IsHitTestVisibleInFrame
&& _parent?.IsVisibleInFrame != false
&& EffectiveOpacity > 0.04;
if (wasVisible != IsVisibleInFrame || positionChanged)
{
invalidateOldBounds |= wasVisible;
invalidateNewBounds |= IsVisibleInFrame;
}
// Invalidate new bounds
if (invalidateNewBounds)
AddDirtyRect(TransformedOwnContentBounds.Intersect(_combinedTransformedClipBounds));
if (invalidateOldBounds)
AddDirtyRect(oldTransformedContentBounds.Intersect(oldCombinedTransformedClipBounds));
_isDirtyForUpdate = false;
// Update readback indices
var i = Root!.Readback;
ref var readback = ref GetReadback(i.WriteIndex);
readback.Revision = root.Revision;
readback.Matrix = GlobalTransformMatrix;
readback.TargetId = Root.Id;
readback.Visible = IsHitTestVisibleInFrame;
return new(TransformedOwnContentBounds, invalidateNewBounds, invalidateOldBounds);
}
protected void AddDirtyRect(Rect rc)
{
if (rc == default)
return;
Root?.AddDirtyRect(rc);
}
/// <summary>
/// Data that can be read from the UI thread
/// </summary>
public struct ReadbackData
{
public Matrix4x4 Matrix;
public ulong Revision;
public long TargetId;
public bool Visible;
}
partial void DeserializeChangesExtra(BatchStreamReader c)
{
ValuesInvalidated();
}
partial void OnRootChanging()
{
if (Root != null)
{
Root.RemoveVisual(this);
OnDetachedFromRoot(Root);
}
}
protected virtual void OnDetachedFromRoot(ServerCompositionTarget target)
{
}
partial void OnRootChanged()
{
if (Root != null)
{
Root.AddVisual(this);
OnAttachedToRoot(Root);
}
}
protected virtual void OnAttachedToRoot(ServerCompositionTarget target)
{
}
protected override void ValuesInvalidated()
{
_isDirtyForUpdate = true;
Root?.Invalidate();
}
public bool IsVisibleInFrame { get; set; }
public bool IsHitTestVisibleInFrame { get; set; }
public double EffectiveOpacity { get; set; }
public Rect TransformedOwnContentBounds { get; set; }
public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y);
}
}