89 changed files with 7106 additions and 16 deletions
@ -0,0 +1,40 @@ |
|||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
internal struct AnimatedValueStore<T> where T : struct |
||||
|
{ |
||||
|
private T _direct; |
||||
|
private IAnimationInstance _animation; |
||||
|
private T? _lastAnimated; |
||||
|
|
||||
|
public T Direct => _direct; |
||||
|
|
||||
|
public T GetAnimated(ServerCompositor compositor) |
||||
|
{ |
||||
|
if (_animation == null) |
||||
|
return _direct; |
||||
|
var v = _animation.Evaluate(compositor.ServerNow, ExpressionVariant.Create(_direct)) |
||||
|
.CastOrDefault<T>(); |
||||
|
_lastAnimated = v; |
||||
|
return v; |
||||
|
} |
||||
|
|
||||
|
private T LastAnimated => _animation != null ? _lastAnimated ?? _direct : _direct; |
||||
|
|
||||
|
public bool IsAnimation => _animation != null; |
||||
|
|
||||
|
public void SetAnimation(ChangeSet cs, IAnimationInstance animation) |
||||
|
{ |
||||
|
_animation = animation; |
||||
|
_animation.Start(cs.Batch.CommitedAt, ExpressionVariant.Create(LastAnimated)); |
||||
|
} |
||||
|
|
||||
|
public static implicit operator AnimatedValueStore<T>(T value) => new AnimatedValueStore<T>() |
||||
|
{ |
||||
|
_direct = value |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
// ReSharper disable InconsistentNaming
|
||||
|
// ReSharper disable CheckNamespace
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase |
||||
|
{ |
||||
|
private readonly CompositionPropertySet _propertySet; |
||||
|
internal CompositionAnimation(Compositor compositor) : base(compositor, null!) |
||||
|
{ |
||||
|
_propertySet = new CompositionPropertySet(compositor); |
||||
|
} |
||||
|
|
||||
|
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException(); |
||||
|
|
||||
|
public void ClearAllParameters() => _propertySet.ClearAll(); |
||||
|
|
||||
|
public void ClearParameter(string key) => _propertySet.Clear(key); |
||||
|
|
||||
|
void SetVariant(string key, ExpressionVariant value) => _propertySet.Set(key, value); |
||||
|
|
||||
|
public void SetColorParameter(string key, Avalonia.Media.Color value) => SetVariant(key, value); |
||||
|
|
||||
|
public void SetMatrix3x2Parameter(string key, Matrix3x2 value) => SetVariant(key, value); |
||||
|
|
||||
|
public void SetMatrix4x4Parameter(string key, Matrix4x4 value) => SetVariant(key, value); |
||||
|
|
||||
|
public void SetQuaternionParameter(string key, Quaternion value) => SetVariant(key, value); |
||||
|
|
||||
|
public void SetReferenceParameter(string key, CompositionObject compositionObject) => |
||||
|
_propertySet.Set(key, compositionObject); |
||||
|
|
||||
|
public void SetScalarParameter(string key, float value) => SetVariant(key, value); |
||||
|
|
||||
|
public void SetVector2Parameter(string key, Vector2 value) => SetVariant(key, value); |
||||
|
|
||||
|
public void SetVector3Parameter(string key, Vector3 value) => SetVariant(key, value); |
||||
|
|
||||
|
public void SetVector4Parameter(string key, Vector4 value) => SetVariant(key, value); |
||||
|
|
||||
|
// TODO: void SetExpressionReferenceParameter(string parameterName, IAnimationObject source)
|
||||
|
|
||||
|
public string? Target { get; set; } |
||||
|
|
||||
|
internal abstract IAnimationInstance CreateInstance(ServerObject targetObject, |
||||
|
ExpressionVariant? finalValue); |
||||
|
|
||||
|
internal PropertySetSnapshot CreateSnapshot(bool server) |
||||
|
=> _propertySet.Snapshot(server, 1); |
||||
|
|
||||
|
void ICompositionAnimationBase.InternalOnly() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
public class CompositionAnimationGroup : CompositionObject, ICompositionAnimationBase |
||||
|
{ |
||||
|
internal List<CompositionAnimation> Animations { get; } = new List<CompositionAnimation>(); |
||||
|
void ICompositionAnimationBase.InternalOnly() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Add(CompositionAnimation value) => Animations.Add(value); |
||||
|
public void Remove(CompositionAnimation value) => Animations.Remove(value); |
||||
|
public void RemoveAll() => Animations.Clear(); |
||||
|
|
||||
|
public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ReSharper disable CheckNamespace
|
||||
|
using System; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
public class ExpressionAnimation : CompositionAnimation |
||||
|
{ |
||||
|
private string? _expression; |
||||
|
private Expression? _parsedExpression; |
||||
|
|
||||
|
internal ExpressionAnimation(Compositor compositor) : base(compositor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public string? Expression |
||||
|
{ |
||||
|
get => _expression; |
||||
|
set |
||||
|
{ |
||||
|
_expression = value; |
||||
|
_parsedExpression = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Expression ParsedExpression => _parsedExpression ??= ExpressionParser.Parse(_expression.AsSpan()); |
||||
|
|
||||
|
internal override IAnimationInstance CreateInstance( |
||||
|
ServerObject targetObject, ExpressionVariant? finalValue) |
||||
|
=> new ExpressionAnimationInstance(ParsedExpression, |
||||
|
targetObject, finalValue, CreateSnapshot(true)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
internal class ExpressionAnimationInstance : IAnimationInstance |
||||
|
{ |
||||
|
private readonly Expression _expression; |
||||
|
private readonly IExpressionObject _target; |
||||
|
private ExpressionVariant _startingValue; |
||||
|
private readonly ExpressionVariant? _finalValue; |
||||
|
private readonly PropertySetSnapshot _parameters; |
||||
|
|
||||
|
public ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue) |
||||
|
{ |
||||
|
var ctx = new ExpressionEvaluationContext |
||||
|
{ |
||||
|
Parameters = _parameters, |
||||
|
Target = _target, |
||||
|
ForeignFunctionInterface = BuiltInExpressionFfi.Instance, |
||||
|
StartingValue = _startingValue, |
||||
|
FinalValue = _finalValue ?? _startingValue, |
||||
|
CurrentValue = currentValue |
||||
|
}; |
||||
|
return _expression.Evaluate(ref ctx); |
||||
|
} |
||||
|
|
||||
|
public void Start(TimeSpan startedAt, ExpressionVariant startingValue) |
||||
|
{ |
||||
|
_startingValue = startingValue; |
||||
|
} |
||||
|
|
||||
|
public ExpressionAnimationInstance(Expression expression, |
||||
|
IExpressionObject target, |
||||
|
ExpressionVariant? finalValue, |
||||
|
PropertySetSnapshot parameters) |
||||
|
{ |
||||
|
_expression = expression; |
||||
|
_target = target; |
||||
|
_finalValue = finalValue; |
||||
|
_parameters = parameters; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
internal interface IAnimationInstance |
||||
|
{ |
||||
|
ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue); |
||||
|
void Start(TimeSpan startedAt, ExpressionVariant startingValue); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
// ReSharper disable CheckNamespace
|
||||
|
|
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
public interface ICompositionAnimationBase |
||||
|
{ |
||||
|
internal void InternalOnly(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
public class ImplicitAnimationCollection : CompositionObject, IDictionary<string, ICompositionAnimationBase> |
||||
|
{ |
||||
|
private Dictionary<string, ICompositionAnimationBase> _inner = new Dictionary<string, ICompositionAnimationBase>(); |
||||
|
private IDictionary<string, ICompositionAnimationBase> _innerface; |
||||
|
internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!) |
||||
|
{ |
||||
|
_innerface = _inner; |
||||
|
} |
||||
|
|
||||
|
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException(); |
||||
|
|
||||
|
public IEnumerator<KeyValuePair<string, ICompositionAnimationBase>> GetEnumerator() => _inner.GetEnumerator(); |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) _inner).GetEnumerator(); |
||||
|
|
||||
|
void ICollection<KeyValuePair<string, ICompositionAnimationBase>>.Add(KeyValuePair<string, ICompositionAnimationBase> item) => _innerface.Add(item); |
||||
|
|
||||
|
public void Clear() => _inner.Clear(); |
||||
|
|
||||
|
bool ICollection<KeyValuePair<string, ICompositionAnimationBase>>.Contains(KeyValuePair<string, ICompositionAnimationBase> item) => _innerface.Contains(item); |
||||
|
|
||||
|
void ICollection<KeyValuePair<string, ICompositionAnimationBase>>.CopyTo(KeyValuePair<string, ICompositionAnimationBase>[] array, int arrayIndex) => _innerface.CopyTo(array, arrayIndex); |
||||
|
|
||||
|
bool ICollection<KeyValuePair<string, ICompositionAnimationBase>>.Remove(KeyValuePair<string, ICompositionAnimationBase> item) => _innerface.Remove(item); |
||||
|
|
||||
|
public int Count => _inner.Count; |
||||
|
|
||||
|
bool ICollection<KeyValuePair<string, ICompositionAnimationBase>>.IsReadOnly => _innerface.IsReadOnly; |
||||
|
|
||||
|
public void Add(string key, ICompositionAnimationBase value) => _inner.Add(key, value); |
||||
|
|
||||
|
public bool ContainsKey(string key) => _inner.ContainsKey(key); |
||||
|
|
||||
|
public bool Remove(string key) => _inner.Remove(key); |
||||
|
|
||||
|
public bool TryGetValue(string key, [MaybeNullWhen(false)] out ICompositionAnimationBase value) => |
||||
|
_inner.TryGetValue(key, out value); |
||||
|
|
||||
|
public ICompositionAnimationBase this[string key] |
||||
|
{ |
||||
|
get => _inner[key]; |
||||
|
set => _inner[key] = value; |
||||
|
} |
||||
|
|
||||
|
ICollection<string> IDictionary<string, ICompositionAnimationBase>.Keys => _innerface.Keys; |
||||
|
|
||||
|
ICollection<ICompositionAnimationBase> IDictionary<string, ICompositionAnimationBase>.Values => |
||||
|
_innerface.Values; |
||||
|
|
||||
|
// UWP compat
|
||||
|
public uint Size => (uint) Count; |
||||
|
|
||||
|
public IReadOnlyDictionary<string, ICompositionAnimationBase> GetView() => |
||||
|
new Dictionary<string, ICompositionAnimationBase>(this); |
||||
|
|
||||
|
public bool HasKey(string key) => ContainsKey(key); |
||||
|
public void Insert(string key, ICompositionAnimationBase animation) => Add(key, animation); |
||||
|
|
||||
|
public ICompositionAnimationBase? Lookup(string key) |
||||
|
{ |
||||
|
_inner.TryGetValue(key, out var rv); |
||||
|
return rv; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
internal interface IInterpolator<T> |
||||
|
{ |
||||
|
T Interpolate(T from, T to, float progress); |
||||
|
} |
||||
|
|
||||
|
class ScalarInterpolator : IInterpolator<float> |
||||
|
{ |
||||
|
public float Interpolate(float @from, float to, float progress) => @from + (to - @from) * progress; |
||||
|
|
||||
|
public static ScalarInterpolator Instance { get; } = new ScalarInterpolator(); |
||||
|
} |
||||
|
|
||||
|
class Vector2Interpolator : IInterpolator<Vector2> |
||||
|
{ |
||||
|
public Vector2 Interpolate(Vector2 @from, Vector2 to, float progress) |
||||
|
=> Vector2.Lerp(@from, to, progress); |
||||
|
|
||||
|
public static Vector2Interpolator Instance { get; } = new Vector2Interpolator(); |
||||
|
} |
||||
|
|
||||
|
class Vector3Interpolator : IInterpolator<Vector3> |
||||
|
{ |
||||
|
public Vector3 Interpolate(Vector3 @from, Vector3 to, float progress) |
||||
|
=> Vector3.Lerp(@from, to, progress); |
||||
|
|
||||
|
public static Vector3Interpolator Instance { get; } = new Vector3Interpolator(); |
||||
|
} |
||||
|
|
||||
|
class Vector4Interpolator : IInterpolator<Vector4> |
||||
|
{ |
||||
|
public Vector4 Interpolate(Vector4 @from, Vector4 to, float progress) |
||||
|
=> Vector4.Lerp(@from, to, progress); |
||||
|
|
||||
|
public static Vector4Interpolator Instance { get; } = new Vector4Interpolator(); |
||||
|
} |
||||
|
|
||||
|
class QuaternionInterpolator : IInterpolator<Quaternion> |
||||
|
{ |
||||
|
public Quaternion Interpolate(Quaternion @from, Quaternion to, float progress) |
||||
|
=> Quaternion.Lerp(@from, to, progress); |
||||
|
|
||||
|
public static QuaternionInterpolator Instance { get; } = new QuaternionInterpolator(); |
||||
|
} |
||||
|
|
||||
|
class ColorInterpolator : IInterpolator<Avalonia.Media.Color> |
||||
|
{ |
||||
|
static byte Lerp(float a, float b, float p) => (byte) Math.Max(0, Math.Min(255, (p * (b - a) + a))); |
||||
|
|
||||
|
public static Avalonia.Media.Color |
||||
|
LerpRGB(Avalonia.Media.Color to, Avalonia.Media.Color from, float progress) => |
||||
|
new Avalonia.Media.Color(Lerp(to.A, @from.A, progress), |
||||
|
Lerp(to.R, @from.R, progress), |
||||
|
Lerp(to.G, @from.G, progress), |
||||
|
Lerp(to.B, @from.B, progress)); |
||||
|
|
||||
|
public Avalonia.Media.Color Interpolate(Avalonia.Media.Color @from, Avalonia.Media.Color to, float progress) |
||||
|
=> LerpRGB(@from, to, progress); |
||||
|
|
||||
|
public static ColorInterpolator Instance { get; } = new ColorInterpolator(); |
||||
|
} |
||||
|
|
||||
|
class BooleanInterpolator : IInterpolator<bool> |
||||
|
{ |
||||
|
public bool Interpolate(bool @from, bool to, float progress) => progress >= 1 ? to : @from; |
||||
|
|
||||
|
public static BooleanInterpolator Instance { get; } = new BooleanInterpolator(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
public abstract class KeyFrameAnimation : CompositionAnimation |
||||
|
{ |
||||
|
internal KeyFrameAnimation(Compositor compositor) : base(compositor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public AnimationDelayBehavior DelayBehavior { get; set; } |
||||
|
public System.TimeSpan DelayTime { get; set; } |
||||
|
public AnimationDirection Direction { get; set; } |
||||
|
public System.TimeSpan Duration { get; set; } |
||||
|
public AnimationIterationBehavior IterationBehavior { get; set; } |
||||
|
public int IterationCount { get; set; } = 1; |
||||
|
public AnimationStopBehavior StopBehavior { get; set; } |
||||
|
|
||||
|
private protected abstract IKeyFrames KeyFrames { get; } |
||||
|
|
||||
|
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, |
||||
|
CompositionEasingFunction easingFunction) => |
||||
|
KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, easingFunction); |
||||
|
|
||||
|
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value) |
||||
|
=> KeyFrames.InsertExpressionKeyFrame(normalizedProgressKey, value, new LinearEasingFunction(Compositor)); |
||||
|
} |
||||
|
|
||||
|
public enum AnimationDelayBehavior |
||||
|
{ |
||||
|
SetInitialValueAfterDelay, |
||||
|
SetInitialValueBeforeDelay |
||||
|
} |
||||
|
|
||||
|
public enum AnimationDirection |
||||
|
{ |
||||
|
Normal, |
||||
|
Reverse, |
||||
|
Alternate, |
||||
|
AlternateReverse |
||||
|
} |
||||
|
|
||||
|
public enum AnimationIterationBehavior |
||||
|
{ |
||||
|
Count, |
||||
|
Forever |
||||
|
} |
||||
|
|
||||
|
public enum AnimationStopBehavior |
||||
|
{ |
||||
|
LeaveCurrentValue, |
||||
|
SetToInitialValue, |
||||
|
SetToFinalValue |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,139 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
class KeyFrameAnimationInstance<T> : IAnimationInstance where T : struct |
||||
|
{ |
||||
|
private readonly IInterpolator<T> _interpolator; |
||||
|
private readonly ServerKeyFrame<T>[] _keyFrames; |
||||
|
private readonly PropertySetSnapshot _snapshot; |
||||
|
private readonly ExpressionVariant? _finalValue; |
||||
|
private readonly IExpressionObject _target; |
||||
|
private readonly AnimationDelayBehavior _delayBehavior; |
||||
|
private readonly TimeSpan _delayTime; |
||||
|
private readonly AnimationDirection _direction; |
||||
|
private readonly TimeSpan _duration; |
||||
|
private readonly AnimationIterationBehavior _iterationBehavior; |
||||
|
private readonly int _iterationCount; |
||||
|
private readonly AnimationStopBehavior _stopBehavior; |
||||
|
private TimeSpan _startedAt; |
||||
|
private T _startingValue; |
||||
|
|
||||
|
public KeyFrameAnimationInstance( |
||||
|
IInterpolator<T> interpolator, ServerKeyFrame<T>[] keyFrames, |
||||
|
PropertySetSnapshot snapshot, ExpressionVariant? finalValue, |
||||
|
IExpressionObject target, |
||||
|
AnimationDelayBehavior delayBehavior, TimeSpan delayTime, |
||||
|
AnimationDirection direction, TimeSpan duration, |
||||
|
AnimationIterationBehavior iterationBehavior, |
||||
|
int iterationCount, AnimationStopBehavior stopBehavior) |
||||
|
{ |
||||
|
_interpolator = interpolator; |
||||
|
_keyFrames = keyFrames; |
||||
|
_snapshot = snapshot; |
||||
|
_finalValue = finalValue; |
||||
|
_target = target; |
||||
|
_delayBehavior = delayBehavior; |
||||
|
_delayTime = delayTime; |
||||
|
_direction = direction; |
||||
|
_duration = duration; |
||||
|
_iterationBehavior = iterationBehavior; |
||||
|
_iterationCount = iterationCount; |
||||
|
_stopBehavior = stopBehavior; |
||||
|
if (_keyFrames.Length == 0) |
||||
|
throw new InvalidOperationException("Animation has no key frames"); |
||||
|
if(_duration.Ticks <= 0) |
||||
|
throw new InvalidOperationException("Invalid animation duration"); |
||||
|
} |
||||
|
|
||||
|
public ExpressionVariant Evaluate(TimeSpan now, ExpressionVariant currentValue) |
||||
|
{ |
||||
|
var elapsed = now - _startedAt; |
||||
|
var starting = ExpressionVariant.Create(_startingValue); |
||||
|
var ctx = new ExpressionEvaluationContext |
||||
|
{ |
||||
|
Parameters = _snapshot, |
||||
|
Target = _target, |
||||
|
CurrentValue = currentValue, |
||||
|
FinalValue = _finalValue ?? starting, |
||||
|
StartingValue = starting, |
||||
|
ForeignFunctionInterface = BuiltInExpressionFfi.Instance |
||||
|
}; |
||||
|
|
||||
|
if (elapsed < _delayTime) |
||||
|
{ |
||||
|
if (_delayBehavior == AnimationDelayBehavior.SetInitialValueBeforeDelay) |
||||
|
return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[0])); |
||||
|
return currentValue; |
||||
|
} |
||||
|
|
||||
|
elapsed -= _delayTime; |
||||
|
var iterationNumber = elapsed.Ticks / _duration.Ticks; |
||||
|
if (_iterationBehavior == AnimationIterationBehavior.Count |
||||
|
&& iterationNumber >= _iterationCount) |
||||
|
return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[_keyFrames.Length - 1])); |
||||
|
|
||||
|
|
||||
|
var evenIterationNumber = iterationNumber % 2 == 0; |
||||
|
elapsed = TimeSpan.FromTicks(elapsed.Ticks % _duration.Ticks); |
||||
|
|
||||
|
var reverse = |
||||
|
_direction == AnimationDirection.Alternate |
||||
|
? !evenIterationNumber |
||||
|
: _direction == AnimationDirection.AlternateReverse |
||||
|
? evenIterationNumber |
||||
|
: _direction == AnimationDirection.Reverse; |
||||
|
|
||||
|
var iterationProgress = elapsed.TotalSeconds / _duration.TotalSeconds; |
||||
|
if (reverse) |
||||
|
iterationProgress = 1 - iterationProgress; |
||||
|
|
||||
|
var left = new ServerKeyFrame<T> |
||||
|
{ |
||||
|
Value = _startingValue |
||||
|
}; |
||||
|
var right = _keyFrames[_keyFrames.Length - 1]; |
||||
|
for (var c = 0; c < _keyFrames.Length; c++) |
||||
|
{ |
||||
|
var kf = _keyFrames[c]; |
||||
|
if (kf.Key < iterationProgress) |
||||
|
{ |
||||
|
// this is the last frame
|
||||
|
if (c == _keyFrames.Length - 1) |
||||
|
return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf)); |
||||
|
|
||||
|
left = kf; |
||||
|
right = _keyFrames[c + 1]; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key))); |
||||
|
|
||||
|
var easedKeyProgress = right.EasingFunction.Ease((float) keyProgress); |
||||
|
if (float.IsNaN(easedKeyProgress) || float.IsInfinity(easedKeyProgress)) |
||||
|
return currentValue; |
||||
|
|
||||
|
return ExpressionVariant.Create(_interpolator.Interpolate( |
||||
|
GetKeyFrame(ref ctx, left), |
||||
|
GetKeyFrame(ref ctx, right), |
||||
|
easedKeyProgress |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
T GetKeyFrame(ref ExpressionEvaluationContext ctx, ServerKeyFrame<T> f) |
||||
|
{ |
||||
|
if (f.Expression != null) |
||||
|
return f.Expression.Evaluate(ref ctx).CastOrDefault<T>(); |
||||
|
else |
||||
|
return f.Value; |
||||
|
} |
||||
|
|
||||
|
public void Start(TimeSpan startedAt, ExpressionVariant startingValue) |
||||
|
{ |
||||
|
_startedAt = startedAt; |
||||
|
_startingValue = startingValue.CastOrDefault<T>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
class KeyFrames<T> : List<KeyFrame<T>>, IKeyFrames |
||||
|
{ |
||||
|
void Validate(float key) |
||||
|
{ |
||||
|
if (key < 0 || key > 1) |
||||
|
throw new ArgumentException("Key frame key"); |
||||
|
if (Count > 0 && this[Count - 1].NormalizedProgressKey > key) |
||||
|
throw new ArgumentException("Key frame key " + key + " is less than the previous one"); |
||||
|
} |
||||
|
|
||||
|
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, |
||||
|
CompositionEasingFunction easingFunction) |
||||
|
{ |
||||
|
Validate(normalizedProgressKey); |
||||
|
Add(new KeyFrame<T> |
||||
|
{ |
||||
|
NormalizedProgressKey = normalizedProgressKey, |
||||
|
Expression = Expression.Parse(value), |
||||
|
EasingFunction = easingFunction |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void Insert(float normalizedProgressKey, T value, CompositionEasingFunction easingFunction) |
||||
|
{ |
||||
|
Validate(normalizedProgressKey); |
||||
|
Add(new KeyFrame<T> |
||||
|
{ |
||||
|
NormalizedProgressKey = normalizedProgressKey, |
||||
|
Value = value, |
||||
|
EasingFunction = easingFunction |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public ServerKeyFrame<T>[] Snapshot() |
||||
|
{ |
||||
|
var frames = new ServerKeyFrame<T>[Count]; |
||||
|
for (var c = 0; c < Count; c++) |
||||
|
{ |
||||
|
var f = this[c]; |
||||
|
frames[c] = new ServerKeyFrame<T> |
||||
|
{ |
||||
|
Expression = f.Expression, |
||||
|
Value = f.Value, |
||||
|
EasingFunction = f.EasingFunction.Snapshot(), |
||||
|
Key = f.NormalizedProgressKey |
||||
|
}; |
||||
|
} |
||||
|
return frames; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
struct KeyFrame<T> |
||||
|
{ |
||||
|
public float NormalizedProgressKey; |
||||
|
public T Value; |
||||
|
public Expression Expression; |
||||
|
public CompositionEasingFunction EasingFunction; |
||||
|
} |
||||
|
|
||||
|
struct ServerKeyFrame<T> |
||||
|
{ |
||||
|
public T Value; |
||||
|
public Expression Expression; |
||||
|
public IEasingFunction EasingFunction; |
||||
|
public float Key; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
interface IKeyFrames |
||||
|
{ |
||||
|
public void InsertExpressionKeyFrame(float normalizedProgressKey, string value, CompositionEasingFunction easingFunction); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Animations |
||||
|
{ |
||||
|
internal class PropertySetSnapshot : IExpressionParameterCollection, IExpressionObject |
||||
|
{ |
||||
|
private readonly Dictionary<string, Value> _dic; |
||||
|
|
||||
|
public struct Value |
||||
|
{ |
||||
|
public ExpressionVariant Variant; |
||||
|
public IExpressionObject Object; |
||||
|
|
||||
|
public Value(IExpressionObject o) |
||||
|
{ |
||||
|
Object = o; |
||||
|
Variant = default; |
||||
|
} |
||||
|
|
||||
|
public static implicit operator Value(ExpressionVariant v) => new Value |
||||
|
{ |
||||
|
Variant = v |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public PropertySetSnapshot(Dictionary<string, Value> dic) |
||||
|
{ |
||||
|
_dic = dic; |
||||
|
} |
||||
|
|
||||
|
public ExpressionVariant GetParameter(string name) |
||||
|
{ |
||||
|
_dic.TryGetValue(name, out var v); |
||||
|
return v.Variant; |
||||
|
} |
||||
|
|
||||
|
public IExpressionObject GetObjectParameter(string name) |
||||
|
{ |
||||
|
_dic.TryGetValue(name, out var v); |
||||
|
return v.Object; |
||||
|
} |
||||
|
|
||||
|
public ExpressionVariant GetProperty(string name) => GetParameter(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,194 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Collections; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Rendering.Composition.Drawing; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Threading; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition; |
||||
|
|
||||
|
public class CompositingRenderer : RendererBase, IRendererWithCompositor |
||||
|
{ |
||||
|
private readonly IRenderRoot _root; |
||||
|
private readonly Compositor _compositor; |
||||
|
private readonly IDeferredRendererLock? _rendererLock; |
||||
|
CompositionDrawingContext _recorder = new(); |
||||
|
DrawingContext _recordingContext; |
||||
|
private HashSet<Visual> _dirty = new(); |
||||
|
private HashSet<Visual> _recalculateChildren = new(); |
||||
|
private readonly CompositionTarget _target; |
||||
|
private bool _queuedUpdate; |
||||
|
private Action _update; |
||||
|
|
||||
|
public CompositingRenderer(IRenderRoot root, |
||||
|
Compositor compositor, |
||||
|
IDeferredRendererLock? rendererLock = null) |
||||
|
{ |
||||
|
_root = root; |
||||
|
_compositor = compositor; |
||||
|
_recordingContext = new DrawingContext(_recorder); |
||||
|
_rendererLock = rendererLock ?? new ManagedDeferredRendererLock(); |
||||
|
_target = compositor.CreateCompositionTarget(root.CreateRenderTarget); |
||||
|
_target.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); |
||||
|
_update = Update; |
||||
|
} |
||||
|
|
||||
|
public bool DrawFps { get; set; } |
||||
|
public bool DrawDirtyRects { get; set; } |
||||
|
public event EventHandler<SceneInvalidatedEventArgs>? SceneInvalidated; |
||||
|
|
||||
|
void QueueUpdate() |
||||
|
{ |
||||
|
if(_queuedUpdate) |
||||
|
return; |
||||
|
_queuedUpdate = true; |
||||
|
Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition); |
||||
|
} |
||||
|
public void AddDirty(IVisual visual) |
||||
|
{ |
||||
|
_dirty.Add((Visual)visual); |
||||
|
QueueUpdate(); |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) |
||||
|
{ |
||||
|
var res = _target.TryHitTest(new Vector2((float)p.X, (float)p.Y)); |
||||
|
if(res == null) |
||||
|
yield break; |
||||
|
for (var index = res.Count - 1; index >= 0; index--) |
||||
|
{ |
||||
|
var v = res[index]; |
||||
|
if (v is CompositionDrawListVisual dv) |
||||
|
{ |
||||
|
if (filter == null || filter(dv.Visual)) |
||||
|
yield return dv.Visual; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter) |
||||
|
{ |
||||
|
// TODO: Optimize
|
||||
|
return HitTest(p, root, filter).FirstOrDefault(); |
||||
|
} |
||||
|
|
||||
|
public void RecalculateChildren(IVisual visual) |
||||
|
{ |
||||
|
_recalculateChildren.Add((Visual)visual); |
||||
|
QueueUpdate(); |
||||
|
} |
||||
|
|
||||
|
private void SyncChildren(Visual v) |
||||
|
{ |
||||
|
//TODO: Optimize by moving that logic to Visual itself
|
||||
|
if(v.CompositionVisual == null) |
||||
|
return; |
||||
|
var compositionChildren = v.CompositionVisual.Children; |
||||
|
var visualChildren = (AvaloniaList<IVisual>)v.GetVisualChildren(); |
||||
|
if (compositionChildren.Count == visualChildren.Count) |
||||
|
{ |
||||
|
bool mismatch = false; |
||||
|
for(var c=0; c<visualChildren.Count; c++) |
||||
|
if(!object.ReferenceEquals(compositionChildren[c], ((Visual)visualChildren[c]).CompositionVisual)) |
||||
|
{ |
||||
|
mismatch = true; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if(!mismatch) |
||||
|
return; |
||||
|
} |
||||
|
compositionChildren.Clear(); |
||||
|
foreach (var ch in v.GetVisualChildren()) |
||||
|
{ |
||||
|
var compositionChild = ((Visual)ch).CompositionVisual; |
||||
|
if (compositionChild != null) |
||||
|
compositionChildren.Add(compositionChild); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void Update() |
||||
|
{ |
||||
|
_queuedUpdate = false; |
||||
|
foreach (var visual in _dirty) |
||||
|
{ |
||||
|
var comp = visual.CompositionVisual; |
||||
|
if(comp == null) |
||||
|
continue; |
||||
|
|
||||
|
// TODO: Optimize all of that by moving to the Visual itself, so we won't have to recalculate every time
|
||||
|
comp.Offset = new Vector3((float)visual.Bounds.Left, (float)visual.Bounds.Top, 0); |
||||
|
comp.Size = new Vector2((float)visual.Bounds.Width, (float)visual.Bounds.Height); |
||||
|
comp.Visible = visual.IsVisible; |
||||
|
comp.Opacity = (float)visual.Opacity; |
||||
|
comp.ClipToBounds = visual.ClipToBounds; |
||||
|
comp.Clip = visual.Clip?.PlatformImpl; |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
if (visual.HasMirrorTransform) |
||||
|
{ |
||||
|
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); |
||||
|
renderTransform *= mirrorMatrix; |
||||
|
} |
||||
|
|
||||
|
comp.TransformMatrix = renderTransform; |
||||
|
|
||||
|
_recorder.BeginUpdate(comp.DrawList ?? new CompositionDrawList()); |
||||
|
visual.Render(_recordingContext); |
||||
|
comp.DrawList = _recorder.EndUpdate(); |
||||
|
|
||||
|
SyncChildren(visual); |
||||
|
} |
||||
|
foreach(var v in _recalculateChildren) |
||||
|
if (!_dirty.Contains(v)) |
||||
|
SyncChildren(v); |
||||
|
_dirty.Clear(); |
||||
|
_recalculateChildren.Clear(); |
||||
|
} |
||||
|
|
||||
|
public void Resized(Size size) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void Paint(Rect rect) |
||||
|
{ |
||||
|
// We render only on the render thread for now
|
||||
|
Update(); |
||||
|
|
||||
|
Compositor.RequestCommitAsync().Wait(); |
||||
|
} |
||||
|
|
||||
|
public void Start() => _target.IsEnabled = true; |
||||
|
|
||||
|
public void Stop() |
||||
|
{ |
||||
|
_target.IsEnabled = true; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
Stop(); |
||||
|
_target.Dispose(); |
||||
|
// Wait for the composition batch to be applied and rendered to guarantee that
|
||||
|
// render target is not used anymore and can be safely disposed
|
||||
|
_compositor.RequestCommitAsync().Wait(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public Compositor Compositor => _compositor; |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Rendering.Composition.Drawing; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition; |
||||
|
|
||||
|
internal class CompositionDrawListVisual : CompositionContainerVisual |
||||
|
{ |
||||
|
public Visual Visual { get; } |
||||
|
|
||||
|
private new DrawListVisualChanges Changes => (DrawListVisualChanges)base.Changes; |
||||
|
private CompositionDrawList? _drawList; |
||||
|
public CompositionDrawList? DrawList |
||||
|
{ |
||||
|
get => _drawList; |
||||
|
set |
||||
|
{ |
||||
|
_drawList?.Dispose(); |
||||
|
_drawList = value; |
||||
|
Changes.DrawCommands = value?.Clone(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private protected override IChangeSetPool ChangeSetPool => DrawListVisualChanges.Pool; |
||||
|
|
||||
|
internal CompositionDrawListVisual(Compositor compositor, ServerCompositionContainerVisual server, Visual visual) : base(compositor, server) |
||||
|
{ |
||||
|
Visual = visual; |
||||
|
} |
||||
|
|
||||
|
internal override bool HitTest(Vector2 point) |
||||
|
{ |
||||
|
if (DrawList == null) |
||||
|
return false; |
||||
|
var pt = new Point(point.X, point.Y); |
||||
|
if (Visual is ICustomHitTest custom) |
||||
|
return custom.HitTest(pt); |
||||
|
foreach (var op in DrawList) |
||||
|
if (op.Item.HitTest(pt)) |
||||
|
return true; |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
using Avalonia.Rendering.Composition.Utils; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public abstract class CompositionEasingFunction : CompositionObject |
||||
|
{ |
||||
|
internal CompositionEasingFunction(Compositor compositor) : base(compositor, null!) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private protected override IChangeSetPool ChangeSetPool => throw new InvalidOperationException(); |
||||
|
|
||||
|
internal abstract IEasingFunction Snapshot(); |
||||
|
} |
||||
|
|
||||
|
internal interface IEasingFunction |
||||
|
{ |
||||
|
float Ease(float progress); |
||||
|
} |
||||
|
|
||||
|
public sealed class DelegateCompositionEasingFunction : CompositionEasingFunction |
||||
|
{ |
||||
|
private readonly Easing _func; |
||||
|
|
||||
|
public delegate float EasingDelegate(float progress); |
||||
|
|
||||
|
internal DelegateCompositionEasingFunction(Compositor compositor, EasingDelegate func) : base(compositor) |
||||
|
{ |
||||
|
_func = new Easing(func); |
||||
|
} |
||||
|
|
||||
|
class Easing : IEasingFunction |
||||
|
{ |
||||
|
private readonly EasingDelegate _func; |
||||
|
|
||||
|
public Easing(EasingDelegate func) |
||||
|
{ |
||||
|
_func = func; |
||||
|
} |
||||
|
|
||||
|
public float Ease(float progress) => _func(progress); |
||||
|
} |
||||
|
|
||||
|
internal override IEasingFunction Snapshot() => _func; |
||||
|
} |
||||
|
|
||||
|
public class LinearEasingFunction : CompositionEasingFunction |
||||
|
{ |
||||
|
public LinearEasingFunction(Compositor compositor) : base(compositor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
class Linear : IEasingFunction |
||||
|
{ |
||||
|
public float Ease(float progress) => progress; |
||||
|
} |
||||
|
|
||||
|
private static readonly Linear Instance = new Linear(); |
||||
|
internal override IEasingFunction Snapshot() => Instance; |
||||
|
} |
||||
|
|
||||
|
public class CubicBezierEasingFunction : CompositionEasingFunction |
||||
|
{ |
||||
|
private CubicBezier _bezier; |
||||
|
public Vector2 ControlPoint1 { get; } |
||||
|
public Vector2 ControlPoint2 { get; } |
||||
|
//cubic-bezier(0.25, 0.1, 0.25, 1.0)
|
||||
|
internal CubicBezierEasingFunction(Compositor compositor, Vector2 controlPoint1, Vector2 controlPoint2) : base(compositor) |
||||
|
{ |
||||
|
ControlPoint1 = controlPoint1; |
||||
|
ControlPoint2 = controlPoint2; |
||||
|
if (controlPoint1.X < 0 || controlPoint1.X > 1 || controlPoint2.X < 0 || controlPoint2.X > 1) |
||||
|
throw new ArgumentException(); |
||||
|
_bezier = new CubicBezier(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y); |
||||
|
} |
||||
|
|
||||
|
class EasingFunction : IEasingFunction |
||||
|
{ |
||||
|
private readonly CubicBezier _bezier; |
||||
|
|
||||
|
public EasingFunction(CubicBezier bezier) |
||||
|
{ |
||||
|
_bezier = bezier; |
||||
|
} |
||||
|
|
||||
|
public float Ease(float progress) => (float)_bezier.Solve(progress); |
||||
|
} |
||||
|
|
||||
|
internal static IEasingFunction Ease { get; } = new EasingFunction(new CubicBezier(0.25, 0.1, 0.25, 1)); |
||||
|
|
||||
|
internal override IEasingFunction Snapshot() => new EasingFunction(_bezier); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public partial class CompositionGradientBrush : CompositionBrush |
||||
|
{ |
||||
|
internal CompositionGradientBrush(Compositor compositor, ServerCompositionGradientBrush server) : base(compositor, server) |
||||
|
{ |
||||
|
ColorStops = new CompositionGradientStopCollection(compositor, server.Stops); |
||||
|
} |
||||
|
|
||||
|
public CompositionGradientStopCollection ColorStops { get; } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Rendering.Composition.Animations; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public abstract class CompositionObject : IDisposable, IExpressionObject |
||||
|
{ |
||||
|
public ImplicitAnimationCollection? ImplicitAnimations { get; set; } |
||||
|
internal CompositionObject(Compositor compositor, ServerObject server) |
||||
|
{ |
||||
|
Compositor = compositor; |
||||
|
Server = server; |
||||
|
} |
||||
|
|
||||
|
public Compositor Compositor { get; } |
||||
|
internal ServerObject Server { get; } |
||||
|
public bool IsDisposed { get; private set; } |
||||
|
private ChangeSet? _changes; |
||||
|
|
||||
|
private static void ThrowInvalidOperation() => |
||||
|
throw new InvalidOperationException("There is no server-side counterpart for this object"); |
||||
|
|
||||
|
private protected ChangeSet Changes |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
|
if (Server == null) ThrowInvalidOperation(); |
||||
|
var currentBatch = Compositor.CurrentBatch; |
||||
|
if (_changes != null && _changes.Batch != currentBatch) |
||||
|
_changes = null; |
||||
|
if (_changes == null) |
||||
|
{ |
||||
|
_changes = ChangeSetPool.Get(Server!, currentBatch); |
||||
|
currentBatch.Changes!.Add(_changes); |
||||
|
Compositor.QueueImplicitBatchCommit(); |
||||
|
} |
||||
|
|
||||
|
return _changes; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private protected abstract IChangeSetPool ChangeSetPool { get; } |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
Changes.Dispose = true; |
||||
|
IsDisposed = true; |
||||
|
} |
||||
|
|
||||
|
internal virtual ExpressionVariant GetPropertyForAnimation(string name) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
ExpressionVariant IExpressionObject.GetProperty(string name) => GetPropertyForAnimation(name); |
||||
|
|
||||
|
public void StartAnimation(string propertyName, CompositionAnimation animation) |
||||
|
=> StartAnimation(propertyName, animation, null); |
||||
|
|
||||
|
internal virtual void StartAnimation(string propertyName, CompositionAnimation animation, ExpressionVariant? finalValue = null) |
||||
|
{ |
||||
|
throw new ArgumentException("Unknown property " + propertyName); |
||||
|
} |
||||
|
|
||||
|
public void StartAnimationGroup(ICompositionAnimationBase grp) |
||||
|
{ |
||||
|
if (grp is CompositionAnimation animation) |
||||
|
{ |
||||
|
if(animation.Target == null) |
||||
|
throw new ArgumentException("Animation Target can't be null"); |
||||
|
StartAnimation(animation.Target, animation); |
||||
|
} |
||||
|
else if (grp is CompositionAnimationGroup group) |
||||
|
{ |
||||
|
foreach (var a in group.Animations) |
||||
|
{ |
||||
|
if (a.Target == null) |
||||
|
throw new ArgumentException("Animation Target can't be null"); |
||||
|
StartAnimation(a.Target, a); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool StartAnimationGroupPart(CompositionAnimation animation, string target, ExpressionVariant finalValue) |
||||
|
{ |
||||
|
if(animation.Target == null) |
||||
|
throw new ArgumentException("Animation Target can't be null"); |
||||
|
if (animation.Target == target) |
||||
|
{ |
||||
|
StartAnimation(animation.Target, animation, finalValue); |
||||
|
return true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
StartAnimation(animation.Target, animation); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal bool StartAnimationGroup(ICompositionAnimationBase grp, string target, ExpressionVariant finalValue) |
||||
|
{ |
||||
|
if (grp is CompositionAnimation animation) |
||||
|
return StartAnimationGroupPart(animation, target, finalValue); |
||||
|
if (grp is CompositionAnimationGroup group) |
||||
|
{ |
||||
|
var matched = false; |
||||
|
foreach (var a in group.Animations) |
||||
|
{ |
||||
|
if (a.Target == null) |
||||
|
throw new ArgumentException("Animation Target can't be null"); |
||||
|
if (StartAnimationGroupPart(a, target, finalValue)) |
||||
|
matched = true; |
||||
|
} |
||||
|
|
||||
|
return matched; |
||||
|
} |
||||
|
|
||||
|
throw new ArgumentException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,132 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Rendering.Composition.Animations; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public class CompositionPropertySet : CompositionObject |
||||
|
{ |
||||
|
private readonly Dictionary<string, ExpressionVariant> _variants = new Dictionary<string, ExpressionVariant>(); |
||||
|
private readonly Dictionary<string, CompositionObject> _objects = new Dictionary<string, CompositionObject>(); |
||||
|
|
||||
|
internal CompositionPropertySet(Compositor compositor) : base(compositor, null!) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private protected override IChangeSetPool ChangeSetPool => throw new NotSupportedException(); |
||||
|
|
||||
|
internal void Set(string key, ExpressionVariant value) |
||||
|
{ |
||||
|
_objects.Remove(key); |
||||
|
_variants[key] = value; |
||||
|
} |
||||
|
|
||||
|
internal void Set(string key, CompositionObject obj) |
||||
|
{ |
||||
|
_objects[key] = obj ?? throw new ArgumentNullException(nameof(obj)); |
||||
|
_variants.Remove(key); |
||||
|
} |
||||
|
public void InsertColor(string propertyName, Avalonia.Media.Color value) => Set(propertyName, value); |
||||
|
|
||||
|
public void InsertMatrix3x2(string propertyName, Matrix3x2 value) => Set(propertyName, value); |
||||
|
|
||||
|
public void InsertMatrix4x4(string propertyName, Matrix4x4 value) => Set(propertyName, value); |
||||
|
|
||||
|
public void InsertQuaternion(string propertyName, Quaternion value) => Set(propertyName, value); |
||||
|
|
||||
|
public void InsertScalar(string propertyName, float value) => Set(propertyName, value); |
||||
|
public void InsertVector2(string propertyName, Vector2 value) => Set(propertyName, value); |
||||
|
|
||||
|
public void InsertVector3(string propertyName, Vector3 value) => Set(propertyName, value); |
||||
|
|
||||
|
public void InsertVector4(string propertyName, Vector4 value) => Set(propertyName, value); |
||||
|
|
||||
|
|
||||
|
CompositionGetValueStatus TryGetVariant<T>(string key, out T value) where T : struct |
||||
|
{ |
||||
|
value = default; |
||||
|
if (!_variants.TryGetValue(key, out var v)) |
||||
|
return _objects.ContainsKey(key) |
||||
|
? CompositionGetValueStatus.TypeMismatch |
||||
|
: CompositionGetValueStatus.NotFound; |
||||
|
|
||||
|
return v.TryCast(out value) ? CompositionGetValueStatus.Succeeded : CompositionGetValueStatus.TypeMismatch; |
||||
|
} |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetColor(string propertyName, out Avalonia.Media.Color value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetMatrix3x2(string propertyName, out Matrix3x2 value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetMatrix4x4(string propertyName, out Matrix4x4 value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetQuaternion(string propertyName, out Quaternion value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
|
||||
|
public CompositionGetValueStatus TryGetScalar(string propertyName, out float value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetVector2(string propertyName, out Vector2 value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetVector3(string propertyName, out Vector3 value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetVector4(string propertyName, out Vector4 value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
|
||||
|
public void InsertBoolean(string propertyName, bool value) => Set(propertyName, value); |
||||
|
|
||||
|
public CompositionGetValueStatus TryGetBoolean(string propertyName, out bool value) |
||||
|
=> TryGetVariant(propertyName, out value); |
||||
|
|
||||
|
internal void ClearAll() |
||||
|
{ |
||||
|
_objects.Clear(); |
||||
|
_variants.Clear(); |
||||
|
} |
||||
|
|
||||
|
internal void Clear(string key) |
||||
|
{ |
||||
|
_objects.Remove(key); |
||||
|
_variants.Remove(key); |
||||
|
} |
||||
|
|
||||
|
internal PropertySetSnapshot Snapshot(bool server, int allowedNestingLevel) |
||||
|
{ |
||||
|
var dic = new Dictionary<string, PropertySetSnapshot.Value>(_objects.Count + _variants.Count); |
||||
|
foreach (var o in _objects) |
||||
|
{ |
||||
|
if (o.Value is CompositionPropertySet ps) |
||||
|
{ |
||||
|
if (allowedNestingLevel <= 0) |
||||
|
throw new InvalidOperationException("PropertySet depth limit reached"); |
||||
|
dic[o.Key] = new PropertySetSnapshot.Value(ps.Snapshot(server, allowedNestingLevel - 1)); |
||||
|
} |
||||
|
else if (o.Value.Server == null) |
||||
|
throw new InvalidOperationException($"Object of type {o.Value.GetType()} is not allowed"); |
||||
|
else |
||||
|
dic[o.Key] = new PropertySetSnapshot.Value(server ? (IExpressionObject) o.Value.Server : o.Value); |
||||
|
} |
||||
|
|
||||
|
foreach (var v in _variants) |
||||
|
dic[v.Key] = v.Value; |
||||
|
|
||||
|
return new PropertySetSnapshot(dic); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public enum CompositionGetValueStatus |
||||
|
{ |
||||
|
Succeeded, |
||||
|
TypeMismatch, |
||||
|
NotFound |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Collections.Pooled; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public partial class CompositionTarget |
||||
|
{ |
||||
|
partial void OnRootChanged() |
||||
|
{ |
||||
|
if (Root != null) |
||||
|
Root.Root = this; |
||||
|
} |
||||
|
|
||||
|
partial void OnRootChanging() |
||||
|
{ |
||||
|
if (Root != null) |
||||
|
Root.Root = null; |
||||
|
} |
||||
|
|
||||
|
public PooledList<CompositionVisual>? TryHitTest(Vector2 point) |
||||
|
{ |
||||
|
Server.Readback.NextRead(); |
||||
|
if (Root == null) |
||||
|
return null; |
||||
|
var res = new PooledList<CompositionVisual>(); |
||||
|
HitTestCore(Root, point, res); |
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
public Vector2? TryTransformToVisual(CompositionVisual visual, Vector2 point) |
||||
|
{ |
||||
|
if (visual.Root != this) |
||||
|
return null; |
||||
|
var v = visual; |
||||
|
var m = Matrix3x2.Identity; |
||||
|
while (v != null) |
||||
|
{ |
||||
|
if (!TryGetInvertedTransform(v, out var cm)) |
||||
|
return null; |
||||
|
m = m * cm; |
||||
|
v = v.Parent; |
||||
|
} |
||||
|
|
||||
|
return Vector2.Transform(point, m); |
||||
|
} |
||||
|
|
||||
|
bool TryGetInvertedTransform(CompositionVisual visual, out Matrix3x2 matrix) |
||||
|
{ |
||||
|
var m = visual.TryGetServerTransform(); |
||||
|
if (m == null) |
||||
|
{ |
||||
|
matrix = default; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// TODO: Use Matrix3x3
|
||||
|
var m32 = new Matrix3x2(m.Value.M11, m.Value.M12, m.Value.M21, m.Value.M22, m.Value.M41, m.Value.M42); |
||||
|
|
||||
|
return Matrix3x2.Invert(m32, out matrix); |
||||
|
} |
||||
|
|
||||
|
bool TryTransformTo(CompositionVisual visual, ref Vector2 v) |
||||
|
{ |
||||
|
if (TryGetInvertedTransform(visual, out var m)) |
||||
|
{ |
||||
|
v = Vector2.Transform(v, m); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool HitTestCore(CompositionVisual visual, Vector2 point, PooledList<CompositionVisual> result) |
||||
|
{ |
||||
|
//TODO: Check readback too
|
||||
|
if (visual.Visible == false) |
||||
|
return false; |
||||
|
if (!TryTransformTo(visual, ref point)) |
||||
|
return false; |
||||
|
if (point.X >= 0 && point.Y >= 0 && point.X <= visual.Size.X && point.Y <= visual.Size.Y) |
||||
|
{ |
||||
|
bool success = false; |
||||
|
// Hit-test the current node
|
||||
|
if (visual.HitTest(point)) |
||||
|
{ |
||||
|
result.Add(visual); |
||||
|
success = true; |
||||
|
} |
||||
|
|
||||
|
// Inspect children too
|
||||
|
if(visual is CompositionContainerVisual cv) |
||||
|
for (var c = cv.Children.Count - 1; c >= 0; c--) |
||||
|
{ |
||||
|
var ch = cv.Children[c]; |
||||
|
var hit = HitTestCore(ch, point, result); |
||||
|
if (hit) |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return success; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,143 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.IO; |
||||
|
using System.Numerics; |
||||
|
using System.Threading.Tasks; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition.Animations; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public partial class Compositor |
||||
|
{ |
||||
|
private ServerCompositor _server; |
||||
|
private Batch _currentBatch; |
||||
|
private bool _implicitBatchCommitQueued; |
||||
|
private Action _implicitBatchCommit; |
||||
|
|
||||
|
internal Batch CurrentBatch => _currentBatch; |
||||
|
internal ServerCompositor Server => _server; |
||||
|
internal CompositionEasingFunction DefaultEasing { get; } |
||||
|
|
||||
|
private Compositor(ServerCompositor server) |
||||
|
{ |
||||
|
_server = server; |
||||
|
_currentBatch = new Batch(); |
||||
|
_implicitBatchCommit = ImplicitBatchCommit; |
||||
|
DefaultEasing = new CubicBezierEasingFunction(this, |
||||
|
new Vector2(0.25f, 0.1f), new Vector2(0.25f, 1f)); |
||||
|
} |
||||
|
|
||||
|
public CompositionTarget CreateCompositionTarget(Func<IRenderTarget> renderTargetFactory) |
||||
|
{ |
||||
|
return new CompositionTarget(this, new ServerCompositionTarget(_server, renderTargetFactory)); |
||||
|
} |
||||
|
|
||||
|
public Task RequestCommitAsync() |
||||
|
{ |
||||
|
var batch = CurrentBatch; |
||||
|
_currentBatch = new Batch(); |
||||
|
batch.CommitedAt = Server.Clock.Elapsed; |
||||
|
_server.EnqueueBatch(batch); |
||||
|
return batch.Completed; |
||||
|
} |
||||
|
|
||||
|
public static Compositor Create(IRenderLoop timer) |
||||
|
{ |
||||
|
return new Compositor(new ServerCompositor(timer)); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public CompositionContainerVisual CreateContainerVisual() => new(this, new ServerCompositionContainerVisual(_server)); |
||||
|
|
||||
|
public CompositionSolidColorVisual CreateSolidColorVisual() => new CompositionSolidColorVisual(this, |
||||
|
new ServerCompositionSolidColorVisual(_server)); |
||||
|
|
||||
|
public CompositionSolidColorVisual CreateSolidColorVisual(Avalonia.Media.Color color) |
||||
|
{ |
||||
|
var v = new CompositionSolidColorVisual(this, new ServerCompositionSolidColorVisual(_server)); |
||||
|
v.Color = color; |
||||
|
return v; |
||||
|
} |
||||
|
|
||||
|
public CompositionSpriteVisual CreateSpriteVisual() => new CompositionSpriteVisual(this, new ServerCompositionSpriteVisual(_server)); |
||||
|
|
||||
|
public CompositionLinearGradientBrush CreateLinearGradientBrush() |
||||
|
=> new CompositionLinearGradientBrush(this, new ServerCompositionLinearGradientBrush(_server)); |
||||
|
|
||||
|
public CompositionColorGradientStop CreateColorGradientStop() |
||||
|
=> new CompositionColorGradientStop(this, new ServerCompositionColorGradientStop(_server)); |
||||
|
|
||||
|
public CompositionColorGradientStop CreateColorGradientStop(float offset, Avalonia.Media.Color color) |
||||
|
{ |
||||
|
var stop = CreateColorGradientStop(); |
||||
|
stop.Offset = offset; |
||||
|
stop.Color = color; |
||||
|
return stop; |
||||
|
} |
||||
|
|
||||
|
// We want to make it 100% async later
|
||||
|
/* |
||||
|
public CompositionBitmapSurface LoadBitmapSurface(Stream stream) |
||||
|
{ |
||||
|
var bmp = _server.Backend.LoadCpuMemoryBitmap(stream); |
||||
|
return new CompositionBitmapSurface(this, bmp); |
||||
|
} |
||||
|
|
||||
|
public async Task<CompositionBitmapSurface> LoadBitmapSurfaceAsync(Stream stream) |
||||
|
{ |
||||
|
var bmp = await Task.Run(() => _server.Backend.LoadCpuMemoryBitmap(stream)); |
||||
|
return new CompositionBitmapSurface(this, bmp); |
||||
|
} |
||||
|
*/ |
||||
|
public CompositionColorBrush CreateColorBrush(Avalonia.Media.Color color) => |
||||
|
new CompositionColorBrush(this, new ServerCompositionColorBrush(_server)) {Color = color}; |
||||
|
|
||||
|
public CompositionSurfaceBrush CreateSurfaceBrush() => |
||||
|
new CompositionSurfaceBrush(this, new ServerCompositionSurfaceBrush(_server)); |
||||
|
|
||||
|
/* |
||||
|
public CompositionGaussianBlurEffectBrush CreateGaussianBlurEffectBrush() => |
||||
|
new CompositionGaussianBlurEffectBrush(this, new ServerCompositionGaussianBlurEffectBrush(_server)); |
||||
|
|
||||
|
public CompositionBackdropBrush CreateBackdropBrush() => |
||||
|
new CompositionBackdropBrush(this, new ServerCompositionBackdropBrush(Server));*/ |
||||
|
|
||||
|
public ExpressionAnimation CreateExpressionAnimation() => new ExpressionAnimation(this); |
||||
|
|
||||
|
public ExpressionAnimation CreateExpressionAnimation(string expression) => new ExpressionAnimation(this) |
||||
|
{ |
||||
|
Expression = expression |
||||
|
}; |
||||
|
|
||||
|
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this); |
||||
|
|
||||
|
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this); |
||||
|
|
||||
|
internal CustomDrawVisual<T> CreateCustomDrawVisual<T>(ICustomDrawVisualRenderer<T> renderer, |
||||
|
ICustomDrawVisualHitTest<T>? hitTest = null) where T : IEquatable<T> => |
||||
|
new CustomDrawVisual<T>(this, renderer, hitTest); |
||||
|
|
||||
|
public void QueueImplicitBatchCommit() |
||||
|
{ |
||||
|
if(_implicitBatchCommitQueued) |
||||
|
return; |
||||
|
_implicitBatchCommitQueued = true; |
||||
|
Dispatcher.UIThread.Post(_implicitBatchCommit, DispatcherPriority.CompositionBatch); |
||||
|
} |
||||
|
|
||||
|
private void ImplicitBatchCommit() |
||||
|
{ |
||||
|
_implicitBatchCommitQueued = false; |
||||
|
RequestCommitAsync(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition; |
||||
|
|
||||
|
partial class Compositor |
||||
|
{ |
||||
|
class CompositorRenderLoopTask : IRenderLoopTask |
||||
|
{ |
||||
|
public bool NeedsUpdate { get; } |
||||
|
public void Update(TimeSpan time) |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
|
||||
|
public void Render() |
||||
|
{ |
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public class CompositionContainerVisual : CompositionVisual |
||||
|
{ |
||||
|
public CompositionVisualCollection Children { get; } |
||||
|
internal CompositionContainerVisual(Compositor compositor, ServerCompositionContainerVisual server) : base(compositor, server) |
||||
|
{ |
||||
|
Children = new CompositionVisualCollection(this, server.Children); |
||||
|
} |
||||
|
|
||||
|
private protected override void OnRootChanged() |
||||
|
{ |
||||
|
foreach (var ch in Children) |
||||
|
ch.Root = Root; |
||||
|
base.OnRootChanged(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
internal class CustomDrawVisual<TData> : CompositionContainerVisual where TData : IEquatable<TData> |
||||
|
{ |
||||
|
private readonly ICustomDrawVisualHitTest<TData>? _hitTest; |
||||
|
|
||||
|
internal CustomDrawVisual(Compositor compositor, ICustomDrawVisualRenderer<TData> renderer, |
||||
|
ICustomDrawVisualHitTest<TData>? hitTest) : base(compositor, |
||||
|
new ServerCustomDrawVisual<TData>(compositor.Server, renderer)) |
||||
|
{ |
||||
|
_hitTest = hitTest; |
||||
|
} |
||||
|
|
||||
|
private TData? _data; |
||||
|
|
||||
|
static bool Eq(TData? left, TData? right) |
||||
|
{ |
||||
|
if (left == null && right == null) |
||||
|
return true; |
||||
|
if (left == null) |
||||
|
return false; |
||||
|
return left.Equals(right); |
||||
|
} |
||||
|
|
||||
|
public TData? Data |
||||
|
{ |
||||
|
get => _data; |
||||
|
set |
||||
|
{ |
||||
|
if (!Eq(_data, value)) |
||||
|
{ |
||||
|
((CustomDrawVisualChanges<TData?>) Changes).Data.Value = value; |
||||
|
_data = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private protected override IChangeSetPool ChangeSetPool => CustomDrawVisualChanges<TData>.Pool; |
||||
|
} |
||||
|
|
||||
|
public interface ICustomDrawVisualRenderer<TData> |
||||
|
{ |
||||
|
void Render(IDrawingContextImpl canvas, TData? data); |
||||
|
} |
||||
|
|
||||
|
public interface ICustomDrawVisualHitTest<TData> |
||||
|
{ |
||||
|
bool HitTest(TData data, Vector2 vector2); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Collections.Pooled; |
||||
|
using Avalonia.Rendering.SceneGraph; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Drawing; |
||||
|
|
||||
|
internal class CompositionDrawList : PooledList<IRef<IDrawOperation>> |
||||
|
{ |
||||
|
public CompositionDrawList() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public CompositionDrawList(int capacity) : base(capacity) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
foreach(var item in this) |
||||
|
item.Dispose(); |
||||
|
base.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public CompositionDrawList Clone() |
||||
|
{ |
||||
|
var clone = new CompositionDrawList(Count); |
||||
|
foreach (var r in this) |
||||
|
clone.Add(r.Clone()); |
||||
|
return clone; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class CompositionDrawListBuilder |
||||
|
{ |
||||
|
private CompositionDrawList? _operations; |
||||
|
private bool _owns; |
||||
|
|
||||
|
public void Reset(CompositionDrawList? previousOperations) |
||||
|
{ |
||||
|
_operations = previousOperations; |
||||
|
_owns = false; |
||||
|
} |
||||
|
|
||||
|
public CompositionDrawList DrawOperations => _operations ?? new CompositionDrawList(); |
||||
|
|
||||
|
void MakeWritable(int atIndex) |
||||
|
{ |
||||
|
if(_owns) |
||||
|
return; |
||||
|
_owns = true; |
||||
|
var newOps = new CompositionDrawList(_operations?.Count ?? Math.Max(1, atIndex)); |
||||
|
if (_operations != null) |
||||
|
{ |
||||
|
for (var c = 0; c < atIndex; c++) |
||||
|
newOps.Add(_operations[c].Clone()); |
||||
|
} |
||||
|
|
||||
|
_operations = newOps; |
||||
|
} |
||||
|
|
||||
|
public void ReplaceDrawOperation(int index, IDrawOperation node) |
||||
|
{ |
||||
|
MakeWritable(index); |
||||
|
DrawOperations.Add(RefCountable.Create(node)); |
||||
|
} |
||||
|
|
||||
|
public void AddDrawOperation(IDrawOperation node) |
||||
|
{ |
||||
|
MakeWritable(DrawOperations.Count); |
||||
|
DrawOperations.Add(RefCountable.Create(node)); |
||||
|
} |
||||
|
|
||||
|
public void TrimTo(int count) |
||||
|
{ |
||||
|
if (count < DrawOperations.Count) |
||||
|
DrawOperations.RemoveRange(count, DrawOperations.Count - count); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,383 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition.Drawing; |
||||
|
using Avalonia.Rendering.SceneGraph; |
||||
|
using Avalonia.Utilities; |
||||
|
using Avalonia.VisualTree; |
||||
|
namespace Avalonia.Rendering.Composition; |
||||
|
|
||||
|
internal class CompositionDrawingContext : IDrawingContextImpl |
||||
|
{ |
||||
|
private CompositionDrawListBuilder _builder = new(); |
||||
|
private int _drawOperationIndex; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Matrix Transform { get; set; } = Matrix.Identity; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Clear(Color color) |
||||
|
{ |
||||
|
// Cannot clear a deferred scene.
|
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
// Nothing to do here since we allocate no unmanaged resources.
|
||||
|
} |
||||
|
|
||||
|
public void BeginUpdate(CompositionDrawList list) |
||||
|
{ |
||||
|
_builder.Reset(list); |
||||
|
_drawOperationIndex = 0; |
||||
|
} |
||||
|
|
||||
|
public CompositionDrawList EndUpdate() |
||||
|
{ |
||||
|
_builder.TrimTo(_drawOperationIndex); |
||||
|
return _builder.DrawOperations!; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) |
||||
|
{ |
||||
|
var next = NextDrawAs<GeometryNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, brush, pen, geometry)) |
||||
|
{ |
||||
|
Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, |
||||
|
BitmapInterpolationMode bitmapInterpolationMode) |
||||
|
{ |
||||
|
var next = NextDrawAs<ImageNode>(); |
||||
|
|
||||
|
if (next == null || |
||||
|
!next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)) |
||||
|
{ |
||||
|
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void DrawBitmap(IRef<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(IPen pen, Point p1, Point p2) |
||||
|
{ |
||||
|
var next = NextDrawAs<LineNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) |
||||
|
{ |
||||
|
Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, |
||||
|
BoxShadows boxShadows = default) |
||||
|
{ |
||||
|
var next = NextDrawAs<RectangleNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows)) |
||||
|
{ |
||||
|
Add(new RectangleNode(Transform, brush, pen, rect, boxShadows, CreateChildScene(brush))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect) |
||||
|
{ |
||||
|
var next = NextDrawAs<ExperimentalAcrylicNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, material, rect)) |
||||
|
{ |
||||
|
Add(new ExperimentalAcrylicNode(Transform, material, rect)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect) |
||||
|
{ |
||||
|
var next = NextDrawAs<EllipseNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, brush, pen, rect)) |
||||
|
{ |
||||
|
Add(new EllipseNode(Transform, brush, pen, rect, CreateChildScene(brush))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Custom(ICustomDrawOperation custom) |
||||
|
{ |
||||
|
var next = NextDrawAs<CustomDrawOperation>(); |
||||
|
if (next == null || !next.Item.Equals(Transform, custom)) |
||||
|
Add(new CustomDrawOperation(custom, Transform)); |
||||
|
else |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) |
||||
|
{ |
||||
|
var next = NextDrawAs<GlyphRunNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) |
||||
|
{ |
||||
|
Add(new GlyphRunNode(Transform, foreground, glyphRun, CreateChildScene(foreground))); |
||||
|
} |
||||
|
|
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IDrawingContextLayerImpl CreateLayer(Size size) |
||||
|
{ |
||||
|
throw new NotSupportedException("Creating layers on a deferred drawing context not supported"); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PopClip() |
||||
|
{ |
||||
|
var next = NextDrawAs<ClipNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(null)) |
||||
|
{ |
||||
|
Add(new ClipNode()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PopGeometryClip() |
||||
|
{ |
||||
|
var next = NextDrawAs<GeometryClipNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(null)) |
||||
|
{ |
||||
|
Add(new GeometryClipNode()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PopBitmapBlendMode() |
||||
|
{ |
||||
|
var next = NextDrawAs<BitmapBlendModeNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(null)) |
||||
|
{ |
||||
|
Add(new BitmapBlendModeNode()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PopOpacity() |
||||
|
{ |
||||
|
var next = NextDrawAs<OpacityNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(null)) |
||||
|
{ |
||||
|
Add(new OpacityNode()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PopOpacityMask() |
||||
|
{ |
||||
|
var next = NextDrawAs<OpacityMaskNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(null, null)) |
||||
|
{ |
||||
|
Add(new OpacityMaskNode()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PushClip(Rect clip) |
||||
|
{ |
||||
|
var next = NextDrawAs<ClipNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, clip)) |
||||
|
{ |
||||
|
Add(new ClipNode(Transform, clip)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void PushClip(RoundedRect clip) |
||||
|
{ |
||||
|
var next = NextDrawAs<ClipNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, clip)) |
||||
|
{ |
||||
|
Add(new ClipNode(Transform, clip)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PushGeometryClip(IGeometryImpl? clip) |
||||
|
{ |
||||
|
if (clip is null) |
||||
|
return; |
||||
|
|
||||
|
var next = NextDrawAs<GeometryClipNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(Transform, clip)) |
||||
|
{ |
||||
|
Add(new GeometryClipNode(Transform, clip)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PushOpacity(double opacity) |
||||
|
{ |
||||
|
var next = NextDrawAs<OpacityNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(opacity)) |
||||
|
{ |
||||
|
Add(new OpacityNode(opacity)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PushOpacityMask(IBrush mask, Rect bounds) |
||||
|
{ |
||||
|
var next = NextDrawAs<OpacityMaskNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(mask, bounds)) |
||||
|
{ |
||||
|
Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) |
||||
|
{ |
||||
|
var next = NextDrawAs<BitmapBlendModeNode>(); |
||||
|
|
||||
|
if (next == null || !next.Item.Equals(blendingMode)) |
||||
|
{ |
||||
|
Add(new BitmapBlendModeNode(blendingMode)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void Add<T>(T node) where T : class, IDrawOperation |
||||
|
{ |
||||
|
if (_drawOperationIndex < _builder!.DrawOperations.Count) |
||||
|
{ |
||||
|
_builder.ReplaceDrawOperation(_drawOperationIndex, node); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_builder.AddDrawOperation(node); |
||||
|
} |
||||
|
|
||||
|
++_drawOperationIndex; |
||||
|
} |
||||
|
|
||||
|
private IRef<T>? NextDrawAs<T>() where T : class, IDrawOperation |
||||
|
{ |
||||
|
return _drawOperationIndex < _builder!.DrawOperations.Count |
||||
|
? _builder.DrawOperations[_drawOperationIndex] as IRef<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,120 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public enum CompositionBlendMode |
||||
|
{ |
||||
|
/// <summary>No regions are enabled. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_clr.svg)</summary>
|
||||
|
Clear, |
||||
|
|
||||
|
/// <summary>Only the source will be present. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src.svg)</summary>
|
||||
|
Src, |
||||
|
|
||||
|
/// <summary>Only the destination will be present. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst.svg)</summary>
|
||||
|
Dst, |
||||
|
|
||||
|
/// <summary>Source is placed over the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-over.svg)</summary>
|
||||
|
SrcOver, |
||||
|
|
||||
|
/// <summary>Destination is placed over the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-over.svg)</summary>
|
||||
|
DstOver, |
||||
|
|
||||
|
/// <summary>The source that overlaps the destination, replaces the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-in.svg)</summary>
|
||||
|
SrcIn, |
||||
|
|
||||
|
/// <summary>Destination which overlaps the source, replaces the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-in.svg)</summary>
|
||||
|
DstIn, |
||||
|
|
||||
|
/// <summary>Source is placed, where it falls outside of the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-out.svg)</summary>
|
||||
|
SrcOut, |
||||
|
|
||||
|
/// <summary>Destination is placed, where it falls outside of the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-out.svg)</summary>
|
||||
|
DstOut, |
||||
|
|
||||
|
/// <summary>Source which overlaps the destination, replaces the destination. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_src-atop.svg)</summary>
|
||||
|
SrcATop, |
||||
|
|
||||
|
/// <summary>Destination which overlaps the source replaces the source. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_dst-atop.svg)</summary>
|
||||
|
DstATop, |
||||
|
|
||||
|
/// <summary>The non-overlapping regions of source and destination are combined. [Porter Duff Compositing Operators] (https://drafts.fxtf.org/compositing-1/examples/PD_xor.svg)</summary>
|
||||
|
Xor, |
||||
|
|
||||
|
/// <summary>Display the sum of the source image and destination image. [Porter Duff Compositing Operators]</summary>
|
||||
|
Plus, |
||||
|
|
||||
|
/// <summary>Multiplies all components (= alpha and color). [Separable Blend Modes]</summary>
|
||||
|
Modulate, |
||||
|
|
||||
|
/// <summary>Multiplies the complements of the backdrop and source CompositionColorvalues, then complements the result. [Separable Blend Modes]</summary>
|
||||
|
Screen, |
||||
|
|
||||
|
/// <summary>Multiplies or screens the colors, depending on the backdrop CompositionColorvalue. [Separable Blend Modes]</summary>
|
||||
|
Overlay, |
||||
|
|
||||
|
/// <summary>Selects the darker of the backdrop and source colors. [Separable Blend Modes]</summary>
|
||||
|
Darken, |
||||
|
|
||||
|
/// <summary>Selects the lighter of the backdrop and source colors. [Separable Blend Modes]</summary>
|
||||
|
Lighten, |
||||
|
|
||||
|
/// <summary>Brightens the backdrop CompositionColorto reflect the source color. [Separable Blend Modes]</summary>
|
||||
|
ColorDodge, |
||||
|
|
||||
|
/// <summary>Darkens the backdrop CompositionColorto reflect the source color. [Separable Blend Modes]</summary>
|
||||
|
ColorBurn, |
||||
|
|
||||
|
/// <summary>Multiplies or screens the colors, depending on the source CompositionColorvalue. [Separable Blend Modes]</summary>
|
||||
|
HardLight, |
||||
|
|
||||
|
/// <summary>Darkens or lightens the colors, depending on the source CompositionColorvalue. [Separable Blend Modes]</summary>
|
||||
|
SoftLight, |
||||
|
|
||||
|
/// <summary>Subtracts the darker of the two constituent colors from the lighter color. [Separable Blend Modes]</summary>
|
||||
|
Difference, |
||||
|
|
||||
|
/// <summary>Produces an effect similar to that of the Difference mode but lower in contrast. [Separable Blend Modes]</summary>
|
||||
|
Exclusion, |
||||
|
|
||||
|
/// <summary>The source CompositionColoris multiplied by the destination CompositionColorand replaces the destination [Separable Blend Modes]</summary>
|
||||
|
Multiply, |
||||
|
|
||||
|
/// <summary>Creates a CompositionColorwith the hue of the source CompositionColorand the saturation and luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
|
||||
|
Hue, |
||||
|
|
||||
|
/// <summary>Creates a CompositionColorwith the saturation of the source CompositionColorand the hue and luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
|
||||
|
Saturation, |
||||
|
|
||||
|
/// <summary>Creates a CompositionColorwith the hue and saturation of the source CompositionColorand the luminosity of the backdrop color. [Non-Separable Blend Modes]</summary>
|
||||
|
Color, |
||||
|
|
||||
|
/// <summary>Creates a CompositionColorwith the luminosity of the source CompositionColorand the hue and saturation of the backdrop color. [Non-Separable Blend Modes]</summary>
|
||||
|
Luminosity, |
||||
|
} |
||||
|
|
||||
|
public enum CompositionGradientExtendMode |
||||
|
{ |
||||
|
Clamp, |
||||
|
Wrap, |
||||
|
Mirror |
||||
|
} |
||||
|
|
||||
|
[Flags] |
||||
|
public enum CompositionTileMode |
||||
|
{ |
||||
|
None = 0, |
||||
|
TileX = 1, |
||||
|
TileY = 2, |
||||
|
FlipX = 4, |
||||
|
FlipY = 8, |
||||
|
Tile = TileX | TileY, |
||||
|
Flip = FlipX | FlipY |
||||
|
} |
||||
|
|
||||
|
public enum CompositionStretch |
||||
|
{ |
||||
|
None = 0, |
||||
|
Fill = 1, |
||||
|
//TODO: Uniform, UniformToFill
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,234 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Rendering.Composition.Animations; |
||||
|
using Avalonia.Rendering.Composition.Utils; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal class BuiltInExpressionFfi : IExpressionForeignFunctionInterface |
||||
|
{ |
||||
|
private readonly DelegateExpressionFfi _registry; |
||||
|
|
||||
|
static float Lerp(float a, float b, float p) => p * (b - a) + a; |
||||
|
|
||||
|
static Matrix3x2 Inverse(Matrix3x2 m) |
||||
|
{ |
||||
|
Matrix3x2.Invert(m, out var r); |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
static Matrix4x4 Inverse(Matrix4x4 m) |
||||
|
{ |
||||
|
Matrix4x4.Invert(m, out var r); |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
static float SmoothStep(float edge0, float edge1, float x) |
||||
|
{ |
||||
|
var t = MathExt.Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); |
||||
|
return t * t * (3.0f - 2.0f * t); |
||||
|
} |
||||
|
|
||||
|
static Vector2 SmoothStep(Vector2 edge0, Vector2 edge1, Vector2 x) |
||||
|
{ |
||||
|
return new Vector2( |
||||
|
SmoothStep(edge0.X, edge1.X, x.X), |
||||
|
SmoothStep(edge0.Y, edge1.Y, x.Y) |
||||
|
|
||||
|
); |
||||
|
} |
||||
|
static Vector3 SmoothStep(Vector3 edge0, Vector3 edge1, Vector3 x) |
||||
|
{ |
||||
|
return new Vector3( |
||||
|
SmoothStep(edge0.X, edge1.X, x.X), |
||||
|
SmoothStep(edge0.Y, edge1.Y, x.Y), |
||||
|
SmoothStep(edge0.Z, edge1.Z, x.Z) |
||||
|
|
||||
|
); |
||||
|
} |
||||
|
|
||||
|
static Vector4 SmoothStep(Vector4 edge0, Vector4 edge1, Vector4 x) |
||||
|
{ |
||||
|
return new Vector4( |
||||
|
SmoothStep(edge0.X, edge1.X, x.X), |
||||
|
SmoothStep(edge0.Y, edge1.Y, x.Y), |
||||
|
SmoothStep(edge0.Z, edge1.Z, x.Z), |
||||
|
SmoothStep(edge0.W, edge1.W, x.W) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private BuiltInExpressionFfi() |
||||
|
{ |
||||
|
_registry = new DelegateExpressionFfi |
||||
|
{ |
||||
|
{"Abs", (float f) => Math.Abs(f)}, |
||||
|
{"Abs", (Vector2 v) => Vector2.Abs(v)}, |
||||
|
{"Abs", (Vector3 v) => Vector3.Abs(v)}, |
||||
|
{"Abs", (Vector4 v) => Vector4.Abs(v)}, |
||||
|
|
||||
|
{"ACos", (float f) => (float) Math.Acos(f)}, |
||||
|
{"ASin", (float f) => (float) Math.Asin(f)}, |
||||
|
{"ATan", (float f) => (float) Math.Atan(f)}, |
||||
|
{"Ceil", (float f) => (float) Math.Ceiling(f)}, |
||||
|
|
||||
|
{"Clamp", (float a1, float a2, float a3) => MathExt.Clamp(a1, a2, a3)}, |
||||
|
{"Clamp", (Vector2 a1, Vector2 a2, Vector2 a3) => Vector2.Clamp(a1, a2, a3)}, |
||||
|
{"Clamp", (Vector3 a1, Vector3 a2, Vector3 a3) => Vector3.Clamp(a1, a2, a3)}, |
||||
|
{"Clamp", (Vector4 a1, Vector4 a2, Vector4 a3) => Vector4.Clamp(a1, a2, a3)}, |
||||
|
|
||||
|
{"Concatenate", (Quaternion a1, Quaternion a2) => Quaternion.Concatenate(a1, a2)}, |
||||
|
{"Cos", (float a) => (float) Math.Cos(a)}, |
||||
|
|
||||
|
/* |
||||
|
TODO: |
||||
|
ColorHsl(Float h, Float s, Float l) |
||||
|
ColorLerpHSL(Color colorTo, CompositionColorcolorFrom, Float progress) |
||||
|
*/ |
||||
|
|
||||
|
{ |
||||
|
"ColorLerp", (Avalonia.Media.Color to, Avalonia.Media.Color from, float progress) => |
||||
|
ColorInterpolator.LerpRGB(to, from, progress) |
||||
|
}, |
||||
|
{ |
||||
|
"ColorLerpRGB", (Avalonia.Media.Color to, Avalonia.Media.Color from, float progress) => |
||||
|
ColorInterpolator.LerpRGB(to, from, progress) |
||||
|
}, |
||||
|
{ |
||||
|
"ColorRGB", (float a, float r, float g, float b) => Avalonia.Media.Color.FromArgb( |
||||
|
(byte) MathExt.Clamp(a, 0, 255), |
||||
|
(byte) MathExt.Clamp(r, 0, 255), |
||||
|
(byte) MathExt.Clamp(g, 0, 255), |
||||
|
(byte) MathExt.Clamp(b, 0, 255) |
||||
|
) |
||||
|
}, |
||||
|
|
||||
|
{"Distance", (Vector2 a1, Vector2 a2) => Vector2.Distance(a1, a2)}, |
||||
|
{"Distance", (Vector3 a1, Vector3 a2) => Vector3.Distance(a1, a2)}, |
||||
|
{"Distance", (Vector4 a1, Vector4 a2) => Vector4.Distance(a1, a2)}, |
||||
|
|
||||
|
{"DistanceSquared", (Vector2 a1, Vector2 a2) => Vector2.DistanceSquared(a1, a2)}, |
||||
|
{"DistanceSquared", (Vector3 a1, Vector3 a2) => Vector3.DistanceSquared(a1, a2)}, |
||||
|
{"DistanceSquared", (Vector4 a1, Vector4 a2) => Vector4.DistanceSquared(a1, a2)}, |
||||
|
|
||||
|
{"Floor", (float v) => (float) Math.Floor(v)}, |
||||
|
|
||||
|
{"Inverse", (Matrix3x2 v) => Inverse(v)}, |
||||
|
{"Inverse", (Matrix4x4 v) => Inverse(v)}, |
||||
|
|
||||
|
|
||||
|
{"Length", (Vector2 a1) => a1.Length()}, |
||||
|
{"Length", (Vector3 a1) => a1.Length()}, |
||||
|
{"Length", (Vector4 a1) => a1.Length()}, |
||||
|
{"Length", (Quaternion a1) => a1.Length()}, |
||||
|
|
||||
|
{"LengthSquared", (Vector2 a1) => a1.LengthSquared()}, |
||||
|
{"LengthSquared", (Vector3 a1) => a1.LengthSquared()}, |
||||
|
{"LengthSquared", (Vector4 a1) => a1.LengthSquared()}, |
||||
|
{"LengthSquared", (Quaternion a1) => a1.LengthSquared()}, |
||||
|
|
||||
|
{"Lerp", (float a1, float a2, float a3) => Lerp(a1, a2, a3)}, |
||||
|
{"Lerp", (Vector2 a1, Vector2 a2, float a3) => Vector2.Lerp(a1, a2, a3)}, |
||||
|
{"Lerp", (Vector3 a1, Vector3 a2, float a3) => Vector3.Lerp(a1, a2, a3)}, |
||||
|
{"Lerp", (Vector4 a1, Vector4 a2, float a3) => Vector4.Lerp(a1, a2, a3)}, |
||||
|
|
||||
|
|
||||
|
{"Ln", (float f) => (float) Math.Log(f)}, |
||||
|
{"Log10", (float f) => (float) Math.Log10(f)}, |
||||
|
|
||||
|
{"Matrix3x2.CreateFromScale", (Vector2 v) => Matrix3x2.CreateScale(v)}, |
||||
|
{"Matrix3x2.CreateFromTranslation", (Vector2 v) => Matrix3x2.CreateTranslation(v)}, |
||||
|
{"Matrix3x2.CreateRotation", (float v) => Matrix3x2.CreateRotation(v)}, |
||||
|
{"Matrix3x2.CreateScale", (Vector2 v) => Matrix3x2.CreateScale(v)}, |
||||
|
{"Matrix3x2.CreateSkew", (float a1, float a2, Vector2 a3) => Matrix3x2.CreateSkew(a1, a2, a3)}, |
||||
|
{"Matrix3x2.CreateTranslation", (Vector2 v) => Matrix3x2.CreateScale(v)}, |
||||
|
{ |
||||
|
"Matrix3x2", (float m11, float m12, float m21, float m22, float m31, float m32) => |
||||
|
new Matrix3x2(m11, m12, m21, m22, m31, m32) |
||||
|
}, |
||||
|
{"Matrix4x4.CreateFromAxisAngle", (Vector3 v, float angle) => Matrix4x4.CreateFromAxisAngle(v, angle)}, |
||||
|
{"Matrix4x4.CreateFromScale", (Vector3 v) => Matrix4x4.CreateScale(v)}, |
||||
|
{"Matrix4x4.CreateFromTranslation", (Vector3 v) => Matrix4x4.CreateTranslation(v)}, |
||||
|
{"Matrix4x4.CreateScale", (Vector3 v) => Matrix4x4.CreateScale(v)}, |
||||
|
{"Matrix4x4.CreateTranslation", (Vector3 v) => Matrix4x4.CreateScale(v)}, |
||||
|
{"Matrix4x4", (Matrix3x2 m) => new Matrix4x4(m)}, |
||||
|
{ |
||||
|
"Matrix4x4", |
||||
|
(float m11, float m12, float m13, float m14, |
||||
|
float m21, float m22, float m23, float m24, |
||||
|
float m31, float m32, float m33, float m34, |
||||
|
float m41, float m42, float m43, float m44) => |
||||
|
new Matrix4x4( |
||||
|
m11, m12, m13, m14, |
||||
|
m21, m22, m23, m24, |
||||
|
m31, m32, m33, m34, |
||||
|
m41, m42, m43, m44) |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
{"Max", (float a1, float a2) => Math.Max(a1, a2)}, |
||||
|
{"Max", (Vector2 a1, Vector2 a2) => Vector2.Max(a1, a2)}, |
||||
|
{"Max", (Vector3 a1, Vector3 a2) => Vector3.Max(a1, a2)}, |
||||
|
{"Max", (Vector4 a1, Vector4 a2) => Vector4.Max(a1, a2)}, |
||||
|
|
||||
|
|
||||
|
{"Min", (float a1, float a2) => Math.Min(a1, a2)}, |
||||
|
{"Min", (Vector2 a1, Vector2 a2) => Vector2.Min(a1, a2)}, |
||||
|
{"Min", (Vector3 a1, Vector3 a2) => Vector3.Min(a1, a2)}, |
||||
|
{"Min", (Vector4 a1, Vector4 a2) => Vector4.Min(a1, a2)}, |
||||
|
|
||||
|
{"Mod", (float a, float b) => a % b}, |
||||
|
|
||||
|
{"Normalize", (Quaternion a) => Quaternion.Normalize(a)}, |
||||
|
{"Normalize", (Vector2 a) => Vector2.Normalize(a)}, |
||||
|
{"Normalize", (Vector3 a) => Vector3.Normalize(a)}, |
||||
|
{"Normalize", (Vector4 a) => Vector4.Normalize(a)}, |
||||
|
|
||||
|
{"Pow", (float a, float b) => (float) Math.Pow(a, b)}, |
||||
|
{"Quaternion.CreateFromAxisAngle", (Vector3 a, float b) => Quaternion.CreateFromAxisAngle(a, b)}, |
||||
|
{"Quaternion", (float a, float b, float c, float d) => new Quaternion(a, b, c, d)}, |
||||
|
|
||||
|
{"Round", (float a) => (float) Math.Round(a)}, |
||||
|
|
||||
|
{"Scale", (Matrix3x2 a, float b) => a * b}, |
||||
|
{"Scale", (Matrix4x4 a, float b) => a * b}, |
||||
|
{"Scale", (Vector2 a, float b) => a * b}, |
||||
|
{"Scale", (Vector3 a, float b) => a * b}, |
||||
|
{"Scale", (Vector4 a, float b) => a * b}, |
||||
|
|
||||
|
{"Sin", (float a) => (float) Math.Sin(a)}, |
||||
|
|
||||
|
{"SmoothStep", (float a1, float a2, float a3) => SmoothStep(a1, a2, a3)}, |
||||
|
{"SmoothStep", (Vector2 a1, Vector2 a2, Vector2 a3) => SmoothStep(a1, a2, a3)}, |
||||
|
{"SmoothStep", (Vector3 a1, Vector3 a2, Vector3 a3) => SmoothStep(a1, a2, a3)}, |
||||
|
{"SmoothStep", (Vector4 a1, Vector4 a2, Vector4 a3) => SmoothStep(a1, a2, a3)}, |
||||
|
|
||||
|
// I have no idea how to do a spherical interpolation for a scalar value, so we are doing a linear one
|
||||
|
{"Slerp", (float a1, float a2, float a3) => Lerp(a1, a2, a3)}, |
||||
|
{"Slerp", (Quaternion a1, Quaternion a2, float a3) => Quaternion.Slerp(a1, a2, a3)}, |
||||
|
|
||||
|
{"Sqrt", (float a) => (float) Math.Sqrt(a)}, |
||||
|
{"Square", (float a) => a * a}, |
||||
|
{"Tan", (float a) => (float) Math.Tan(a)}, |
||||
|
|
||||
|
{"ToRadians", (float a) => (float) (a * Math.PI / 180)}, |
||||
|
{"ToDegrees", (float a) => (float) (a * 180d / Math.PI)}, |
||||
|
|
||||
|
{"Transform", (Vector2 a, Matrix3x2 b) => Vector2.Transform(a, b)}, |
||||
|
{"Transform", (Vector3 a, Matrix4x4 b) => Vector3.Transform(a, b)}, |
||||
|
|
||||
|
{"Vector2", (float a, float b) => new Vector2(a, b)}, |
||||
|
{"Vector3", (float a, float b, float c) => new Vector3(a, b, c)}, |
||||
|
{"Vector3", (Vector2 v2, float z) => new Vector3(v2, z)}, |
||||
|
{"Vector4", (float a, float b, float c, float d) => new Vector4(a, b, c, d)}, |
||||
|
{"Vector4", (Vector2 v2, float z, float w) => new Vector4(v2, z, w)}, |
||||
|
{"Vector4", (Vector3 v3, float w) => new Vector4(v3, w)}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result) => |
||||
|
_registry.Call(name, arguments, out result); |
||||
|
|
||||
|
public static BuiltInExpressionFfi Instance { get; } = new BuiltInExpressionFfi(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,181 @@ |
|||||
|
using System; |
||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal class DelegateExpressionFfi : IExpressionForeignFunctionInterface, IEnumerable |
||||
|
{ |
||||
|
struct FfiRecord |
||||
|
{ |
||||
|
public VariantType[] Types; |
||||
|
public Func<IReadOnlyList<ExpressionVariant>, ExpressionVariant> Delegate; |
||||
|
} |
||||
|
|
||||
|
private readonly Dictionary<string, Dictionary<int, List<FfiRecord>>> |
||||
|
_registry = new Dictionary<string, Dictionary<int, List<FfiRecord>>>(); |
||||
|
|
||||
|
public bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result) |
||||
|
{ |
||||
|
result = default; |
||||
|
if (!_registry.TryGetValue(name, out var nameGroup)) |
||||
|
return false; |
||||
|
if (!nameGroup.TryGetValue(arguments.Count, out var countGroup)) |
||||
|
return false; |
||||
|
foreach (var record in countGroup) |
||||
|
{ |
||||
|
var match = true; |
||||
|
for (var c = 0; c < arguments.Count; c++) |
||||
|
{ |
||||
|
if (record.Types[c] != arguments[c].Type) |
||||
|
{ |
||||
|
match = false; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (match) |
||||
|
{ |
||||
|
result = record.Delegate(arguments); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// Stub for collection initializer
|
||||
|
IEnumerator IEnumerable.GetEnumerator() => Array.Empty<object>().GetEnumerator(); |
||||
|
|
||||
|
void Add(string name, Func<IReadOnlyList<ExpressionVariant>, ExpressionVariant> cb, |
||||
|
params Type[] types) |
||||
|
{ |
||||
|
if (!_registry.TryGetValue(name, out var nameGroup)) |
||||
|
_registry[name] = nameGroup = |
||||
|
new Dictionary<int, List<FfiRecord>>(); |
||||
|
if (!nameGroup.TryGetValue(types.Length, out var countGroup)) |
||||
|
nameGroup[types.Length] = countGroup = new List<FfiRecord>(); |
||||
|
|
||||
|
countGroup.Add(new FfiRecord |
||||
|
{ |
||||
|
Types = types.Select(t => TypeMap[t]).ToArray(), |
||||
|
Delegate = cb |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
static readonly Dictionary<Type, VariantType> TypeMap = new Dictionary<Type, VariantType> |
||||
|
{ |
||||
|
[typeof(bool)] = VariantType.Boolean, |
||||
|
[typeof(float)] = VariantType.Scalar, |
||||
|
[typeof(Vector2)] = VariantType.Vector2, |
||||
|
[typeof(Vector3)] = VariantType.Vector3, |
||||
|
[typeof(Vector4)] = VariantType.Vector4, |
||||
|
[typeof(Matrix3x2)] = VariantType.Matrix3x2, |
||||
|
[typeof(Matrix4x4)] = VariantType.Matrix4x4, |
||||
|
[typeof(Quaternion)] = VariantType.Quaternion, |
||||
|
[typeof(Color)] = VariantType.Color |
||||
|
}; |
||||
|
|
||||
|
public void Add<T1>(string name, Func<T1, ExpressionVariant> cb) where T1 : struct |
||||
|
{ |
||||
|
Add(name, args => cb(args[0].CastOrDefault<T1>()), typeof(T1)); |
||||
|
} |
||||
|
|
||||
|
public void Add<T1, T2>(string name, Func<T1, T2, ExpressionVariant> cb) where T1 : struct where T2 : struct |
||||
|
{ |
||||
|
Add(name, args => cb(args[0].CastOrDefault<T1>(), args[1].CastOrDefault<T2>()), typeof(T1), typeof(T2)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void Add<T1, T2, T3>(string name, Func<T1, T2, T3, ExpressionVariant> cb) |
||||
|
where T1 : struct where T2 : struct where T3 : struct |
||||
|
{ |
||||
|
Add(name, args => cb(args[0].CastOrDefault<T1>(), args[1].CastOrDefault<T2>(), args[2].CastOrDefault<T3>()), typeof(T1), typeof(T2), |
||||
|
typeof(T3)); |
||||
|
} |
||||
|
|
||||
|
public void Add<T1, T2, T3, T4>(string name, Func<T1, T2, T3, T4, ExpressionVariant> cb) |
||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct |
||||
|
{ |
||||
|
Add(name, args => cb( |
||||
|
args[0].CastOrDefault<T1>(), |
||||
|
args[1].CastOrDefault<T2>(), |
||||
|
args[2].CastOrDefault<T3>(), |
||||
|
args[3].CastOrDefault<T4>()), |
||||
|
typeof(T1), typeof(T2), typeof(T3), typeof(T4)); |
||||
|
} |
||||
|
|
||||
|
public void Add<T1, T2, T3, T4, T5>(string name, Func<T1, T2, T3, T4, T5, ExpressionVariant> cb) |
||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct |
||||
|
{ |
||||
|
Add(name, args => cb( |
||||
|
args[0].CastOrDefault<T1>(), |
||||
|
args[1].CastOrDefault<T2>(), |
||||
|
args[2].CastOrDefault<T3>(), |
||||
|
args[3].CastOrDefault<T4>(), |
||||
|
args[4].CastOrDefault<T5>()), |
||||
|
typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)); |
||||
|
} |
||||
|
|
||||
|
public void Add<T1, T2, T3, T4, T5, T6>(string name, Func<T1, T2, T3, T4, T5, T6, ExpressionVariant> cb) |
||||
|
where T1 : struct where T2 : struct where T3 : struct where T4 : struct where T5 : struct where T6 : struct |
||||
|
{ |
||||
|
Add(name, args => cb( |
||||
|
args[0].CastOrDefault<T1>(), |
||||
|
args[1].CastOrDefault<T2>(), |
||||
|
args[2].CastOrDefault<T3>(), |
||||
|
args[3].CastOrDefault<T4>(), |
||||
|
args[4].CastOrDefault<T5>(), |
||||
|
args[4].CastOrDefault<T6>()), |
||||
|
typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public void Add<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(string name, |
||||
|
Func<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, ExpressionVariant> cb) |
||||
|
where T1 : struct |
||||
|
where T2 : struct |
||||
|
where T3 : struct |
||||
|
where T4 : struct |
||||
|
where T5 : struct |
||||
|
where T6 : struct |
||||
|
where T7 : struct |
||||
|
where T8 : struct |
||||
|
where T9 : struct |
||||
|
where T10 : struct |
||||
|
where T11 : struct |
||||
|
where T12 : struct |
||||
|
where T13 : struct |
||||
|
where T14 : struct |
||||
|
where T15 : struct |
||||
|
where T16 : struct |
||||
|
{ |
||||
|
Add(name, args => cb( |
||||
|
args[0].CastOrDefault<T1>(), |
||||
|
args[1].CastOrDefault<T2>(), |
||||
|
args[2].CastOrDefault<T3>(), |
||||
|
args[3].CastOrDefault<T4>(), |
||||
|
args[4].CastOrDefault<T5>(), |
||||
|
args[4].CastOrDefault<T6>(), |
||||
|
args[4].CastOrDefault<T7>(), |
||||
|
args[4].CastOrDefault<T8>(), |
||||
|
args[4].CastOrDefault<T9>(), |
||||
|
args[4].CastOrDefault<T10>(), |
||||
|
args[4].CastOrDefault<T11>(), |
||||
|
args[4].CastOrDefault<T12>(), |
||||
|
args[4].CastOrDefault<T13>(), |
||||
|
args[4].CastOrDefault<T14>(), |
||||
|
args[4].CastOrDefault<T15>(), |
||||
|
args[4].CastOrDefault<T16>() |
||||
|
), |
||||
|
typeof(T1), typeof(T2), typeof(T3), typeof(T4), |
||||
|
typeof(T5), typeof(T6), typeof(T7), typeof(T8), |
||||
|
typeof(T9), typeof(T10), typeof(T11), typeof(T12), |
||||
|
typeof(T13), typeof(T14), typeof(T15), typeof(T16) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,331 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal abstract class Expression |
||||
|
{ |
||||
|
public abstract ExpressionType Type { get; } |
||||
|
public static Expression Parse(string expression) |
||||
|
{ |
||||
|
return ExpressionParser.Parse(expression.AsSpan()); |
||||
|
} |
||||
|
|
||||
|
public abstract ExpressionVariant Evaluate(ref ExpressionEvaluationContext context); |
||||
|
|
||||
|
protected abstract string Print(); |
||||
|
public override string ToString() => Print(); |
||||
|
|
||||
|
internal static string OperatorName(ExpressionType t) |
||||
|
{ |
||||
|
var attr = typeof(ExpressionType).GetMember(t.ToString())[0] |
||||
|
.GetCustomAttribute<PrettyPrintStringAttribute>(); |
||||
|
if (attr != null) |
||||
|
return attr.Name; |
||||
|
return t.ToString(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class PrettyPrintStringAttribute : Attribute |
||||
|
{ |
||||
|
public string Name { get; } |
||||
|
|
||||
|
public PrettyPrintStringAttribute(string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal enum ExpressionType |
||||
|
{ |
||||
|
// Binary operators
|
||||
|
[PrettyPrintString("+")] |
||||
|
Add, |
||||
|
[PrettyPrintString("-")] |
||||
|
Subtract, |
||||
|
[PrettyPrintString("/")] |
||||
|
Divide, |
||||
|
[PrettyPrintString("*")] |
||||
|
Multiply, |
||||
|
[PrettyPrintString(">")] |
||||
|
MoreThan, |
||||
|
[PrettyPrintString("<")] |
||||
|
LessThan, |
||||
|
[PrettyPrintString(">=")] |
||||
|
MoreThanOrEqual, |
||||
|
[PrettyPrintString("<=")] |
||||
|
LessThanOrEqual, |
||||
|
[PrettyPrintString("&&")] |
||||
|
LogicalAnd, |
||||
|
[PrettyPrintString("||")] |
||||
|
LogicalOr, |
||||
|
[PrettyPrintString("%")] |
||||
|
Remainder, |
||||
|
[PrettyPrintString("==")] |
||||
|
Equals, |
||||
|
[PrettyPrintString("!=")] |
||||
|
NotEquals, |
||||
|
// Unary operators
|
||||
|
[PrettyPrintString("!")] |
||||
|
Not, |
||||
|
[PrettyPrintString("-")] |
||||
|
UnaryMinus, |
||||
|
// The rest
|
||||
|
MemberAccess, |
||||
|
Parameter, |
||||
|
FunctionCall, |
||||
|
Keyword, |
||||
|
Constant, |
||||
|
ConditionalExpression |
||||
|
} |
||||
|
|
||||
|
internal enum ExpressionKeyword |
||||
|
{ |
||||
|
StartingValue, |
||||
|
CurrentValue, |
||||
|
FinalValue, |
||||
|
Target, |
||||
|
Pi, |
||||
|
True, |
||||
|
False |
||||
|
} |
||||
|
|
||||
|
internal class ConditionalExpression : Expression |
||||
|
{ |
||||
|
public Expression Condition { get; } |
||||
|
public Expression TruePart { get; } |
||||
|
public Expression FalsePart { get; } |
||||
|
public override ExpressionType Type => ExpressionType.ConditionalExpression; |
||||
|
|
||||
|
public ConditionalExpression(Expression condition, Expression truePart, Expression falsePart) |
||||
|
{ |
||||
|
Condition = condition; |
||||
|
TruePart = truePart; |
||||
|
FalsePart = falsePart; |
||||
|
} |
||||
|
|
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) |
||||
|
{ |
||||
|
var cond = Condition.Evaluate(ref context); |
||||
|
if (cond.Type == VariantType.Boolean && cond.Boolean) |
||||
|
return TruePart.Evaluate(ref context); |
||||
|
return FalsePart.Evaluate(ref context); |
||||
|
} |
||||
|
|
||||
|
protected override string Print() => $"({Condition}) ? ({TruePart}) : ({FalsePart})"; |
||||
|
} |
||||
|
|
||||
|
internal class ConstantExpression : Expression |
||||
|
{ |
||||
|
public float Constant { get; } |
||||
|
public override ExpressionType Type => ExpressionType.Constant; |
||||
|
|
||||
|
public ConstantExpression(float constant) |
||||
|
{ |
||||
|
Constant = constant; |
||||
|
} |
||||
|
|
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) => Constant; |
||||
|
protected override string Print() => Constant.ToString(CultureInfo.InvariantCulture); |
||||
|
} |
||||
|
|
||||
|
internal class FunctionCallExpression : Expression |
||||
|
{ |
||||
|
public string Name { get; } |
||||
|
public List<Expression> Parameters { get; } |
||||
|
public override ExpressionType Type => ExpressionType.FunctionCall; |
||||
|
|
||||
|
public FunctionCallExpression(string name, List<Expression> parameters) |
||||
|
{ |
||||
|
Name = name; |
||||
|
Parameters = parameters; |
||||
|
} |
||||
|
|
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) |
||||
|
{ |
||||
|
if (context.ForeignFunctionInterface == null) |
||||
|
return default; |
||||
|
var args = new List<ExpressionVariant>(); |
||||
|
foreach (var expr in Parameters) |
||||
|
args.Add(expr.Evaluate(ref context)); |
||||
|
if (!context.ForeignFunctionInterface.Call(Name, args, out var res)) |
||||
|
return default; |
||||
|
return res; |
||||
|
} |
||||
|
|
||||
|
protected override string Print() |
||||
|
{ |
||||
|
return Name + "( (" + string.Join("), (", Parameters) + ") )"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class MemberAccessExpression : Expression |
||||
|
{ |
||||
|
public override ExpressionType Type => ExpressionType.MemberAccess; |
||||
|
public Expression Target { get; } |
||||
|
public string Member { get; } |
||||
|
|
||||
|
public MemberAccessExpression(Expression target, string member) |
||||
|
{ |
||||
|
Target = target; |
||||
|
Member = string.Intern(member); |
||||
|
} |
||||
|
|
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) |
||||
|
{ |
||||
|
if (Target is KeywordExpression ke |
||||
|
&& ke.Keyword == ExpressionKeyword.Target) |
||||
|
return context.Target.GetProperty(Member); |
||||
|
if (Target is ParameterExpression pe) |
||||
|
{ |
||||
|
var obj = context.Parameters?.GetObjectParameter(pe.Name); |
||||
|
if (obj != null) |
||||
|
return obj.GetProperty(Member); |
||||
|
} |
||||
|
|
||||
|
return Target.Evaluate(ref context).GetProperty(Member); |
||||
|
} |
||||
|
|
||||
|
protected override string Print() |
||||
|
{ |
||||
|
return "(" + Target.ToString() + ")." + Member; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class ParameterExpression : Expression |
||||
|
{ |
||||
|
public string Name { get; } |
||||
|
public override ExpressionType Type => ExpressionType.Parameter; |
||||
|
|
||||
|
public ParameterExpression(string name) |
||||
|
{ |
||||
|
Name = name; |
||||
|
} |
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) |
||||
|
{ |
||||
|
return context.Parameters?.GetParameter(Name) ?? default; |
||||
|
} |
||||
|
|
||||
|
protected override string Print() |
||||
|
{ |
||||
|
return "{" + Name + "}"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class KeywordExpression : Expression |
||||
|
{ |
||||
|
public override ExpressionType Type => ExpressionType.Keyword; |
||||
|
public ExpressionKeyword Keyword { get; } |
||||
|
|
||||
|
public KeywordExpression(ExpressionKeyword keyword) |
||||
|
{ |
||||
|
Keyword = keyword; |
||||
|
} |
||||
|
|
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) |
||||
|
{ |
||||
|
if (Keyword == ExpressionKeyword.StartingValue) |
||||
|
return context.StartingValue; |
||||
|
if (Keyword == ExpressionKeyword.CurrentValue) |
||||
|
return context.CurrentValue; |
||||
|
if (Keyword == ExpressionKeyword.FinalValue) |
||||
|
return context.FinalValue; |
||||
|
if (Keyword == ExpressionKeyword.Target) |
||||
|
// should be handled by MemberAccess
|
||||
|
return default; |
||||
|
if (Keyword == ExpressionKeyword.True) |
||||
|
return true; |
||||
|
if (Keyword == ExpressionKeyword.False) |
||||
|
return false; |
||||
|
if (Keyword == ExpressionKeyword.Pi) |
||||
|
return (float) Math.PI; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
protected override string Print() |
||||
|
{ |
||||
|
return "[" + Keyword + "]"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class UnaryExpression : Expression |
||||
|
{ |
||||
|
public Expression Parameter { get; } |
||||
|
public override ExpressionType Type { get; } |
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) |
||||
|
{ |
||||
|
if (Type == ExpressionType.Not) |
||||
|
return !Parameter.Evaluate(ref context); |
||||
|
if (Type == ExpressionType.UnaryMinus) |
||||
|
return -Parameter.Evaluate(ref context); |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
protected override string Print() |
||||
|
{ |
||||
|
return OperatorName(Type) + Parameter; |
||||
|
} |
||||
|
|
||||
|
public UnaryExpression(Expression parameter, ExpressionType type) |
||||
|
{ |
||||
|
Parameter = parameter; |
||||
|
Type = type; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class BinaryExpression : Expression |
||||
|
{ |
||||
|
public Expression Left { get; } |
||||
|
public Expression Right { get; } |
||||
|
public override ExpressionType Type { get; } |
||||
|
public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) |
||||
|
{ |
||||
|
var left = Left.Evaluate(ref context); |
||||
|
var right = Right.Evaluate(ref context); |
||||
|
if (Type == ExpressionType.Add) |
||||
|
return left + right; |
||||
|
if (Type == ExpressionType.Subtract) |
||||
|
return left - right; |
||||
|
if (Type == ExpressionType.Multiply) |
||||
|
return left * right; |
||||
|
if (Type == ExpressionType.Divide) |
||||
|
return left / right; |
||||
|
if (Type == ExpressionType.Remainder) |
||||
|
return left % right; |
||||
|
if (Type == ExpressionType.MoreThan) |
||||
|
return left > right; |
||||
|
if (Type == ExpressionType.LessThan) |
||||
|
return left < right; |
||||
|
if (Type == ExpressionType.MoreThanOrEqual) |
||||
|
return left > right; |
||||
|
if (Type == ExpressionType.LessThanOrEqual) |
||||
|
return left < right; |
||||
|
if (Type == ExpressionType.LogicalAnd) |
||||
|
return left.And(right); |
||||
|
if (Type == ExpressionType.LogicalOr) |
||||
|
return left.Or(right); |
||||
|
if (Type == ExpressionType.Equals) |
||||
|
return left.EqualsTo(right); |
||||
|
if (Type == ExpressionType.NotEquals) |
||||
|
return left.NotEqualsTo(right); |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
protected override string Print() |
||||
|
{ |
||||
|
return "(" + Left + OperatorName(Type) + Right + ")"; |
||||
|
} |
||||
|
|
||||
|
public BinaryExpression(Expression left, Expression right, ExpressionType type) |
||||
|
{ |
||||
|
Left = left; |
||||
|
Right = right; |
||||
|
Type = type; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal struct ExpressionEvaluationContext |
||||
|
{ |
||||
|
public ExpressionVariant StartingValue { get; set; } |
||||
|
public ExpressionVariant CurrentValue { get; set; } |
||||
|
public ExpressionVariant FinalValue { get; set; } |
||||
|
public IExpressionObject Target { get; set; } |
||||
|
public IExpressionParameterCollection Parameters { get; set; } |
||||
|
public IExpressionForeignFunctionInterface ForeignFunctionInterface { get; set; } |
||||
|
} |
||||
|
|
||||
|
internal interface IExpressionObject |
||||
|
{ |
||||
|
ExpressionVariant GetProperty(string name); |
||||
|
} |
||||
|
|
||||
|
internal interface IExpressionParameterCollection |
||||
|
{ |
||||
|
public ExpressionVariant GetParameter(string name); |
||||
|
|
||||
|
public IExpressionObject GetObjectParameter(string name); |
||||
|
} |
||||
|
|
||||
|
internal interface IExpressionForeignFunctionInterface |
||||
|
{ |
||||
|
bool Call(string name, IReadOnlyList<ExpressionVariant> arguments, out ExpressionVariant result); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal class ExpressionParseException : Exception |
||||
|
{ |
||||
|
public int Position { get; } |
||||
|
|
||||
|
public ExpressionParseException(string message, int position) : base(message) |
||||
|
{ |
||||
|
Position = position; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,298 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Linq; |
||||
|
|
||||
|
// ReSharper disable StringLiteralTypo
|
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal class ExpressionParser |
||||
|
{ |
||||
|
public static Expression Parse(ReadOnlySpan<char> s) |
||||
|
{ |
||||
|
var p = new TokenParser(s); |
||||
|
var parsed = ParseTillTerminator(ref p, "", false, false, out _); |
||||
|
p.SkipWhitespace(); |
||||
|
if (p.Length != 0) |
||||
|
throw new ExpressionParseException("Unexpected data ", p.Position); |
||||
|
return parsed; |
||||
|
} |
||||
|
|
||||
|
private static ReadOnlySpan<char> Dot => ".".AsSpan(); |
||||
|
static bool TryParseAtomic(ref TokenParser parser, |
||||
|
[MaybeNullWhen(returnValue: false)] out Expression expr) |
||||
|
{ |
||||
|
// We can parse keywords, parameter names and constants
|
||||
|
expr = null; |
||||
|
if (parser.TryParseKeywordLowerCase("this.startingvalue")) |
||||
|
expr = new KeywordExpression(ExpressionKeyword.StartingValue); |
||||
|
else if(parser.TryParseKeywordLowerCase("this.currentvalue")) |
||||
|
expr = new KeywordExpression(ExpressionKeyword.CurrentValue); |
||||
|
else if(parser.TryParseKeywordLowerCase("this.finalvalue")) |
||||
|
expr = new KeywordExpression(ExpressionKeyword.FinalValue); |
||||
|
else if(parser.TryParseKeywordLowerCase("pi")) |
||||
|
expr = new KeywordExpression(ExpressionKeyword.Pi); |
||||
|
else if(parser.TryParseKeywordLowerCase("true")) |
||||
|
expr = new KeywordExpression(ExpressionKeyword.True); |
||||
|
else if(parser.TryParseKeywordLowerCase("false")) |
||||
|
expr = new KeywordExpression(ExpressionKeyword.False); |
||||
|
else if (parser.TryParseKeywordLowerCase("this.target")) |
||||
|
expr = new KeywordExpression(ExpressionKeyword.Target); |
||||
|
|
||||
|
if (expr != null) |
||||
|
return true; |
||||
|
|
||||
|
if (parser.TryParseIdentifier(out var identifier)) |
||||
|
{ |
||||
|
expr = new ParameterExpression(identifier.ToString()); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if(parser.TryParseFloat(out var scalar)) |
||||
|
{ |
||||
|
expr = new ConstantExpression(scalar); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
static bool TryParseOperator(ref TokenParser parser, out ExpressionType op) |
||||
|
{ |
||||
|
op = (ExpressionType) (-1); |
||||
|
if (parser.TryConsume("||")) |
||||
|
op = ExpressionType.LogicalOr; |
||||
|
else if (parser.TryConsume("&&")) |
||||
|
op = ExpressionType.LogicalAnd; |
||||
|
else if (parser.TryConsume(">=")) |
||||
|
op = ExpressionType.MoreThanOrEqual; |
||||
|
else if (parser.TryConsume("<=")) |
||||
|
op = ExpressionType.LessThanOrEqual; |
||||
|
else if (parser.TryConsume("==")) |
||||
|
op = ExpressionType.Equals; |
||||
|
else if (parser.TryConsume("!=")) |
||||
|
op = ExpressionType.NotEquals; |
||||
|
else if (parser.TryConsumeAny("+-/*><%".AsSpan(), out var sop)) |
||||
|
{ |
||||
|
#pragma warning disable CS8509
|
||||
|
op = sop switch |
||||
|
#pragma warning restore CS8509
|
||||
|
{ |
||||
|
'+' => ExpressionType.Add, |
||||
|
'-' => ExpressionType.Subtract, |
||||
|
'/' => ExpressionType.Divide, |
||||
|
'*' => ExpressionType.Multiply, |
||||
|
'<' => ExpressionType.LessThan, |
||||
|
'>' => ExpressionType.MoreThan, |
||||
|
'%' => ExpressionType.Remainder |
||||
|
}; |
||||
|
} |
||||
|
else |
||||
|
return false; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
struct ExpressionOperatorGroup |
||||
|
{ |
||||
|
private List<Expression> _expressions; |
||||
|
private List<ExpressionType> _operators; |
||||
|
private Expression? _first; |
||||
|
|
||||
|
public bool NotEmpty => !Empty; |
||||
|
public bool Empty => _expressions == null && _first == null; |
||||
|
|
||||
|
public void AppendFirst(Expression expr) |
||||
|
{ |
||||
|
if (NotEmpty) |
||||
|
throw new InvalidOperationException(); |
||||
|
_first = expr; |
||||
|
} |
||||
|
|
||||
|
public void AppendWithOperator(Expression expr, ExpressionType op) |
||||
|
{ |
||||
|
if (_expressions == null) |
||||
|
{ |
||||
|
if (_first == null) |
||||
|
throw new InvalidOperationException(); |
||||
|
_expressions = new List<Expression>(); |
||||
|
_expressions.Add(_first); |
||||
|
_first = null; |
||||
|
_operators = new List<ExpressionType>(); |
||||
|
} |
||||
|
_expressions.Add(expr); |
||||
|
_operators.Add(op); |
||||
|
} |
||||
|
|
||||
|
// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/
|
||||
|
private static readonly ExpressionType[][] OperatorPrecedenceGroups = new[] |
||||
|
{ |
||||
|
// multiplicative
|
||||
|
new[] {ExpressionType.Multiply, ExpressionType.Divide, ExpressionType.Remainder}, |
||||
|
// additive
|
||||
|
new[] {ExpressionType.Add, ExpressionType.Subtract}, |
||||
|
// relational
|
||||
|
new[] {ExpressionType.MoreThan, ExpressionType.MoreThanOrEqual, ExpressionType.LessThan, ExpressionType.LessThanOrEqual}, |
||||
|
// equality
|
||||
|
new[] {ExpressionType.Equals, ExpressionType.NotEquals}, |
||||
|
// conditional AND
|
||||
|
new[] {ExpressionType.LogicalAnd}, |
||||
|
// conditional OR
|
||||
|
new[]{ ExpressionType.LogicalOr}, |
||||
|
}; |
||||
|
|
||||
|
private static readonly ExpressionType[][] OperatorPrecedenceGroupsReversed = |
||||
|
OperatorPrecedenceGroups.Reverse().ToArray(); |
||||
|
|
||||
|
// a*b+c [a,b,c] [*,+], call with (0, 2)
|
||||
|
// ToExpression(a*b) + ToExpression(c)
|
||||
|
// a+b*c -> ToExpression(a) + ToExpression(b*c)
|
||||
|
Expression ToExpression(int from, int to) |
||||
|
{ |
||||
|
if (to - from == 0) |
||||
|
return _expressions[from]; |
||||
|
|
||||
|
if (to - from == 1) |
||||
|
return new BinaryExpression(_expressions[from], _expressions[to], _operators[from]); |
||||
|
|
||||
|
foreach (var grp in OperatorPrecedenceGroupsReversed) |
||||
|
{ |
||||
|
for (var c = from; c < to; c++) |
||||
|
{ |
||||
|
var currentOperator = _operators[c]; |
||||
|
foreach(var operatorFromGroup in grp) |
||||
|
if (currentOperator == operatorFromGroup) |
||||
|
{ |
||||
|
// We are dividing the expression right here
|
||||
|
var left = ToExpression(from, c); |
||||
|
var right = ToExpression(c + 1, to); |
||||
|
return new BinaryExpression(left, right, currentOperator); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// We shouldn't ever get here, if we are, there is something wrong in the code
|
||||
|
throw new ExpressionParseException("Expression parsing algorithm bug in ToExpression", 0); |
||||
|
} |
||||
|
|
||||
|
public Expression ToExpression() |
||||
|
{ |
||||
|
if (_expressions == null) |
||||
|
return _first ?? throw new InvalidOperationException(); |
||||
|
return ToExpression(0, _expressions.Count - 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static Expression ParseTillTerminator(ref TokenParser parser, string terminatorChars, |
||||
|
bool throwOnTerminator, |
||||
|
bool throwOnEnd, |
||||
|
out char? token) |
||||
|
{ |
||||
|
ExpressionOperatorGroup left = default; |
||||
|
token = null; |
||||
|
while (true) |
||||
|
{ |
||||
|
if (parser.TryConsumeAny(terminatorChars.AsSpan(), out var consumedToken)) |
||||
|
{ |
||||
|
if (throwOnTerminator || left.Empty) |
||||
|
throw new ExpressionParseException($"Unexpected '{token}'", parser.Position - 1); |
||||
|
token = consumedToken; |
||||
|
return left.ToExpression(); |
||||
|
} |
||||
|
parser.SkipWhitespace(); |
||||
|
if (parser.Length == 0) |
||||
|
{ |
||||
|
if (throwOnEnd || left.Empty) |
||||
|
throw new ExpressionParseException("Unexpected end of expression", parser.Position); |
||||
|
return left.ToExpression(); |
||||
|
} |
||||
|
|
||||
|
ExpressionType? op = null; |
||||
|
if (left.NotEmpty) |
||||
|
{ |
||||
|
if (parser.TryConsume('?')) |
||||
|
{ |
||||
|
var truePart = ParseTillTerminator(ref parser, ":", |
||||
|
false, true, out _); |
||||
|
// pass through the current parsing rules to consume the rest
|
||||
|
var falsePart = ParseTillTerminator(ref parser, terminatorChars, throwOnTerminator, throwOnEnd, |
||||
|
out token); |
||||
|
|
||||
|
return new ConditionalExpression(left.ToExpression(), truePart, falsePart); |
||||
|
} |
||||
|
|
||||
|
// We expect a binary operator here
|
||||
|
if (!TryParseOperator(ref parser, out var sop)) |
||||
|
throw new ExpressionParseException("Unexpected token", parser.Position); |
||||
|
op = sop; |
||||
|
} |
||||
|
|
||||
|
// We expect an expression to be parsed (either due to expecting a binary operator or parsing the first part
|
||||
|
var applyNegation = false; |
||||
|
while (parser.TryConsume('!')) |
||||
|
applyNegation = !applyNegation; |
||||
|
|
||||
|
var applyUnaryMinus = false; |
||||
|
while (parser.TryConsume('-')) |
||||
|
applyUnaryMinus = !applyUnaryMinus; |
||||
|
|
||||
|
Expression? parsed; |
||||
|
|
||||
|
if (parser.TryConsume('(')) |
||||
|
parsed = ParseTillTerminator(ref parser, ")", false, true, out _); |
||||
|
else if (parser.TryParseCall(out var functionName)) |
||||
|
{ |
||||
|
var parameterList = new List<Expression>(); |
||||
|
while (true) |
||||
|
{ |
||||
|
parameterList.Add(ParseTillTerminator(ref parser, ",)", false, true, out var closingToken)); |
||||
|
if (closingToken == ')') |
||||
|
break; |
||||
|
if (closingToken != ',') |
||||
|
throw new ExpressionParseException("Unexpected end of the expression", parser.Position); |
||||
|
} |
||||
|
|
||||
|
parsed = new FunctionCallExpression(functionName.ToString(), parameterList); |
||||
|
} |
||||
|
else if (TryParseAtomic(ref parser, out parsed)) |
||||
|
{ |
||||
|
// do nothing
|
||||
|
} |
||||
|
else |
||||
|
throw new ExpressionParseException("Unexpected token", parser.Position); |
||||
|
|
||||
|
|
||||
|
// Parse any following member accesses
|
||||
|
while (parser.TryConsume('.')) |
||||
|
{ |
||||
|
if(!parser.TryParseIdentifier(out var memberName)) |
||||
|
throw new ExpressionParseException("Unexpected token", parser.Position); |
||||
|
|
||||
|
parsed = new MemberAccessExpression(parsed, memberName.ToString()); |
||||
|
} |
||||
|
|
||||
|
// Apply ! operator
|
||||
|
if (applyNegation) |
||||
|
parsed = new UnaryExpression(parsed, ExpressionType.Not); |
||||
|
|
||||
|
if (applyUnaryMinus) |
||||
|
{ |
||||
|
if(parsed is ConstantExpression constexpr) |
||||
|
parsed = new ConstantExpression(-constexpr.Constant); |
||||
|
else parsed = new UnaryExpression(parsed, ExpressionType.UnaryMinus); |
||||
|
} |
||||
|
|
||||
|
if (left.Empty) |
||||
|
left.AppendFirst(parsed); |
||||
|
else |
||||
|
left.AppendWithOperator(parsed, op!.Value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,739 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal enum VariantType |
||||
|
{ |
||||
|
Invalid, |
||||
|
Boolean, |
||||
|
Scalar, |
||||
|
Double, |
||||
|
Vector2, |
||||
|
Vector3, |
||||
|
Vector4, |
||||
|
AvaloniaMatrix, |
||||
|
Matrix3x2, |
||||
|
Matrix4x4, |
||||
|
Quaternion, |
||||
|
Color |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Explicit)] |
||||
|
internal struct ExpressionVariant |
||||
|
{ |
||||
|
[FieldOffset(0)] public VariantType Type; |
||||
|
|
||||
|
[FieldOffset(4)] public bool Boolean; |
||||
|
[FieldOffset(4)] public float Scalar; |
||||
|
[FieldOffset(4)] public double Double; |
||||
|
[FieldOffset(4)] public Vector2 Vector2; |
||||
|
[FieldOffset(4)] public Vector3 Vector3; |
||||
|
[FieldOffset(4)] public Vector4 Vector4; |
||||
|
[FieldOffset(4)] public Matrix AvaloniaMatrix; |
||||
|
[FieldOffset(4)] public Matrix3x2 Matrix3x2; |
||||
|
[FieldOffset(4)] public Matrix4x4 Matrix4x4; |
||||
|
[FieldOffset(4)] public Quaternion Quaternion; |
||||
|
[FieldOffset(4)] public Color Color; |
||||
|
|
||||
|
|
||||
|
public ExpressionVariant GetProperty(string property) |
||||
|
{ |
||||
|
if (Type == VariantType.Vector2) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "X")) |
||||
|
return Vector2.X; |
||||
|
if (ReferenceEquals(property, "Y")) |
||||
|
return Vector2.Y; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
if (Type == VariantType.Vector3) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "X")) |
||||
|
return Vector3.X; |
||||
|
if (ReferenceEquals(property, "Y")) |
||||
|
return Vector3.Y; |
||||
|
if (ReferenceEquals(property, "Z")) |
||||
|
return Vector3.Z; |
||||
|
if(ReferenceEquals(property, "XY")) |
||||
|
return new Vector2(Vector3.X, Vector3.Y); |
||||
|
if(ReferenceEquals(property, "YX")) |
||||
|
return new Vector2(Vector3.Y, Vector3.X); |
||||
|
if(ReferenceEquals(property, "XZ")) |
||||
|
return new Vector2(Vector3.X, Vector3.Z); |
||||
|
if(ReferenceEquals(property, "ZX")) |
||||
|
return new Vector2(Vector3.Z, Vector3.X); |
||||
|
if(ReferenceEquals(property, "YZ")) |
||||
|
return new Vector2(Vector3.Y, Vector3.Z); |
||||
|
if(ReferenceEquals(property, "ZY")) |
||||
|
return new Vector2(Vector3.Z, Vector3.Y); |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
if (Type == VariantType.Vector4) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "X")) |
||||
|
return Vector4.X; |
||||
|
if (ReferenceEquals(property, "Y")) |
||||
|
return Vector4.Y; |
||||
|
if (ReferenceEquals(property, "Z")) |
||||
|
return Vector4.Z; |
||||
|
if (ReferenceEquals(property, "W")) |
||||
|
return Vector4.W; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
if (Type == VariantType.Matrix3x2) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "M11")) |
||||
|
return Matrix3x2.M11; |
||||
|
if (ReferenceEquals(property, "M12")) |
||||
|
return Matrix3x2.M12; |
||||
|
if (ReferenceEquals(property, "M21")) |
||||
|
return Matrix3x2.M21; |
||||
|
if (ReferenceEquals(property, "M22")) |
||||
|
return Matrix3x2.M22; |
||||
|
if (ReferenceEquals(property, "M31")) |
||||
|
return Matrix3x2.M31; |
||||
|
if (ReferenceEquals(property, "M32")) |
||||
|
return Matrix3x2.M32; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
if (Type == VariantType.AvaloniaMatrix) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "M11")) |
||||
|
return AvaloniaMatrix.M11; |
||||
|
if (ReferenceEquals(property, "M12")) |
||||
|
return AvaloniaMatrix.M12; |
||||
|
if (ReferenceEquals(property, "M21")) |
||||
|
return AvaloniaMatrix.M21; |
||||
|
if (ReferenceEquals(property, "M22")) |
||||
|
return AvaloniaMatrix.M22; |
||||
|
if (ReferenceEquals(property, "M31")) |
||||
|
return AvaloniaMatrix.M31; |
||||
|
if (ReferenceEquals(property, "M32")) |
||||
|
return AvaloniaMatrix.M32; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
if (Type == VariantType.Matrix4x4) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "M11")) |
||||
|
return Matrix4x4.M11; |
||||
|
if (ReferenceEquals(property, "M12")) |
||||
|
return Matrix4x4.M12; |
||||
|
if (ReferenceEquals(property, "M13")) |
||||
|
return Matrix4x4.M13; |
||||
|
if (ReferenceEquals(property, "M14")) |
||||
|
return Matrix4x4.M14; |
||||
|
if (ReferenceEquals(property, "M21")) |
||||
|
return Matrix4x4.M21; |
||||
|
if (ReferenceEquals(property, "M22")) |
||||
|
return Matrix4x4.M22; |
||||
|
if (ReferenceEquals(property, "M23")) |
||||
|
return Matrix4x4.M23; |
||||
|
if (ReferenceEquals(property, "M24")) |
||||
|
return Matrix4x4.M24; |
||||
|
if (ReferenceEquals(property, "M31")) |
||||
|
return Matrix4x4.M31; |
||||
|
if (ReferenceEquals(property, "M32")) |
||||
|
return Matrix4x4.M32; |
||||
|
if (ReferenceEquals(property, "M33")) |
||||
|
return Matrix4x4.M33; |
||||
|
if (ReferenceEquals(property, "M34")) |
||||
|
return Matrix4x4.M34; |
||||
|
if (ReferenceEquals(property, "M41")) |
||||
|
return Matrix4x4.M41; |
||||
|
if (ReferenceEquals(property, "M42")) |
||||
|
return Matrix4x4.M42; |
||||
|
if (ReferenceEquals(property, "M43")) |
||||
|
return Matrix4x4.M43; |
||||
|
if (ReferenceEquals(property, "M44")) |
||||
|
return Matrix4x4.M44; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
if (Type == VariantType.Quaternion) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "X")) |
||||
|
return Quaternion.X; |
||||
|
if (ReferenceEquals(property, "Y")) |
||||
|
return Quaternion.Y; |
||||
|
if (ReferenceEquals(property, "Z")) |
||||
|
return Quaternion.Z; |
||||
|
if (ReferenceEquals(property, "W")) |
||||
|
return Quaternion.W; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
if (Type == VariantType.Color) |
||||
|
{ |
||||
|
if (ReferenceEquals(property, "A")) |
||||
|
return Color.A; |
||||
|
if (ReferenceEquals(property, "R")) |
||||
|
return Color.R; |
||||
|
if (ReferenceEquals(property, "G")) |
||||
|
return Color.G; |
||||
|
if (ReferenceEquals(property, "B")) |
||||
|
return Color.B; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(bool value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Boolean, |
||||
|
Boolean = value |
||||
|
}; |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(float scalar) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Scalar, |
||||
|
Scalar = scalar |
||||
|
}; |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(double d) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Double, |
||||
|
Double = d |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Vector2 value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Vector2, |
||||
|
Vector2 = value |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Vector3 value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Vector3, |
||||
|
Vector3 = value |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Vector4 value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Vector4, |
||||
|
Vector4 = value |
||||
|
}; |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Matrix3x2 value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Matrix3x2, |
||||
|
Matrix3x2 = value |
||||
|
}; |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Matrix value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Matrix3x2, |
||||
|
AvaloniaMatrix = value |
||||
|
}; |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Matrix4x4 value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Matrix4x4, |
||||
|
Matrix4x4 = value |
||||
|
}; |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Quaternion value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Quaternion, |
||||
|
Quaternion = value |
||||
|
}; |
||||
|
|
||||
|
public static implicit operator ExpressionVariant(Avalonia.Media.Color value) => |
||||
|
new ExpressionVariant |
||||
|
{ |
||||
|
Type = VariantType.Color, |
||||
|
Color = value |
||||
|
}; |
||||
|
|
||||
|
public static ExpressionVariant operator +(ExpressionVariant left, ExpressionVariant right) |
||||
|
{ |
||||
|
if (left.Type != right.Type || left.Type == VariantType.Invalid) |
||||
|
return default; |
||||
|
|
||||
|
if (left.Type == VariantType.Scalar) |
||||
|
return left.Scalar + right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Double) |
||||
|
return left.Double + right.Double; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector2) |
||||
|
return left.Vector2 + right.Vector2; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector3) |
||||
|
return left.Vector3 + right.Vector3; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector4) |
||||
|
return left.Vector4 + right.Vector4; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix3x2) |
||||
|
return left.Matrix3x2 + right.Matrix3x2; |
||||
|
|
||||
|
if (left.Type == VariantType.AvaloniaMatrix) |
||||
|
return left.AvaloniaMatrix + right.AvaloniaMatrix; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix4x4) |
||||
|
return left.Matrix4x4 + right.Matrix4x4; |
||||
|
|
||||
|
if (left.Type == VariantType.Quaternion) |
||||
|
return left.Quaternion + right.Quaternion; |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator -(ExpressionVariant left, ExpressionVariant right) |
||||
|
{ |
||||
|
if (left.Type != right.Type || left.Type == VariantType.Invalid) |
||||
|
return default; |
||||
|
|
||||
|
if (left.Type == VariantType.Scalar) |
||||
|
return left.Scalar - right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Double) |
||||
|
return left.Double - right.Double; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector2) |
||||
|
return left.Vector2 - right.Vector2; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector3) |
||||
|
return left.Vector3 - right.Vector3; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector4) |
||||
|
return left.Vector4 - right.Vector4; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix3x2) |
||||
|
return left.Matrix3x2 - right.Matrix3x2; |
||||
|
|
||||
|
if (left.Type == VariantType.AvaloniaMatrix) |
||||
|
return left.AvaloniaMatrix - right.AvaloniaMatrix; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix4x4) |
||||
|
return left.Matrix4x4 - right.Matrix4x4; |
||||
|
|
||||
|
if (left.Type == VariantType.Quaternion) |
||||
|
return left.Quaternion - right.Quaternion; |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator -(ExpressionVariant left) |
||||
|
{ |
||||
|
|
||||
|
if (left.Type == VariantType.Scalar) |
||||
|
return -left.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Double) |
||||
|
return -left.Double; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector2) |
||||
|
return -left.Vector2; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector3) |
||||
|
return -left.Vector3; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector4) |
||||
|
return -left.Vector4; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix3x2) |
||||
|
return -left.Matrix3x2; |
||||
|
|
||||
|
if (left.Type == VariantType.AvaloniaMatrix) |
||||
|
return -left.AvaloniaMatrix; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix4x4) |
||||
|
return -left.Matrix4x4; |
||||
|
|
||||
|
if (left.Type == VariantType.Quaternion) |
||||
|
return -left.Quaternion; |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator *(ExpressionVariant left, ExpressionVariant right) |
||||
|
{ |
||||
|
if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid) |
||||
|
return default; |
||||
|
|
||||
|
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) |
||||
|
return left.Scalar * right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Double && right.Type == VariantType.Double) |
||||
|
return left.Double * right.Double; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2) |
||||
|
return left.Vector2 * right.Vector2; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) |
||||
|
return left.Vector2 * right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3) |
||||
|
return left.Vector3 * right.Vector3; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar) |
||||
|
return left.Vector3 * right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4) |
||||
|
return left.Vector4 * right.Vector4; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar) |
||||
|
return left.Vector4 * right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Matrix3x2) |
||||
|
return left.Matrix3x2 * right.Matrix3x2; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix3x2 && right.Type == VariantType.Scalar) |
||||
|
return left.Matrix3x2 * right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.AvaloniaMatrix) |
||||
|
return left.AvaloniaMatrix * right.AvaloniaMatrix; |
||||
|
|
||||
|
if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.Scalar) |
||||
|
return left.AvaloniaMatrix * (double)right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.AvaloniaMatrix && right.Type == VariantType.Double) |
||||
|
return left.AvaloniaMatrix * right.Double; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Matrix4x4) |
||||
|
return left.Matrix4x4 * right.Matrix4x4; |
||||
|
|
||||
|
if (left.Type == VariantType.Matrix4x4 && right.Type == VariantType.Scalar) |
||||
|
return left.Matrix4x4 * right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion) |
||||
|
return left.Quaternion * right.Quaternion; |
||||
|
|
||||
|
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Scalar) |
||||
|
return left.Quaternion * right.Scalar; |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator /(ExpressionVariant left, ExpressionVariant right) |
||||
|
{ |
||||
|
if (left.Type == VariantType.Invalid || right.Type == VariantType.Invalid) |
||||
|
return default; |
||||
|
|
||||
|
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) |
||||
|
return left.Scalar / right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Double && right.Type == VariantType.Double) |
||||
|
return left.Double / right.Double; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Vector2) |
||||
|
return left.Vector2 / right.Vector2; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector2 && right.Type == VariantType.Scalar) |
||||
|
return left.Vector2 / right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Vector3) |
||||
|
return left.Vector3 / right.Vector3; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector3 && right.Type == VariantType.Scalar) |
||||
|
return left.Vector3 / right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Vector4) |
||||
|
return left.Vector4 / right.Vector4; |
||||
|
|
||||
|
if (left.Type == VariantType.Vector4 && right.Type == VariantType.Scalar) |
||||
|
return left.Vector4 / right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Quaternion && right.Type == VariantType.Quaternion) |
||||
|
return left.Quaternion / right.Quaternion; |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public ExpressionVariant EqualsTo(ExpressionVariant right) |
||||
|
{ |
||||
|
if (Type != right.Type || Type == VariantType.Invalid) |
||||
|
return default; |
||||
|
|
||||
|
if (Type == VariantType.Scalar) |
||||
|
return Scalar == right.Scalar; |
||||
|
|
||||
|
|
||||
|
if (Type == VariantType.Double) |
||||
|
return Double == right.Double; |
||||
|
|
||||
|
if (Type == VariantType.Vector2) |
||||
|
return Vector2 == right.Vector2; |
||||
|
|
||||
|
if (Type == VariantType.Vector3) |
||||
|
return Vector3 == right.Vector3; |
||||
|
|
||||
|
if (Type == VariantType.Vector4) |
||||
|
return Vector4 == right.Vector4; |
||||
|
|
||||
|
if (Type == VariantType.Boolean) |
||||
|
return Boolean == right.Boolean; |
||||
|
|
||||
|
if (Type == VariantType.Matrix3x2) |
||||
|
return Matrix3x2 == right.Matrix3x2; |
||||
|
|
||||
|
if (Type == VariantType.AvaloniaMatrix) |
||||
|
return AvaloniaMatrix == right.AvaloniaMatrix; |
||||
|
|
||||
|
if (Type == VariantType.Matrix4x4) |
||||
|
return Matrix4x4 == right.Matrix4x4; |
||||
|
|
||||
|
if (Type == VariantType.Quaternion) |
||||
|
return Quaternion == right.Quaternion; |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public ExpressionVariant NotEqualsTo(ExpressionVariant right) |
||||
|
{ |
||||
|
var r = EqualsTo(right); |
||||
|
if (r.Type == VariantType.Boolean) |
||||
|
return !r.Boolean; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator !(ExpressionVariant v) |
||||
|
{ |
||||
|
if (v.Type == VariantType.Boolean) |
||||
|
return !v.Boolean; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator %(ExpressionVariant left, ExpressionVariant right) |
||||
|
{ |
||||
|
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) |
||||
|
return left.Scalar % right.Scalar; |
||||
|
if (left.Type == VariantType.Double && right.Type == VariantType.Double) |
||||
|
return left.Double % right.Double; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator <(ExpressionVariant left, ExpressionVariant right) |
||||
|
{ |
||||
|
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) |
||||
|
return left.Scalar < right.Scalar; |
||||
|
if (left.Type == VariantType.Double && right.Type == VariantType.Double) |
||||
|
return left.Double < right.Double; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant operator >(ExpressionVariant left, ExpressionVariant right) |
||||
|
{ |
||||
|
if (left.Type == VariantType.Scalar && right.Type == VariantType.Scalar) |
||||
|
return left.Scalar > right.Scalar; |
||||
|
|
||||
|
if (left.Type == VariantType.Double && right.Type == VariantType.Double) |
||||
|
return left.Double > right.Double; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public ExpressionVariant And(ExpressionVariant right) |
||||
|
{ |
||||
|
if (Type == VariantType.Boolean && right.Type == VariantType.Boolean) |
||||
|
return Boolean && right.Boolean; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public ExpressionVariant Or(ExpressionVariant right) |
||||
|
{ |
||||
|
if (Type == VariantType.Boolean && right.Type == VariantType.Boolean) |
||||
|
return Boolean && right.Boolean; |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
public bool TryCast<T>(out T res) where T : struct |
||||
|
{ |
||||
|
if (typeof(T) == typeof(bool)) |
||||
|
{ |
||||
|
if (Type == VariantType.Boolean) |
||||
|
{ |
||||
|
res = (T) (object) Boolean; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(float)) |
||||
|
{ |
||||
|
if (Type == VariantType.Scalar) |
||||
|
{ |
||||
|
res = (T) (object) Scalar; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(double)) |
||||
|
{ |
||||
|
if (Type == VariantType.Double) |
||||
|
{ |
||||
|
res = (T) (object) Double; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Vector2)) |
||||
|
{ |
||||
|
if (Type == VariantType.Vector2) |
||||
|
{ |
||||
|
res = (T) (object) Vector2; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Vector3)) |
||||
|
{ |
||||
|
if (Type == VariantType.Vector3) |
||||
|
{ |
||||
|
res = (T) (object) Vector3; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Vector4)) |
||||
|
{ |
||||
|
if (Type == VariantType.Vector4) |
||||
|
{ |
||||
|
res = (T) (object) Vector4; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Matrix3x2)) |
||||
|
{ |
||||
|
if (Type == VariantType.Matrix3x2) |
||||
|
{ |
||||
|
res = (T) (object) Matrix3x2; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Matrix)) |
||||
|
{ |
||||
|
if (Type == VariantType.AvaloniaMatrix) |
||||
|
{ |
||||
|
res = (T) (object) Matrix3x2; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Matrix4x4)) |
||||
|
{ |
||||
|
if (Type == VariantType.Matrix4x4) |
||||
|
{ |
||||
|
res = (T) (object) Matrix4x4; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Quaternion)) |
||||
|
{ |
||||
|
if (Type == VariantType.Quaternion) |
||||
|
{ |
||||
|
res = (T) (object) Quaternion; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (typeof(T) == typeof(Avalonia.Media.Color)) |
||||
|
{ |
||||
|
if (Type == VariantType.Color) |
||||
|
{ |
||||
|
res = (T) (object) Color; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
res = default(T); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionVariant Create<T>(T v) where T : struct |
||||
|
{ |
||||
|
if (typeof(T) == typeof(bool)) |
||||
|
return (bool) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(float)) |
||||
|
return (float) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Vector2)) |
||||
|
return (Vector2) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Vector3)) |
||||
|
return (Vector3) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Vector4)) |
||||
|
return (Vector4) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Matrix3x2)) |
||||
|
return (Matrix3x2) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Matrix)) |
||||
|
return (Matrix) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Matrix4x4)) |
||||
|
return (Matrix4x4) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Quaternion)) |
||||
|
return (Quaternion) (object) v; |
||||
|
|
||||
|
if (typeof(T) == typeof(Avalonia.Media.Color)) |
||||
|
return (Avalonia.Media.Color) (object) v; |
||||
|
|
||||
|
throw new ArgumentException("Invalid variant type: " + typeof(T)); |
||||
|
} |
||||
|
|
||||
|
public T CastOrDefault<T>() where T : struct |
||||
|
{ |
||||
|
TryCast<T>(out var r); |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
if (Type == VariantType.Boolean) |
||||
|
return Boolean.ToString(); |
||||
|
if (Type == VariantType.Scalar) |
||||
|
return Scalar.ToString(CultureInfo.InvariantCulture); |
||||
|
if (Type == VariantType.Double) |
||||
|
return Double.ToString(CultureInfo.InvariantCulture); |
||||
|
if (Type == VariantType.Vector2) |
||||
|
return Vector2.ToString(); |
||||
|
if (Type == VariantType.Vector3) |
||||
|
return Vector3.ToString(); |
||||
|
if (Type == VariantType.Vector4) |
||||
|
return Vector4.ToString(); |
||||
|
if (Type == VariantType.Quaternion) |
||||
|
return Quaternion.ToString(); |
||||
|
if (Type == VariantType.Matrix3x2) |
||||
|
return Matrix3x2.ToString(); |
||||
|
if (Type == VariantType.AvaloniaMatrix) |
||||
|
return AvaloniaMatrix.ToString(); |
||||
|
if (Type == VariantType.Matrix4x4) |
||||
|
return Matrix4x4.ToString(); |
||||
|
if (Type == VariantType.Color) |
||||
|
return Color.ToString(); |
||||
|
if (Type == VariantType.Invalid) |
||||
|
return "Invalid"; |
||||
|
return "Unknown"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,256 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Expressions |
||||
|
{ |
||||
|
internal ref struct TokenParser |
||||
|
{ |
||||
|
private ReadOnlySpan<char> _s; |
||||
|
public int Position { get; private set; } |
||||
|
public TokenParser(ReadOnlySpan<char> s) |
||||
|
{ |
||||
|
_s = s; |
||||
|
Position = 0; |
||||
|
} |
||||
|
|
||||
|
public void SkipWhitespace() |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
if (_s.Length > 0 && char.IsWhiteSpace(_s[0])) |
||||
|
Advance(1); |
||||
|
else |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || |
||||
|
(ch >= 'A' && ch <= 'Z'); |
||||
|
|
||||
|
public bool TryConsume(char c) |
||||
|
{ |
||||
|
SkipWhitespace(); |
||||
|
if (_s.Length == 0 || _s[0] != c) |
||||
|
return false; |
||||
|
|
||||
|
Advance(1); |
||||
|
return true; |
||||
|
} |
||||
|
public bool TryConsume(string s) |
||||
|
{ |
||||
|
SkipWhitespace(); |
||||
|
if (_s.Length < s.Length) |
||||
|
return false; |
||||
|
for (var c = 0; c < s.Length; c++) |
||||
|
{ |
||||
|
if (_s[c] != s[c]) |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
Advance(s.Length); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public bool TryConsumeAny(ReadOnlySpan<char> chars, out char token) |
||||
|
{ |
||||
|
SkipWhitespace(); |
||||
|
token = default; |
||||
|
if (_s.Length == 0) |
||||
|
return false; |
||||
|
|
||||
|
foreach (var c in chars) |
||||
|
{ |
||||
|
if (c == _s[0]) |
||||
|
{ |
||||
|
token = c; |
||||
|
Advance(1); |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public bool TryParseKeyword(string keyword) |
||||
|
{ |
||||
|
SkipWhitespace(); |
||||
|
if (keyword.Length > _s.Length) |
||||
|
return false; |
||||
|
for(var c=0; c<keyword.Length;c++) |
||||
|
if (keyword[c] != _s[c]) |
||||
|
return false; |
||||
|
|
||||
|
if (_s.Length > keyword.Length && IsAlphaNumeric(_s[keyword.Length])) |
||||
|
return false; |
||||
|
|
||||
|
Advance(keyword.Length); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public bool TryParseKeywordLowerCase(string keywordInLowerCase) |
||||
|
{ |
||||
|
SkipWhitespace(); |
||||
|
if (keywordInLowerCase.Length > _s.Length) |
||||
|
return false; |
||||
|
for(var c=0; c<keywordInLowerCase.Length;c++) |
||||
|
if (keywordInLowerCase[c] != char.ToLowerInvariant(_s[c])) |
||||
|
return false; |
||||
|
|
||||
|
if (_s.Length > keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length])) |
||||
|
return false; |
||||
|
|
||||
|
Advance(keywordInLowerCase.Length); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public void Advance(int c) |
||||
|
{ |
||||
|
_s = _s.Slice(c); |
||||
|
Position += c; |
||||
|
} |
||||
|
|
||||
|
public int Length => _s.Length; |
||||
|
|
||||
|
public bool TryParseIdentifier(ReadOnlySpan<char> extraValidChars, out ReadOnlySpan<char> res) |
||||
|
{ |
||||
|
res = ReadOnlySpan<char>.Empty; |
||||
|
SkipWhitespace(); |
||||
|
if (_s.Length == 0) |
||||
|
return false; |
||||
|
var first = _s[0]; |
||||
|
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) |
||||
|
return false; |
||||
|
int len = 1; |
||||
|
for (var c = 1; c < _s.Length; c++) |
||||
|
{ |
||||
|
var ch = _s[c]; |
||||
|
if (IsAlphaNumeric(ch)) |
||||
|
len++; |
||||
|
else |
||||
|
{ |
||||
|
var found = false; |
||||
|
foreach(var vc in extraValidChars) |
||||
|
if (vc == ch) |
||||
|
{ |
||||
|
found = true; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (found) |
||||
|
len++; |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
res = _s.Slice(0, len); |
||||
|
Advance(len); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public bool TryParseIdentifier(out ReadOnlySpan<char> res) |
||||
|
{ |
||||
|
res = ReadOnlySpan<char>.Empty; |
||||
|
SkipWhitespace(); |
||||
|
if (_s.Length == 0) |
||||
|
return false; |
||||
|
var first = _s[0]; |
||||
|
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) |
||||
|
return false; |
||||
|
int len = 1; |
||||
|
for (var c = 1; c < _s.Length; c++) |
||||
|
{ |
||||
|
var ch = _s[c]; |
||||
|
if (IsAlphaNumeric(ch)) |
||||
|
len++; |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
res = _s.Slice(0, len); |
||||
|
Advance(len); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public bool TryParseCall(out ReadOnlySpan<char> res) |
||||
|
{ |
||||
|
res = ReadOnlySpan<char>.Empty; |
||||
|
SkipWhitespace(); |
||||
|
if (_s.Length == 0) |
||||
|
return false; |
||||
|
var first = _s[0]; |
||||
|
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) |
||||
|
return false; |
||||
|
int len = 1; |
||||
|
for (var c = 1; c < _s.Length; c++) |
||||
|
{ |
||||
|
var ch = _s[c]; |
||||
|
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.') |
||||
|
len++; |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
res = _s.Slice(0, len); |
||||
|
|
||||
|
// Find '('
|
||||
|
for (var c = len; c < _s.Length; c++) |
||||
|
{ |
||||
|
if(char.IsWhiteSpace(_s[c])) |
||||
|
continue; |
||||
|
if(_s[c]=='(') |
||||
|
{ |
||||
|
Advance(c + 1); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
public bool TryParseFloat(out float res) |
||||
|
{ |
||||
|
res = 0; |
||||
|
SkipWhitespace(); |
||||
|
if (_s.Length == 0) |
||||
|
return false; |
||||
|
|
||||
|
var len = 0; |
||||
|
var dotCount = 0; |
||||
|
for (var c = 0; c < _s.Length; c++) |
||||
|
{ |
||||
|
var ch = _s[c]; |
||||
|
if (ch >= '0' && ch <= '9') |
||||
|
len = c + 1; |
||||
|
else if (ch == '.' && dotCount == 0) |
||||
|
{ |
||||
|
len = c + 1; |
||||
|
dotCount++; |
||||
|
} |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
var span = _s.Slice(0, len); |
||||
|
|
||||
|
#if NETSTANDARD2_0
|
||||
|
if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res)) |
||||
|
return false; |
||||
|
#else
|
||||
|
if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res)) |
||||
|
return false; |
||||
|
#endif
|
||||
|
Advance(len); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public override string ToString() => _s.ToString(); |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public interface ICompositionSurface |
||||
|
{ |
||||
|
internal ServerCompositionSurface Server { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System.Numerics; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
static class MatrixUtils |
||||
|
{ |
||||
|
public static Matrix4x4 ComputeTransform(Vector2 size, Vector2 anchorPoint, Vector3 centerPoint, |
||||
|
Matrix4x4 transformMatrix, Vector3 scale, float rotationAngle, Quaternion orientation, Vector3 offset) |
||||
|
{ |
||||
|
// The math here follows the *observed* UWP behavior since there are no docs on how it's supposed to work
|
||||
|
|
||||
|
var anchor = size * anchorPoint; |
||||
|
var mat = Matrix4x4.CreateTranslation(-anchor.X, -anchor.Y, 0); |
||||
|
|
||||
|
var center = new Vector3(centerPoint.X, centerPoint.Y, centerPoint.Z); |
||||
|
|
||||
|
if (!transformMatrix.IsIdentity) |
||||
|
mat = transformMatrix * mat; |
||||
|
|
||||
|
|
||||
|
if (scale != new Vector3(1, 1, 1)) |
||||
|
mat *= Matrix4x4.CreateScale(scale, center); |
||||
|
|
||||
|
//TODO: RotationAxis support
|
||||
|
if (rotationAngle != 0) |
||||
|
mat *= Matrix4x4.CreateRotationZ(rotationAngle, center); |
||||
|
|
||||
|
if (orientation != Quaternion.Identity) |
||||
|
{ |
||||
|
if (centerPoint != default) |
||||
|
{ |
||||
|
mat *= Matrix4x4.CreateTranslation(-center) |
||||
|
* Matrix4x4.CreateFromQuaternion(orientation) |
||||
|
* Matrix4x4.CreateTranslation(center); |
||||
|
} |
||||
|
else |
||||
|
mat *= Matrix4x4.CreateFromQuaternion(orientation); |
||||
|
} |
||||
|
|
||||
|
if (offset != default) |
||||
|
mat *= Matrix4x4.CreateTranslation(offset); |
||||
|
|
||||
|
return mat; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,142 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Media.Imaging; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.SceneGraph; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
internal class CompositorDrawingContextProxy : IDrawingContextImpl |
||||
|
{ |
||||
|
private IDrawingContextImpl _impl; |
||||
|
|
||||
|
public CompositorDrawingContextProxy(IDrawingContextImpl impl) |
||||
|
{ |
||||
|
_impl = impl; |
||||
|
} |
||||
|
|
||||
|
public Matrix PreTransform { get; set; } = Matrix.Identity; |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_impl.Dispose(); |
||||
|
} |
||||
|
|
||||
|
Matrix _transform; |
||||
|
public Matrix Transform |
||||
|
{ |
||||
|
get => _transform; |
||||
|
set => _impl.Transform = PreTransform * (_transform = value); |
||||
|
} |
||||
|
|
||||
|
public void Clear(Color color) |
||||
|
{ |
||||
|
_impl.Clear(color); |
||||
|
} |
||||
|
|
||||
|
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, |
||||
|
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) |
||||
|
{ |
||||
|
_impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode); |
||||
|
} |
||||
|
|
||||
|
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) |
||||
|
{ |
||||
|
_impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect); |
||||
|
} |
||||
|
|
||||
|
public void DrawLine(IPen pen, Point p1, Point p2) |
||||
|
{ |
||||
|
_impl.DrawLine(pen, p1, p2); |
||||
|
} |
||||
|
|
||||
|
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) |
||||
|
{ |
||||
|
_impl.DrawGeometry(brush, pen, geometry); |
||||
|
} |
||||
|
|
||||
|
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default) |
||||
|
{ |
||||
|
_impl.DrawRectangle(brush, pen, rect, boxShadows); |
||||
|
} |
||||
|
|
||||
|
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect) |
||||
|
{ |
||||
|
_impl.DrawEllipse(brush, pen, rect); |
||||
|
} |
||||
|
|
||||
|
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) |
||||
|
{ |
||||
|
_impl.DrawGlyphRun(foreground, glyphRun); |
||||
|
} |
||||
|
|
||||
|
public IDrawingContextLayerImpl CreateLayer(Size size) |
||||
|
{ |
||||
|
return _impl.CreateLayer(size); |
||||
|
} |
||||
|
|
||||
|
public void PushClip(Rect clip) |
||||
|
{ |
||||
|
_impl.PushClip(clip); |
||||
|
} |
||||
|
|
||||
|
public void PushClip(RoundedRect clip) |
||||
|
{ |
||||
|
_impl.PushClip(clip); |
||||
|
} |
||||
|
|
||||
|
public void PopClip() |
||||
|
{ |
||||
|
_impl.PopClip(); |
||||
|
} |
||||
|
|
||||
|
public void PushOpacity(double opacity) |
||||
|
{ |
||||
|
_impl.PushOpacity(opacity); |
||||
|
} |
||||
|
|
||||
|
public void PopOpacity() |
||||
|
{ |
||||
|
_impl.PopOpacity(); |
||||
|
} |
||||
|
|
||||
|
public void PushOpacityMask(IBrush mask, Rect bounds) |
||||
|
{ |
||||
|
_impl.PushOpacityMask(mask, bounds); |
||||
|
} |
||||
|
|
||||
|
public void PopOpacityMask() |
||||
|
{ |
||||
|
_impl.PopOpacityMask(); |
||||
|
} |
||||
|
|
||||
|
public void PushGeometryClip(IGeometryImpl clip) |
||||
|
{ |
||||
|
_impl.PushGeometryClip(clip); |
||||
|
} |
||||
|
|
||||
|
public void PopGeometryClip() |
||||
|
{ |
||||
|
_impl.PopGeometryClip(); |
||||
|
} |
||||
|
|
||||
|
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) |
||||
|
{ |
||||
|
_impl.PushBitmapBlendMode(blendingMode); |
||||
|
} |
||||
|
|
||||
|
public void PopBitmapBlendMode() |
||||
|
{ |
||||
|
_impl.PopBitmapBlendMode(); |
||||
|
} |
||||
|
|
||||
|
public void Custom(ICustomDrawOperation custom) |
||||
|
{ |
||||
|
_impl.Custom(custom); |
||||
|
} |
||||
|
|
||||
|
public Matrix CutTransform(Matrix4x4 transform) => new Matrix(transform.M11, transform.M12, transform.M21, |
||||
|
transform.M22, transform.M41, |
||||
|
transform.M42); |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal class ReadbackIndices |
||||
|
{ |
||||
|
private readonly object _lock = new object(); |
||||
|
public int ReadIndex { get; private set; } = 0; |
||||
|
public int WriteIndex { get; private set; } = -1; |
||||
|
public ulong ReadRevision { get; private set; } |
||||
|
public ulong WriteRevision { get; private set; } |
||||
|
private ulong[] _revisions = new ulong[3]; |
||||
|
|
||||
|
|
||||
|
public void NextRead() |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
for (var c = 0; c < 3; c++) |
||||
|
{ |
||||
|
if (c != WriteIndex && c != ReadIndex && _revisions[c] > ReadRevision) |
||||
|
{ |
||||
|
ReadIndex = c; |
||||
|
ReadRevision = _revisions[c]; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void NextWrite(ulong revision) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
for (var c = 0; c < 3; c++) |
||||
|
{ |
||||
|
if (c != WriteIndex && c != ReadIndex) |
||||
|
{ |
||||
|
WriteIndex = c; |
||||
|
WriteRevision = revision; |
||||
|
_revisions[c] = revision; |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal abstract partial class ServerCompositionBrush : ServerObject |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Collections.Pooled; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition.Drawing; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
using Avalonia.Rendering.SceneGraph; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual |
||||
|
{ |
||||
|
private CompositionDrawList? _renderCommands; |
||||
|
|
||||
|
public ServerCompositionDrawListVisual(ServerCompositor compositor) : base(compositor) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override void ApplyCore(ChangeSet changes) |
||||
|
{ |
||||
|
var ch = (DrawListVisualChanges)changes; |
||||
|
if (ch.DrawCommandsIsSet) |
||||
|
{ |
||||
|
_renderCommands?.Dispose(); |
||||
|
_renderCommands = ch.AcquireDrawCommands(); |
||||
|
} |
||||
|
base.ApplyCore(changes); |
||||
|
} |
||||
|
|
||||
|
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform) |
||||
|
{ |
||||
|
if (_renderCommands != null) |
||||
|
{ |
||||
|
foreach (var cmd in _renderCommands) |
||||
|
{ |
||||
|
cmd.Item.Render(canvas); |
||||
|
} |
||||
|
} |
||||
|
base.RenderCore(canvas, transform); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal abstract partial class ServerCompositionGradientBrush : ServerCompositionBrush |
||||
|
{ |
||||
|
public ServerCompositionGradientStopCollection Stops { get; } |
||||
|
public ServerCompositionGradientBrush(ServerCompositor compositor) : base(compositor) |
||||
|
{ |
||||
|
Stops = new ServerCompositionGradientStopCollection(compositor); |
||||
|
} |
||||
|
|
||||
|
public override long LastChangedBy => Math.Max(base.LastChangedBy, (long)Stops.LastChangedBy); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal partial class ServerCompositionLinearGradientBrush |
||||
|
{ |
||||
|
/* |
||||
|
protected override void UpdateBackendBrush(ICbBrush brush) |
||||
|
{ |
||||
|
var stopColors = new Color[Stops.List.Count]; |
||||
|
var offsets = new float[Stops.List.Count]; |
||||
|
for (var c = 0; c < Stops.List.Count; c++) |
||||
|
{ |
||||
|
stopColors[c] = Stops.List[c].Color; |
||||
|
offsets[c] = Stops.List[c].Offset; |
||||
|
} |
||||
|
|
||||
|
((ICbLinearGradientBrush) brush).Update(StartPoint, EndPoint, stopColors, offsets, ExtendMode); |
||||
|
}*/ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal abstract class ServerCompositionSurface : ServerObject |
||||
|
{ |
||||
|
protected ServerCompositionSurface(ServerCompositor compositor) : base(compositor) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using System.Threading; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal partial class ServerCompositionTarget |
||||
|
{ |
||||
|
private readonly ServerCompositor _compositor; |
||||
|
private readonly Func<IRenderTarget> _renderTargetFactory; |
||||
|
private static long s_nextId = 1; |
||||
|
public long Id { get; } |
||||
|
private ulong _frame = 1; |
||||
|
private IRenderTarget? _renderTarget; |
||||
|
|
||||
|
public ReadbackIndices Readback { get; } = new(); |
||||
|
|
||||
|
public ServerCompositionTarget(ServerCompositor compositor, Func<IRenderTarget> renderTargetFactory) : |
||||
|
base(compositor) |
||||
|
{ |
||||
|
_compositor = compositor; |
||||
|
_renderTargetFactory = renderTargetFactory; |
||||
|
Id = Interlocked.Increment(ref s_nextId); |
||||
|
} |
||||
|
|
||||
|
partial void OnIsEnabledChanged() |
||||
|
{ |
||||
|
if (IsEnabled) |
||||
|
_compositor.AddCompositionTarget(this); |
||||
|
else |
||||
|
_compositor.RemoveCompositionTarget(this); |
||||
|
} |
||||
|
|
||||
|
public void Render() |
||||
|
{ |
||||
|
if (Root == null) |
||||
|
return; |
||||
|
_renderTarget ??= _renderTargetFactory(); |
||||
|
|
||||
|
Compositor.UpdateServerTime(); |
||||
|
using (var context = _renderTarget.CreateDrawingContext(null)) |
||||
|
{ |
||||
|
context.Clear(Colors.Transparent); |
||||
|
Root.Render(new CompositorDrawingContextProxy(context), Root.CombinedTransformMatrix); |
||||
|
} |
||||
|
|
||||
|
Readback.NextWrite(_frame); |
||||
|
_frame++; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal class ServerCompositor : IRenderLoopTask |
||||
|
{ |
||||
|
private readonly IRenderLoop _renderLoop; |
||||
|
private readonly Queue<Batch> _batches = new Queue<Batch>(); |
||||
|
public long LastBatchId { get; private set; } |
||||
|
public Stopwatch Clock { get; } = Stopwatch.StartNew(); |
||||
|
public TimeSpan ServerNow { get; private set; } |
||||
|
private List<ServerCompositionTarget> _activeTargets = new(); |
||||
|
|
||||
|
public ServerCompositor(IRenderLoop renderLoop) |
||||
|
{ |
||||
|
_renderLoop = renderLoop; |
||||
|
_renderLoop.Add(this); |
||||
|
} |
||||
|
|
||||
|
public void EnqueueBatch(Batch batch) |
||||
|
{ |
||||
|
lock (_batches) |
||||
|
_batches.Enqueue(batch); |
||||
|
} |
||||
|
|
||||
|
internal void UpdateServerTime() => ServerNow = Clock.Elapsed; |
||||
|
|
||||
|
List<Batch> _reusableToCompleteList = new(); |
||||
|
void ApplyPendingBatches() |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
Batch batch; |
||||
|
lock (_batches) |
||||
|
{ |
||||
|
if(_batches.Count == 0) |
||||
|
break; |
||||
|
batch = _batches.Dequeue(); |
||||
|
} |
||||
|
|
||||
|
foreach (var change in batch.Changes) |
||||
|
{ |
||||
|
if (change.Dispose) |
||||
|
{ |
||||
|
//TODO
|
||||
|
} |
||||
|
change.Target!.Apply(change); |
||||
|
change.Reset(); |
||||
|
} |
||||
|
|
||||
|
_reusableToCompleteList.Add(batch); |
||||
|
LastBatchId = batch.SequenceId; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CompletePendingBatches() |
||||
|
{ |
||||
|
foreach(var batch in _reusableToCompleteList) |
||||
|
batch.Complete(); |
||||
|
_reusableToCompleteList.Clear(); |
||||
|
} |
||||
|
|
||||
|
bool IRenderLoopTask.NeedsUpdate => false; |
||||
|
void IRenderLoopTask.Update(TimeSpan time) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
void IRenderLoopTask.Render() |
||||
|
{ |
||||
|
ApplyPendingBatches(); |
||||
|
foreach (var t in _activeTargets) |
||||
|
t.Render(); |
||||
|
|
||||
|
CompletePendingBatches(); |
||||
|
} |
||||
|
|
||||
|
public void AddCompositionTarget(ServerCompositionTarget target) |
||||
|
{ |
||||
|
_activeTargets.Add(target); |
||||
|
} |
||||
|
|
||||
|
public void RemoveCompositionTarget(ServerCompositionTarget target) |
||||
|
{ |
||||
|
_activeTargets.Remove(target); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal class ServerCompositionContainerVisual : ServerCompositionVisual |
||||
|
{ |
||||
|
public ServerCompositionVisualCollection Children { get; } |
||||
|
|
||||
|
|
||||
|
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform) |
||||
|
{ |
||||
|
base.RenderCore(canvas, transform); |
||||
|
|
||||
|
foreach (var ch in Children) |
||||
|
{ |
||||
|
var t = transform; |
||||
|
|
||||
|
t = ch.CombinedTransformMatrix * t; |
||||
|
ch.Render(canvas, t); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ServerCompositionContainerVisual(ServerCompositor compositor) : base(compositor) |
||||
|
{ |
||||
|
Children = new ServerCompositionVisualCollection(compositor); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
class ServerCustomDrawVisual<TData> : ServerCompositionContainerVisual |
||||
|
{ |
||||
|
private readonly ICustomDrawVisualRenderer<TData> _renderer; |
||||
|
private TData? _data; |
||||
|
public ServerCustomDrawVisual(ServerCompositor compositor, ICustomDrawVisualRenderer<TData> renderer) : base(compositor) |
||||
|
{ |
||||
|
_renderer = renderer; |
||||
|
} |
||||
|
|
||||
|
protected override void ApplyCore(ChangeSet changes) |
||||
|
{ |
||||
|
var c = (CustomDrawVisualChanges<TData>) changes; |
||||
|
if (c.Data.IsSet) |
||||
|
_data = c.Data.Value; |
||||
|
|
||||
|
base.ApplyCore(changes); |
||||
|
} |
||||
|
|
||||
|
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform) |
||||
|
{ |
||||
|
_renderer.Render(canvas, _data); |
||||
|
base.RenderCore(canvas, transform); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
class ServerList<T> : ServerObject where T : ServerObject |
||||
|
{ |
||||
|
public List<T> List { get; } = new List<T>(); |
||||
|
protected override void ApplyCore(ChangeSet changes) |
||||
|
{ |
||||
|
var c = (ListChangeSet<T>) changes; |
||||
|
if (c.HasListChanges) |
||||
|
{ |
||||
|
foreach (var lc in c.ListChanges) |
||||
|
{ |
||||
|
if(lc.Action == ListChangeAction.Clear) |
||||
|
List.Clear(); |
||||
|
if(lc.Action == ListChangeAction.RemoveAt) |
||||
|
List.RemoveAt(lc.Index); |
||||
|
if(lc.Action == ListChangeAction.InsertAt) |
||||
|
List.Insert(lc.Index, lc.Added!); |
||||
|
if (lc.Action == ListChangeAction.ReplaceAt) |
||||
|
List[lc.Index] = lc.Added!; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override long LastChangedBy |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var seq = base.LastChangedBy; |
||||
|
foreach (var i in List) |
||||
|
seq = Math.Max(i.LastChangedBy, seq); |
||||
|
return seq; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public List<T>.Enumerator GetEnumerator() => List.GetEnumerator(); |
||||
|
|
||||
|
public ServerList(ServerCompositor compositor) : base(compositor) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal abstract class ServerObject : IExpressionObject |
||||
|
{ |
||||
|
public ServerCompositor Compositor { get; } |
||||
|
|
||||
|
public virtual long LastChangedBy => ItselfLastChangedBy; |
||||
|
public long ItselfLastChangedBy { get; private set; } |
||||
|
|
||||
|
public ServerObject(ServerCompositor compositor) |
||||
|
{ |
||||
|
Compositor = compositor; |
||||
|
} |
||||
|
|
||||
|
protected virtual void ApplyCore(ChangeSet changes) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Apply(ChangeSet changes) |
||||
|
{ |
||||
|
ApplyCore(changes); |
||||
|
ItselfLastChangedBy = changes.Batch!.SequenceId; |
||||
|
} |
||||
|
|
||||
|
public virtual ExpressionVariant GetPropertyForAnimation(string name) |
||||
|
{ |
||||
|
return default; |
||||
|
} |
||||
|
|
||||
|
ExpressionVariant IExpressionObject.GetProperty(string name) => GetPropertyForAnimation(name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Media.Immutable; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal partial class ServerCompositionSolidColorVisual |
||||
|
{ |
||||
|
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform) |
||||
|
{ |
||||
|
canvas.Transform = canvas.CutTransform(transform); |
||||
|
canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new RoundedRect(new Rect(new Size(Size)))); |
||||
|
base.RenderCore(canvas, transform); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
internal partial class ServerCompositionSpriteVisual |
||||
|
{ |
||||
|
|
||||
|
protected override void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform) |
||||
|
{ |
||||
|
if (Brush != null) |
||||
|
{ |
||||
|
//SetTransform(canvas, transform);
|
||||
|
//canvas.FillRect((Vector2)Size, (ICbBrush)Brush.Brush!);
|
||||
|
} |
||||
|
|
||||
|
base.RenderCore(canvas, transform); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
using System.Numerics; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Server |
||||
|
{ |
||||
|
unsafe partial class ServerCompositionVisual : ServerObject |
||||
|
{ |
||||
|
protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Render(CompositorDrawingContextProxy canvas, Matrix4x4 transform) |
||||
|
{ |
||||
|
if(Visible == false) |
||||
|
return; |
||||
|
if(Opacity == 0) |
||||
|
return; |
||||
|
canvas.PreTransform = canvas.CutTransform(transform); |
||||
|
canvas.Transform = Matrix.Identity; |
||||
|
if (Opacity != 1) |
||||
|
canvas.PushOpacity(Opacity); |
||||
|
if(ClipToBounds) |
||||
|
canvas.PushClip(new Rect(new Size(Size.X, Size.Y))); |
||||
|
if (Clip != null) |
||||
|
canvas.PushGeometryClip(Clip); |
||||
|
|
||||
|
RenderCore(canvas, transform); |
||||
|
|
||||
|
if (Clip != null) |
||||
|
canvas.PopGeometryClip(); |
||||
|
if (ClipToBounds) |
||||
|
canvas.PopClip(); |
||||
|
if(Opacity != 1) |
||||
|
canvas.PopOpacity(); |
||||
|
} |
||||
|
|
||||
|
private ReadbackData _readback0, _readback1, _readback2; |
||||
|
|
||||
|
|
||||
|
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 |
||||
|
{ |
||||
|
if (Root == null) |
||||
|
return default; |
||||
|
|
||||
|
var res = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix, |
||||
|
Scale, RotationAngle, Orientation, Offset); |
||||
|
var i = Root.Readback; |
||||
|
ref var readback = ref GetReadback(i.WriteIndex); |
||||
|
readback.Revision = i.WriteRevision; |
||||
|
readback.Matrix = res; |
||||
|
readback.TargetId = Root.Id; |
||||
|
|
||||
|
return res; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public struct ReadbackData |
||||
|
{ |
||||
|
public Matrix4x4 Matrix; |
||||
|
public bool Visible; |
||||
|
public ulong Revision; |
||||
|
public long TargetId; |
||||
|
} |
||||
|
|
||||
|
partial void ApplyChangesExtra(CompositionVisualChanges c) |
||||
|
{ |
||||
|
if (c.Parent.IsSet) |
||||
|
Parent = c.Parent.Value; |
||||
|
if (c.Root.IsSet) |
||||
|
Root = c.Root.Value; |
||||
|
} |
||||
|
|
||||
|
public ServerCompositionTarget? Root { get; private set; } |
||||
|
|
||||
|
public ServerCompositionVisual? Parent { get; private set; } |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
internal class Batch |
||||
|
{ |
||||
|
private static long _nextSequenceId = 1; |
||||
|
private static ConcurrentBag<List<ChangeSet>> _pool = new ConcurrentBag<List<ChangeSet>>(); |
||||
|
public long SequenceId { get; } |
||||
|
|
||||
|
public Batch() |
||||
|
{ |
||||
|
SequenceId = Interlocked.Increment(ref _nextSequenceId); |
||||
|
if (!_pool.TryTake(out var lst)) |
||||
|
lst = new List<ChangeSet>(); |
||||
|
Changes = lst; |
||||
|
} |
||||
|
private TaskCompletionSource<int> _tcs = new TaskCompletionSource<int>(); |
||||
|
public List<ChangeSet> Changes { get; private set; } |
||||
|
public TimeSpan CommitedAt { get; set; } |
||||
|
|
||||
|
public void Complete() |
||||
|
{ |
||||
|
Changes.Clear(); |
||||
|
_pool.Add(Changes); |
||||
|
Changes = null!; |
||||
|
|
||||
|
_tcs.TrySetResult(0); |
||||
|
} |
||||
|
|
||||
|
public Task Completed => _tcs.Task; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using Avalonia.Rendering.Composition.Animations; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
struct Change<T> |
||||
|
{ |
||||
|
private T? _value; |
||||
|
|
||||
|
public bool IsSet { get; private set; } |
||||
|
|
||||
|
public T? Value |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if(!IsSet) |
||||
|
throw new InvalidOperationException(); |
||||
|
return _value; |
||||
|
} |
||||
|
set |
||||
|
{ |
||||
|
IsSet = true; |
||||
|
_value = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Reset() |
||||
|
{ |
||||
|
_value = default; |
||||
|
IsSet = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
struct AnimatedChange<T> |
||||
|
{ |
||||
|
private T? _value; |
||||
|
private IAnimationInstance? _animation; |
||||
|
|
||||
|
public bool IsValue { get; private set; } |
||||
|
public bool IsAnimation { get; private set; } |
||||
|
|
||||
|
public T Value |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if(!IsValue) |
||||
|
throw new InvalidOperationException(); |
||||
|
return _value!; |
||||
|
} |
||||
|
set |
||||
|
{ |
||||
|
IsAnimation = false; |
||||
|
_animation = null; |
||||
|
IsValue = true; |
||||
|
_value = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public IAnimationInstance Animation |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if(!IsAnimation) |
||||
|
throw new InvalidOperationException(); |
||||
|
return _animation!; |
||||
|
} |
||||
|
set |
||||
|
{ |
||||
|
IsValue = false; |
||||
|
_value = default; |
||||
|
IsAnimation = true; |
||||
|
_animation = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Reset() |
||||
|
{ |
||||
|
this = default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
internal abstract class ChangeSet |
||||
|
{ |
||||
|
private readonly IChangeSetPool _pool; |
||||
|
public Batch Batch = null!; |
||||
|
public ServerObject? Target; |
||||
|
public bool Dispose; |
||||
|
|
||||
|
public ChangeSet(IChangeSetPool pool) |
||||
|
{ |
||||
|
_pool = pool; |
||||
|
} |
||||
|
|
||||
|
public virtual void Reset() |
||||
|
{ |
||||
|
Batch = null!; |
||||
|
Target = null; |
||||
|
Dispose = false; |
||||
|
} |
||||
|
|
||||
|
public void Return() |
||||
|
{ |
||||
|
_pool.Return(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class CompositionObjectChanges : ChangeSet |
||||
|
{ |
||||
|
public CompositionObjectChanges(IChangeSetPool pool) : base(pool) |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
interface IChangeSetPool |
||||
|
{ |
||||
|
void Return(ChangeSet changes); |
||||
|
ChangeSet Get(ServerObject target, Batch batch); |
||||
|
} |
||||
|
|
||||
|
class ChangeSetPool<T> : IChangeSetPool where T : ChangeSet |
||||
|
{ |
||||
|
private readonly Func<IChangeSetPool, T> _factory; |
||||
|
private readonly ConcurrentBag<T> _pool = new ConcurrentBag<T>(); |
||||
|
|
||||
|
public ChangeSetPool(Func<IChangeSetPool, T> factory) |
||||
|
{ |
||||
|
_factory = factory; |
||||
|
} |
||||
|
|
||||
|
public void Return(T changes) |
||||
|
{ |
||||
|
changes.Reset(); |
||||
|
_pool.Add(changes); |
||||
|
} |
||||
|
|
||||
|
void IChangeSetPool.Return(ChangeSet changes) => Return((T) changes); |
||||
|
ChangeSet IChangeSetPool.Get(ServerObject target, Batch batch) => Get(target, batch); |
||||
|
|
||||
|
public T Get(ServerObject target, Batch batch) |
||||
|
{ |
||||
|
if (!_pool.TryTake(out var res)) |
||||
|
res = _factory(this); |
||||
|
res.Target = target; |
||||
|
res.Batch = batch; |
||||
|
res.Dispose = false; |
||||
|
return res; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
class CustomDrawVisualChanges<TData> : CompositionVisualChanges |
||||
|
{ |
||||
|
public CustomDrawVisualChanges(IChangeSetPool pool) : base(pool) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Change<TData> Data; |
||||
|
|
||||
|
public override void Reset() |
||||
|
{ |
||||
|
Data.Reset(); |
||||
|
base.Reset(); |
||||
|
} |
||||
|
|
||||
|
public new static ChangeSetPool<CustomDrawVisualChanges<TData>> Pool { get; } = |
||||
|
new ChangeSetPool<CustomDrawVisualChanges<TData>>(pool => new CustomDrawVisualChanges<TData>(pool)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Collections.Pooled; |
||||
|
using Avalonia.Rendering.Composition.Drawing; |
||||
|
using Avalonia.Rendering.SceneGraph; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport; |
||||
|
|
||||
|
internal class DrawListVisualChanges : CompositionVisualChanges |
||||
|
{ |
||||
|
private CompositionDrawList? _drawCommands; |
||||
|
|
||||
|
public DrawListVisualChanges(IChangeSetPool pool) : base(pool) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public CompositionDrawList? DrawCommands |
||||
|
{ |
||||
|
get => _drawCommands; |
||||
|
set |
||||
|
{ |
||||
|
_drawCommands?.Dispose(); |
||||
|
_drawCommands = value; |
||||
|
DrawCommandsIsSet = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool DrawCommandsIsSet { get; private set; } |
||||
|
|
||||
|
public CompositionDrawList? AcquireDrawCommands() |
||||
|
{ |
||||
|
var rv = _drawCommands; |
||||
|
_drawCommands = null; |
||||
|
DrawCommandsIsSet = false; |
||||
|
return rv; |
||||
|
} |
||||
|
|
||||
|
public override void Reset() |
||||
|
{ |
||||
|
_drawCommands?.Dispose(); |
||||
|
_drawCommands = null; |
||||
|
DrawCommandsIsSet = false; |
||||
|
base.Reset(); |
||||
|
} |
||||
|
|
||||
|
public new static ChangeSetPool<DrawListVisualChanges> Pool { get; } = |
||||
|
new ChangeSetPool<DrawListVisualChanges>(pool => new(pool)); |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
internal class ListChange<T> where T : ServerObject |
||||
|
{ |
||||
|
public int Index; |
||||
|
public ListChangeAction Action; |
||||
|
public T? Added; |
||||
|
} |
||||
|
|
||||
|
internal enum ListChangeAction |
||||
|
{ |
||||
|
InsertAt, |
||||
|
RemoveAt, |
||||
|
Clear, |
||||
|
ReplaceAt |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
class ListChangeSet<T> : ChangeSet where T : ServerObject |
||||
|
{ |
||||
|
private List<ListChange<T>>? _listChanges; |
||||
|
public List<ListChange<T>> ListChanges => _listChanges ??= new List<ListChange<T>>(); |
||||
|
public bool HasListChanges => _listChanges != null; |
||||
|
|
||||
|
public override void Reset() |
||||
|
{ |
||||
|
_listChanges?.Clear(); |
||||
|
base.Reset(); |
||||
|
} |
||||
|
|
||||
|
public ListChangeSet(IChangeSetPool pool) : base(pool) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public static readonly ChangeSetPool<ListChangeSet<T>> Pool = |
||||
|
new ChangeSetPool<ListChangeSet<T>>(pool => new ListChangeSet<T>(pool)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
using System.Collections; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
class ServerListProxyHelper<TClient, TServer> : IList<TClient> |
||||
|
where TServer : ServerObject |
||||
|
where TClient : CompositionObject |
||||
|
{ |
||||
|
private readonly IGetChanges _parent; |
||||
|
private readonly List<TClient> _list = new List<TClient>(); |
||||
|
|
||||
|
public interface IGetChanges |
||||
|
{ |
||||
|
ListChangeSet<TServer> GetChanges(); |
||||
|
} |
||||
|
|
||||
|
public ServerListProxyHelper(IGetChanges parent) |
||||
|
{ |
||||
|
_parent = parent; |
||||
|
} |
||||
|
|
||||
|
IEnumerator<TClient> IEnumerable<TClient>.GetEnumerator() => GetEnumerator(); |
||||
|
public List<TClient>.Enumerator GetEnumerator() => _list.GetEnumerator(); |
||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
||||
|
|
||||
|
public void Add(TClient item) => Insert(_list.Count, item); |
||||
|
|
||||
|
public void Clear() |
||||
|
{ |
||||
|
_list.Clear(); |
||||
|
_parent.GetChanges().ListChanges.Add(new ListChange<TServer> |
||||
|
{ |
||||
|
Action = ListChangeAction.Clear |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public bool Contains(TClient item) => _list.Contains(item); |
||||
|
|
||||
|
public void CopyTo(TClient[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); |
||||
|
|
||||
|
public bool Remove(TClient item) |
||||
|
{ |
||||
|
var idx = _list.IndexOf(item); |
||||
|
if (idx == -1) |
||||
|
return false; |
||||
|
RemoveAt(idx); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public int Count => _list.Count; |
||||
|
public bool IsReadOnly => false; |
||||
|
public int IndexOf(TClient item) => _list.IndexOf(item); |
||||
|
|
||||
|
public void Insert(int index, TClient item) |
||||
|
{ |
||||
|
_list.Insert(index, item); |
||||
|
_parent.GetChanges().ListChanges.Add(new ListChange<TServer> |
||||
|
{ |
||||
|
Action = ListChangeAction.InsertAt, |
||||
|
Index = index, |
||||
|
Added = (TServer) item.Server |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void RemoveAt(int index) |
||||
|
{ |
||||
|
_list.RemoveAt(index); |
||||
|
_parent.GetChanges().ListChanges.Add(new ListChange<TServer> |
||||
|
{ |
||||
|
Action = ListChangeAction.RemoveAt, |
||||
|
Index = index |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public TClient this[int index] |
||||
|
{ |
||||
|
get => _list[index]; |
||||
|
set |
||||
|
{ |
||||
|
_list[index] = value; |
||||
|
_parent.GetChanges().ListChanges.Add(new ListChange<TServer> |
||||
|
{ |
||||
|
Action = ListChangeAction.ReplaceAt, |
||||
|
Index = index, |
||||
|
Added = (TServer) value.Server |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Transport |
||||
|
{ |
||||
|
partial class CompositionVisualChanges |
||||
|
{ |
||||
|
public Change<ServerCompositionVisual> Parent; |
||||
|
public Change<ServerCompositionTarget> Root; |
||||
|
|
||||
|
partial void ResetExtra() |
||||
|
{ |
||||
|
Parent.Reset(); |
||||
|
Root.Reset(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,302 @@ |
|||||
|
// ReSharper disable InconsistentNaming
|
||||
|
// Ported from Chromium project https://github.com/chromium/chromium/blob/374d31b7704475fa59f7b2cb836b3b68afdc3d79/ui/gfx/geometry/cubic_bezier.cc
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
|
// ReSharper disable CommentTypo
|
||||
|
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
// ReSharper disable TooWideLocalVariableScope
|
||||
|
// ReSharper disable UnusedMember.Global
|
||||
|
#pragma warning disable 649
|
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Utils |
||||
|
{ |
||||
|
internal unsafe struct CubicBezier |
||||
|
{ |
||||
|
const int CUBIC_BEZIER_SPLINE_SAMPLES = 11; |
||||
|
double ax_; |
||||
|
double bx_; |
||||
|
double cx_; |
||||
|
|
||||
|
double ay_; |
||||
|
double by_; |
||||
|
double cy_; |
||||
|
|
||||
|
double start_gradient_; |
||||
|
double end_gradient_; |
||||
|
|
||||
|
double range_min_; |
||||
|
double range_max_; |
||||
|
private bool monotonically_increasing_; |
||||
|
|
||||
|
fixed double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES]; |
||||
|
|
||||
|
public CubicBezier(double p1x, double p1y, double p2x, double p2y) : this() |
||||
|
{ |
||||
|
InitCoefficients(p1x, p1y, p2x, p2y); |
||||
|
InitGradients(p1x, p1y, p2x, p2y); |
||||
|
InitRange(p1y, p2y); |
||||
|
InitSpline(); |
||||
|
} |
||||
|
|
||||
|
public readonly double SampleCurveX(double t) |
||||
|
{ |
||||
|
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
|
||||
|
return ((ax_ * t + bx_) * t + cx_) * t; |
||||
|
} |
||||
|
|
||||
|
readonly double SampleCurveY(double t) |
||||
|
{ |
||||
|
return ((ay_ * t + by_) * t + cy_) * t; |
||||
|
} |
||||
|
|
||||
|
readonly double SampleCurveDerivativeX(double t) |
||||
|
{ |
||||
|
return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_; |
||||
|
} |
||||
|
|
||||
|
readonly double SampleCurveDerivativeY(double t) |
||||
|
{ |
||||
|
return (3.0 * ay_ * t + 2.0 * by_) * t + cy_; |
||||
|
} |
||||
|
|
||||
|
public readonly double SolveWithEpsilon(double x, double epsilon) |
||||
|
{ |
||||
|
if (x < 0.0) |
||||
|
return 0.0 + start_gradient_ * x; |
||||
|
if (x > 1.0) |
||||
|
return 1.0 + end_gradient_ * (x - 1.0); |
||||
|
return SampleCurveY(SolveCurveX(x, epsilon)); |
||||
|
} |
||||
|
|
||||
|
void InitCoefficients(double p1x, |
||||
|
double p1y, |
||||
|
double p2x, |
||||
|
double p2y) |
||||
|
{ |
||||
|
// Calculate the polynomial coefficients, implicit first and last control
|
||||
|
// points are (0,0) and (1,1).
|
||||
|
cx_ = 3.0 * p1x; |
||||
|
bx_ = 3.0 * (p2x - p1x) - cx_; |
||||
|
ax_ = 1.0 - cx_ - bx_; |
||||
|
|
||||
|
cy_ = 3.0 * p1y; |
||||
|
by_ = 3.0 * (p2y - p1y) - cy_; |
||||
|
ay_ = 1.0 - cy_ - by_; |
||||
|
|
||||
|
#if DEBUG
|
||||
|
// Bezier curves with x-coordinates outside the range [0,1] for internal
|
||||
|
// control points may have multiple values for t for a given value of x.
|
||||
|
// In this case, calls to SolveCurveX may produce ambiguous results.
|
||||
|
monotonically_increasing_ = p1x >= 0 && p1x <= 1 && p2x >= 0 && p2x <= 1; |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
void InitGradients(double p1x, |
||||
|
double p1y, |
||||
|
double p2x, |
||||
|
double p2y) |
||||
|
{ |
||||
|
// End-point gradients are used to calculate timing function results
|
||||
|
// outside the range [0, 1].
|
||||
|
//
|
||||
|
// There are four possibilities for the gradient at each end:
|
||||
|
// (1) the closest control point is not horizontally coincident with regard to
|
||||
|
// (0, 0) or (1, 1). In this case the line between the end point and
|
||||
|
// the control point is tangent to the bezier at the end point.
|
||||
|
// (2) the closest control point is coincident with the end point. In
|
||||
|
// this case the line between the end point and the far control
|
||||
|
// point is tangent to the bezier at the end point.
|
||||
|
// (3) both internal control points are coincident with an endpoint. There
|
||||
|
// are two special case that fall into this category:
|
||||
|
// CubicBezier(0, 0, 0, 0) and CubicBezier(1, 1, 1, 1). Both are
|
||||
|
// equivalent to linear.
|
||||
|
// (4) the closest control point is horizontally coincident with the end
|
||||
|
// point, but vertically distinct. In this case the gradient at the
|
||||
|
// end point is Infinite. However, this causes issues when
|
||||
|
// interpolating. As a result, we break down to a simple case of
|
||||
|
// 0 gradient under these conditions.
|
||||
|
|
||||
|
if (p1x > 0) |
||||
|
start_gradient_ = p1y / p1x; |
||||
|
else if (p1y == 0 && p2x > 0) |
||||
|
start_gradient_ = p2y / p2x; |
||||
|
else if (p1y == 0 && p2y == 0) |
||||
|
start_gradient_ = 1; |
||||
|
else |
||||
|
start_gradient_ = 0; |
||||
|
|
||||
|
if (p2x < 1) |
||||
|
end_gradient_ = (p2y - 1) / (p2x - 1); |
||||
|
else if (p2y == 1 && p1x < 1) |
||||
|
end_gradient_ = (p1y - 1) / (p1x - 1); |
||||
|
else if (p2y == 1 && p1y == 1) |
||||
|
end_gradient_ = 1; |
||||
|
else |
||||
|
end_gradient_ = 0; |
||||
|
} |
||||
|
|
||||
|
const double kBezierEpsilon = 1e-7; |
||||
|
|
||||
|
void InitRange(double p1y, double p2y) |
||||
|
{ |
||||
|
range_min_ = 0; |
||||
|
range_max_ = 1; |
||||
|
if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1) |
||||
|
return; |
||||
|
|
||||
|
double epsilon = kBezierEpsilon; |
||||
|
|
||||
|
// Represent the function's derivative in the form at^2 + bt + c
|
||||
|
// as in sampleCurveDerivativeY.
|
||||
|
// (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros
|
||||
|
// but does not actually give the slope of the curve.)
|
||||
|
double a = 3.0 * ay_; |
||||
|
double b = 2.0 * by_; |
||||
|
double c = cy_; |
||||
|
|
||||
|
// Check if the derivative is constant.
|
||||
|
if (Math.Abs(a) < epsilon && Math.Abs(b) < epsilon) |
||||
|
return; |
||||
|
|
||||
|
// Zeros of the function's derivative.
|
||||
|
double t1; |
||||
|
double t2 = 0; |
||||
|
|
||||
|
if (Math.Abs(a) < epsilon) |
||||
|
{ |
||||
|
// The function's derivative is linear.
|
||||
|
t1 = -c / b; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// The function's derivative is a quadratic. We find the zeros of this
|
||||
|
// quadratic using the quadratic formula.
|
||||
|
double discriminant = b * b - 4 * a * c; |
||||
|
if (discriminant < 0) |
||||
|
return; |
||||
|
double discriminant_sqrt = Math.Sqrt(discriminant); |
||||
|
t1 = (-b + discriminant_sqrt) / (2 * a); |
||||
|
t2 = (-b - discriminant_sqrt) / (2 * a); |
||||
|
} |
||||
|
|
||||
|
double sol1 = 0; |
||||
|
double sol2 = 0; |
||||
|
|
||||
|
// If the solution is in the range [0,1] then we include it, otherwise we
|
||||
|
// ignore it.
|
||||
|
|
||||
|
// An interesting fact about these beziers is that they are only
|
||||
|
// actually evaluated in [0,1]. After that we take the tangent at that point
|
||||
|
// and linearly project it out.
|
||||
|
if (0 < t1 && t1 < 1) |
||||
|
sol1 = SampleCurveY(t1); |
||||
|
|
||||
|
if (0 < t2 && t2 < 1) |
||||
|
sol2 = SampleCurveY(t2); |
||||
|
|
||||
|
range_min_ = Math.Min(Math.Min(range_min_, sol1), sol2); |
||||
|
range_max_ = Math.Max(Math.Max(range_max_, sol1), sol2); |
||||
|
} |
||||
|
|
||||
|
void InitSpline() |
||||
|
{ |
||||
|
double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1); |
||||
|
for (int i = 0; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) |
||||
|
{ |
||||
|
spline_samples_[i] = SampleCurveX(i * delta_t); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const int kMaxNewtonIterations = 4; |
||||
|
|
||||
|
|
||||
|
public readonly double SolveCurveX(double x, double epsilon) |
||||
|
{ |
||||
|
if (x < 0 || x > 1) |
||||
|
throw new ArgumentException(); |
||||
|
|
||||
|
double t0 = 0; |
||||
|
double t1 = 0; |
||||
|
double t2 = x; |
||||
|
double x2 = 0; |
||||
|
double d2; |
||||
|
int i; |
||||
|
|
||||
|
#if DEBUG
|
||||
|
if (!monotonically_increasing_) |
||||
|
throw new InvalidOperationException(); |
||||
|
#endif
|
||||
|
|
||||
|
// Linear interpolation of spline curve for initial guess.
|
||||
|
double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1); |
||||
|
for (i = 1; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) |
||||
|
{ |
||||
|
if (x <= spline_samples_[i]) |
||||
|
{ |
||||
|
t1 = delta_t * i; |
||||
|
t0 = t1 - delta_t; |
||||
|
t2 = t0 + (t1 - t0) * (x - spline_samples_[i - 1]) / |
||||
|
(spline_samples_[i] - spline_samples_[i - 1]); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Perform a few iterations of Newton's method -- normally very fast.
|
||||
|
// See https://en.wikipedia.org/wiki/Newton%27s_method.
|
||||
|
double newton_epsilon = Math.Min(kBezierEpsilon, epsilon); |
||||
|
for (i = 0; i < kMaxNewtonIterations; i++) |
||||
|
{ |
||||
|
x2 = SampleCurveX(t2) - x; |
||||
|
if (Math.Abs(x2) < newton_epsilon) |
||||
|
return t2; |
||||
|
d2 = SampleCurveDerivativeX(t2); |
||||
|
if (Math.Abs(d2) < kBezierEpsilon) |
||||
|
break; |
||||
|
t2 = t2 - x2 / d2; |
||||
|
} |
||||
|
|
||||
|
if (Math.Abs(x2) < epsilon) |
||||
|
return t2; |
||||
|
|
||||
|
// Fall back to the bisection method for reliability.
|
||||
|
while (t0 < t1) |
||||
|
{ |
||||
|
x2 = SampleCurveX(t2); |
||||
|
if (Math.Abs(x2 - x) < epsilon) |
||||
|
return t2; |
||||
|
if (x > x2) |
||||
|
t0 = t2; |
||||
|
else |
||||
|
t1 = t2; |
||||
|
t2 = (t1 + t0) * .5; |
||||
|
} |
||||
|
|
||||
|
// Failure.
|
||||
|
return t2; |
||||
|
} |
||||
|
|
||||
|
public readonly double Solve(double x) |
||||
|
{ |
||||
|
return SolveWithEpsilon(x, kBezierEpsilon); |
||||
|
} |
||||
|
|
||||
|
public readonly double SlopeWithEpsilon(double x, double epsilon) |
||||
|
{ |
||||
|
x = MathExt.Clamp(x, 0.0, 1.0); |
||||
|
double t = SolveCurveX(x, epsilon); |
||||
|
double dx = SampleCurveDerivativeX(t); |
||||
|
double dy = SampleCurveDerivativeY(t); |
||||
|
return dy / dx; |
||||
|
} |
||||
|
|
||||
|
public readonly double Slope(double x) |
||||
|
{ |
||||
|
return SlopeWithEpsilon(x, kBezierEpsilon); |
||||
|
} |
||||
|
|
||||
|
public readonly double RangeMin => range_min_; |
||||
|
public readonly double RangeMax => range_max_; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition.Utils |
||||
|
{ |
||||
|
static class MathExt |
||||
|
{ |
||||
|
public static float Clamp(float value, float min, float max) |
||||
|
{ |
||||
|
var amax = Math.Max(min, max); |
||||
|
var amin = Math.Min(min, max); |
||||
|
return Math.Min(Math.Max(value, amin), amax); |
||||
|
} |
||||
|
|
||||
|
public static double Clamp(double value, double min, double max) |
||||
|
{ |
||||
|
var amax = Math.Max(min, max); |
||||
|
var amin = Math.Min(min, max); |
||||
|
return Math.Min(Math.Max(value, amin), amax); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
using System.Numerics; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public abstract partial class CompositionVisual |
||||
|
{ |
||||
|
private CompositionVisual? _parent; |
||||
|
private CompositionTarget? _root; |
||||
|
|
||||
|
public CompositionVisual? Parent |
||||
|
{ |
||||
|
get => _parent; |
||||
|
internal set |
||||
|
{ |
||||
|
if (_parent == value) |
||||
|
return; |
||||
|
_parent = value; |
||||
|
Changes.Parent.Value = value?.Server; |
||||
|
Root = _parent?.Root; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TODO: hide behind private-ish API
|
||||
|
public CompositionTarget? Root |
||||
|
{ |
||||
|
get => _root; |
||||
|
internal set |
||||
|
{ |
||||
|
var changed = _root != value; |
||||
|
_root = value; |
||||
|
Changes.Root.Value = value?.Server; |
||||
|
if (changed) |
||||
|
OnRootChanged(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private protected virtual void OnRootChanged() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
|
||||
|
internal Matrix4x4? TryGetServerTransform() |
||||
|
{ |
||||
|
if (Root == null) |
||||
|
return null; |
||||
|
var i = Root.Server.Readback; |
||||
|
ref var readback = ref Server.GetReadback(i.ReadIndex); |
||||
|
|
||||
|
// CompositionVisual wasn't visible
|
||||
|
if (readback.Revision < i.ReadRevision) |
||||
|
return null; |
||||
|
|
||||
|
// CompositionVisual was reparented (potential race here)
|
||||
|
if (readback.TargetId != Root.Server.Id) |
||||
|
return null; |
||||
|
|
||||
|
return readback.Matrix; |
||||
|
} |
||||
|
|
||||
|
internal object? Tag { get; set; } |
||||
|
|
||||
|
internal virtual bool HitTest(Vector2 point) => true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Rendering.Composition.Server; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{ |
||||
|
public partial class CompositionVisualCollection : CompositionObject |
||||
|
{ |
||||
|
private CompositionVisual _owner; |
||||
|
internal CompositionVisualCollection(CompositionVisual parent, ServerCompositionVisualCollection server) : base(parent.Compositor, server) |
||||
|
{ |
||||
|
_owner = parent; |
||||
|
InitializeDefaults(); |
||||
|
} |
||||
|
|
||||
|
public void InsertAbove(CompositionVisual newChild, CompositionVisual sibling) |
||||
|
{ |
||||
|
var idx = _list.IndexOf(sibling); |
||||
|
if (idx == -1) |
||||
|
throw new InvalidOperationException(); |
||||
|
|
||||
|
Insert(idx + 1, newChild); |
||||
|
} |
||||
|
|
||||
|
public void InsertBelow(CompositionVisual newChild, CompositionVisual sibling) |
||||
|
{ |
||||
|
var idx = _list.IndexOf(sibling); |
||||
|
if (idx == -1) |
||||
|
throw new InvalidOperationException(); |
||||
|
Insert(idx, newChild); |
||||
|
} |
||||
|
|
||||
|
public void InsertAtTop(CompositionVisual newChild) => Insert(_list.Count, newChild); |
||||
|
|
||||
|
public void InsertAtBottom(CompositionVisual newChild) => Insert(0, newChild); |
||||
|
|
||||
|
public void RemoveAll() => Clear(); |
||||
|
|
||||
|
partial void OnAdded(CompositionVisual item) => item.Parent = _owner; |
||||
|
|
||||
|
partial void OnBeforeReplace(CompositionVisual oldItem, CompositionVisual newItem) |
||||
|
{ |
||||
|
if (oldItem != newItem) |
||||
|
OnBeforeAdded(newItem); |
||||
|
} |
||||
|
|
||||
|
partial void OnReplace(CompositionVisual oldItem, CompositionVisual newItem) |
||||
|
{ |
||||
|
if (oldItem != newItem) |
||||
|
{ |
||||
|
OnRemoved(oldItem); |
||||
|
OnAdded(newItem); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
partial void OnRemoved(CompositionVisual item) => item.Parent = null; |
||||
|
|
||||
|
|
||||
|
partial void OnBeforeAdded(CompositionVisual item) |
||||
|
{ |
||||
|
if (item.Parent != null) |
||||
|
throw new InvalidOperationException("Visual already has a parent"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<NComposition> |
||||
|
<Using>System.Numerics</Using> |
||||
|
<Using>Avalonia.Rendering.Composition.Server</Using> |
||||
|
<Using>Avalonia.Rendering.Composition.Transport</Using> |
||||
|
<Using>Avalonia.Rendering.Composition.Animations</Using> |
||||
|
|
||||
|
<Manual Name="CompositionContainerVisual" /> |
||||
|
<Manual Name="ICompositionSurface" ServerName="ServerCompositionSurface" /> |
||||
|
<Object Name="CompositionVisual" Abstract="true"> |
||||
|
<Property Name="Visible" Type="bool" Animated="true" DefaultValue="true"/> |
||||
|
<Property Name="Opacity" Type="float" Animated="true" DefaultValue="1"/> |
||||
|
<Property Name="Clip" Type="Avalonia.Platform.IGeometryImpl?" /> |
||||
|
<Property Name="ClipToBounds" Type="bool" Animated="true" DefaultValue="true"/> |
||||
|
<Property Name="Offset" Type="Vector3" Animated="true"/> |
||||
|
<Property Name="Size" Type="Vector2" Animated="true"/> |
||||
|
<Property Name="AnchorPoint" Type="Vector2" Animated="true"/> |
||||
|
<Property Name="CenterPoint" Type="Vector3" Animated="true"/> |
||||
|
<Property Name="RotationAngle" Type="float" Animated="true"/> |
||||
|
<Property Name="Orientation" Type="Quaternion" DefaultValue="Quaternion.Identity" Animated="true"/> |
||||
|
<Property Name="Scale" Type="Vector3" DefaultValue="new Vector3(1, 1, 1)" Animated="true"/> |
||||
|
<Property Name="TransformMatrix" Type="Matrix4x4" DefaultValue="Matrix4x4.Identity" Animated="true"/> |
||||
|
</Object> |
||||
|
<List Name="CompositionVisualCollection" ItemType="CompositionVisual" CustomCtor="true"/> |
||||
|
<Object Name="CompositionTarget" CustomServerCtor="true"> |
||||
|
<Property Name="Root" Type="CompositionVisual?"/> |
||||
|
<Property Name="IsEnabled" Type="bool"/> |
||||
|
</Object> |
||||
|
|
||||
|
<Object Name="CompositionSolidColorVisual" Inherits="CompositionContainerVisual" ChangesBase="CompositionVisual"> |
||||
|
<Property Name="Color" Type="Avalonia.Media.Color" Animated="true"/> |
||||
|
</Object> |
||||
|
|
||||
|
<Object Name="CompositionSpriteVisual" Inherits="CompositionContainerVisual" ChangesBase="CompositionVisual"> |
||||
|
<Property Name="Brush" Type="CompositionBrush?"/> |
||||
|
</Object> |
||||
|
|
||||
|
<Object Name="CompositionBrush" Abstract="true"> |
||||
|
|
||||
|
</Object> |
||||
|
|
||||
|
<Object Name="CompositionColorGradientStop"> |
||||
|
<Property Name="Color" Type="Avalonia.Media.Color" Animated="true"/> |
||||
|
<Property Name="Offset" Type="float" Animated="true"/> |
||||
|
</Object> |
||||
|
<List Name="CompositionGradientStopCollection" ItemType="CompositionColorGradientStop" /> |
||||
|
<Brush Name="CompositionGradientBrush" Abstract="true" CustomCtor="true" CustomServerCtor="true"> |
||||
|
<Property Name="ExtendMode" Type="CompositionGradientExtendMode"/> |
||||
|
</Brush> |
||||
|
<Brush Name="CompositionLinearGradientBrush" Inherits="CompositionGradientBrush" CustomUpdate="true"> |
||||
|
<Property Name="StartPoint" Type="Vector2" Animated="true"/> |
||||
|
<Property Name="EndPoint" Type="Vector2" Animated="true"/> |
||||
|
</Brush> |
||||
|
<Brush Name="CompositionColorBrush"> |
||||
|
<Property Name="Color" Type="Avalonia.Media.Color" Animated="true"/> |
||||
|
</Brush> |
||||
|
<Brush Name="CompositionSurfaceBrush"> |
||||
|
<Property Name="Surface" Type="ICompositionSurface?"/> |
||||
|
<Property Name="Stretch" Type="CompositionStretch" DefaultValue="CompositionStretch.Fill" /> |
||||
|
<Property Name="TileMode" Type="CompositionTileMode" /> |
||||
|
</Brush> |
||||
|
|
||||
|
<KeyFrameAnimation Name="Scalar" Type="float"/> |
||||
|
<KeyFrameAnimation Name="Boolean" Type="bool"/> |
||||
|
<KeyFrameAnimation Name="Color" Type="Avalonia.Media.Color"/> |
||||
|
<KeyFrameAnimation Type="Vector2"/> |
||||
|
<KeyFrameAnimation Type="Vector3"/> |
||||
|
<KeyFrameAnimation Type="Vector4"/> |
||||
|
<KeyFrameAnimation Type="Quaternion"/> |
||||
|
</NComposition> |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.IO; |
||||
|
using System.Xml.Serialization; |
||||
|
using Microsoft.CodeAnalysis; |
||||
|
|
||||
|
namespace Avalonia.SourceGenerator.CompositionGenerator; |
||||
|
|
||||
|
[Generator(LanguageNames.CSharp)] |
||||
|
public class CompositionRoslynGenerator : IIncrementalGenerator |
||||
|
{ |
||||
|
public void Initialize(IncrementalGeneratorInitializationContext context) |
||||
|
{ |
||||
|
var schema = context.AdditionalTextsProvider.Where(static file => file.Path.EndsWith("composition-schema.xml")); |
||||
|
var configs = schema.Select((t, _) => |
||||
|
(GConfig)new XmlSerializer(typeof(GConfig)).Deserialize(new StringReader(t.GetText().ToString()))); |
||||
|
context.RegisterSourceOutput(configs, (spc, config) => |
||||
|
{ |
||||
|
var generator = new Generator(spc, config); |
||||
|
generator.Generate(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
#nullable disable |
||||
|
using System.Collections.Generic; |
||||
|
using System.Xml.Serialization; |
||||
|
|
||||
|
namespace Avalonia.SourceGenerator.CompositionGenerator |
||||
|
{ |
||||
|
|
||||
|
|
||||
|
[XmlRoot("NComposition")] |
||||
|
public class GConfig |
||||
|
{ |
||||
|
[XmlElement("Using")] |
||||
|
public List<GUsing> Usings { get; set; } = new List<GUsing>(); |
||||
|
|
||||
|
[XmlElement(typeof(GManualClass), ElementName = "Manual")] |
||||
|
public List<GManualClass> ManualClasses { get; set; } = new List<GManualClass>(); |
||||
|
|
||||
|
[XmlElement(typeof(GClass), ElementName = "Object")] |
||||
|
[XmlElement(typeof(GBrush), ElementName = "Brush")] |
||||
|
[XmlElement(typeof(GList), ElementName = "List")] |
||||
|
public List<GClass> Classes { get; set; } = new List<GClass>(); |
||||
|
|
||||
|
[XmlElement(typeof(GAnimationType), ElementName = "KeyFrameAnimation")] |
||||
|
public List<GAnimationType> KeyFrameAnimations { get; set; } = new List<GAnimationType>(); |
||||
|
} |
||||
|
|
||||
|
public class GUsing |
||||
|
{ |
||||
|
[XmlText] |
||||
|
public string Name { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class GManualClass |
||||
|
{ |
||||
|
[XmlAttribute] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[XmlAttribute] |
||||
|
public string ServerName { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class GImplements |
||||
|
{ |
||||
|
[XmlAttribute] |
||||
|
public string Name { get; set; } |
||||
|
[XmlAttribute] |
||||
|
public string ServerName { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class GClass |
||||
|
{ |
||||
|
[XmlAttribute] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
[XmlAttribute] |
||||
|
public string Inherits { get; set; } |
||||
|
|
||||
|
[XmlAttribute] |
||||
|
public string ChangesBase { get; set; } |
||||
|
|
||||
|
[XmlAttribute] |
||||
|
public string ServerBase { get; set; } |
||||
|
|
||||
|
[XmlAttribute] |
||||
|
public bool CustomCtor { get; set; } |
||||
|
|
||||
|
[XmlAttribute] |
||||
|
public bool CustomServerCtor { get; set; } |
||||
|
|
||||
|
[XmlElement(typeof(GImplements), ElementName = "Implements")] |
||||
|
public List<GImplements> Implements { get; set; } = new List<GImplements>(); |
||||
|
|
||||
|
[XmlAttribute] |
||||
|
public bool Abstract { get; set; } |
||||
|
|
||||
|
[XmlElement(typeof(GProperty), ElementName = "Property")] |
||||
|
public List<GProperty> Properties { get; set; } = new List<GProperty>(); |
||||
|
} |
||||
|
|
||||
|
public class GBrush : GClass |
||||
|
{ |
||||
|
[XmlAttribute] |
||||
|
public bool CustomUpdate { get; set; } |
||||
|
|
||||
|
public GBrush() |
||||
|
{ |
||||
|
Inherits = "CompositionBrush"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class GList : GClass |
||||
|
{ |
||||
|
[XmlAttribute] |
||||
|
public string ItemType { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class GProperty |
||||
|
{ |
||||
|
[XmlAttribute] |
||||
|
public string Name { get; set; } |
||||
|
[XmlAttribute] |
||||
|
public string Type { get; set; } |
||||
|
[XmlAttribute] |
||||
|
public string DefaultValue { get; set; } |
||||
|
[XmlAttribute] |
||||
|
public bool Animated { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class GAnimationType |
||||
|
{ |
||||
|
[XmlAttribute] |
||||
|
public string Name { get; set; } |
||||
|
[XmlAttribute] |
||||
|
public string Type { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Microsoft.CodeAnalysis.CSharp; |
||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
||||
|
namespace Avalonia.SourceGenerator.CompositionGenerator |
||||
|
{ |
||||
|
public static class Extensions |
||||
|
{ |
||||
|
public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers) |
||||
|
{ |
||||
|
if (modifiers == null) |
||||
|
return cl; |
||||
|
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
||||
|
} |
||||
|
|
||||
|
public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers) |
||||
|
{ |
||||
|
if (modifiers == null) |
||||
|
return cl; |
||||
|
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
||||
|
} |
||||
|
|
||||
|
public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers) |
||||
|
{ |
||||
|
if (modifiers == null) |
||||
|
return cl; |
||||
|
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
||||
|
} |
||||
|
|
||||
|
public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers) |
||||
|
{ |
||||
|
if (modifiers == null) |
||||
|
return cl; |
||||
|
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
||||
|
} |
||||
|
|
||||
|
public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers) |
||||
|
{ |
||||
|
if (modifiers == null) |
||||
|
return cl; |
||||
|
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); |
||||
|
} |
||||
|
|
||||
|
public static string WithLowerFirst(this string s) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(s)) |
||||
|
return s; |
||||
|
return char.ToLowerInvariant(s[0]) + s.Substring(1); |
||||
|
} |
||||
|
|
||||
|
public static ExpressionSyntax MemberAccess(params string[] identifiers) |
||||
|
{ |
||||
|
if (identifiers == null || identifiers.Length == 0) |
||||
|
throw new ArgumentException(); |
||||
|
var expr = (ExpressionSyntax)IdentifierName(identifiers[0]); |
||||
|
for (var c = 1; c < identifiers.Length; c++) |
||||
|
expr = MemberAccess(expr, identifiers[c]); |
||||
|
return expr; |
||||
|
} |
||||
|
|
||||
|
public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers) |
||||
|
{ |
||||
|
foreach (var i in identifiers) |
||||
|
expr = MemberAccess(expr, i); |
||||
|
return expr; |
||||
|
} |
||||
|
|
||||
|
public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) => |
||||
|
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier)); |
||||
|
|
||||
|
public static ExpressionSyntax ConditionalMemberAccess(ExpressionSyntax expr, string member, bool checkNull) |
||||
|
{ |
||||
|
if (checkNull) |
||||
|
return ConditionalAccessExpression(expr, MemberBindingExpression(IdentifierName(member))); |
||||
|
return MemberAccess(expr, member); |
||||
|
} |
||||
|
|
||||
|
public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt) |
||||
|
{ |
||||
|
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt))); |
||||
|
} |
||||
|
|
||||
|
public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s) |
||||
|
? s |
||||
|
: s.StartsWith(prefix) |
||||
|
? s.Substring(prefix.Length) |
||||
|
: s; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
namespace Avalonia.SourceGenerator.CompositionGenerator |
||||
|
{ |
||||
|
public partial class Generator |
||||
|
{ |
||||
|
void GenerateAnimations() |
||||
|
{ |
||||
|
var code = $@"using System.Numerics;
|
||||
|
using Avalonia.Rendering.Composition.Animations; |
||||
|
using Avalonia.Rendering.Composition.Expressions; |
||||
|
|
||||
|
namespace Avalonia.Rendering.Composition |
||||
|
{{ |
||||
|
";
|
||||
|
|
||||
|
foreach (var a in _config.KeyFrameAnimations) |
||||
|
{ |
||||
|
var name = a.Name ?? a.Type; |
||||
|
|
||||
|
code += $@"
|
||||
|
public class {name}KeyFrameAnimation : KeyFrameAnimation |
||||
|
{{ |
||||
|
public {name}KeyFrameAnimation(Compositor compositor) : base(compositor) |
||||
|
{{ |
||||
|
}} |
||||
|
|
||||
|
internal override IAnimationInstance CreateInstance(Avalonia.Rendering.Composition.Server.ServerObject targetObject, ExpressionVariant? finalValue) |
||||
|
{{ |
||||
|
return new KeyFrameAnimationInstance<{a.Type}>({name}Interpolator.Instance, _keyFrames.Snapshot(), CreateSnapshot(true), |
||||
|
finalValue?.CastOrDefault<{a.Type}>(), targetObject, |
||||
|
DelayBehavior, DelayTime, Direction, Duration, IterationBehavior, |
||||
|
IterationCount, StopBehavior); |
||||
|
}} |
||||
|
|
||||
|
private KeyFrames<{a.Type}> _keyFrames = new KeyFrames<{a.Type}>(); |
||||
|
private protected override IKeyFrames KeyFrames => _keyFrames; |
||||
|
|
||||
|
public void InsertKeyFrame(float normalizedProgressKey, {a.Type} value, CompositionEasingFunction easingFunction) |
||||
|
{{ |
||||
|
_keyFrames.Insert(normalizedProgressKey, value, easingFunction); |
||||
|
}} |
||||
|
|
||||
|
public void InsertKeyFrame(float normalizedProgressKey, {a.Type} value) |
||||
|
{{ |
||||
|
_keyFrames.Insert(normalizedProgressKey, value, Compositor.DefaultEasing); |
||||
|
}} |
||||
|
}} |
||||
|
|
||||
|
public partial class Compositor |
||||
|
{{ |
||||
|
public {name}KeyFrameAnimation Create{name}KeyFrameAnimation() => new {name}KeyFrameAnimation(this); |
||||
|
}} |
||||
|
";
|
||||
|
} |
||||
|
|
||||
|
code += "}"; |
||||
|
_output.AddSource("CompositionAnimations.cs", code); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,121 @@ |
|||||
|
using System.Linq; |
||||
|
using Microsoft.CodeAnalysis; |
||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
||||
|
namespace Avalonia.SourceGenerator.CompositionGenerator |
||||
|
{ |
||||
|
public partial class Generator |
||||
|
{ |
||||
|
private const string ListProxyTemplate = @"
|
||||
|
class Template |
||||
|
{ |
||||
|
private ServerListProxyHelper<ItemTypeName, ServerItemTypeName> _list = null!; |
||||
|
|
||||
|
ListChangeSet<ServerItemTypeName> |
||||
|
ServerListProxyHelper<ItemTypeName, ServerItemTypeName>.IGetChanges. |
||||
|
GetChanges() => Changes; |
||||
|
|
||||
|
public List<ItemTypeName>.Enumerator GetEnumerator() => _list.GetEnumerator(); |
||||
|
|
||||
|
IEnumerator<ItemTypeName> IEnumerable<ItemTypeName>.GetEnumerator() => |
||||
|
GetEnumerator(); |
||||
|
|
||||
|
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) _list).GetEnumerator(); |
||||
|
|
||||
|
public void Add(ItemTypeName item) |
||||
|
{ |
||||
|
OnBeforeAdded(item); |
||||
|
_list.Add(item); |
||||
|
OnAdded(item); |
||||
|
} |
||||
|
|
||||
|
public void Clear() |
||||
|
{ |
||||
|
OnBeforeClear(); |
||||
|
_list.Clear(); |
||||
|
OnClear(); |
||||
|
} |
||||
|
|
||||
|
public bool Contains(ItemTypeName item) => _list.Contains(item); |
||||
|
|
||||
|
public void CopyTo(ItemTypeName[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); |
||||
|
|
||||
|
public bool Remove(ItemTypeName item) |
||||
|
{ |
||||
|
var removed = _list.Remove(item); |
||||
|
if(removed) |
||||
|
OnRemoved(item); |
||||
|
return removed; |
||||
|
} |
||||
|
|
||||
|
public int Count => _list.Count; |
||||
|
|
||||
|
public bool IsReadOnly => _list.IsReadOnly; |
||||
|
|
||||
|
public int IndexOf(ItemTypeName item) => _list.IndexOf(item); |
||||
|
|
||||
|
public void Insert(int index, ItemTypeName item) |
||||
|
{ |
||||
|
OnBeforeAdded(item); |
||||
|
_list.Insert(index, item); |
||||
|
OnAdded(item); |
||||
|
} |
||||
|
|
||||
|
public void RemoveAt(int index) |
||||
|
{ |
||||
|
var item = _list[index]; |
||||
|
_list.RemoveAt(index); |
||||
|
OnRemoved(item); |
||||
|
} |
||||
|
|
||||
|
public ItemTypeName this[int index] |
||||
|
{ |
||||
|
get => _list[index]; |
||||
|
set |
||||
|
{ |
||||
|
var old = _list[index]; |
||||
|
OnBeforeReplace(old, value); |
||||
|
_list[index] = value; |
||||
|
OnReplace(old, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
partial void OnBeforeAdded(ItemTypeName item); |
||||
|
partial void OnAdded(ItemTypeName item); |
||||
|
partial void OnRemoved(ItemTypeName item); |
||||
|
partial void OnBeforeClear(); |
||||
|
partial void OnBeforeReplace(ItemTypeName oldItem, ItemTypeName newItem); |
||||
|
partial void OnReplace(ItemTypeName oldItem, ItemTypeName newItem); |
||||
|
partial void OnClear(); |
||||
|
} |
||||
|
";
|
||||
|
|
||||
|
private ClassDeclarationSyntax AppendListProxy(GList list, ClassDeclarationSyntax cl) |
||||
|
{ |
||||
|
|
||||
|
var itemType = list.ItemType; |
||||
|
var serverItemType = ServerName(itemType); |
||||
|
|
||||
|
cl = cl.AddBaseListTypes(SimpleBaseType( |
||||
|
ParseTypeName("ServerListProxyHelper<" + itemType + ", " + serverItemType + ">.IGetChanges")), |
||||
|
SimpleBaseType(ParseTypeName("IList<" + itemType + ">")) |
||||
|
); |
||||
|
var code = ListProxyTemplate.Replace("ListTypeName", list.Name) |
||||
|
.Replace("ItemTypeName", itemType); |
||||
|
|
||||
|
var parsed = ParseCompilationUnit(code); |
||||
|
var parsedClass = (ClassDeclarationSyntax)parsed.Members.First(); |
||||
|
|
||||
|
cl = cl.AddMembers(parsedClass.Members.ToArray()); |
||||
|
|
||||
|
var defs = cl.Members.OfType<MethodDeclarationSyntax>().First(m => m.Identifier.Text == "InitializeDefaults"); |
||||
|
|
||||
|
cl = cl.ReplaceNode(defs.Body, defs.Body.AddStatements( |
||||
|
|
||||
|
ParseStatement($"_list = new ServerListProxyHelper<{itemType}, {serverItemType}>(this);"))); |
||||
|
|
||||
|
return cl; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using Microsoft.CodeAnalysis; |
||||
|
using Microsoft.CodeAnalysis.CSharp; |
||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
||||
|
namespace Avalonia.SourceGenerator.CompositionGenerator |
||||
|
{ |
||||
|
public partial class Generator |
||||
|
{ |
||||
|
static void CleanDirectory(string path) |
||||
|
{ |
||||
|
Directory.CreateDirectory(path); |
||||
|
Directory.Delete(path, true); |
||||
|
Directory.CreateDirectory(path); |
||||
|
} |
||||
|
|
||||
|
CompilationUnitSyntax Unit() |
||||
|
=> CompilationUnit().WithUsings(List(new[] |
||||
|
{ |
||||
|
"System", |
||||
|
"System.Text", |
||||
|
"System.Collections", |
||||
|
"System.Collections.Generic" |
||||
|
} |
||||
|
.Concat(_config.Usings |
||||
|
.Select(x => x.Name)).Select(u => UsingDirective(IdentifierName(u))))); |
||||
|
|
||||
|
void SaveTo(CompilationUnitSyntax unit, params string[] path) |
||||
|
{ |
||||
|
var text = @"
|
||||
|
#nullable enable |
||||
|
#pragma warning disable CS0108, CS0114
|
||||
|
|
||||
|
" +
|
||||
|
|
||||
|
unit.NormalizeWhitespace().ToFullString(); |
||||
|
_output.AddSource(string.Join("_", path), text); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken); |
||||
|
|
||||
|
|
||||
|
FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value) |
||||
|
=> FieldDeclaration( |
||||
|
VariableDeclaration(ParseTypeName(type), |
||||
|
SingletonSeparatedList( |
||||
|
VariableDeclarator(name).WithInitializer(EqualsValueClause(value)) |
||||
|
)) |
||||
|
).WithSemicolonToken(Semicolon()) |
||||
|
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword))); |
||||
|
|
||||
|
FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) => |
||||
|
DeclareField(type, name, null, modifiers); |
||||
|
|
||||
|
FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer, |
||||
|
params SyntaxKind[] modifiers) => |
||||
|
FieldDeclaration( |
||||
|
VariableDeclaration(ParseTypeName(type), |
||||
|
SingletonSeparatedList( |
||||
|
VariableDeclarator(name).WithInitializer(initializer)))) |
||||
|
.WithSemicolonToken(Semicolon()) |
||||
|
.WithModifiers(TokenList(modifiers.Select(x => Token(x)))); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,504 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Microsoft.CodeAnalysis; |
||||
|
using Microsoft.CodeAnalysis.CSharp; |
||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; |
||||
|
using static Avalonia.SourceGenerator.CompositionGenerator.Extensions; |
||||
|
namespace Avalonia.SourceGenerator.CompositionGenerator |
||||
|
{ |
||||
|
partial class Generator |
||||
|
{ |
||||
|
private readonly SourceProductionContext _output; |
||||
|
private readonly GConfig _config; |
||||
|
private readonly HashSet<string> _objects; |
||||
|
private readonly HashSet<string> _brushes; |
||||
|
private readonly Dictionary<string, GManualClass> _manuals; |
||||
|
public Generator(SourceProductionContext output, GConfig config) |
||||
|
{ |
||||
|
_output = output; |
||||
|
_config = config; |
||||
|
_manuals = _config.ManualClasses.ToDictionary(x => x.Name); |
||||
|
_objects = new HashSet<string>(_config.ManualClasses.Select(x => x.Name) |
||||
|
.Concat(_config.Classes.Select(x => x.Name))); |
||||
|
_brushes = new HashSet<string>(_config.Classes.OfType<GBrush>().Select(x => x.Name)) {"CompositionBrush"}; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
public void Generate() |
||||
|
{ |
||||
|
foreach (var cl in _config.Classes) |
||||
|
GenerateClass(cl); |
||||
|
|
||||
|
GenerateAnimations(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
string ServerName(string c) => c != null ? ("Server" + c) : "ServerObject"; |
||||
|
string ChangesName(string c) => c != null ? (c + "Changes") : "ChangeSet"; |
||||
|
|
||||
|
void GenerateClass(GClass cl) |
||||
|
{ |
||||
|
var list = cl as GList; |
||||
|
|
||||
|
var unit = Unit(); |
||||
|
|
||||
|
var clientNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition")); |
||||
|
var serverNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition.Server")); |
||||
|
var transportNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition.Transport")); |
||||
|
|
||||
|
var inherits = cl.Inherits ?? "CompositionObject"; |
||||
|
var abstractModifier = cl.Abstract ? new[] {SyntaxKind.AbstractKeyword} : null; |
||||
|
|
||||
|
var client = ClassDeclaration(cl.Name) |
||||
|
.AddModifiers(abstractModifier) |
||||
|
.AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword) |
||||
|
.WithBaseType(inherits); |
||||
|
|
||||
|
var serverName = ServerName(cl.Name); |
||||
|
var serverBase = cl.ServerBase ?? ServerName(cl.Inherits); |
||||
|
if (list != null) |
||||
|
serverBase = "ServerList<" + ServerName(list.ItemType) + ">"; |
||||
|
|
||||
|
var server = ClassDeclaration(serverName) |
||||
|
.AddModifiers(abstractModifier) |
||||
|
.AddModifiers(SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword) |
||||
|
.WithBaseType(serverBase); |
||||
|
|
||||
|
string changesName = ChangesName(cl.Name); |
||||
|
var changesBase = ChangesName(cl.ChangesBase ?? cl.Inherits); |
||||
|
|
||||
|
if (list != null) |
||||
|
changesBase = "ListChangeSet<" + ServerName(list.ItemType) + ">"; |
||||
|
|
||||
|
var changeSetPoolType = "ChangeSetPool<" + changesName + ">"; |
||||
|
var transport = ClassDeclaration(changesName) |
||||
|
.AddModifiers(SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword) |
||||
|
.WithBaseType(changesBase) |
||||
|
.AddMembers(DeclareField(changeSetPoolType, "Pool", |
||||
|
EqualsValueClause( |
||||
|
ParseExpression($"new {changeSetPoolType}(pool => new {changesName}(pool))") |
||||
|
), |
||||
|
SyntaxKind.PublicKeyword, |
||||
|
SyntaxKind.StaticKeyword, SyntaxKind.ReadOnlyKeyword)) |
||||
|
.AddMembers(ParseMemberDeclaration($"public {changesName}(IChangeSetPool pool) : base(pool){{}}")); |
||||
|
|
||||
|
client = client |
||||
|
.AddMembers( |
||||
|
PropertyDeclaration(ParseTypeName("IChangeSetPool"), "ChangeSetPool") |
||||
|
.AddModifiers(SyntaxKind.PrivateKeyword, SyntaxKind.ProtectedKeyword, |
||||
|
SyntaxKind.OverrideKeyword) |
||||
|
.WithExpressionBody( |
||||
|
ArrowExpressionClause(MemberAccess(changesName, "Pool"))) |
||||
|
.WithSemicolonToken(Semicolon())) |
||||
|
.AddMembers(PropertyDeclaration(ParseTypeName(changesName), "Changes") |
||||
|
.AddModifiers(SyntaxKind.PrivateKeyword, SyntaxKind.NewKeyword) |
||||
|
.WithExpressionBody(ArrowExpressionClause(CastExpression(ParseTypeName(changesName), |
||||
|
MemberAccess(BaseExpression(), "Changes")))) |
||||
|
.WithSemicolonToken(Semicolon())); |
||||
|
|
||||
|
if (!cl.CustomCtor) |
||||
|
{ |
||||
|
client = client.AddMembers(PropertyDeclaration(ParseTypeName(serverName), "Server") |
||||
|
.AddModifiers(SyntaxKind.InternalKeyword, SyntaxKind.NewKeyword) |
||||
|
.AddAccessorListAccessors(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) |
||||
|
.WithSemicolonToken(Semicolon()))); |
||||
|
client = client.AddMembers( |
||||
|
ConstructorDeclaration(cl.Name) |
||||
|
.AddModifiers(SyntaxKind.InternalKeyword) |
||||
|
.WithParameterList(ParameterList(SeparatedList(new[] |
||||
|
{ |
||||
|
Parameter(Identifier("compositor")).WithType(ParseTypeName("Compositor")), |
||||
|
Parameter(Identifier("server")).WithType(ParseTypeName(serverName)), |
||||
|
}))) |
||||
|
.WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, |
||||
|
ArgumentList(SeparatedList(new[] |
||||
|
{ |
||||
|
Argument(IdentifierName("compositor")), |
||||
|
Argument(IdentifierName("server")), |
||||
|
})))).WithBody(Block( |
||||
|
ExpressionStatement( |
||||
|
AssignmentExpression( |
||||
|
SyntaxKind.SimpleAssignmentExpression, |
||||
|
IdentifierName("Server"), |
||||
|
CastExpression(ParseTypeName(serverName), IdentifierName("server")))), |
||||
|
ExpressionStatement(InvocationExpression(IdentifierName("InitializeDefaults"))) |
||||
|
))); |
||||
|
} |
||||
|
|
||||
|
if (!cl.CustomServerCtor) |
||||
|
{ |
||||
|
server = server.AddMembers( |
||||
|
ConstructorDeclaration(serverName) |
||||
|
.AddModifiers(SyntaxKind.InternalKeyword) |
||||
|
.WithParameterList(ParameterList(SeparatedList(new[] |
||||
|
{ |
||||
|
Parameter(Identifier("compositor")).WithType(ParseTypeName("ServerCompositor")), |
||||
|
}))) |
||||
|
.WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, |
||||
|
ArgumentList(SeparatedList(new[] |
||||
|
{ |
||||
|
Argument(IdentifierName("compositor")), |
||||
|
})))).WithBody(Block())); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
var changesVarName = "c"; |
||||
|
var changesVar = IdentifierName(changesVarName); |
||||
|
|
||||
|
server = server.AddMembers( |
||||
|
MethodDeclaration(ParseTypeName("void"), "ApplyChangesExtra") |
||||
|
.AddParameterListParameters(Parameter(Identifier("c")).WithType(ParseTypeName(changesName))) |
||||
|
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); |
||||
|
|
||||
|
transport = transport.AddMembers( |
||||
|
MethodDeclaration(ParseTypeName("void"), "ResetExtra") |
||||
|
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); |
||||
|
|
||||
|
var applyMethodBody = Block( |
||||
|
ExpressionStatement(InvocationExpression(MemberAccess(IdentifierName("base"), "ApplyCore"), |
||||
|
ArgumentList(SeparatedList(new[] {Argument(IdentifierName("changes"))})))), |
||||
|
LocalDeclarationStatement(VariableDeclaration(ParseTypeName("var")) |
||||
|
.WithVariables(SingletonSeparatedList( |
||||
|
VariableDeclarator(changesVarName) |
||||
|
.WithInitializer(EqualsValueClause(CastExpression(ParseTypeName(changesName), |
||||
|
IdentifierName("changes"))))))), |
||||
|
ExpressionStatement(InvocationExpression(IdentifierName("ApplyChangesExtra")) |
||||
|
.AddArgumentListArguments(Argument(IdentifierName("c")))) |
||||
|
); |
||||
|
|
||||
|
var resetBody = Block(); |
||||
|
var startAnimationBody = Block(); |
||||
|
var getPropertyBody = Block(); |
||||
|
var serverGetPropertyBody = Block(); |
||||
|
|
||||
|
var defaultsMethodBody = Block(); |
||||
|
|
||||
|
foreach (var prop in cl.Properties) |
||||
|
{ |
||||
|
var fieldName = "_" + prop.Name.WithLowerFirst(); |
||||
|
var propType = ParseTypeName(prop.Type); |
||||
|
var filteredPropertyType = prop.Type.TrimEnd('?'); |
||||
|
var isObject = _objects.Contains(filteredPropertyType); |
||||
|
var isNullable = prop.Type.EndsWith("?"); |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
client = client |
||||
|
.AddMembers(DeclareField(prop.Type, fieldName)) |
||||
|
.AddMembers(PropertyDeclaration(propType, prop.Name) |
||||
|
.AddModifiers(SyntaxKind.PublicKeyword) |
||||
|
.AddAccessorListAccessors( |
||||
|
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, |
||||
|
Block(ReturnStatement(IdentifierName(fieldName)))), |
||||
|
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, |
||||
|
Block( |
||||
|
ParseStatement("var changed = false;"), |
||||
|
IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression, |
||||
|
IdentifierName(fieldName), |
||||
|
IdentifierName("value")), |
||||
|
Block( |
||||
|
ParseStatement("On" + prop.Name + "Changing();"), |
||||
|
ParseStatement("changed = true;"), |
||||
|
GeneratePropertySetterAssignment(prop, fieldName, isObject, isNullable)) |
||||
|
), |
||||
|
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, |
||||
|
IdentifierName(fieldName), IdentifierName("value"))), |
||||
|
ParseStatement($"if(changed) On" + prop.Name + "Changed();") |
||||
|
)) |
||||
|
)) |
||||
|
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed") |
||||
|
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())) |
||||
|
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing") |
||||
|
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); |
||||
|
|
||||
|
|
||||
|
var animatedServer = prop.Animated; |
||||
|
|
||||
|
var serverPropertyType = ((isObject ? "Server" : "") + prop.Type); |
||||
|
if (_manuals.TryGetValue(filteredPropertyType, out var manual) && manual.ServerName != null) |
||||
|
serverPropertyType = manual.ServerName + (isNullable ? "?" : ""); |
||||
|
|
||||
|
|
||||
|
transport = transport |
||||
|
.AddMembers(DeclareField((animatedServer ? "Animated" : "") + "Change<" + serverPropertyType + ">", |
||||
|
prop.Name, SyntaxKind.PublicKeyword)); |
||||
|
|
||||
|
if (animatedServer) |
||||
|
server = server.AddMembers( |
||||
|
DeclareField("AnimatedValueStore<" + serverPropertyType + ">", fieldName), |
||||
|
PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name) |
||||
|
.AddModifiers(SyntaxKind.PublicKeyword) |
||||
|
.WithExpressionBody(ArrowExpressionClause( |
||||
|
InvocationExpression(MemberAccess(fieldName, "GetAnimated"), |
||||
|
ArgumentList(SingletonSeparatedList(Argument(IdentifierName("Compositor"))))))) |
||||
|
.WithSemicolonToken(Semicolon()) |
||||
|
); |
||||
|
else |
||||
|
{ |
||||
|
server = server |
||||
|
.AddMembers(DeclareField(serverPropertyType, fieldName)) |
||||
|
.AddMembers(PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name) |
||||
|
.AddModifiers(SyntaxKind.PublicKeyword) |
||||
|
.AddAccessorListAccessors( |
||||
|
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, |
||||
|
Block(ReturnStatement(IdentifierName(fieldName)))), |
||||
|
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, |
||||
|
Block( |
||||
|
ParseStatement("var changed = false;"), |
||||
|
IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression, |
||||
|
IdentifierName(fieldName), |
||||
|
IdentifierName("value")), |
||||
|
Block( |
||||
|
ParseStatement("On" + prop.Name + "Changing();"), |
||||
|
ParseStatement($"changed = true;")) |
||||
|
), |
||||
|
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, |
||||
|
IdentifierName(fieldName), IdentifierName("value"))), |
||||
|
ParseStatement($"if(changed) On" + prop.Name + "Changed();") |
||||
|
)) |
||||
|
)) |
||||
|
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed") |
||||
|
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())) |
||||
|
.AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing") |
||||
|
.AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); |
||||
|
} |
||||
|
|
||||
|
if (animatedServer) |
||||
|
applyMethodBody = applyMethodBody.AddStatements( |
||||
|
IfStatement(MemberAccess(changesVar, prop.Name, "IsValue"), |
||||
|
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, |
||||
|
IdentifierName(fieldName), MemberAccess(changesVar, prop.Name, "Value")))), |
||||
|
IfStatement(MemberAccess(changesVar, prop.Name, "IsAnimation"), |
||||
|
ExpressionStatement( |
||||
|
InvocationExpression(MemberAccess(fieldName, "SetAnimation"), |
||||
|
ArgumentList(SeparatedList(new[] |
||||
|
{ |
||||
|
Argument(changesVar), |
||||
|
Argument(MemberAccess(changesVar, prop.Name, "Animation")) |
||||
|
}))))) |
||||
|
); |
||||
|
else |
||||
|
applyMethodBody = applyMethodBody.AddStatements( |
||||
|
IfStatement(MemberAccess(changesVar, prop.Name, "IsSet"), |
||||
|
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, |
||||
|
IdentifierName(prop.Name), MemberAccess(changesVar, prop.Name, "Value")))) |
||||
|
|
||||
|
); |
||||
|
|
||||
|
|
||||
|
resetBody = resetBody.AddStatements( |
||||
|
ExpressionStatement(InvocationExpression(MemberAccess(prop.Name, "Reset")))); |
||||
|
|
||||
|
if (animatedServer) |
||||
|
startAnimationBody = ApplyStartAnimation(startAnimationBody, prop, fieldName); |
||||
|
|
||||
|
getPropertyBody = ApplyGetProperty(getPropertyBody, prop); |
||||
|
serverGetPropertyBody = ApplyGetProperty(getPropertyBody, prop); |
||||
|
|
||||
|
if (prop.DefaultValue != null) |
||||
|
{ |
||||
|
defaultsMethodBody = defaultsMethodBody.AddStatements( |
||||
|
ExpressionStatement( |
||||
|
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, |
||||
|
IdentifierName(prop.Name), ParseExpression(prop.DefaultValue)))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (cl is GBrush brush && !cl.Abstract) |
||||
|
{ |
||||
|
var brushName = brush.Name.StripPrefix("Composition"); |
||||
|
/* |
||||
|
server = server.AddMembers( |
||||
|
MethodDeclaration(ParseTypeName("ICbBrush"), "CreateBackendBrush") |
||||
|
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword) |
||||
|
.WithExpressionBody(ArrowExpressionClause( |
||||
|
InvocationExpression(MemberAccess("Compositor", "Backend", "Create" + brushName)) |
||||
|
)).WithSemicolonToken(Semicolon()) |
||||
|
); |
||||
|
if (!brush.CustomUpdate) |
||||
|
server = server.AddMembers( |
||||
|
MethodDeclaration(ParseTypeName("void"), "UpdateBackendBrush") |
||||
|
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword) |
||||
|
.AddParameterListParameters(Parameter(Identifier("brush")) |
||||
|
.WithType(ParseTypeName("ICbBrush"))) |
||||
|
.AddBodyStatements( |
||||
|
ExpressionStatement( |
||||
|
InvocationExpression( |
||||
|
MemberAccess( |
||||
|
ParenthesizedExpression( |
||||
|
CastExpression(ParseTypeName("ICb" + brushName), IdentifierName("brush"))), "Update"), |
||||
|
ArgumentList(SeparatedList(cl.Properties.Select(x => |
||||
|
{ |
||||
|
if(x.Type.TrimEnd('?') == "ICompositionSurface") |
||||
|
return Argument( |
||||
|
ConditionalAccessExpression(IdentifierName(x.Name), |
||||
|
MemberBindingExpression(IdentifierName("BackendSurface"))) |
||||
|
); |
||||
|
if (_brushes.Contains(x.Type)) |
||||
|
return Argument( |
||||
|
ConditionalAccessExpression(IdentifierName(x.Name), |
||||
|
MemberBindingExpression(IdentifierName("Brush"))) |
||||
|
); |
||||
|
return Argument(IdentifierName(x.Name)); |
||||
|
})))) |
||||
|
))); |
||||
|
|
||||
|
*/ |
||||
|
} |
||||
|
|
||||
|
server = server.AddMembers( |
||||
|
MethodDeclaration(ParseTypeName("void"), "ApplyCore") |
||||
|
.AddModifiers(SyntaxKind.ProtectedKeyword, SyntaxKind.OverrideKeyword) |
||||
|
.AddParameterListParameters( |
||||
|
Parameter(Identifier("changes")).WithType(ParseTypeName("ChangeSet"))) |
||||
|
.WithBody(applyMethodBody)); |
||||
|
|
||||
|
client = client.AddMembers( |
||||
|
MethodDeclaration(ParseTypeName("void"), "InitializeDefaults").WithBody(defaultsMethodBody)); |
||||
|
|
||||
|
transport = transport.AddMembers(MethodDeclaration(ParseTypeName("void"), "Reset") |
||||
|
.AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.OverrideKeyword) |
||||
|
.WithBody(resetBody.AddStatements( |
||||
|
ExpressionStatement(InvocationExpression(IdentifierName("ResetExtra"))), |
||||
|
ExpressionStatement(InvocationExpression(MemberAccess("base", "Reset")))))); |
||||
|
|
||||
|
if (list != null) |
||||
|
client = AppendListProxy(list, client); |
||||
|
|
||||
|
if (startAnimationBody.Statements.Count != 0) |
||||
|
client = WithStartAnimation(client, startAnimationBody); |
||||
|
|
||||
|
client = WithGetProperty(client, getPropertyBody, false); |
||||
|
server = WithGetProperty(server, serverGetPropertyBody, true); |
||||
|
|
||||
|
if(cl.Implements.Count > 0) |
||||
|
foreach (var impl in cl.Implements) |
||||
|
{ |
||||
|
client = client.WithBaseList(client.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.Name)))); |
||||
|
if (impl.ServerName != null) |
||||
|
server = server.WithBaseList( |
||||
|
server.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.ServerName)))); |
||||
|
|
||||
|
client = client.AddMembers( |
||||
|
ParseMemberDeclaration($"{impl.ServerName} {impl.Name}.Server => Server;")); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
SaveTo(unit.AddMembers(clientNs.AddMembers(client)), |
||||
|
cl.Name + ".generated.cs"); |
||||
|
SaveTo(unit.AddMembers(serverNs.AddMembers(server)), |
||||
|
"Server", "Server" + cl.Name + ".generated.cs"); |
||||
|
SaveTo(unit.AddMembers(transportNs.AddMembers(transport)), |
||||
|
"Transport", cl.Name + "Changes.generated.cs"); |
||||
|
} |
||||
|
|
||||
|
StatementSyntax GeneratePropertySetterAssignment(GProperty prop, string fieldName, bool isObject, bool isNullable) |
||||
|
{ |
||||
|
var normalChangesAssignment = (StatementSyntax)ExpressionStatement(AssignmentExpression( |
||||
|
SyntaxKind.SimpleAssignmentExpression, |
||||
|
MemberAccess((ExpressionSyntax) IdentifierName("Changes"), prop.Name, |
||||
|
"Value"), |
||||
|
isObject |
||||
|
? |
||||
|
ConditionalMemberAccess(IdentifierName("value"), "Server", isNullable) |
||||
|
: IdentifierName("value"))); |
||||
|
if (!prop.Animated) |
||||
|
return normalChangesAssignment; |
||||
|
|
||||
|
var code = $@"
|
||||
|
{{ |
||||
|
if(animation is CompositionAnimation a) |
||||
|
Changes.{prop.Name}.Animation = a.CreateInstance(this.Server, value); |
||||
|
else |
||||
|
{{ |
||||
|
var saved = Changes.{prop.Name}; |
||||
|
if(!StartAnimationGroup(animation, ""{prop.Name}"", value)) |
||||
|
Changes.{prop.Name}.Value = value; |
||||
|
}} |
||||
|
}} |
||||
|
|
||||
|
";
|
||||
|
|
||||
|
return IfStatement( |
||||
|
ParseExpression( |
||||
|
$"ImplicitAnimations != null && ImplicitAnimations.TryGetValue(\"{prop.Name}\", out var animation) == true"), |
||||
|
ParseStatement(code), |
||||
|
ElseClause(normalChangesAssignment) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
BlockSyntax ApplyStartAnimation(BlockSyntax body, GProperty prop, string fieldName) |
||||
|
{ |
||||
|
var code = $@"
|
||||
|
if (propertyName == ""{prop.Name}"") |
||||
|
{{ |
||||
|
var current = {fieldName}; |
||||
|
var server = animation.CreateInstance(this.Server, finalValue); |
||||
|
Changes.{prop.Name}.Animation = server; |
||||
|
return; |
||||
|
}} |
||||
|
";
|
||||
|
return body.AddStatements(ParseStatement(code)); |
||||
|
} |
||||
|
|
||||
|
private static HashSet<string> VariantPropertyTypes = new HashSet<string> |
||||
|
{ |
||||
|
"bool", |
||||
|
"float", |
||||
|
"Vector2", |
||||
|
"Vector3", |
||||
|
"Vector4", |
||||
|
"Matrix3x2", |
||||
|
"Matrix4x4", |
||||
|
"Quaternion", |
||||
|
"CompositionColor" |
||||
|
}; |
||||
|
|
||||
|
BlockSyntax ApplyGetProperty(BlockSyntax body, GProperty prop) |
||||
|
{ |
||||
|
if (VariantPropertyTypes.Contains(prop.Type)) |
||||
|
return body.AddStatements( |
||||
|
ParseStatement($"if(name == \"{prop.Name}\")\n return {prop.Name};\n") |
||||
|
); |
||||
|
|
||||
|
return body; |
||||
|
} |
||||
|
|
||||
|
ClassDeclarationSyntax WithGetProperty(ClassDeclarationSyntax cl, BlockSyntax body, bool server) |
||||
|
{ |
||||
|
if (body.Statements.Count == 0) |
||||
|
return cl; |
||||
|
body = body.AddStatements( |
||||
|
ParseStatement("return base.GetPropertyForAnimation(name);")); |
||||
|
var method = ((MethodDeclarationSyntax) ParseMemberDeclaration( |
||||
|
$"{(server ? "public" : "internal")} override Avalonia.Rendering.Composition.Expressions.ExpressionVariant GetPropertyForAnimation(string name){{}}")) |
||||
|
.WithBody(body); |
||||
|
|
||||
|
return cl.AddMembers(method); |
||||
|
} |
||||
|
|
||||
|
ClassDeclarationSyntax WithStartAnimation(ClassDeclarationSyntax cl, BlockSyntax body) |
||||
|
{ |
||||
|
body = body.AddStatements( |
||||
|
ExpressionStatement(InvocationExpression(MemberAccess("base", "StartAnimation"), |
||||
|
ArgumentList(SeparatedList(new[] |
||||
|
{ |
||||
|
Argument(IdentifierName("propertyName")), |
||||
|
Argument(IdentifierName("animation")), |
||||
|
Argument(IdentifierName("finalValue")), |
||||
|
})))) |
||||
|
); |
||||
|
return cl.AddMembers( |
||||
|
((MethodDeclarationSyntax) ParseMemberDeclaration( |
||||
|
"internal override void StartAnimation(string propertyName, CompositionAnimation animation, Avalonia.Rendering.Composition.Expressions.ExpressionVariant? finalValue){}")) |
||||
|
.WithBody(body)); |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue