diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml b/samples/ControlCatalog/Pages/CompositionPage.axaml index 403f45b8eb..602b9b768d 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml @@ -2,44 +2,57 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pages="using:ControlCatalog.Pages" x:Class="ControlCatalog.Pages.CompositionPage"> - - Implicit animations + + + - - - - - - - - - - - - - - - - - - - - - - Resize me - - + + + + + + + + + + + + + + + + + + + + + Resize me + + - - - + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index c70675b606..8b12a2d663 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -1,28 +1,39 @@ using System; using System.Collections.Generic; +using System.Numerics; +using System.Threading; using Avalonia; +using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Animations; using Avalonia.VisualTree; +using Math = System.Math; namespace ControlCatalog.Pages; public partial class CompositionPage : UserControl { private ImplicitAnimationCollection? _implicitAnimations; + private CompositionCustomVisual? _customVisual; + private CompositionSolidColorVisual? _solidVisual; public CompositionPage() { AvaloniaXamlLoader.Load(this); + AttachAnimatedSolidVisual(this.FindControl("SolidVisualHost")!); + AttachCustomVisual(this.FindControl("CustomVisualHost")!); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); this.Get("Items").Items = CreateColorItems(); + } private static List CreateColorItems() @@ -126,6 +137,167 @@ public partial class CompositionPage : UserControl compositionVisual.ImplicitAnimations = page._implicitAnimations; } } + + void AttachAnimatedSolidVisual(Visual v) + { + void Update() + { + if(_solidVisual == null) + return; + _solidVisual.Size = new Vector2((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3); + _solidVisual.Offset = new Vector3((float)v.Bounds.Width / 3, (float)v.Bounds.Height / 3, 0); + } + v.AttachedToVisualTree += delegate + { + var compositor = ElementComposition.GetElementVisual(v)?.Compositor; + if(compositor == null || _solidVisual?.Compositor == compositor) + return; + _solidVisual = compositor.CreateSolidColorVisual(); + ElementComposition.SetElementChildVisual(v, _solidVisual); + _solidVisual.Color = Colors.Red; + var animation = _solidVisual.Compositor.CreateColorKeyFrameAnimation(); + animation.InsertKeyFrame(0, Colors.Red); + animation.InsertKeyFrame(0.5f, Colors.Blue); + animation.InsertKeyFrame(1, Colors.Green); + animation.Duration = TimeSpan.FromSeconds(5); + animation.IterationBehavior = AnimationIterationBehavior.Forever; + animation.Direction = PlaybackDirection.Alternate; + _solidVisual.StartAnimation("Color", animation); + + _solidVisual.AnchorPoint = new Vector2(0, 0); + + var scale = _solidVisual.Compositor.CreateVector3KeyFrameAnimation(); + scale.Duration = TimeSpan.FromSeconds(5); + scale.IterationBehavior = AnimationIterationBehavior.Forever; + scale.InsertKeyFrame(0, new Vector3(1, 1, 0)); + scale.InsertKeyFrame(0.5f, new Vector3(1.5f, 1.5f, 0)); + scale.InsertKeyFrame(1, new Vector3(1, 1, 0)); + + _solidVisual.StartAnimation("Scale", scale); + + var center = + _solidVisual.Compositor.CreateExpressionAnimation( + "Vector3(this.Target.Size.X * 0.5, this.Target.Size.Y * 0.5, 1)"); + _solidVisual.StartAnimation("CenterPoint", center); + Update(); + }; + v.PropertyChanged += (_, a) => + { + if (a.Property == BoundsProperty) + Update(); + }; + } + + void AttachCustomVisual(Visual v) + { + void Update() + { + if (_customVisual == null) + return; + var h = (float)Math.Min(v.Bounds.Height, v.Bounds.Width / 3); + _customVisual.Size = new Vector2((float)v.Bounds.Width, h); + _customVisual.Offset = new Vector3(0, (float)(v.Bounds.Height - h) / 2, 0); + } + v.AttachedToVisualTree += delegate + { + var compositor = ElementComposition.GetElementVisual(v)?.Compositor; + if(compositor == null || _customVisual?.Compositor == compositor) + return; + _customVisual = compositor.CreateCustomVisual(new CustomVisualHandler()); + ElementComposition.SetElementChildVisual(v, _customVisual); + _customVisual.SendHandlerMessage(CustomVisualHandler.StartMessage); + Update(); + }; + + v.PropertyChanged += (_, a) => + { + if (a.Property == BoundsProperty) + Update(); + }; + } + + class CustomVisualHandler : CompositionCustomVisualHandler + { + private TimeSpan _animationElapsed; + private TimeSpan? _lastServerTime; + private bool _running; + + public static readonly object StopMessage = new(), StartMessage = new(); + + public override void OnRender(ImmediateDrawingContext drawingContext) + { + if (_running) + { + if (_lastServerTime.HasValue) _animationElapsed += (CompositionNow - _lastServerTime.Value); + _lastServerTime = CompositionNow; + } + + const int cnt = 20; + var maxPointSizeX = EffectiveSize.X / (cnt * 1.6); + var maxPointSizeY = EffectiveSize.Y / 4; + var pointSize = Math.Min(maxPointSizeX, maxPointSizeY); + var animationLength = TimeSpan.FromSeconds(4); + var animationStage = _animationElapsed.TotalSeconds / animationLength.TotalSeconds; + + var sinOffset = Math.Cos(_animationElapsed.TotalSeconds) * 1.5; + + for (var c = 0; c < cnt; c++) + { + var stage = (animationStage + (double)c / cnt) % 1; + var colorStage = + (animationStage + (Math.Sin(_animationElapsed.TotalSeconds * 2) + 1) / 2 + (double)c / cnt) % 1; + var posX = (EffectiveSize.X + pointSize * 3) * stage - pointSize; + var posY = (EffectiveSize.Y - pointSize) * (1 + Math.Sin(stage * 3.14 * 3 + sinOffset)) / 2 + pointSize / 2; + var opacity = Math.Sin(stage * 3.14); + + + drawingContext.DrawEllipse(new ImmutableSolidColorBrush(Color.FromArgb( + 255, + (byte)(255 - 255 * colorStage), + (byte)(255 * Math.Abs(0.5 - colorStage) * 2), + (byte)(255 * colorStage) + ), opacity), null, + new Point(posX, posY), pointSize / 2, pointSize / 2); + } + + } + + public override void OnMessage(object message) + { + if (message == StartMessage) + { + _running = true; + _lastServerTime = null; + RegisterForNextAnimationFrameUpdate(); + } + else if (message == StopMessage) + _running = false; + } + + public override void OnAnimationFrameUpdate() + { + if (_running) + { + Invalidate(); + RegisterForNextAnimationFrameUpdate(); + } + } + } + + private void ButtonThreadSleep(object? sender, RoutedEventArgs e) + { + Thread.Sleep(10000); + } + + private void ButtonStartCustomVisual(object? sender, RoutedEventArgs e) + { + _customVisual?.SendHandlerMessage(CustomVisualHandler.StartMessage); + } + + private void ButtonStopCustomVisual(object? sender, RoutedEventArgs e) + { + _customVisual?.SendHandlerMessage(CustomVisualHandler.StopMessage); + } } public class CompositionPageColorItem diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index 8d531e9394..4138f1c891 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media /// Describes how an area is painted. /// [TypeConverter(typeof(BrushConverter))] - public abstract class Brush : Animatable, IMutableBrush + public abstract class Brush : Animatable { /// /// Defines the property. @@ -92,9 +92,6 @@ namespace Avalonia.Media throw new FormatException($"Invalid brush string: '{s}'."); } - /// - public abstract IBrush ToImmutable(); - /// /// Marks a property as affecting the brush's visual representation. /// diff --git a/src/Avalonia.Base/Media/BrushExtensions.cs b/src/Avalonia.Base/Media/BrushExtensions.cs index 2fc8778a5e..d0208c187a 100644 --- a/src/Avalonia.Base/Media/BrushExtensions.cs +++ b/src/Avalonia.Base/Media/BrushExtensions.cs @@ -16,11 +16,11 @@ namespace Avalonia.Media /// The result of calling if the brush is mutable, /// otherwise . /// - public static IBrush ToImmutable(this IBrush brush) + public static IImmutableBrush ToImmutable(this IBrush brush) { _ = brush ?? throw new ArgumentNullException(nameof(brush)); - return (brush as IMutableBrush)?.ToImmutable() ?? brush; + return (brush as IMutableBrush)?.ToImmutable() ?? (IImmutableBrush)brush; } /// diff --git a/src/Avalonia.Base/Media/Brushes.cs b/src/Avalonia.Base/Media/Brushes.cs index 5957775f39..7ecba6df3e 100644 --- a/src/Avalonia.Base/Media/Brushes.cs +++ b/src/Avalonia.Base/Media/Brushes.cs @@ -8,706 +8,706 @@ namespace Avalonia.Media /// /// Gets an colored brush. /// - public static ISolidColorBrush AliceBlue => KnownColor.AliceBlue.ToBrush(); + public static IImmutableSolidColorBrush AliceBlue => KnownColor.AliceBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush AntiqueWhite => KnownColor.AntiqueWhite.ToBrush(); + public static IImmutableSolidColorBrush AntiqueWhite => KnownColor.AntiqueWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Aqua => KnownColor.Aqua.ToBrush(); + public static IImmutableSolidColorBrush Aqua => KnownColor.Aqua.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Aquamarine => KnownColor.Aquamarine.ToBrush(); + public static IImmutableSolidColorBrush Aquamarine => KnownColor.Aquamarine.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Azure => KnownColor.Azure.ToBrush(); + public static IImmutableSolidColorBrush Azure => KnownColor.Azure.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Beige => KnownColor.Beige.ToBrush(); + public static IImmutableSolidColorBrush Beige => KnownColor.Beige.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Bisque => KnownColor.Bisque.ToBrush(); + public static IImmutableSolidColorBrush Bisque => KnownColor.Bisque.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Black => KnownColor.Black.ToBrush(); + public static IImmutableSolidColorBrush Black => KnownColor.Black.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BlanchedAlmond => KnownColor.BlanchedAlmond.ToBrush(); + public static IImmutableSolidColorBrush BlanchedAlmond => KnownColor.BlanchedAlmond.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Blue => KnownColor.Blue.ToBrush(); + public static IImmutableSolidColorBrush Blue => KnownColor.Blue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BlueViolet => KnownColor.BlueViolet.ToBrush(); + public static IImmutableSolidColorBrush BlueViolet => KnownColor.BlueViolet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Brown => KnownColor.Brown.ToBrush(); + public static IImmutableSolidColorBrush Brown => KnownColor.Brown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush BurlyWood => KnownColor.BurlyWood.ToBrush(); + public static IImmutableSolidColorBrush BurlyWood => KnownColor.BurlyWood.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush CadetBlue => KnownColor.CadetBlue.ToBrush(); + public static IImmutableSolidColorBrush CadetBlue => KnownColor.CadetBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Chartreuse => KnownColor.Chartreuse.ToBrush(); + public static IImmutableSolidColorBrush Chartreuse => KnownColor.Chartreuse.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Chocolate => KnownColor.Chocolate.ToBrush(); + public static IImmutableSolidColorBrush Chocolate => KnownColor.Chocolate.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Coral => KnownColor.Coral.ToBrush(); + public static IImmutableSolidColorBrush Coral => KnownColor.Coral.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush CornflowerBlue => KnownColor.CornflowerBlue.ToBrush(); + public static IImmutableSolidColorBrush CornflowerBlue => KnownColor.CornflowerBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Cornsilk => KnownColor.Cornsilk.ToBrush(); + public static IImmutableSolidColorBrush Cornsilk => KnownColor.Cornsilk.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Crimson => KnownColor.Crimson.ToBrush(); + public static IImmutableSolidColorBrush Crimson => KnownColor.Crimson.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Cyan => KnownColor.Cyan.ToBrush(); + public static IImmutableSolidColorBrush Cyan => KnownColor.Cyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkBlue => KnownColor.DarkBlue.ToBrush(); + public static IImmutableSolidColorBrush DarkBlue => KnownColor.DarkBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkCyan => KnownColor.DarkCyan.ToBrush(); + public static IImmutableSolidColorBrush DarkCyan => KnownColor.DarkCyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGoldenrod => KnownColor.DarkGoldenrod.ToBrush(); + public static IImmutableSolidColorBrush DarkGoldenrod => KnownColor.DarkGoldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGray => KnownColor.DarkGray.ToBrush(); + public static IImmutableSolidColorBrush DarkGray => KnownColor.DarkGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkGreen => KnownColor.DarkGreen.ToBrush(); + public static IImmutableSolidColorBrush DarkGreen => KnownColor.DarkGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkKhaki => KnownColor.DarkKhaki.ToBrush(); + public static IImmutableSolidColorBrush DarkKhaki => KnownColor.DarkKhaki.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkMagenta => KnownColor.DarkMagenta.ToBrush(); + public static IImmutableSolidColorBrush DarkMagenta => KnownColor.DarkMagenta.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOliveGreen => KnownColor.DarkOliveGreen.ToBrush(); + public static IImmutableSolidColorBrush DarkOliveGreen => KnownColor.DarkOliveGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOrange => KnownColor.DarkOrange.ToBrush(); + public static IImmutableSolidColorBrush DarkOrange => KnownColor.DarkOrange.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkOrchid => KnownColor.DarkOrchid.ToBrush(); + public static IImmutableSolidColorBrush DarkOrchid => KnownColor.DarkOrchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkRed => KnownColor.DarkRed.ToBrush(); + public static IImmutableSolidColorBrush DarkRed => KnownColor.DarkRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSalmon => KnownColor.DarkSalmon.ToBrush(); + public static IImmutableSolidColorBrush DarkSalmon => KnownColor.DarkSalmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSeaGreen => KnownColor.DarkSeaGreen.ToBrush(); + public static IImmutableSolidColorBrush DarkSeaGreen => KnownColor.DarkSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSlateBlue => KnownColor.DarkSlateBlue.ToBrush(); + public static IImmutableSolidColorBrush DarkSlateBlue => KnownColor.DarkSlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkSlateGray => KnownColor.DarkSlateGray.ToBrush(); + public static IImmutableSolidColorBrush DarkSlateGray => KnownColor.DarkSlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkTurquoise => KnownColor.DarkTurquoise.ToBrush(); + public static IImmutableSolidColorBrush DarkTurquoise => KnownColor.DarkTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DarkViolet => KnownColor.DarkViolet.ToBrush(); + public static IImmutableSolidColorBrush DarkViolet => KnownColor.DarkViolet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DeepPink => KnownColor.DeepPink.ToBrush(); + public static IImmutableSolidColorBrush DeepPink => KnownColor.DeepPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DeepSkyBlue => KnownColor.DeepSkyBlue.ToBrush(); + public static IImmutableSolidColorBrush DeepSkyBlue => KnownColor.DeepSkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DimGray => KnownColor.DimGray.ToBrush(); + public static IImmutableSolidColorBrush DimGray => KnownColor.DimGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush DodgerBlue => KnownColor.DodgerBlue.ToBrush(); + public static IImmutableSolidColorBrush DodgerBlue => KnownColor.DodgerBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Firebrick => KnownColor.Firebrick.ToBrush(); + public static IImmutableSolidColorBrush Firebrick => KnownColor.Firebrick.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush FloralWhite => KnownColor.FloralWhite.ToBrush(); + public static IImmutableSolidColorBrush FloralWhite => KnownColor.FloralWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush ForestGreen => KnownColor.ForestGreen.ToBrush(); + public static IImmutableSolidColorBrush ForestGreen => KnownColor.ForestGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Fuchsia => KnownColor.Fuchsia.ToBrush(); + public static IImmutableSolidColorBrush Fuchsia => KnownColor.Fuchsia.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gainsboro => KnownColor.Gainsboro.ToBrush(); + public static IImmutableSolidColorBrush Gainsboro => KnownColor.Gainsboro.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush GhostWhite => KnownColor.GhostWhite.ToBrush(); + public static IImmutableSolidColorBrush GhostWhite => KnownColor.GhostWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gold => KnownColor.Gold.ToBrush(); + public static IImmutableSolidColorBrush Gold => KnownColor.Gold.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Goldenrod => KnownColor.Goldenrod.ToBrush(); + public static IImmutableSolidColorBrush Goldenrod => KnownColor.Goldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Gray => KnownColor.Gray.ToBrush(); + public static IImmutableSolidColorBrush Gray => KnownColor.Gray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Green => KnownColor.Green.ToBrush(); + public static IImmutableSolidColorBrush Green => KnownColor.Green.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush GreenYellow => KnownColor.GreenYellow.ToBrush(); + public static IImmutableSolidColorBrush GreenYellow => KnownColor.GreenYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Honeydew => KnownColor.Honeydew.ToBrush(); + public static IImmutableSolidColorBrush Honeydew => KnownColor.Honeydew.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush HotPink => KnownColor.HotPink.ToBrush(); + public static IImmutableSolidColorBrush HotPink => KnownColor.HotPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush IndianRed => KnownColor.IndianRed.ToBrush(); + public static IImmutableSolidColorBrush IndianRed => KnownColor.IndianRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Indigo => KnownColor.Indigo.ToBrush(); + public static IImmutableSolidColorBrush Indigo => KnownColor.Indigo.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Ivory => KnownColor.Ivory.ToBrush(); + public static IImmutableSolidColorBrush Ivory => KnownColor.Ivory.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Khaki => KnownColor.Khaki.ToBrush(); + public static IImmutableSolidColorBrush Khaki => KnownColor.Khaki.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Lavender => KnownColor.Lavender.ToBrush(); + public static IImmutableSolidColorBrush Lavender => KnownColor.Lavender.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LavenderBlush => KnownColor.LavenderBlush.ToBrush(); + public static IImmutableSolidColorBrush LavenderBlush => KnownColor.LavenderBlush.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LawnGreen => KnownColor.LawnGreen.ToBrush(); + public static IImmutableSolidColorBrush LawnGreen => KnownColor.LawnGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LemonChiffon => KnownColor.LemonChiffon.ToBrush(); + public static IImmutableSolidColorBrush LemonChiffon => KnownColor.LemonChiffon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightBlue => KnownColor.LightBlue.ToBrush(); + public static IImmutableSolidColorBrush LightBlue => KnownColor.LightBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightCoral => KnownColor.LightCoral.ToBrush(); + public static IImmutableSolidColorBrush LightCoral => KnownColor.LightCoral.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightCyan => KnownColor.LightCyan.ToBrush(); + public static IImmutableSolidColorBrush LightCyan => KnownColor.LightCyan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToBrush(); + public static IImmutableSolidColorBrush LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGray => KnownColor.LightGray.ToBrush(); + public static IImmutableSolidColorBrush LightGray => KnownColor.LightGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightGreen => KnownColor.LightGreen.ToBrush(); + public static IImmutableSolidColorBrush LightGreen => KnownColor.LightGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightPink => KnownColor.LightPink.ToBrush(); + public static IImmutableSolidColorBrush LightPink => KnownColor.LightPink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSalmon => KnownColor.LightSalmon.ToBrush(); + public static IImmutableSolidColorBrush LightSalmon => KnownColor.LightSalmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSeaGreen => KnownColor.LightSeaGreen.ToBrush(); + public static IImmutableSolidColorBrush LightSeaGreen => KnownColor.LightSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSkyBlue => KnownColor.LightSkyBlue.ToBrush(); + public static IImmutableSolidColorBrush LightSkyBlue => KnownColor.LightSkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSlateGray => KnownColor.LightSlateGray.ToBrush(); + public static IImmutableSolidColorBrush LightSlateGray => KnownColor.LightSlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightSteelBlue => KnownColor.LightSteelBlue.ToBrush(); + public static IImmutableSolidColorBrush LightSteelBlue => KnownColor.LightSteelBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LightYellow => KnownColor.LightYellow.ToBrush(); + public static IImmutableSolidColorBrush LightYellow => KnownColor.LightYellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Lime => KnownColor.Lime.ToBrush(); + public static IImmutableSolidColorBrush Lime => KnownColor.Lime.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush LimeGreen => KnownColor.LimeGreen.ToBrush(); + public static IImmutableSolidColorBrush LimeGreen => KnownColor.LimeGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Linen => KnownColor.Linen.ToBrush(); + public static IImmutableSolidColorBrush Linen => KnownColor.Linen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Magenta => KnownColor.Magenta.ToBrush(); + public static IImmutableSolidColorBrush Magenta => KnownColor.Magenta.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Maroon => KnownColor.Maroon.ToBrush(); + public static IImmutableSolidColorBrush Maroon => KnownColor.Maroon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumAquamarine => KnownColor.MediumAquamarine.ToBrush(); + public static IImmutableSolidColorBrush MediumAquamarine => KnownColor.MediumAquamarine.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumBlue => KnownColor.MediumBlue.ToBrush(); + public static IImmutableSolidColorBrush MediumBlue => KnownColor.MediumBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumOrchid => KnownColor.MediumOrchid.ToBrush(); + public static IImmutableSolidColorBrush MediumOrchid => KnownColor.MediumOrchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumPurple => KnownColor.MediumPurple.ToBrush(); + public static IImmutableSolidColorBrush MediumPurple => KnownColor.MediumPurple.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSeaGreen => KnownColor.MediumSeaGreen.ToBrush(); + public static IImmutableSolidColorBrush MediumSeaGreen => KnownColor.MediumSeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSlateBlue => KnownColor.MediumSlateBlue.ToBrush(); + public static IImmutableSolidColorBrush MediumSlateBlue => KnownColor.MediumSlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumSpringGreen => KnownColor.MediumSpringGreen.ToBrush(); + public static IImmutableSolidColorBrush MediumSpringGreen => KnownColor.MediumSpringGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumTurquoise => KnownColor.MediumTurquoise.ToBrush(); + public static IImmutableSolidColorBrush MediumTurquoise => KnownColor.MediumTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MediumVioletRed => KnownColor.MediumVioletRed.ToBrush(); + public static IImmutableSolidColorBrush MediumVioletRed => KnownColor.MediumVioletRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MidnightBlue => KnownColor.MidnightBlue.ToBrush(); + public static IImmutableSolidColorBrush MidnightBlue => KnownColor.MidnightBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MintCream => KnownColor.MintCream.ToBrush(); + public static IImmutableSolidColorBrush MintCream => KnownColor.MintCream.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush MistyRose => KnownColor.MistyRose.ToBrush(); + public static IImmutableSolidColorBrush MistyRose => KnownColor.MistyRose.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Moccasin => KnownColor.Moccasin.ToBrush(); + public static IImmutableSolidColorBrush Moccasin => KnownColor.Moccasin.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush NavajoWhite => KnownColor.NavajoWhite.ToBrush(); + public static IImmutableSolidColorBrush NavajoWhite => KnownColor.NavajoWhite.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Navy => KnownColor.Navy.ToBrush(); + public static IImmutableSolidColorBrush Navy => KnownColor.Navy.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OldLace => KnownColor.OldLace.ToBrush(); + public static IImmutableSolidColorBrush OldLace => KnownColor.OldLace.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Olive => KnownColor.Olive.ToBrush(); + public static IImmutableSolidColorBrush Olive => KnownColor.Olive.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OliveDrab => KnownColor.OliveDrab.ToBrush(); + public static IImmutableSolidColorBrush OliveDrab => KnownColor.OliveDrab.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Orange => KnownColor.Orange.ToBrush(); + public static IImmutableSolidColorBrush Orange => KnownColor.Orange.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush OrangeRed => KnownColor.OrangeRed.ToBrush(); + public static IImmutableSolidColorBrush OrangeRed => KnownColor.OrangeRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Orchid => KnownColor.Orchid.ToBrush(); + public static IImmutableSolidColorBrush Orchid => KnownColor.Orchid.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleGoldenrod => KnownColor.PaleGoldenrod.ToBrush(); + public static IImmutableSolidColorBrush PaleGoldenrod => KnownColor.PaleGoldenrod.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleGreen => KnownColor.PaleGreen.ToBrush(); + public static IImmutableSolidColorBrush PaleGreen => KnownColor.PaleGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleTurquoise => KnownColor.PaleTurquoise.ToBrush(); + public static IImmutableSolidColorBrush PaleTurquoise => KnownColor.PaleTurquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PaleVioletRed => KnownColor.PaleVioletRed.ToBrush(); + public static IImmutableSolidColorBrush PaleVioletRed => KnownColor.PaleVioletRed.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PapayaWhip => KnownColor.PapayaWhip.ToBrush(); + public static IImmutableSolidColorBrush PapayaWhip => KnownColor.PapayaWhip.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PeachPuff => KnownColor.PeachPuff.ToBrush(); + public static IImmutableSolidColorBrush PeachPuff => KnownColor.PeachPuff.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Peru => KnownColor.Peru.ToBrush(); + public static IImmutableSolidColorBrush Peru => KnownColor.Peru.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Pink => KnownColor.Pink.ToBrush(); + public static IImmutableSolidColorBrush Pink => KnownColor.Pink.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Plum => KnownColor.Plum.ToBrush(); + public static IImmutableSolidColorBrush Plum => KnownColor.Plum.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush PowderBlue => KnownColor.PowderBlue.ToBrush(); + public static IImmutableSolidColorBrush PowderBlue => KnownColor.PowderBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Purple => KnownColor.Purple.ToBrush(); + public static IImmutableSolidColorBrush Purple => KnownColor.Purple.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Red => KnownColor.Red.ToBrush(); + public static IImmutableSolidColorBrush Red => KnownColor.Red.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush RosyBrown => KnownColor.RosyBrown.ToBrush(); + public static IImmutableSolidColorBrush RosyBrown => KnownColor.RosyBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush RoyalBlue => KnownColor.RoyalBlue.ToBrush(); + public static IImmutableSolidColorBrush RoyalBlue => KnownColor.RoyalBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SaddleBrown => KnownColor.SaddleBrown.ToBrush(); + public static IImmutableSolidColorBrush SaddleBrown => KnownColor.SaddleBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Salmon => KnownColor.Salmon.ToBrush(); + public static IImmutableSolidColorBrush Salmon => KnownColor.Salmon.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SandyBrown => KnownColor.SandyBrown.ToBrush(); + public static IImmutableSolidColorBrush SandyBrown => KnownColor.SandyBrown.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SeaGreen => KnownColor.SeaGreen.ToBrush(); + public static IImmutableSolidColorBrush SeaGreen => KnownColor.SeaGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SeaShell => KnownColor.SeaShell.ToBrush(); + public static IImmutableSolidColorBrush SeaShell => KnownColor.SeaShell.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Sienna => KnownColor.Sienna.ToBrush(); + public static IImmutableSolidColorBrush Sienna => KnownColor.Sienna.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Silver => KnownColor.Silver.ToBrush(); + public static IImmutableSolidColorBrush Silver => KnownColor.Silver.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SkyBlue => KnownColor.SkyBlue.ToBrush(); + public static IImmutableSolidColorBrush SkyBlue => KnownColor.SkyBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SlateBlue => KnownColor.SlateBlue.ToBrush(); + public static IImmutableSolidColorBrush SlateBlue => KnownColor.SlateBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SlateGray => KnownColor.SlateGray.ToBrush(); + public static IImmutableSolidColorBrush SlateGray => KnownColor.SlateGray.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Snow => KnownColor.Snow.ToBrush(); + public static IImmutableSolidColorBrush Snow => KnownColor.Snow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SpringGreen => KnownColor.SpringGreen.ToBrush(); + public static IImmutableSolidColorBrush SpringGreen => KnownColor.SpringGreen.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush SteelBlue => KnownColor.SteelBlue.ToBrush(); + public static IImmutableSolidColorBrush SteelBlue => KnownColor.SteelBlue.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Tan => KnownColor.Tan.ToBrush(); + public static IImmutableSolidColorBrush Tan => KnownColor.Tan.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Teal => KnownColor.Teal.ToBrush(); + public static IImmutableSolidColorBrush Teal => KnownColor.Teal.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Thistle => KnownColor.Thistle.ToBrush(); + public static IImmutableSolidColorBrush Thistle => KnownColor.Thistle.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Tomato => KnownColor.Tomato.ToBrush(); + public static IImmutableSolidColorBrush Tomato => KnownColor.Tomato.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Transparent => KnownColor.Transparent.ToBrush(); + public static IImmutableSolidColorBrush Transparent => KnownColor.Transparent.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Turquoise => KnownColor.Turquoise.ToBrush(); + public static IImmutableSolidColorBrush Turquoise => KnownColor.Turquoise.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Violet => KnownColor.Violet.ToBrush(); + public static IImmutableSolidColorBrush Violet => KnownColor.Violet.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Wheat => KnownColor.Wheat.ToBrush(); + public static IImmutableSolidColorBrush Wheat => KnownColor.Wheat.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush White => KnownColor.White.ToBrush(); + public static IImmutableSolidColorBrush White => KnownColor.White.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush WhiteSmoke => KnownColor.WhiteSmoke.ToBrush(); + public static IImmutableSolidColorBrush WhiteSmoke => KnownColor.WhiteSmoke.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush Yellow => KnownColor.Yellow.ToBrush(); + public static IImmutableSolidColorBrush Yellow => KnownColor.Yellow.ToBrush(); /// /// Gets an colored brush. /// - public static ISolidColorBrush YellowGreen => KnownColor.YellowGreen.ToBrush(); + public static IImmutableSolidColorBrush YellowGreen => KnownColor.YellowGreen.ToBrush(); } } diff --git a/src/Avalonia.Base/Media/ConicGradientBrush.cs b/src/Avalonia.Base/Media/ConicGradientBrush.cs index 4b50019ddc..bce7a5af19 100644 --- a/src/Avalonia.Base/Media/ConicGradientBrush.cs +++ b/src/Avalonia.Base/Media/ConicGradientBrush.cs @@ -47,7 +47,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public override IImmutableBrush ToImmutable() { return new ImmutableConicGradientBrush(this); } diff --git a/src/Avalonia.Base/Media/GradientBrush.cs b/src/Avalonia.Base/Media/GradientBrush.cs index c84413ecbb..83cdaa1694 100644 --- a/src/Avalonia.Base/Media/GradientBrush.cs +++ b/src/Avalonia.Base/Media/GradientBrush.cs @@ -12,7 +12,7 @@ namespace Avalonia.Media /// /// Base class for brushes that draw with a gradient. /// - public abstract class GradientBrush : Brush, IGradientBrush + public abstract class GradientBrush : Brush, IGradientBrush, IMutableBrush { /// /// Defines the property. @@ -92,5 +92,7 @@ namespace Avalonia.Media { RaiseInvalidated(EventArgs.Empty); } + + public abstract IImmutableBrush ToImmutable(); } } diff --git a/src/Avalonia.Base/Media/IImmutableBrush.cs b/src/Avalonia.Base/Media/IImmutableBrush.cs new file mode 100644 index 0000000000..5781b0117a --- /dev/null +++ b/src/Avalonia.Base/Media/IImmutableBrush.cs @@ -0,0 +1,9 @@ +namespace Avalonia.Media; + +/// +/// Represents an immutable brush which can be safely used with various threading contexts +/// +public interface IImmutableBrush : IBrush +{ + +} \ No newline at end of file diff --git a/src/Avalonia.Base/Media/IMutableBrush.cs b/src/Avalonia.Base/Media/IMutableBrush.cs index fef124ba36..f128aba4df 100644 --- a/src/Avalonia.Base/Media/IMutableBrush.cs +++ b/src/Avalonia.Base/Media/IMutableBrush.cs @@ -7,12 +7,12 @@ namespace Avalonia.Media /// Represents a mutable brush which can return an immutable clone of itself. /// [NotClientImplementable] - public interface IMutableBrush : IBrush, IAffectsRender + internal interface IMutableBrush : IBrush, IAffectsRender { /// /// Creates an immutable clone of the brush. /// /// The immutable clone. - IBrush ToImmutable(); + internal IImmutableBrush ToImmutable(); } } diff --git a/src/Avalonia.Base/Media/ISolidColorBrush.cs b/src/Avalonia.Base/Media/ISolidColorBrush.cs index 29e11210f1..f58768ef09 100644 --- a/src/Avalonia.Base/Media/ISolidColorBrush.cs +++ b/src/Avalonia.Base/Media/ISolidColorBrush.cs @@ -13,4 +13,13 @@ namespace Avalonia.Media /// Color Color { get; } } + + /// + /// Fills an area with a solid color. + /// + [NotClientImplementable] + public interface IImmutableSolidColorBrush : ISolidColorBrush, IImmutableBrush + { + + } } diff --git a/src/Avalonia.Base/Media/ImageBrush.cs b/src/Avalonia.Base/Media/ImageBrush.cs index 19dcf00901..2f2a0fb627 100644 --- a/src/Avalonia.Base/Media/ImageBrush.cs +++ b/src/Avalonia.Base/Media/ImageBrush.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media /// /// Paints an area with an . /// - public class ImageBrush : TileBrush, IImageBrush + public class ImageBrush : TileBrush, IImageBrush, IMutableBrush { /// /// Defines the property. @@ -45,7 +45,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public IImmutableBrush ToImmutable() { return new ImmutableImageBrush(this); } diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs new file mode 100644 index 0000000000..1e1a73437d --- /dev/null +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; +using Avalonia.Threading; +using Avalonia.Utilities; +using Avalonia.Media.Imaging; +using Avalonia.Media.Immutable; + +namespace Avalonia.Media +{ + public sealed class ImmediateDrawingContext : IDisposable, IOptionalFeatureProvider + { + private readonly bool _ownsImpl; + private int _currentLevel; + + private static ThreadSafeObjectPool> StateStackPool { get; } = + ThreadSafeObjectPool>.Default; + + private static ThreadSafeObjectPool> TransformStackPool { get; } = + ThreadSafeObjectPool>.Default; + + private Stack? _states = StateStackPool.Get(); + + private Stack? _transformContainers = TransformStackPool.Get(); + + readonly struct TransformContainer + { + public readonly Matrix LocalTransform; + public readonly Matrix ContainerTransform; + + public TransformContainer(Matrix localTransform, Matrix containerTransform) + { + LocalTransform = localTransform; + ContainerTransform = containerTransform; + } + } + + internal ImmediateDrawingContext(IDrawingContextImpl impl, bool ownsImpl) + { + _ownsImpl = ownsImpl; + PlatformImpl = impl; + _currentContainerTransform = impl.Transform; + } + + public IDrawingContextImpl PlatformImpl { get; } + + private Matrix _currentTransform = Matrix.Identity; + + private Matrix _currentContainerTransform; + + /// + /// Gets the current transform of the drawing context. + /// + public Matrix CurrentTransform + { + get { return _currentTransform; } + private set + { + _currentTransform = value; + var transform = _currentTransform * _currentContainerTransform; + PlatformImpl.Transform = transform; + } + } + + /// + /// Draws an bitmap. + /// + /// The bitmap. + /// The rect in the output to draw to. + public void DrawBitmap(IBitmap source, Rect rect) + { + _ = source ?? throw new ArgumentNullException(nameof(source)); + DrawBitmap(source, new Rect(source.Size), rect); + } + + /// + /// Draws an image. + /// + /// The bitmap. + /// The rect in the image to draw. + /// The rect in the output to draw to. + /// The bitmap interpolation mode. + public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default) + { + _ = source ?? throw new ArgumentNullException(nameof(source)); + PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect, bitmapInterpolationMode); + } + + /// + /// Draws a line. + /// + /// The stroke pen. + /// The first point of the line. + /// The second point of the line. + public void DrawLine(ImmutablePen pen, Point p1, Point p2) + { + if (PenIsVisible(pen)) + { + PlatformImpl.DrawLine(pen, p1, p2); + } + } + + /// + /// Draws a rectangle with the specified Brush and Pen. + /// + /// The brush used to fill the rectangle, or null for no fill. + /// The pen used to stroke the rectangle, or null for no stroke. + /// The rectangle bounds. + /// The radius in the X dimension of the rounded corners. + /// This value will be clamped to the range of 0 to Width/2 + /// + /// The radius in the Y dimension of the rounded corners. + /// This value will be clamped to the range of 0 to Height/2 + /// + /// Box shadow effect parameters + /// + /// The brush and the pen can both be null. If the brush is null, then no fill is performed. + /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. + /// + public void DrawRectangle(IImmutableBrush? brush, ImmutablePen? pen, Rect rect, double radiusX = 0, double radiusY = 0, + BoxShadows boxShadows = default) + { + if (brush == null && !PenIsVisible(pen)) + { + return; + } + + if (!MathUtilities.IsZero(radiusX)) + { + radiusX = Math.Min(radiusX, rect.Width / 2); + } + + if (!MathUtilities.IsZero(radiusY)) + { + radiusY = Math.Min(radiusY, rect.Height / 2); + } + + PlatformImpl.DrawRectangle(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows); + } + + /// + /// Draws the outline of a rectangle. + /// + /// The pen. + /// The rectangle bounds. + /// The corner radius. + public void DrawRectangle(ImmutablePen pen, Rect rect, float cornerRadius = 0.0f) + { + DrawRectangle(null, pen, rect, cornerRadius, cornerRadius); + } + + /// + /// Draws an ellipse with the specified Brush and Pen. + /// + /// The brush used to fill the ellipse, or null for no fill. + /// The pen used to stroke the ellipse, or null for no stroke. + /// The location of the center of the ellipse. + /// The horizontal radius of the ellipse. + /// The vertical radius of the ellipse. + /// + /// The brush and the pen can both be null. If the brush is null, then no fill is performed. + /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. + /// + public void DrawEllipse(IImmutableBrush? brush, ImmutablePen? pen, Point center, double radiusX, double radiusY) + { + if (brush == null && !PenIsVisible(pen)) + { + return; + } + + var originX = center.X - radiusX; + var originY = center.Y - radiusY; + var width = radiusX * 2; + var height = radiusY * 2; + + PlatformImpl.DrawEllipse(brush, pen, new Rect(originX, originY, width, height)); + } + + /// + /// Draws a glyph run. + /// + /// The foreground brush. + /// The glyph run. + public void DrawGlyphRun(IImmutableBrush foreground, GlyphRun glyphRun) + { + _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); + + PlatformImpl.DrawGlyphRun(foreground, glyphRun); + } + + /// + /// Draws a filled rectangle. + /// + /// The brush. + /// The rectangle bounds. + /// The corner radius. + public void FillRectangle(IImmutableBrush brush, Rect rect, float cornerRadius = 0.0f) + { + DrawRectangle(brush, null, rect, cornerRadius, cornerRadius); + } + + public readonly record struct PushedState : IDisposable + { + private readonly int _level; + private readonly ImmediateDrawingContext _context; + private readonly Matrix _matrix; + private readonly PushedStateType _type; + + public enum PushedStateType + { + None, + Matrix, + Opacity, + Clip, + MatrixContainer, + GeometryClip, + OpacityMask, + } + + internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) + { + if (context._states is null) + throw new ObjectDisposedException(nameof(ImmediateDrawingContext)); + + _context = context; + _type = type; + _matrix = matrix; + _level = context._currentLevel += 1; + context._states.Push(this); + } + + public void Dispose() + { + if (_type == PushedStateType.None) + return; + if (_context._states is null || _context._transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); + if (_context._currentLevel != _level) + throw new InvalidOperationException("Wrong Push/Pop state order"); + _context._currentLevel--; + _context._states.Pop(); + if (_type == PushedStateType.Matrix) + _context.CurrentTransform = _matrix; + else if (_type == PushedStateType.Clip) + _context.PlatformImpl.PopClip(); + else if (_type == PushedStateType.Opacity) + _context.PlatformImpl.PopOpacity(); + else if (_type == PushedStateType.GeometryClip) + _context.PlatformImpl.PopGeometryClip(); + else if (_type == PushedStateType.OpacityMask) + _context.PlatformImpl.PopOpacityMask(); + else if (_type == PushedStateType.MatrixContainer) + { + var cont = _context._transformContainers.Pop(); + _context._currentContainerTransform = cont.ContainerTransform; + _context.CurrentTransform = cont.LocalTransform; + } + } + } + + + public PushedState PushClip(RoundedRect clip) + { + PlatformImpl.PushClip(clip); + return new PushedState(this, PushedState.PushedStateType.Clip); + } + + /// + /// Pushes a clip rectangle. + /// + /// The clip rectangle. + /// A disposable used to undo the clip rectangle. + public PushedState PushClip(Rect clip) + { + PlatformImpl.PushClip(clip); + return new PushedState(this, PushedState.PushedStateType.Clip); + } + + /// + /// Pushes an opacity value. + /// + /// The opacity. + /// A disposable used to undo the opacity. + public PushedState PushOpacity(double opacity) + //TODO: Eliminate platform-specific push opacity call + { + PlatformImpl.PushOpacity(opacity); + return new PushedState(this, PushedState.PushedStateType.Opacity); + } + + /// + /// Pushes an opacity mask. + /// + /// The opacity mask. + /// + /// The size of the brush's target area. TODO: Are we sure this is needed? + /// + /// A disposable to undo the opacity mask. + public PushedState PushOpacityMask(IImmutableBrush mask, Rect bounds) + { + PlatformImpl.PushOpacityMask(mask, bounds); + return new PushedState(this, PushedState.PushedStateType.OpacityMask); + } + + /// + /// Pushes a matrix post-transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform * matrix); + + /// + /// Pushes a matrix pre-transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix * CurrentTransform); + + /// + /// Sets the current matrix transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + public PushedState PushSetTransform(Matrix matrix) + { + var oldMatrix = CurrentTransform; + CurrentTransform = matrix; + + return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); + } + + /// + /// Pushes a new transform context. + /// + /// A disposable used to undo the transformation. + public PushedState PushTransformContainer() + { + if (_transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); + _transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform)); + _currentContainerTransform = CurrentTransform * _currentContainerTransform; + _currentTransform = Matrix.Identity; + return new PushedState(this, PushedState.PushedStateType.MatrixContainer); + } + + /// + /// Disposes of any resources held by the . + /// + public void Dispose() + { + if (_states is null || _transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); + while (_states.Count != 0) + _states.Peek().Dispose(); + StateStackPool.Return(_states); + _states = null; + if (_transformContainers.Count != 0) + throw new InvalidOperationException("Transform container stack is non-empty"); + TransformStackPool.Return(_transformContainers); + _transformContainers = null; + if (_ownsImpl) + PlatformImpl.Dispose(); + } + + private static bool PenIsVisible(IPen? pen) + { + return pen?.Brush != null && pen.Thickness > 0; + } + + public object? TryGetFeature(Type type) => PlatformImpl.GetFeature(type); + } +} diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs index 1e95acbf22..c86d86d20a 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableGradientBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// A brush that draws with a gradient. /// - public abstract class ImmutableGradientBrush : IGradientBrush + public abstract class ImmutableGradientBrush : IGradientBrush, IImmutableBrush { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs index 8a53eaf7b8..ef4468fccf 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutablePen.cs @@ -38,15 +38,13 @@ namespace Avalonia.Media.Immutable /// The line join. /// The miter limit. public ImmutablePen( - IBrush? brush, + IImmutableBrush? brush, double thickness = 1.0, ImmutableDashStyle? dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) { - Debug.Assert(!(brush is IMutableBrush)); - Brush = brush; Thickness = thickness; LineCap = lineCap; diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs index 6755dfd236..4e623f02c5 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableSolidColorBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// Fills an area with a solid color. /// - public class ImmutableSolidColorBrush : ISolidColorBrush, IEquatable + public class ImmutableSolidColorBrush : IImmutableSolidColorBrush, IEquatable { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs index 6df27872fc..1ee52365e0 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable /// /// A brush which displays a repeating image. /// - public abstract class ImmutableTileBrush : ITileBrush + public abstract class ImmutableTileBrush : ITileBrush, IImmutableBrush { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Media/KnownColors.cs b/src/Avalonia.Base/Media/KnownColors.cs index ae2fca5a60..64cf1ef2f1 100644 --- a/src/Avalonia.Base/Media/KnownColors.cs +++ b/src/Avalonia.Base/Media/KnownColors.cs @@ -10,7 +10,7 @@ namespace Avalonia.Media private static readonly IReadOnlyDictionary _knownColorNames; private static readonly IReadOnlyDictionary _knownColors; #if !BUILDTASK - private static readonly Dictionary _knownBrushes; + private static readonly Dictionary _knownBrushes; #endif [GenerateEnumValueDictionary()] @@ -39,7 +39,7 @@ namespace Avalonia.Media _knownColors = knownColors; #if !BUILDTASK - _knownBrushes = new Dictionary(); + _knownBrushes = new (); #endif } @@ -72,7 +72,7 @@ namespace Avalonia.Media } #if !BUILDTASK - public static ISolidColorBrush ToBrush(this KnownColor color) + public static IImmutableSolidColorBrush ToBrush(this KnownColor color) { lock (_knownBrushes) { diff --git a/src/Avalonia.Base/Media/LinearGradientBrush.cs b/src/Avalonia.Base/Media/LinearGradientBrush.cs index a51adf0949..9b9ce5e4b7 100644 --- a/src/Avalonia.Base/Media/LinearGradientBrush.cs +++ b/src/Avalonia.Base/Media/LinearGradientBrush.cs @@ -47,7 +47,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public override IImmutableBrush ToImmutable() { return new ImmutableLinearGradientBrush(this); } diff --git a/src/Avalonia.Base/Media/RadialGradientBrush.cs b/src/Avalonia.Base/Media/RadialGradientBrush.cs index 16c367c0d0..f803051676 100644 --- a/src/Avalonia.Base/Media/RadialGradientBrush.cs +++ b/src/Avalonia.Base/Media/RadialGradientBrush.cs @@ -67,7 +67,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public override IImmutableBrush ToImmutable() { return new ImmutableRadialGradientBrush(this); } diff --git a/src/Avalonia.Base/Media/SolidColorBrush.cs b/src/Avalonia.Base/Media/SolidColorBrush.cs index 962819a1a1..d8e25fe748 100644 --- a/src/Avalonia.Base/Media/SolidColorBrush.cs +++ b/src/Avalonia.Base/Media/SolidColorBrush.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media /// /// Fills an area with a solid color. /// - public class SolidColorBrush : Brush, ISolidColorBrush + public class SolidColorBrush : Brush, ISolidColorBrush, IMutableBrush { /// /// Defines the property. @@ -80,7 +80,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + public IImmutableBrush ToImmutable() { return new ImmutableSolidColorBrush(this); } diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index 8dce79cf11..1261d233ac 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media /// /// Paints an area with an . /// - public class VisualBrush : TileBrush, IVisualBrush + public class VisualBrush : TileBrush, IVisualBrush, IMutableBrush { /// /// Defines the property. @@ -45,7 +45,7 @@ namespace Avalonia.Media } /// - public override IBrush ToImmutable() + IImmutableBrush IMutableBrush.ToImmutable() { return new ImmutableVisualBrush(this); } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs index 889c1d34c8..80b6bb9d2a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/AnimationInstanceBase.cs @@ -80,4 +80,6 @@ internal abstract class AnimationInstanceBase : IAnimationInstance _invalidated = true; TargetObject.NotifyAnimatedValueChanged(Property); } + + public void OnTick() => Invalidate(); } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs index 8ec4ec19bc..b14d2c0b19 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/IAnimationInstance.cs @@ -6,7 +6,7 @@ using Avalonia.Rendering.Composition.Server; namespace Avalonia.Rendering.Composition.Animations { - internal interface IAnimationInstance + internal interface IAnimationInstance : IServerClockItem { ServerObject TargetObject { get; } ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs new file mode 100644 index 0000000000..f816231781 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisual.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Numerics; +using Avalonia.Rendering.Composition.Server; +using Avalonia.Rendering.Composition.Transport; + +namespace Avalonia.Rendering.Composition; + +public class CompositionCustomVisual : CompositionContainerVisual +{ + private List? _messages; + + internal CompositionCustomVisual(Compositor compositor, CompositionCustomVisualHandler handler) + : base(compositor, new ServerCompositionCustomVisual(compositor.Server, handler)) + { + + } + + public void SendHandlerMessage(object message) + { + (_messages ??= new()).Add(message); + RegisterForSerialization(); + } + + private protected override void SerializeChangesCore(BatchStreamWriter writer) + { + base.SerializeChangesCore(writer); + if (_messages == null || _messages.Count == 0) + writer.Write(0); + else + { + writer.Write(_messages.Count); + foreach (var m in _messages) + writer.WriteObject(m); + _messages.Clear(); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs new file mode 100644 index 0000000000..b7ed6fe612 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/CompositionCustomVisualHandler.cs @@ -0,0 +1,65 @@ +using System; +using System.Numerics; +using Avalonia.Media; +using Avalonia.Rendering.Composition.Server; + +namespace Avalonia.Rendering.Composition; + +public abstract class CompositionCustomVisualHandler +{ + private ServerCompositionCustomVisual? _host; + + public virtual void OnMessage(object message) + { + + } + + public virtual void OnAnimationFrameUpdate() + { + + } + + public abstract void OnRender(ImmediateDrawingContext drawingContext); + + void VerifyAccess() + { + if (_host == null) + throw new InvalidOperationException("Object is not yet attached to the compositor"); + _host.Compositor.VerifyAccess(); + } + + protected Vector2 EffectiveSize + { + get + { + VerifyAccess(); + return _host!.Size; + } + } + + protected TimeSpan CompositionNow + { + get + { + VerifyAccess(); + return _host!.Compositor.ServerNow; + } + } + + public virtual Rect GetRenderBounds() => + new(0, 0, EffectiveSize.X, EffectiveSize.Y); + + internal void Attach(ServerCompositionCustomVisual visual) => _host = visual; + + protected void Invalidate() + { + VerifyAccess(); + _host!.HandlerInvalidate(); + } + + protected void RegisterForNextAnimationFrameUpdate() + { + VerifyAccess(); + _host!.HandlerRegisterForNextAnimationFrameUpdate(); + } +} diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs index 4ce87b67a5..2b1b3f461f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs @@ -33,4 +33,6 @@ public partial class Compositor public CompositionSolidColorVisual CreateSolidColorVisual() => new(this, new ServerCompositionSolidColorVisual(Server)); + + public CompositionCustomVisual CreateCustomVisual(CompositionCustomVisualHandler handler) => new(this, handler); } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/IServerClockItem.cs b/src/Avalonia.Base/Rendering/Composition/Server/IServerClockItem.cs new file mode 100644 index 0000000000..d13f1158c8 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/IServerClockItem.cs @@ -0,0 +1,6 @@ +namespace Avalonia.Rendering.Composition.Server; + +internal interface IServerClockItem +{ + void OnTick(); +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 0fc5cecef3..b172430fbb 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -87,8 +87,6 @@ namespace Avalonia.Rendering.Composition.Server } _renderTarget ??= _compositor.CreateRenderTarget(_surfaces()); - - Compositor.UpdateServerTime(); if(_dirtyRect.IsDefault && !_redrawRequested) return; diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 387998f8d6..0e15cbd54b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -216,13 +216,29 @@ namespace Avalonia.Rendering.Composition.Server 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() diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs index 041a3dd6af..e7405995f5 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Threading; using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Rendering.Composition.Animations; @@ -26,11 +27,12 @@ namespace Avalonia.Rendering.Composition.Server public Stopwatch Clock { get; } = Stopwatch.StartNew(); public TimeSpan ServerNow { get; private set; } private List _activeTargets = new(); - private HashSet _activeAnimations = new(); - private List _animationsToUpdate = new(); + private HashSet _clockItems = new(); + private List _clockItemsToUpdate = new(); internal BatchStreamObjectPool BatchObjectPool; internal BatchStreamMemoryPool BatchMemoryPool; private object _lock = new object(); + private Thread? _safeThread; public PlatformRenderInterfaceContextManager RenderInterface { get; } internal static readonly object RenderThreadJobsStartMarker = new(); internal static readonly object RenderThreadJobsEndMarker = new(); @@ -129,22 +131,31 @@ namespace Avalonia.Rendering.Composition.Server { lock (_lock) { - RenderCore(); + try + { + _safeThread = Thread.CurrentThread; + RenderCore(); + } + finally + { + _safeThread = null; + } } } private void RenderCore() { + UpdateServerTime(); ApplyPendingBatches(); CompletePendingBatches(); - foreach(var animation in _activeAnimations) - _animationsToUpdate.Add(animation); - - foreach(var animation in _animationsToUpdate) - animation.Invalidate(); + foreach(var animation in _clockItems) + _clockItemsToUpdate.Add(animation); + + foreach (var animation in _clockItemsToUpdate) + animation.OnTick(); - _animationsToUpdate.Clear(); + _clockItemsToUpdate.Clear(); try { @@ -168,16 +179,23 @@ namespace Avalonia.Rendering.Composition.Server _activeTargets.Remove(target); } - public void AddToClock(IAnimationInstance animationInstance) => - _activeAnimations.Add(animationInstance); + public void AddToClock(IServerClockItem item) => + _clockItems.Add(item); - public void RemoveFromClock(IAnimationInstance animationInstance) => - _activeAnimations.Remove(animationInstance); + public void RemoveFromClock(IServerClockItem item) => + _clockItems.Remove(item); public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { using (RenderInterface.EnsureCurrent()) return RenderInterface.CreateRenderTarget(surfaces); } + + public bool CheckAccess() => _safeThread == Thread.CurrentThread; + public void VerifyAccess() + { + if (!CheckAccess()) + throw new InvalidOperationException("This object can be only accessed under compositor lock"); + } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs new file mode 100644 index 0000000000..227df87a87 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs @@ -0,0 +1,82 @@ +using System; +using System.Numerics; +using Avalonia.Logging; +using Avalonia.Media; +using Avalonia.Rendering.Composition.Transport; + +namespace Avalonia.Rendering.Composition.Server; + +internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, IServerClockItem +{ + private readonly CompositionCustomVisualHandler _handler; + private bool _wantsNextAnimationFrameAfterTick; + internal ServerCompositionCustomVisual(ServerCompositor compositor, CompositionCustomVisualHandler handler) : base(compositor) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + _handler.Attach(this); + } + + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + { + base.DeserializeChangesCore(reader, commitedAt); + var count = reader.Read(); + for (var c = 0; c < count; c++) + { + try + { + _handler.OnMessage(reader.ReadObject()!); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual) + ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnMessage)} {{0}}", e); + } + } + } + + public void OnTick() + { + _wantsNextAnimationFrameAfterTick = false; + _handler.OnAnimationFrameUpdate(); + if (!_wantsNextAnimationFrameAfterTick) + Compositor.RemoveFromClock(this); + } + + public override Rect OwnContentBounds => _handler.GetRenderBounds(); + + protected override void OnAttachedToRoot(ServerCompositionTarget target) + { + if (_wantsNextAnimationFrameAfterTick) + Compositor.AddToClock(this); + base.OnAttachedToRoot(target); + } + + protected override void OnDetachedFromRoot(ServerCompositionTarget target) + { + Compositor.RemoveFromClock(this); + base.OnDetachedFromRoot(target); + } + + internal void HandlerInvalidate() => ValuesInvalidated(); + + internal void HandlerRegisterForNextAnimationFrameUpdate() + { + _wantsNextAnimationFrameAfterTick = true; + if (Root != null) + Compositor.AddToClock(this); + } + + protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) + { + using var context = new ImmediateDrawingContext(canvas, false); + try + { + _handler.OnRender(context); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual) + ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnRender)} {{0}}", e); + } + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs index 6981dc39e3..93e9f4d740 100644 --- a/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/VisualCollection.cs @@ -69,7 +69,7 @@ namespace Avalonia.Rendering.Composition { if (item.Parent != null) throw new InvalidOperationException("Visual already has a parent"); - item.Parent = item; + item.Parent = _owner; } } } diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index e3dc4fbb75..ddaea01b05 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -540,6 +540,9 @@ namespace Avalonia OnDetachedFromVisualTree(e); if (CompositionVisual != null) { + if (ChildCompositionVisual != null) + CompositionVisual.Children.Remove(ChildCompositionVisual); + CompositionVisual.DrawList = null; CompositionVisual = null; }