diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
index e20a4a9ad8..570c6a6d07 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
@@ -128,8 +128,11 @@ namespace Avalonia.Rendering.Composition.Animations
left = kf;
right = _keyFrames[c + 1];
- break;
}
+ else if (c == 0)
+ return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf));
+ else
+ break;
}
var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key)));
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index 98a6a3600e..38d9b34937 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -146,8 +146,18 @@ public class CompositingRenderer : IRendererWithCompositor
return result == 0 ? lhs.index.CompareTo(rhs.index) : result;
});
}
-
- if (compositionChildren.Count == visualChildren.Count)
+
+ var childVisual = v.ChildCompositionVisual;
+
+ // Check if the current visual somehow got migrated to another compositor
+ if (childVisual != null && childVisual.Compositor != v.CompositionVisual.Compositor)
+ childVisual = null;
+
+ var expectedCount = visualChildren.Count;
+ if (childVisual != null)
+ expectedCount++;
+
+ if (compositionChildren.Count == expectedCount)
{
bool mismatch = false;
if (sortedChildren != null)
@@ -167,6 +177,9 @@ public class CompositingRenderer : IRendererWithCompositor
break;
}
+ if (childVisual != null &&
+ !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual))
+ mismatch = true;
if (!mismatch)
{
@@ -193,6 +206,9 @@ public class CompositingRenderer : IRendererWithCompositor
if (compositionChild != null)
compositionChildren.Add(compositionChild);
}
+
+ if (childVisual != null)
+ compositionChildren.Add(childVisual);
}
private void UpdateCore()
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
index 1a13d23acd..00fa7b3315 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs
@@ -29,4 +29,7 @@ public partial class Compositor
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this);
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this);
+
+ public CompositionSolidColorVisual CreateSolidColorVisual() =>
+ new(this, new ServerCompositionSolidColorVisual(Server));
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
index 5bd8e4a4d3..b01321edd8 100644
--- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
+++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs
@@ -1,5 +1,8 @@
// Special license applies License.md
+using System;
+using Avalonia.VisualTree;
+
namespace Avalonia.Rendering.Composition;
///
@@ -13,4 +16,22 @@ public static class ElementComposition
///
///
public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual;
+
+ ///
+ /// Sets a custom as the last child of the element’s visual tree.
+ ///
+ public static void SetElementChildVisual(Visual visual, CompositionVisual? compositionVisual)
+ {
+ if (compositionVisual != null && visual.CompositionVisual != null &&
+ compositionVisual.Compositor != visual.CompositionVisual.Compositor)
+ throw new InvalidOperationException("Composition visuals belong to different compositor instances");
+
+ visual.ChildCompositionVisual = compositionVisual;
+ visual.GetVisualRoot()?.Renderer.RecalculateChildren(visual);
+ }
+
+ ///
+ /// Retrieves a object previously set by a call to .
+ ///
+ public static CompositionVisual? GetElementChildVisual(Visual visual) => visual.ChildCompositionVisual;
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs
new file mode 100644
index 0000000000..79abd7ee17
--- /dev/null
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs
@@ -0,0 +1,11 @@
+using Avalonia.Media.Immutable;
+
+namespace Avalonia.Rendering.Composition.Server;
+
+internal partial class ServerCompositionSolidColorVisual
+{
+ protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip)
+ {
+ canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y));
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index b742f1d44a..d724e14298 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -48,7 +48,7 @@ namespace Avalonia.Rendering.Composition.Server
if (Opacity != 1)
canvas.PushOpacity(Opacity);
var boundsRect = new Rect(new Size(Size.X, Size.Y));
- if(ClipToBounds)
+ if (ClipToBounds && !HandlesClipToBounds)
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
if (Clip != null)
canvas.PushGeometryClip(Clip);
@@ -65,11 +65,13 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopOpacityMask();
if (Clip != null)
canvas.PopGeometryClip();
- if (ClipToBounds)
+ if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip();
if(Opacity != 1)
canvas.PopOpacity();
}
+
+ protected virtual bool HandlesClipToBounds => false;
private ReadbackData _readback0, _readback1, _readback2;
diff --git a/src/Avalonia.Base/Threading/DispatcherPriority.cs b/src/Avalonia.Base/Threading/DispatcherPriority.cs
index f041590fc0..b3194e249b 100644
--- a/src/Avalonia.Base/Threading/DispatcherPriority.cs
+++ b/src/Avalonia.Base/Threading/DispatcherPriority.cs
@@ -45,32 +45,37 @@ namespace Avalonia.Threading
///
/// The job will be processed after other non-idle operations have completed.
///
- public static readonly DispatcherPriority Background = new(1);
+ public static readonly DispatcherPriority Background = new(MinValue + 1);
///
/// The job will be processed with the same priority as input.
///
- public static readonly DispatcherPriority Input = new(2);
+ public static readonly DispatcherPriority Input = new(Background + 1);
///
/// The job will be processed after layout and render but before input.
///
- public static readonly DispatcherPriority Loaded = new(3);
-
+ public static readonly DispatcherPriority Loaded = new(Input + 1);
+
///
/// The job will be processed with the same priority as render.
///
- public static readonly DispatcherPriority Render = new(5);
-
+ public static readonly DispatcherPriority Render = new(Loaded + 1);
+
///
/// The job will be processed with the same priority as composition updates.
///
- public static readonly DispatcherPriority Composition = new(6);
-
+ public static readonly DispatcherPriority Composition = new(Render + 1);
+
///
- /// The job will be processed with the same priority as render.
+ /// The job will be processed with before composition updates.
+ ///
+ public static readonly DispatcherPriority PreComposition = new(Composition + 1);
+
+ ///
+ /// The job will be processed with the same priority as layout.
///
- public static readonly DispatcherPriority Layout = new(7);
+ public static readonly DispatcherPriority Layout = new(PreComposition + 1);
///
/// The job will be processed with the same priority as data binding.
@@ -80,7 +85,7 @@ namespace Avalonia.Threading
///
/// The job will be processed before other asynchronous operations.
///
- public static readonly DispatcherPriority Send = new(8);
+ public static readonly DispatcherPriority Send = new(Layout + 1);
///
/// Maximum possible priority
@@ -123,4 +128,4 @@ namespace Avalonia.Threading
///
public int CompareTo(DispatcherPriority other) => Value.CompareTo(other.Value);
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 29559a8618..2bb4a29b3c 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -292,6 +292,7 @@ namespace Avalonia
protected IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot);
internal CompositionDrawListVisual? CompositionVisual { get; private set; }
+ internal CompositionVisual? ChildCompositionVisual { get; set; }
public bool HasNonUniformZIndexChildren { get; private set; }
@@ -452,12 +453,15 @@ namespace Avalonia
}
}
+ private protected virtual CompositionDrawListVisual CreateCompositionVisual(Compositor compositor)
+ => new CompositionDrawListVisual(compositor,
+ new ServerCompositionDrawListVisual(compositor.Server, this), this);
+
internal CompositionVisual AttachToCompositor(Compositor compositor)
{
if (CompositionVisual == null || CompositionVisual.Compositor != compositor)
{
- CompositionVisual = new CompositionDrawListVisual(compositor,
- new ServerCompositionDrawListVisual(compositor.Server, this), this);
+ CompositionVisual = CreateCompositionVisual(compositor);
}
return CompositionVisual;
diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml
index e0e177da44..6dfcb2e74d 100644
--- a/src/Avalonia.Base/composition-schema.xml
+++ b/src/Avalonia.Base/composition-schema.xml
@@ -27,6 +27,9 @@
+