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