diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs
index aa436f5f4e..4e777b36ed 100644
--- a/src/Avalonia.Animation/Animation.cs
+++ b/src/Avalonia.Animation/Animation.cs
@@ -68,7 +68,7 @@ namespace Avalonia.Animation
///
/// The value fill mode for this animation.
///
- public FillMode FillMode { get; set; }
+ public FillMode FillMode { get; set; }
///
/// Easing function to be used.
@@ -80,10 +80,10 @@ namespace Avalonia.Animation
this.CollectionChanged += delegate { _isChildrenChanged = true; };
}
- private void InterpretKeyframes()
+ private IList InterpretKeyframes(Animatable control)
{
- var handlerList = new List<(Type, AvaloniaProperty)>();
- var kfList = new List();
+ var handlerList = new List<(Type type, AvaloniaProperty property)>();
+ var animatorKeyFrames = new List();
foreach (var keyframe in this)
{
@@ -99,41 +99,38 @@ namespace Avalonia.Animation
if (!handlerList.Contains((handler, setter.Property)))
handlerList.Add((handler, setter.Property));
- var newKF = new AnimatorKeyFrame()
+ var cue = keyframe.Cue;
+
+ if (keyframe.TimingMode == KeyFrameTimingMode.TimeSpan)
{
- Handler = handler,
- Property = setter.Property,
- Cue = keyframe.Cue,
- KeyTime = keyframe.KeyTime,
- timeSpanSet = keyframe.timeSpanSet,
- cueSet = keyframe.cueSet,
- Value = setter.Value
- };
-
- kfList.Add(newKF);
+ cue = new Cue(keyframe.KeyTime.Ticks / Duration.Ticks);
+ }
+
+ var newKF = new AnimatorKeyFrame(handler, cue);
+
+ _subscription.Add(newKF.BindSetter(setter, control));
+
+ animatorKeyFrames.Add(newKF);
}
}
- var newAnimatorInstances = new List<(Type handler, AvaloniaProperty prop, IAnimator inst)>();
+ var newAnimatorInstances = new List();
- foreach (var handler in handlerList)
+ foreach (var (handlerType, property) in handlerList)
{
- var newInstance = (IAnimator)Activator.CreateInstance(handler.Item1);
- newInstance.Property = handler.Item2;
- newAnimatorInstances.Add((handler.Item1, handler.Item2, newInstance));
+ var newInstance = (IAnimator)Activator.CreateInstance(handlerType);
+ newInstance.Property = property;
+ newAnimatorInstances.Add(newInstance);
}
- foreach (var kf in kfList)
+ foreach (var keyframe in animatorKeyFrames)
{
- var parent = newAnimatorInstances.Where(p => p.handler == kf.Handler &&
- p.prop == kf.Property)
- .First();
- parent.inst.Add(kf);
+ var animator = newAnimatorInstances.First(a => a.GetType() == keyframe.AnimatorType &&
+ a.Property == keyframe.Property);
+ animator.Add(keyframe);
}
- foreach(var instance in newAnimatorInstances)
- _animators.Add(instance.inst);
-
+ return newAnimatorInstances;
}
///
@@ -150,17 +147,11 @@ namespace Avalonia.Animation
///
public IDisposable Apply(Animatable control, IObservable matchObs)
{
- if (_isChildrenChanged)
- {
- InterpretKeyframes();
- _isChildrenChanged = false;
- }
-
- foreach (IAnimator keyframes in _animators)
+ foreach (IAnimator animator in InterpretKeyframes(control))
{
- _subscription.Add(keyframes.Apply(this, control, matchObs));
+ _subscription.Add(animator.Apply(this, control, matchObs));
}
return this;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs
index 02457cb9aa..bd9c7a0184 100644
--- a/src/Avalonia.Animation/AnimatorKeyFrame.cs
+++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs
@@ -4,6 +4,8 @@ using System.Text;
using System.ComponentModel;
using Avalonia.Metadata;
using Avalonia.Collections;
+using Avalonia.Data;
+using Avalonia.Reactive;
namespace Avalonia.Animation
{
@@ -11,13 +13,63 @@ namespace Avalonia.Animation
/// Defines a KeyFrame that is used for
/// objects.
///
- public class AnimatorKeyFrame
+ public class AnimatorKeyFrame : AvaloniaObject
{
- public Type Handler;
- public Cue Cue;
- public TimeSpan KeyTime;
- internal bool timeSpanSet, cueSet;
- public AvaloniaProperty Property;
- public object Value;
+ public static readonly DirectProperty ValueProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Value), k => k._value, (k, v) => k._value = v);
+
+ public AnimatorKeyFrame()
+ {
+
+ }
+
+ public AnimatorKeyFrame(Type animatorType, Cue cue)
+ {
+ AnimatorType = animatorType;
+ Cue = cue;
+ }
+
+ public Type AnimatorType { get; }
+ public Cue Cue { get; }
+ public AvaloniaProperty Property { get; private set; }
+
+ private object _value;
+
+ public object Value
+ {
+ get => _value;
+ set => SetAndRaise(ValueProperty, ref _value, value);
+ }
+
+ public IDisposable BindSetter(IAnimationSetter setter, Animatable targetControl)
+ {
+ Property = setter.Property;
+ var value = setter.Value;
+
+ if (value is IBinding binding)
+ {
+ return this.Bind(ValueProperty, binding, targetControl);
+ }
+ else
+ {
+ return this.Bind(ValueProperty, ObservableEx.SingleValue(value).ToBinding(), targetControl);
+ }
+ }
+
+ public T GetTypedValue()
+ {
+ var typeConv = TypeDescriptor.GetConverter(typeof(T));
+
+ if (Value == null)
+ {
+ throw new ArgumentNullException($"KeyFrame value can't be null.");
+ }
+ if (!typeConv.CanConvertTo(Value.GetType()))
+ {
+ throw new InvalidCastException($"KeyFrame value doesnt match property type.");
+ }
+
+ return (T)typeConv.ConvertTo(Value, typeof(T));
+ }
}
}
diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs
index e37b0e592a..1a51b897c0 100644
--- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs
+++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs
@@ -51,9 +51,9 @@ namespace Avalonia.Animation
Disposed
}
- public void Initialize(Animation animation, Animatable control, Animator keyframes)
+ public void Initialize(Animation animation, Animatable control, Animator animator)
{
- _parent = keyframes;
+ _parent = animator;
_targetAnimation = animation;
_targetControl = control;
_neutralValue = (T)_targetControl.GetValue(_parent.Property);
@@ -123,121 +123,133 @@ namespace Avalonia.Animation
double _tempDuration = 0d, _easedTime;
- checkstate:
- switch (_currentState)
+ bool handled = false;
+
+ while (!handled)
{
- case KeyFramesStates.DoDelay:
+ switch (_currentState)
+ {
+ case KeyFramesStates.DoDelay:
- if (_fillMode == FillMode.Backward
- || _fillMode == FillMode.Both)
- {
- if (_currentIteration == 0)
+ if (_fillMode == FillMode.Backward
+ || _fillMode == FillMode.Both)
{
- _targetObserver.OnNext(_firstKFValue);
+ if (_currentIteration == 0)
+ {
+ _targetObserver.OnNext(_firstKFValue);
+ }
+ else
+ {
+ _targetObserver.OnNext(_lastInterpValue);
+ }
+ }
+
+ if (_delayFrameCount > _delayTotalFrameCount)
+ {
+ _currentState = KeyFramesStates.DoRun;
}
else
{
- _targetObserver.OnNext(_lastInterpValue);
+ handled = true;
+ _delayFrameCount++;
}
- }
-
- if (_delayFrameCount > _delayTotalFrameCount)
- {
- _currentState = KeyFramesStates.DoRun;
- goto checkstate;
- }
- _delayFrameCount++;
- break;
-
- case KeyFramesStates.DoRun:
-
- if (_isReversed)
- _currentState = KeyFramesStates.RunBackwards;
- else
- _currentState = KeyFramesStates.RunForwards;
-
- goto checkstate;
-
- case KeyFramesStates.RunForwards:
-
- if (_durationFrameCount > _durationTotalFrameCount)
- {
- _currentState = KeyFramesStates.RunComplete;
- goto checkstate;
- }
+ break;
- _tempDuration = (double)_durationFrameCount / _durationTotalFrameCount;
- _currentState = KeyFramesStates.RunApplyValue;
+ case KeyFramesStates.DoRun:
- goto checkstate;
+ if (_isReversed)
+ _currentState = KeyFramesStates.RunBackwards;
+ else
+ _currentState = KeyFramesStates.RunForwards;
- case KeyFramesStates.RunBackwards:
+ break;
- if (_durationFrameCount > _durationTotalFrameCount)
- {
- _currentState = KeyFramesStates.RunComplete;
- goto checkstate;
- }
+ case KeyFramesStates.RunForwards:
- _tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount;
- _currentState = KeyFramesStates.RunApplyValue;
+ if (_durationFrameCount > _durationTotalFrameCount)
+ {
+ _currentState = KeyFramesStates.RunComplete;
+ }
+ else
+ {
+ _tempDuration = (double)_durationFrameCount / _durationTotalFrameCount;
+ _currentState = KeyFramesStates.RunApplyValue;
- goto checkstate;
+ }
+ break;
- case KeyFramesStates.RunApplyValue:
+ case KeyFramesStates.RunBackwards:
- _easedTime = _targetAnimation.Easing.Ease(_tempDuration);
+ if (_durationFrameCount > _durationTotalFrameCount)
+ {
+ _currentState = KeyFramesStates.RunComplete;
+ }
+ else
+ {
+ _tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount;
+ _currentState = KeyFramesStates.RunApplyValue;
+ }
+ break;
- _durationFrameCount++;
- _lastInterpValue = Interpolator(_easedTime, _neutralValue);
- _targetObserver.OnNext(_lastInterpValue);
- _currentState = KeyFramesStates.DoRun;
+ case KeyFramesStates.RunApplyValue:
- break;
+ _easedTime = _targetAnimation.Easing.Ease(_tempDuration);
- case KeyFramesStates.RunComplete:
+ _durationFrameCount++;
+ _lastInterpValue = Interpolator(_easedTime, _neutralValue);
+ _targetObserver.OnNext(_lastInterpValue);
+ _currentState = KeyFramesStates.DoRun;
+ handled = true;
+ break;
- if (_checkLoopAndRepeat)
- {
- _delayFrameCount = 0;
- _durationFrameCount = 0;
+ case KeyFramesStates.RunComplete:
- if (_isLooping)
- {
- _currentState = KeyFramesStates.DoRun;
- }
- else if (_isRepeating)
+ if (_checkLoopAndRepeat)
{
- if (_currentIteration >= _repeatCount)
+ _delayFrameCount = 0;
+ _durationFrameCount = 0;
+
+ if (_isLooping)
{
- _currentState = KeyFramesStates.Stop;
+ _currentState = KeyFramesStates.DoRun;
}
- else
+ else if (_isRepeating)
{
- _currentState = KeyFramesStates.DoRun;
+ if (_currentIteration >= _repeatCount)
+ {
+ _currentState = KeyFramesStates.Stop;
+ }
+ else
+ {
+ _currentState = KeyFramesStates.DoRun;
+ }
+ _currentIteration++;
}
- _currentIteration++;
- }
- if (_animationDirection == PlaybackDirection.Alternate
- || _animationDirection == PlaybackDirection.AlternateReverse)
- _isReversed = !_isReversed;
+ if (_animationDirection == PlaybackDirection.Alternate
+ || _animationDirection == PlaybackDirection.AlternateReverse)
+ _isReversed = !_isReversed;
- break;
- }
+ break;
+ }
- _currentState = KeyFramesStates.Stop;
- goto checkstate;
+ _currentState = KeyFramesStates.Stop;
+ break;
- case KeyFramesStates.Stop:
+ case KeyFramesStates.Stop:
- if (_fillMode == FillMode.Forward
- || _fillMode == FillMode.Both)
- {
- _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
- }
- _targetObserver.OnCompleted();
- break;
+ if (_fillMode == FillMode.Forward
+ || _fillMode == FillMode.Both)
+ {
+ _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
+ }
+ _targetObserver.OnCompleted();
+ handled = true;
+ break;
+ default:
+ handled = true;
+ break;
+ }
}
}
@@ -253,4 +265,4 @@ namespace Avalonia.Animation
_currentState = KeyFramesStates.Disposed;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs
index 6d4ae7d8e2..a1eef87e1e 100644
--- a/src/Avalonia.Animation/Animator`1.cs
+++ b/src/Avalonia.Animation/Animator`1.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Animation
///
/// List of type-converted keyframes.
///
- private Dictionary _convertedKeyframes = new Dictionary();
+ private readonly SortedList _convertedKeyframes = new SortedList();
private bool _isVerfifiedAndConverted;
@@ -38,12 +38,11 @@ namespace Avalonia.Animation
public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch)
{
if (!_isVerfifiedAndConverted)
- VerifyConvertKeyFrames(animation, typeof(T));
+ VerifyConvertKeyFrames();
return obsMatch
- .Where(p => p == true)
// Ignore triggers when global timers are paused.
- .Where(p => Timing.GetGlobalPlayState() != PlayState.Pause)
+ .Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause)
.Subscribe(_ =>
{
var timerObs = RunKeyFrames(animation, control);
@@ -60,8 +59,8 @@ namespace Avalonia.Animation
/// The time parameter, relative to the total animation time
protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double t)
{
- KeyValuePair firstCue, lastCue;
- int kvCount = _convertedKeyframes.Count();
+ KeyValuePair firstCue, lastCue;
+ int kvCount = _convertedKeyframes.Count;
if (kvCount > 2)
{
if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0)
@@ -76,8 +75,8 @@ namespace Avalonia.Animation
}
else
{
- firstCue = _convertedKeyframes.Where(j => j.Key <= t).Last();
- lastCue = _convertedKeyframes.Where(j => j.Key >= t).First();
+ firstCue = _convertedKeyframes.Last(j => j.Key <= t);
+ lastCue = _convertedKeyframes.First(j => j.Key >= t);
}
}
else
@@ -89,7 +88,9 @@ namespace Avalonia.Animation
double t0 = firstCue.Key;
double t1 = lastCue.Key;
var intraframeTime = (t - t0) / (t1 - t0);
- return (intraframeTime, new KeyFramePair(firstCue, lastCue));
+ var firstFrameData = (firstCue.Value.frame.GetTypedValue(), firstCue.Value.isNeutral);
+ var lastFrameData = (lastCue.Value.frame.GetTypedValue(), lastCue.Value.isNeutral);
+ return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData));
}
@@ -98,17 +99,14 @@ namespace Avalonia.Animation
///
private IDisposable RunKeyFrames(Animation animation, Animatable control)
{
- var _kfStateMach = new AnimatorStateMachine();
- _kfStateMach.Initialize(animation, control, this);
+ var stateMachine = new AnimatorStateMachine();
+ stateMachine.Initialize(animation, control, this);
Timing.AnimationStateTimer
- .TakeWhile(_ => !_kfStateMach._unsubscribe)
- .Subscribe(p =>
- {
- _kfStateMach.Step(p, DoInterpolation);
- });
+ .TakeWhile(_ => !stateMachine._unsubscribe)
+ .Subscribe(p => stateMachine.Step(p, DoInterpolation));
- return control.Bind(Property, _kfStateMach, BindingPriority.Animation);
+ return control.Bind(Property, stateMachine, BindingPriority.Animation);
}
///
@@ -119,39 +117,19 @@ namespace Avalonia.Animation
///
/// Verifies and converts keyframe values according to this class's target type.
///
- private void VerifyConvertKeyFrames(Animation animation, Type type)
+ private void VerifyConvertKeyFrames()
{
- var typeConv = TypeDescriptor.GetConverter(type);
-
- foreach (AnimatorKeyFrame k in this)
+ foreach (AnimatorKeyFrame keyframe in this)
{
- if (k.Value == null)
- {
- throw new ArgumentNullException($"KeyFrame value can't be null.");
- }
- if (!typeConv.CanConvertTo(k.Value.GetType()))
- {
- throw new InvalidCastException($"KeyFrame value doesnt match property type.");
- }
-
- T convertedValue = (T)typeConv.ConvertTo(k.Value, type);
-
- Cue _normalizedCue = k.Cue;
-
- if (k.timeSpanSet)
- {
- _normalizedCue = new Cue(k.KeyTime.Ticks / animation.Duration.Ticks);
- }
-
- _convertedKeyframes.Add(_normalizedCue.CueValue, (convertedValue, false));
+ _convertedKeyframes.Add(keyframe.Cue.CueValue, (keyframe, false));
}
- SortKeyFrameCues(_convertedKeyframes);
+ AddNeutralKeyFramesIfNeeded();
_isVerfifiedAndConverted = true;
}
- private void SortKeyFrameCues(Dictionary convertedValues)
+ private void AddNeutralKeyFramesIfNeeded()
{
bool hasStartKey, hasEndKey;
hasStartKey = hasEndKey = false;
@@ -170,23 +148,20 @@ namespace Avalonia.Animation
}
if (!hasStartKey || !hasEndKey)
- AddNeutralKeyFrames(hasStartKey, hasEndKey, _convertedKeyframes);
-
- _convertedKeyframes = _convertedKeyframes.OrderBy(p => p.Key)
- .ToDictionary((k) => k.Key, (v) => v.Value);
+ AddNeutralKeyFrames(hasStartKey, hasEndKey);
}
- private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey, Dictionary convertedKeyframes)
+ private void AddNeutralKeyFrames(bool hasStartKey, bool hasEndKey)
{
if (!hasStartKey)
{
- convertedKeyframes.Add(0.0d, (default(T), true));
+ _convertedKeyframes.Add(0.0d, (new AnimatorKeyFrame { Value = default(T) }, true));
}
if (!hasEndKey)
{
- convertedKeyframes.Add(1.0d, (default(T), true));
+ _convertedKeyframes.Add(1.0d, (new AnimatorKeyFrame { Value = default(T) }, true));
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Animation/Cue.cs b/src/Avalonia.Animation/Cue.cs
index fe36b13495..5a95c108e3 100644
--- a/src/Avalonia.Animation/Cue.cs
+++ b/src/Avalonia.Animation/Cue.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Animation
/// A Cue object for .
///
[TypeConverter(typeof(CueTypeConverter))]
- public struct Cue : IEquatable, IEquatable
+ public readonly struct Cue : IEquatable, IEquatable
{
///
/// The normalized percent value, ranging from 0.0 to 1.0
diff --git a/src/Avalonia.Animation/DoubleAnimator.cs b/src/Avalonia.Animation/DoubleAnimator.cs
index 5b994377f1..154f37360c 100644
--- a/src/Avalonia.Animation/DoubleAnimator.cs
+++ b/src/Avalonia.Animation/DoubleAnimator.cs
@@ -24,15 +24,15 @@ namespace Avalonia.Animation
var firstKF = pair.KFPair.FirstKeyFrame;
var secondKF = pair.KFPair.SecondKeyFrame;
- if (firstKF.Value.isNeutral)
+ if (firstKF.isNeutral)
y0 = neutralValue;
else
- y0 = firstKF.Value.TargetValue;
+ y0 = firstKF.TargetValue;
- if (secondKF.Value.isNeutral)
+ if (secondKF.isNeutral)
y1 = neutralValue;
else
- y1 = secondKF.Value.TargetValue;
+ y1 = secondKF.TargetValue;
// Do linear parametric interpolation
return y0 + (pair.IntraKFTime) * (y1 - y0);
diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs
index f2a94c9ed6..2d22377286 100644
--- a/src/Avalonia.Animation/IAnimationSetter.cs
+++ b/src/Avalonia.Animation/IAnimationSetter.cs
@@ -5,4 +5,4 @@ namespace Avalonia.Animation
AvaloniaProperty Property { get; set; }
object Value { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Animation/KeyFrame.cs b/src/Avalonia.Animation/KeyFrame.cs
index 46be119c36..ea04aa0aab 100644
--- a/src/Avalonia.Animation/KeyFrame.cs
+++ b/src/Avalonia.Animation/KeyFrame.cs
@@ -7,6 +7,11 @@ using Avalonia.Collections;
namespace Avalonia.Animation
{
+ internal enum KeyFrameTimingMode
+ {
+ TimeSpan = 1,
+ Cue
+ }
///
/// Stores data regarding a specific key
@@ -14,7 +19,6 @@ namespace Avalonia.Animation
///
public class KeyFrame : AvaloniaList
{
- internal bool timeSpanSet, cueSet;
private TimeSpan _ktimeSpan;
private Cue _kCue;
@@ -30,6 +34,8 @@ namespace Avalonia.Animation
{
}
+ internal KeyFrameTimingMode TimingMode { get; private set; }
+
///
/// Gets or sets the key time of this .
///
@@ -42,11 +48,11 @@ namespace Avalonia.Animation
}
set
{
- if (cueSet)
+ if (TimingMode == KeyFrameTimingMode.Cue)
{
throw new InvalidOperationException($"You can only set either {nameof(KeyTime)} or {nameof(Cue)}.");
}
- timeSpanSet = true;
+ TimingMode = KeyFrameTimingMode.TimeSpan;
_ktimeSpan = value;
}
}
@@ -63,11 +69,11 @@ namespace Avalonia.Animation
}
set
{
- if (timeSpanSet)
+ if (TimingMode == KeyFrameTimingMode.TimeSpan)
{
throw new InvalidOperationException($"You can only set either {nameof(KeyTime)} or {nameof(Cue)}.");
}
- cueSet = true;
+ TimingMode = KeyFrameTimingMode.Cue;
_kCue = value;
}
}
diff --git a/src/Avalonia.Animation/KeyFramePair`1.cs b/src/Avalonia.Animation/KeyFramePair`1.cs
index c192479a1d..408b13e0d8 100644
--- a/src/Avalonia.Animation/KeyFramePair`1.cs
+++ b/src/Avalonia.Animation/KeyFramePair`1.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Animation
///
///
///
- public KeyFramePair(KeyValuePair FirstKeyFrame, KeyValuePair LastKeyFrame) : this()
+ public KeyFramePair((T TargetValue, bool isNeutral) FirstKeyFrame, (T TargetValue, bool isNeutral) LastKeyFrame) : this()
{
this.FirstKeyFrame = FirstKeyFrame;
this.SecondKeyFrame = LastKeyFrame;
@@ -31,11 +31,11 @@ namespace Avalonia.Animation
///
/// First object.
///
- public KeyValuePair FirstKeyFrame { get; private set; }
+ public (T TargetValue, bool isNeutral) FirstKeyFrame { get; }
///
/// Second object.
///
- public KeyValuePair SecondKeyFrame { get; private set; }
+ public (T TargetValue, bool isNeutral) SecondKeyFrame { get; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index c0a4ace6ed..e29e7339ae 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -106,7 +106,7 @@ namespace Avalonia
}
///
- /// Finds a registered non-attached property on a type by name.
+ /// Finds a registered property on a type by name.
///
/// The type.
/// The property name.
@@ -130,7 +130,7 @@ namespace Avalonia
}
///
- /// Finds a registered non-attached property on a type by name.
+ /// Finds a registered property on an object by name.
///
/// The object.
/// The property name.
@@ -148,52 +148,6 @@ namespace Avalonia
return FindRegistered(o.GetType(), name);
}
- ///
- /// Finds a registered attached property on a type by name.
- ///
- /// The type.
- /// The owner type.
- /// The property name.
- ///
- /// The registered property or null if no matching property found.
- ///
- ///
- /// The property name contains a '.'.
- ///
- public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name)
- {
- Contract.Requires(type != null);
- Contract.Requires(ownerType != null);
- Contract.Requires(name != null);
-
- if (name.Contains('.'))
- {
- throw new InvalidOperationException("Attached properties not supported.");
- }
-
- return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name);
- }
-
- ///
- /// Finds a registered non-attached property on a type by name.
- ///
- /// The object.
- /// The owner type.
- /// The property name.
- ///
- /// The registered property or null if no matching property found.
- ///
- ///
- /// The property name contains a '.'.
- ///
- public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name)
- {
- Contract.Requires(o != null);
- Contract.Requires(name != null);
-
- return FindRegisteredAttached(o.GetType(), ownerType, name);
- }
-
///
/// Checks whether a is registered on a type.
///
@@ -287,4 +241,4 @@ namespace Avalonia
_attachedCache.Clear();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs b/src/Avalonia.Base/Utilities/CharacterReader.cs
similarity index 89%
rename from src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs
rename to src/Avalonia.Base/Utilities/CharacterReader.cs
index 9355bc9aa3..0910d5b969 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/Reader.cs
+++ b/src/Avalonia.Base/Utilities/CharacterReader.cs
@@ -3,14 +3,14 @@
using System;
-namespace Avalonia.Markup.Parsers
+namespace Avalonia.Utilities
{
- internal class Reader
+ public class CharacterReader
{
private readonly string _s;
private int _i;
- public Reader(string s)
+ public CharacterReader(string s)
{
_s = s;
}
diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs
similarity index 91%
rename from src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs
rename to src/Avalonia.Base/Utilities/IdentifierParser.cs
index f86f2db321..14b8affbdd 100644
--- a/src/Markup/Avalonia.Markup/Markup/Parsers/IdentifierParser.cs
+++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs
@@ -4,11 +4,11 @@
using System.Globalization;
using System.Text;
-namespace Avalonia.Markup.Parsers
+namespace Avalonia.Utilities
{
- internal static class IdentifierParser
+ public static class IdentifierParser
{
- public static string Parse(Reader r)
+ public static string Parse(CharacterReader r)
{
if (IsValidIdentifierStart(r.Peek))
{
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index 5e5a460368..b7db352c74 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -21,18 +21,27 @@ namespace Avalonia.Controls
public static readonly StyledProperty OrientationProperty =
AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal);
+ private static readonly DirectProperty IndeterminateStartingOffsetProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(IndeterminateStartingOffset),
+ p => p.IndeterminateStartingOffset,
+ (p, o) => p.IndeterminateStartingOffset = o);
+
+ private static readonly DirectProperty IndeterminateEndingOffsetProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(IndeterminateEndingOffset),
+ p => p.IndeterminateEndingOffset,
+ (p, o) => p.IndeterminateEndingOffset = o);
+
private Border _indicator;
- private IndeterminateAnimation _indeterminateAnimation;
static ProgressBar()
{
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
+ PseudoClass(IsIndeterminateProperty, ":indeterminate");
ValueProperty.Changed.AddClassHandler(x => x.ValueChanged);
-
- IsIndeterminateProperty.Changed.AddClassHandler(
- (p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
}
public bool IsIndeterminate
@@ -46,6 +55,19 @@ namespace Avalonia.Controls
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
+ private double _indeterminateStartingOffset;
+ private double IndeterminateStartingOffset
+ {
+ get => _indeterminateStartingOffset;
+ set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
+ }
+
+ private double _indeterminateEndingOffset;
+ private double IndeterminateEndingOffset
+ {
+ get => _indeterminateEndingOffset;
+ set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
+ }
///
protected override Size ArrangeOverride(Size finalSize)
@@ -60,7 +82,6 @@ namespace Avalonia.Controls
_indicator = e.NameScope.Get("PART_Indicator");
UpdateIndicator(Bounds.Size);
- UpdateIsIndeterminate(IsIndeterminate);
}
private void UpdateIndicator(Size bounds)
@@ -70,9 +91,20 @@ namespace Avalonia.Controls
if (IsIndeterminate)
{
if (Orientation == Orientation.Horizontal)
- _indicator.Width = bounds.Width / 5.0;
+ {
+ var width = bounds.Width / 5.0;
+ IndeterminateStartingOffset = -width;
+ _indicator.Width = width;
+ IndeterminateEndingOffset = bounds.Width;
+
+ }
else
- _indicator.Height = bounds.Height / 5.0;
+ {
+ var height = bounds.Height / 5.0;
+ IndeterminateStartingOffset = -bounds.Height;
+ _indicator.Height = height;
+ IndeterminateEndingOffset = height;
+ }
}
else
{
@@ -86,53 +118,9 @@ namespace Avalonia.Controls
}
}
- private void UpdateIsIndeterminate(bool isIndeterminate)
- {
- if (isIndeterminate)
- {
- if (_indeterminateAnimation == null || _indeterminateAnimation.Disposed)
- _indeterminateAnimation = IndeterminateAnimation.StartAnimation(this);
- }
- else
- _indeterminateAnimation?.Dispose();
- }
-
private void ValueChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateIndicator(Bounds.Size);
}
-
- // TODO: Implement Indeterminate Progress animation
- // in xaml (most ideal) or if it's not possible
- // then on this class.
- private class IndeterminateAnimation : IDisposable
- {
- private WeakReference _progressBar;
-
- private bool _disposed;
-
- public bool Disposed => _disposed;
-
- private IndeterminateAnimation(ProgressBar progressBar)
- {
- _progressBar = new WeakReference(progressBar);
-
- }
-
- public static IndeterminateAnimation StartAnimation(ProgressBar progressBar)
- {
- return new IndeterminateAnimation(progressBar);
- }
-
- private Rect GetAnimationRect(TimeSpan time)
- {
- return Rect.Empty;
- }
-
- public void Dispose()
- {
- _disposed = true;
- }
- }
}
}
diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs
index 31b685f6b1..c75bae4db8 100644
--- a/src/Avalonia.Styling/Styling/Setter.cs
+++ b/src/Avalonia.Styling/Styling/Setter.cs
@@ -126,7 +126,7 @@ namespace Avalonia.Styling
if (source != null)
{
- var cloned = Clone(source, style, activator);
+ var cloned = Clone(source, source.Mode == BindingMode.Default ? Property.GetMetadata(control.GetType()).DefaultBindingMode : source.Mode, style, activator);
return BindingOperations.Apply(control, Property, cloned, null);
}
}
@@ -134,13 +134,13 @@ namespace Avalonia.Styling
return Disposable.Empty;
}
- private InstancedBinding Clone(InstancedBinding sourceInstance, IStyle style, IObservable activator)
+ private InstancedBinding Clone(InstancedBinding sourceInstance, BindingMode mode, IStyle style, IObservable activator)
{
if (activator != null)
{
var description = style?.ToString();
- switch (sourceInstance.Mode)
+ switch (mode)
{
case BindingMode.OneTime:
if (sourceInstance.Observable != null)
diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml
index c9c898562c..c4cbfed350 100644
--- a/src/Avalonia.Themes.Default/ProgressBar.xaml
+++ b/src/Avalonia.Themes.Default/ProgressBar.xaml
@@ -7,14 +7,9 @@
-
-
-
-
+
@@ -35,4 +30,36 @@
-
\ No newline at end of file
+
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index cdc22f4102..8c843a4b49 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -33,6 +33,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
index 63b7811dbc..627a646bcf 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
@@ -4,19 +4,19 @@
using System;
using System.ComponentModel;
using System.Globalization;
-using System.Text.RegularExpressions;
+using Avalonia.Controls;
+using Avalonia.Logging;
+using Avalonia.Markup.Parsers;
+using Avalonia.Markup.Xaml.Parsers;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Styling;
-using Portable.Xaml;
+using Avalonia.Utilities;
using Portable.Xaml.ComponentModel;
-using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.Converters
{
public class AvaloniaPropertyTypeConverter : TypeConverter
{
- private static readonly Regex regex = new Regex(@"^\(?(\w*)\.(\w*)\)?|(.*)$");
-
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
@@ -24,39 +24,48 @@ namespace Avalonia.Markup.Xaml.Converters
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
- var (owner, propertyName) = ParseProperty((string)value);
- var ownerType = TryResolveOwnerByName(context, owner) ??
- context.GetFirstAmbientValue()?.TargetType ??
- context.GetFirstAmbientValue
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = (TextBlock)window.Content;
+
+ window.DataContext = 5.6;
+ window.ApplyTemplate();
+
+ Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock));
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
index 2c7e850fee..beaf7477d0 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
@@ -7,6 +7,7 @@ using Avalonia.Markup.Xaml.Styling;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
+using Portable.Xaml;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
@@ -146,5 +147,56 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.NotNull(target.FocusAdorner);
}
}
+
+ [Fact]
+ public void Setter_Can_Set_Attached_Property()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = (TextBlock)window.Content;
+
+ window.ApplyTemplate();
+
+ Assert.Equal(Dock.Right, DockPanel.GetDock(textBlock));
+ }
+ }
+
+ [Fact(Skip = "The animation system currently needs to be able to set any property on any object")]
+ public void Disallows_Setting_Non_Registered_Property()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+";
+ var loader = new AvaloniaXamlLoader();
+ var ex = Assert.Throws(() => loader.Load(xaml));
+
+ Assert.Equal(
+ "Property 'Button.IsDefault' is not registered on 'Avalonia.Controls.TextBlock'.",
+ ex.InnerException.Message);
+ }
+ }
}
-}
\ No newline at end of file
+}