From 0624d300376d2140b069a53a9808ff59933a44c5 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 2 Aug 2018 01:24:17 +0800 Subject: [PATCH 001/512] Make ASM more Genericized. --- src/Avalonia.Animation/AnimatorStateMachine`1.cs | 12 ++++++------ src/Avalonia.Animation/Animator`1.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 1a51b897c0..414f07190a 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -7,10 +7,10 @@ namespace Avalonia.Animation /// /// Provides statefulness for an iteration of a keyframe animation. /// - internal class AnimatorStateMachine : IObservable, IDisposable + internal class AnimatorStateMachine : IObservable, IDisposable { - object _lastInterpValue; - object _firstKFValue; + T _lastInterpValue; + T _firstKFValue; private ulong _delayTotalFrameCount; private ulong _durationTotalFrameCount; @@ -34,7 +34,7 @@ namespace Avalonia.Animation private Animatable _targetControl; private T _neutralValue; internal bool _unsubscribe = false; - private IObserver _targetObserver; + private IObserver _targetObserver; [Flags] private enum KeyFramesStates @@ -100,7 +100,7 @@ namespace Avalonia.Animation { if (!_gotFirstKFValue) { - _firstKFValue = _parent.First().Value; + _firstKFValue = (T)_parent.First().Value; _gotFirstKFValue = true; } @@ -253,7 +253,7 @@ namespace Avalonia.Animation } } - public IDisposable Subscribe(IObserver observer) + public IDisposable Subscribe(IObserver observer) { _targetObserver = observer; return this; diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index a1eef87e1e..37764b34e8 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -106,7 +106,7 @@ namespace Avalonia.Animation .TakeWhile(_ => !stateMachine._unsubscribe) .Subscribe(p => stateMachine.Step(p, DoInterpolation)); - return control.Bind(Property, stateMachine, BindingPriority.Animation); + return control.Bind((AvaloniaProperty)Property, stateMachine, BindingPriority.Animation); } /// From 87934f2bfda67acb52dd363e910e4864db543c31 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 2 Aug 2018 01:30:43 +0800 Subject: [PATCH 002/512] Remove underscores on ASM fields. --- .../AnimatorStateMachine`1.cs | 218 +++++++++--------- src/Avalonia.Animation/Animator`1.cs | 2 +- 2 files changed, 110 insertions(+), 110 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 414f07190a..923cdd07b3 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -9,32 +9,32 @@ namespace Avalonia.Animation /// internal class AnimatorStateMachine : IObservable, IDisposable { - T _lastInterpValue; - T _firstKFValue; - - private ulong _delayTotalFrameCount; - private ulong _durationTotalFrameCount; - private ulong _delayFrameCount; - private ulong _durationFrameCount; - private ulong _repeatCount; - private ulong _currentIteration; - - private bool _isLooping; - private bool _isRepeating; - private bool _isReversed; - private bool _checkLoopAndRepeat; - private bool _gotFirstKFValue; - - private FillMode _fillMode; - private PlaybackDirection _animationDirection; - private KeyFramesStates _currentState; - private KeyFramesStates _savedState; - private Animator _parent; - private Animation _targetAnimation; - private Animatable _targetControl; - private T _neutralValue; - internal bool _unsubscribe = false; - private IObserver _targetObserver; + T lastInterpValue; + T firstKFValue; + + private ulong delayTotalFrameCount; + private ulong durationTotalFrameCount; + private ulong delayFrameCount; + private ulong durationFrameCount; + private ulong repeatCount; + private ulong currentIteration; + + private bool isLooping; + private bool isRepeating; + private bool isReversed; + private bool checkLoopAndRepeat; + private bool gotFirstKFValue; + + private FillMode fillMode; + private PlaybackDirection animationDirection; + private KeyFramesStates currentState; + private KeyFramesStates savedState; + private Animator parent; + private Animation targetAnimation; + private Animatable targetControl; + private T neutralValue; + internal bool unsubscribe = false; + private IObserver targetObserver; [Flags] private enum KeyFramesStates @@ -53,197 +53,197 @@ namespace Avalonia.Animation public void Initialize(Animation animation, Animatable control, Animator animator) { - _parent = animator; - _targetAnimation = animation; - _targetControl = control; - _neutralValue = (T)_targetControl.GetValue(_parent.Property); + parent = animator; + targetAnimation = animation; + targetControl = control; + neutralValue = (T)targetControl.GetValue(parent.Property); - _delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks); - _durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks); + delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks); + durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks); switch (animation.RepeatCount.RepeatType) { case RepeatType.Loop: - _isLooping = true; - _checkLoopAndRepeat = true; + isLooping = true; + checkLoopAndRepeat = true; break; case RepeatType.Repeat: - _isRepeating = true; - _checkLoopAndRepeat = true; - _repeatCount = animation.RepeatCount.Value; + isRepeating = true; + checkLoopAndRepeat = true; + repeatCount = animation.RepeatCount.Value; break; } - _isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0; - _animationDirection = _targetAnimation.PlaybackDirection; - _fillMode = _targetAnimation.FillMode; + isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0; + animationDirection = targetAnimation.PlaybackDirection; + fillMode = targetAnimation.FillMode; - if (_durationTotalFrameCount > 0) - _currentState = KeyFramesStates.DoDelay; + if (durationTotalFrameCount > 0) + currentState = KeyFramesStates.DoDelay; else - _currentState = KeyFramesStates.DoRun; + currentState = KeyFramesStates.DoRun; } - public void Step(PlayState _playState, Func Interpolator) + public void Step(PlayState playState, Func Interpolator) { try { - InternalStep(_playState, Interpolator); + InternalStep(playState, Interpolator); } catch (Exception e) { - _targetObserver?.OnError(e); + targetObserver?.OnError(e); } } - private void InternalStep(PlayState _playState, Func Interpolator) + private void InternalStep(PlayState playState, Func Interpolator) { - if (!_gotFirstKFValue) + if (!gotFirstKFValue) { - _firstKFValue = (T)_parent.First().Value; - _gotFirstKFValue = true; + firstKFValue = (T)parent.First().Value; + gotFirstKFValue = true; } - if (_currentState == KeyFramesStates.Disposed) + if (currentState == KeyFramesStates.Disposed) throw new InvalidProgramException("This KeyFrames Animation is already disposed."); - if (_playState == PlayState.Stop) - _currentState = KeyFramesStates.Stop; + if (playState == PlayState.Stop) + currentState = KeyFramesStates.Stop; - // Save state and pause the machine - if (_playState == PlayState.Pause && _currentState != KeyFramesStates.Pause) - { - _savedState = _currentState; - _currentState = KeyFramesStates.Pause; - } + // // Save state and pause the machine + // if (playState == PlayState.Pause && currentState != KeyFramesStates.Pause) + // { + // savedState = currentState; + // currentState = KeyFramesStates.Pause; + // } - // Resume the previous state - if (_playState != PlayState.Pause && _currentState == KeyFramesStates.Pause) - _currentState = _savedState; + // // Resume the previous state + // if (playState != PlayState.Pause && currentState == KeyFramesStates.Pause) + // currentState = savedState; - double _tempDuration = 0d, _easedTime; + double tempDuration = 0d, easedTime; bool handled = false; while (!handled) { - switch (_currentState) + switch (currentState) { case KeyFramesStates.DoDelay: - if (_fillMode == FillMode.Backward - || _fillMode == FillMode.Both) + if (fillMode == FillMode.Backward + || fillMode == FillMode.Both) { - if (_currentIteration == 0) + if (currentIteration == 0) { - _targetObserver.OnNext(_firstKFValue); + targetObserver.OnNext(firstKFValue); } else { - _targetObserver.OnNext(_lastInterpValue); + targetObserver.OnNext(lastInterpValue); } } - if (_delayFrameCount > _delayTotalFrameCount) + if (delayFrameCount > delayTotalFrameCount) { - _currentState = KeyFramesStates.DoRun; + currentState = KeyFramesStates.DoRun; } else { handled = true; - _delayFrameCount++; + delayFrameCount++; } break; case KeyFramesStates.DoRun: - if (_isReversed) - _currentState = KeyFramesStates.RunBackwards; + if (isReversed) + currentState = KeyFramesStates.RunBackwards; else - _currentState = KeyFramesStates.RunForwards; + currentState = KeyFramesStates.RunForwards; break; case KeyFramesStates.RunForwards: - if (_durationFrameCount > _durationTotalFrameCount) + if (durationFrameCount > durationTotalFrameCount) { - _currentState = KeyFramesStates.RunComplete; + currentState = KeyFramesStates.RunComplete; } else { - _tempDuration = (double)_durationFrameCount / _durationTotalFrameCount; - _currentState = KeyFramesStates.RunApplyValue; + tempDuration = (double)durationFrameCount / durationTotalFrameCount; + currentState = KeyFramesStates.RunApplyValue; } break; case KeyFramesStates.RunBackwards: - if (_durationFrameCount > _durationTotalFrameCount) + if (durationFrameCount > durationTotalFrameCount) { - _currentState = KeyFramesStates.RunComplete; + currentState = KeyFramesStates.RunComplete; } else { - _tempDuration = (double)(_durationTotalFrameCount - _durationFrameCount) / _durationTotalFrameCount; - _currentState = KeyFramesStates.RunApplyValue; + tempDuration = (double)(durationTotalFrameCount - durationFrameCount) / durationTotalFrameCount; + currentState = KeyFramesStates.RunApplyValue; } break; case KeyFramesStates.RunApplyValue: - _easedTime = _targetAnimation.Easing.Ease(_tempDuration); + easedTime = targetAnimation.Easing.Ease(tempDuration); - _durationFrameCount++; - _lastInterpValue = Interpolator(_easedTime, _neutralValue); - _targetObserver.OnNext(_lastInterpValue); - _currentState = KeyFramesStates.DoRun; + durationFrameCount++; + lastInterpValue = Interpolator(easedTime, neutralValue); + targetObserver.OnNext(lastInterpValue); + currentState = KeyFramesStates.DoRun; handled = true; break; case KeyFramesStates.RunComplete: - if (_checkLoopAndRepeat) + if (checkLoopAndRepeat) { - _delayFrameCount = 0; - _durationFrameCount = 0; + delayFrameCount = 0; + durationFrameCount = 0; - if (_isLooping) + if (isLooping) { - _currentState = KeyFramesStates.DoRun; + currentState = KeyFramesStates.DoRun; } - else if (_isRepeating) + else if (isRepeating) { - if (_currentIteration >= _repeatCount) + if (currentIteration >= repeatCount) { - _currentState = KeyFramesStates.Stop; + currentState = KeyFramesStates.Stop; } else { - _currentState = KeyFramesStates.DoRun; + currentState = KeyFramesStates.DoRun; } - _currentIteration++; + currentIteration++; } - if (_animationDirection == PlaybackDirection.Alternate - || _animationDirection == PlaybackDirection.AlternateReverse) - _isReversed = !_isReversed; + if (animationDirection == PlaybackDirection.Alternate + || animationDirection == PlaybackDirection.AlternateReverse) + isReversed = !isReversed; break; } - _currentState = KeyFramesStates.Stop; + currentState = KeyFramesStates.Stop; break; case KeyFramesStates.Stop: - if (_fillMode == FillMode.Forward - || _fillMode == FillMode.Both) + if (fillMode == FillMode.Forward + || fillMode == FillMode.Both) { - _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); + targetControl.SetValue(parent.Property, lastInterpValue, BindingPriority.LocalValue); } - _targetObserver.OnCompleted(); + targetObserver.OnCompleted(); handled = true; break; default: @@ -255,14 +255,14 @@ namespace Avalonia.Animation public IDisposable Subscribe(IObserver observer) { - _targetObserver = observer; + targetObserver = observer; return this; } public void Dispose() { - _unsubscribe = true; - _currentState = KeyFramesStates.Disposed; + unsubscribe = true; + currentState = KeyFramesStates.Disposed; } } } diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 37764b34e8..654ee327f8 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -103,7 +103,7 @@ namespace Avalonia.Animation stateMachine.Initialize(animation, control, this); Timing.AnimationStateTimer - .TakeWhile(_ => !stateMachine._unsubscribe) + .TakeWhile(_ => !stateMachine.unsubscribe) .Subscribe(p => stateMachine.Step(p, DoInterpolation)); return control.Bind((AvaloniaProperty)Property, stateMachine, BindingPriority.Animation); From 127d060f6611045628ea0afa5eeae78f7d33a207 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 2 Aug 2018 01:47:45 +0800 Subject: [PATCH 003/512] Add new properties for Animations. --- src/Avalonia.Animation/Animation.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 4e777b36ed..eaf6b280bc 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -75,6 +75,16 @@ namespace Avalonia.Animation /// public Easing Easing { get; set; } = new LinearEasing(); + /// + /// Sets the speed multiple for this animation. + /// + public double SpeedRatio { get; set; } = 1d; + + /// + /// Sets the behavior for having a delay between repeats for this animation. + /// + public bool DelayBetweenRepeats { get; set; } + public Animation() { this.CollectionChanged += delegate { _isChildrenChanged = true; }; From b3ebfa574865a5aee54594e6dd48ca38e11e0713 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 2 Aug 2018 02:03:14 +0800 Subject: [PATCH 004/512] Add the global frame count to AnimationStateTimer. --- src/Avalonia.Animation/Timing.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index 10d65cca7f..3eddc0ac26 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -16,6 +16,7 @@ namespace Avalonia.Animation public static class Timing { static ulong _transitionsFrameCount; + static long _tickStartTimeStamp; static PlayState _globalState = PlayState.Run; /// @@ -33,12 +34,16 @@ namespace Avalonia.Animation /// static Timing() { + + _tickStartTimeStamp = Stopwatch.GetTimestamp(); + var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); AnimationStateTimer = globalTimer .Select(_ => { - return _globalState; + return (_globalState, (Stopwatch.GetTimestamp() - _tickStartTimeStamp) + / (Stopwatch.Frequency / FramesPerSecond)); }) .Publish() .RefCount(); @@ -76,7 +81,7 @@ namespace Avalonia.Animation /// defined in . /// The parameter passed to a subsciber is the current playstate of the animation. /// - internal static IObservable AnimationStateTimer + internal static IObservable<(PlayState, long)> AnimationStateTimer { get; } From 5365fd0a41220d840acb7d6d2511f3f3fda8478b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 2 Aug 2018 02:04:38 +0800 Subject: [PATCH 005/512] Add the new animation algorithm. --- src/Avalonia.Animation/Animatable.cs | 4 +- .../AnimatorStateMachine`1.cs | 284 +++++++----------- src/Avalonia.Animation/Animator`1.cs | 2 +- 3 files changed, 113 insertions(+), 177 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index a27d996301..85317af1a8 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -29,7 +29,7 @@ namespace Avalonia.Animation { if (this._playState == PlayState.Pause) { - return PlayState.Pause; + return (PlayState.Pause, p.Item2); } else return p; }) @@ -41,7 +41,7 @@ namespace Avalonia.Animation /// The specific animations timer for this control. /// /// - public IObservable AnimatableTimer; + public IObservable<(PlayState, long)> AnimatableTimer; /// /// Defines the property. diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 923cdd07b3..6854645cc2 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Avalonia.Animation.Utils; using Avalonia.Data; namespace Avalonia.Animation @@ -12,91 +13,97 @@ namespace Avalonia.Animation T lastInterpValue; T firstKFValue; - private ulong delayTotalFrameCount; - private ulong durationTotalFrameCount; - private ulong delayFrameCount; - private ulong durationFrameCount; - private ulong repeatCount; - private ulong currentIteration; + private long delayFC; + private long durationFC; + private long repeatCount; + private long currentIteration; + private long firstFrameCount; private bool isLooping; private bool isRepeating; - private bool isReversed; - private bool checkLoopAndRepeat; private bool gotFirstKFValue; + private bool gotFirstFrameCount; + private bool delayBetweenIterations; private FillMode fillMode; private PlaybackDirection animationDirection; - private KeyFramesStates currentState; - private KeyFramesStates savedState; private Animator parent; - private Animation targetAnimation; private Animatable targetControl; private T neutralValue; - internal bool unsubscribe = false; - private IObserver targetObserver; + private double speedRatio; + internal bool unsubscribe; + private bool isDisposed; - [Flags] - private enum KeyFramesStates - { - Initialize, - DoDelay, - DoRun, - RunForwards, - RunBackwards, - RunApplyValue, - RunComplete, - Pause, - Stop, - Disposed - } + private Easings.Easing EaseFunc; + private IObserver targetObserver; public void Initialize(Animation animation, Animatable control, Animator animator) { + + if (animation.SpeedRatio <= 0 || DoubleUtils.AboutEqual(animation.SpeedRatio, 0)) + throw new InvalidOperationException("Speed ratio cannot be negative or zero."); + + if (animation.Duration.TotalSeconds <= 0 || DoubleUtils.AboutEqual(animation.Duration.TotalSeconds, 0)) + throw new InvalidOperationException("Animation duration cannot be negative or zero."); + parent = animator; - targetAnimation = animation; + EaseFunc = animation.Easing; targetControl = control; neutralValue = (T)targetControl.GetValue(parent.Property); - delayTotalFrameCount = (ulong)(animation.Delay.Ticks / Timing.FrameTick.Ticks); - durationTotalFrameCount = (ulong)(animation.Duration.Ticks / Timing.FrameTick.Ticks); + speedRatio = animation.SpeedRatio; + delayFC = (long)((animation.Delay.Ticks / Timing.FrameTick.Ticks) * speedRatio); + durationFC = (long)((animation.Duration.Ticks / Timing.FrameTick.Ticks) * speedRatio); switch (animation.RepeatCount.RepeatType) { + case RepeatType.None: + repeatCount = 1; + break; case RepeatType.Loop: isLooping = true; - checkLoopAndRepeat = true; break; case RepeatType.Repeat: isRepeating = true; - checkLoopAndRepeat = true; - repeatCount = animation.RepeatCount.Value; + repeatCount = (long)animation.RepeatCount.Value; break; } - isReversed = (animation.PlaybackDirection & PlaybackDirection.Reverse) != 0; - animationDirection = targetAnimation.PlaybackDirection; - fillMode = targetAnimation.FillMode; + animationDirection = animation.PlaybackDirection; + fillMode = animation.FillMode; - if (durationTotalFrameCount > 0) - currentState = KeyFramesStates.DoDelay; - else - currentState = KeyFramesStates.DoRun; } - public void Step(PlayState playState, Func Interpolator) + public void Step(PlayState playState, long frameTick, Func Interpolator) { try { - InternalStep(playState, Interpolator); + InternalStep(playState, frameTick, Interpolator); } catch (Exception e) { targetObserver?.OnError(e); } } + + private void DoComplete() + { + if (fillMode == FillMode.Forward || fillMode == FillMode.Both) + targetControl.SetValue(parent.Property, lastInterpValue, BindingPriority.LocalValue); + + targetObserver.OnCompleted(); + } - private void InternalStep(PlayState playState, Func Interpolator) + private void DoDelay() + { + if (fillMode == FillMode.Backward || fillMode == FillMode.Both) + if (currentIteration == 0) + targetObserver.OnNext(firstKFValue); + else + targetObserver.OnNext(lastInterpValue); + } + + private void InternalStep(PlayState playState, long frameTick, Func Interpolator) { if (!gotFirstKFValue) { @@ -104,13 +111,19 @@ namespace Avalonia.Animation gotFirstKFValue = true; } - if (currentState == KeyFramesStates.Disposed) + if (!gotFirstFrameCount) + { + firstFrameCount = frameTick; + gotFirstFrameCount = true; + } + + if (isDisposed) throw new InvalidProgramException("This KeyFrames Animation is already disposed."); if (playState == PlayState.Stop) - currentState = KeyFramesStates.Stop; + DoComplete(); - // // Save state and pause the machine + // Save state and pause the machine // if (playState == PlayState.Pause && currentState != KeyFramesStates.Pause) // { // savedState = currentState; @@ -121,135 +134,58 @@ namespace Avalonia.Animation // if (playState != PlayState.Pause && currentState == KeyFramesStates.Pause) // currentState = savedState; - double tempDuration = 0d, easedTime; + // get the time with the initial fc as point of origin. + var t = (frameTick - firstFrameCount); - bool handled = false; + // check if t is within the zeroth iteration + if (t <= (delayFC + durationFC)) + { + currentIteration = 0; + t = t % (delayFC + durationFC); + } + else + { + var totalDur = (double)((delayBetweenIterations ? delayFC : 0) + durationFC + 1); + currentIteration = (long)Math.Floor((double)t / totalDur); + t = t % (long)totalDur; + } + + // check if it's over the repeat count + if (currentIteration > ((long)repeatCount - 1) && !isLooping) + { + DoComplete(); + } + + // check if the current iteration should be reversed or not. + bool isCurIterReverse = animationDirection == PlaybackDirection.Normal ? false : + animationDirection == PlaybackDirection.Alternate ? (currentIteration % 2 == 0) ? false : true : + animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false : + animationDirection == PlaybackDirection.Reverse ? true : false; - while (!handled) + long x1 = delayFC; + long x2 = x1 + durationFC; + + if (delayFC > 0 & t >= 0 & t <= x1) + { + if (currentIteration == 0 && delayBetweenIterations) + DoDelay(); + + } + else if (t >= x1 & t <= x2) + { + var interpVal = t / (double)durationFC; + + if (isCurIterReverse) + interpVal = 1 - interpVal; + + var easedTime = EaseFunc.Ease(interpVal); + + lastInterpValue = Interpolator(easedTime, neutralValue); + targetObserver.OnNext(lastInterpValue); + } + else if (t > x2 & (currentIteration + 1 > repeatCount & !isLooping)) { - switch (currentState) - { - case KeyFramesStates.DoDelay: - - if (fillMode == FillMode.Backward - || fillMode == FillMode.Both) - { - if (currentIteration == 0) - { - targetObserver.OnNext(firstKFValue); - } - else - { - targetObserver.OnNext(lastInterpValue); - } - } - - if (delayFrameCount > delayTotalFrameCount) - { - currentState = KeyFramesStates.DoRun; - } - else - { - handled = true; - delayFrameCount++; - } - break; - - case KeyFramesStates.DoRun: - - if (isReversed) - currentState = KeyFramesStates.RunBackwards; - else - currentState = KeyFramesStates.RunForwards; - - break; - - case KeyFramesStates.RunForwards: - - if (durationFrameCount > durationTotalFrameCount) - { - currentState = KeyFramesStates.RunComplete; - } - else - { - tempDuration = (double)durationFrameCount / durationTotalFrameCount; - currentState = KeyFramesStates.RunApplyValue; - - } - break; - - case KeyFramesStates.RunBackwards: - - if (durationFrameCount > durationTotalFrameCount) - { - currentState = KeyFramesStates.RunComplete; - } - else - { - tempDuration = (double)(durationTotalFrameCount - durationFrameCount) / durationTotalFrameCount; - currentState = KeyFramesStates.RunApplyValue; - } - break; - - case KeyFramesStates.RunApplyValue: - - easedTime = targetAnimation.Easing.Ease(tempDuration); - - durationFrameCount++; - lastInterpValue = Interpolator(easedTime, neutralValue); - targetObserver.OnNext(lastInterpValue); - currentState = KeyFramesStates.DoRun; - handled = true; - break; - - case KeyFramesStates.RunComplete: - - if (checkLoopAndRepeat) - { - delayFrameCount = 0; - durationFrameCount = 0; - - if (isLooping) - { - currentState = KeyFramesStates.DoRun; - } - else if (isRepeating) - { - if (currentIteration >= repeatCount) - { - currentState = KeyFramesStates.Stop; - } - else - { - currentState = KeyFramesStates.DoRun; - } - currentIteration++; - } - - if (animationDirection == PlaybackDirection.Alternate - || animationDirection == PlaybackDirection.AlternateReverse) - isReversed = !isReversed; - - break; - } - - currentState = KeyFramesStates.Stop; - break; - - case KeyFramesStates.Stop: - - if (fillMode == FillMode.Forward - || fillMode == FillMode.Both) - { - targetControl.SetValue(parent.Property, lastInterpValue, BindingPriority.LocalValue); - } - targetObserver.OnCompleted(); - handled = true; - break; - default: - handled = true; - break; - } + DoComplete(); } } @@ -262,7 +198,7 @@ namespace Avalonia.Animation public void Dispose() { unsubscribe = true; - currentState = KeyFramesStates.Disposed; + isDisposed = true; } } -} +} \ No newline at end of file diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 654ee327f8..cdf15c4c2f 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -104,7 +104,7 @@ namespace Avalonia.Animation Timing.AnimationStateTimer .TakeWhile(_ => !stateMachine.unsubscribe) - .Subscribe(p => stateMachine.Step(p, DoInterpolation)); + .Subscribe(p => stateMachine.Step(p.Item1, p.Item2, DoInterpolation)); return control.Bind((AvaloniaProperty)Property, stateMachine, BindingPriority.Animation); } From 0b248e6bb27509584eebf2b69899282882314d1d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 2 Aug 2018 02:18:55 +0800 Subject: [PATCH 006/512] Optimize & remove excess numeric castings. --- .../AnimatorStateMachine`1.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 6854645cc2..21a9fe7a50 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -13,10 +13,10 @@ namespace Avalonia.Animation T lastInterpValue; T firstKFValue; - private long delayFC; - private long durationFC; + private double delayFC; + private double durationFC; private long repeatCount; - private long currentIteration; + private double currentIteration; private long firstFrameCount; private bool isLooping; @@ -52,8 +52,9 @@ namespace Avalonia.Animation neutralValue = (T)targetControl.GetValue(parent.Property); speedRatio = animation.SpeedRatio; - delayFC = (long)((animation.Delay.Ticks / Timing.FrameTick.Ticks) * speedRatio); - durationFC = (long)((animation.Duration.Ticks / Timing.FrameTick.Ticks) * speedRatio); + delayFC = ((animation.Delay.Ticks / Timing.FrameTick.Ticks) * speedRatio); + durationFC = ((animation.Duration.Ticks / Timing.FrameTick.Ticks) * speedRatio); + delayBetweenIterations = animation.DelayBetweenRepeats; switch (animation.RepeatCount.RepeatType) { @@ -85,7 +86,7 @@ namespace Avalonia.Animation targetObserver?.OnError(e); } } - + private void DoComplete() { if (fillMode == FillMode.Forward || fillMode == FillMode.Both) @@ -135,7 +136,7 @@ namespace Avalonia.Animation // currentState = savedState; // get the time with the initial fc as point of origin. - var t = (frameTick - firstFrameCount); + double t = (frameTick - firstFrameCount); // check if t is within the zeroth iteration if (t <= (delayFC + durationFC)) @@ -146,12 +147,12 @@ namespace Avalonia.Animation else { var totalDur = (double)((delayBetweenIterations ? delayFC : 0) + durationFC + 1); - currentIteration = (long)Math.Floor((double)t / totalDur); - t = t % (long)totalDur; + currentIteration = Math.Floor(t / totalDur); + t = t % totalDur; } // check if it's over the repeat count - if (currentIteration > ((long)repeatCount - 1) && !isLooping) + if (currentIteration > (repeatCount - 1) && !isLooping) { DoComplete(); } @@ -162,8 +163,8 @@ namespace Avalonia.Animation animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false : animationDirection == PlaybackDirection.Reverse ? true : false; - long x1 = delayFC; - long x2 = x1 + durationFC; + double x1 = delayFC; + double x2 = x1 + durationFC; if (delayFC > 0 & t >= 0 & t <= x1) { @@ -173,7 +174,7 @@ namespace Avalonia.Animation } else if (t >= x1 & t <= x2) { - var interpVal = t / (double)durationFC; + var interpVal = t / durationFC; if (isCurIterReverse) interpVal = 1 - interpVal; From cfabbe9b65b9514c5bf3a4a27701f2af37d32e2b Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 3 Aug 2018 12:46:07 +0800 Subject: [PATCH 007/512] More optimizations. --- .../AnimatorStateMachine`1.cs | 12 +++++------ src/Avalonia.Animation/Timing.cs | 21 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 21a9fe7a50..8bfdf8b04a 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -163,16 +163,16 @@ namespace Avalonia.Animation animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false : animationDirection == PlaybackDirection.Reverse ? true : false; - double x1 = delayFC; - double x2 = x1 + durationFC; + double delayEndpoint = delayFC; + double iterationEndpoint = delayEndpoint + durationFC; - if (delayFC > 0 & t >= 0 & t <= x1) + if (delayFC > 0 & t >= 0 & t <= delayEndpoint) { - if (currentIteration == 0 && delayBetweenIterations) + if (currentIteration == 0 || delayBetweenIterations) DoDelay(); } - else if (t >= x1 & t <= x2) + else if (t >= delayEndpoint & t <= iterationEndpoint) { var interpVal = t / durationFC; @@ -184,7 +184,7 @@ namespace Avalonia.Animation lastInterpValue = Interpolator(easedTime, neutralValue); targetObserver.OnNext(lastInterpValue); } - else if (t > x2 & (currentIteration + 1 > repeatCount & !isLooping)) + else if (t > iterationEndpoint & (currentIteration + 1 > repeatCount & !isLooping)) { DoComplete(); } diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index 3eddc0ac26..fb61e15f05 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -15,15 +15,17 @@ namespace Avalonia.Animation /// public static class Timing { - static ulong _transitionsFrameCount; static long _tickStartTimeStamp; static PlayState _globalState = PlayState.Run; + static long TicksPerFrame = Stopwatch.Frequency / FramesPerSecond; + /// /// The number of frames per second. /// public const int FramesPerSecond = 60; + /// /// The time span of each frame. /// @@ -39,17 +41,18 @@ namespace Avalonia.Animation var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); + AnimationStateTimer = globalTimer .Select(_ => { return (_globalState, (Stopwatch.GetTimestamp() - _tickStartTimeStamp) - / (Stopwatch.Frequency / FramesPerSecond)); + / TicksPerFrame); }) .Publish() .RefCount(); TransitionsTimer = globalTimer - .Select(p => _transitionsFrameCount++) + .Select(p => p) .Publish() .RefCount(); } @@ -95,7 +98,7 @@ namespace Avalonia.Animation /// The parameter passed to a subsciber is the number of frames since the animation system was /// initialized. /// - public static IObservable TransitionsTimer + public static IObservable TransitionsTimer { get; } @@ -113,16 +116,14 @@ namespace Avalonia.Animation /// public static IObservable GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan)) { - var startTime = _transitionsFrameCount; - var _duration = (ulong)(duration.Ticks / FrameTick.Ticks); - var endTime = startTime + _duration; + var _duration = (duration.Ticks / FrameTick.Ticks); + var endTime = _duration; return TransitionsTimer .TakeWhile(x => x < endTime) - .Select(x => (double)(x - startTime) / _duration) + .Select(x => (double)x / _duration) .StartWith(0.0) .Concat(Observable.Return(1.0)); } - } -} +} \ No newline at end of file From 1757604dab071f6d09b4e1b63a1eddad1c708f72 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 3 Aug 2018 12:47:01 +0800 Subject: [PATCH 008/512] Fix onComplete assignment. --- src/Avalonia.Animation/AnimatorStateMachine`1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 32acafcfc7..18ad76c51e 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -73,7 +73,7 @@ namespace Avalonia.Animation animationDirection = animation.PlaybackDirection; fillMode = animation.FillMode; - onComplete = onComplete; + this.onComplete = onComplete; } public void Step(PlayState playState, long frameTick, Func Interpolator) From b020bddb20bae6f4ca299cff7858ee9d60af7e95 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 3 Aug 2018 12:47:01 +0800 Subject: [PATCH 009/512] Fix onComplete assignment. --- src/Avalonia.Animation/Animation.cs | 7 +------ src/Avalonia.Animation/AnimatorStateMachine`1.cs | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 46179aa3f4..344b63d15c 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -85,12 +85,7 @@ namespace Avalonia.Animation /// Sets the behavior for having a delay between repeats for this animation. /// public bool DelayBetweenRepeats { get; set; } - - public Animation() - { - this.CollectionChanged += delegate { _isChildrenChanged = true; }; - } - + private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control) { var handlerList = new List<(Type type, AvaloniaProperty property)>(); diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 32acafcfc7..18ad76c51e 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -73,7 +73,7 @@ namespace Avalonia.Animation animationDirection = animation.PlaybackDirection; fillMode = animation.FillMode; - onComplete = onComplete; + this.onComplete = onComplete; } public void Step(PlayState playState, long frameTick, Func Interpolator) From 1b37397e69b97a1f9a7274a8ceb5644129475ae0 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 3 Aug 2018 17:38:41 +0800 Subject: [PATCH 010/512] Fix bug on PageTransitions by adding 1 to the zeroth iteration frame count modulo. --- src/Avalonia.Animation/AnimatorStateMachine`1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 18ad76c51e..38ccce6cb5 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -145,7 +145,7 @@ namespace Avalonia.Animation if (t <= (delayFC + durationFC)) { currentIteration = 0; - t = t % (delayFC + durationFC); + t = t % (delayFC + durationFC + 1); } else { From bfefeb9e9c63830e3cd17311be7f3c5ef7b6bb7c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Fri, 3 Aug 2018 17:39:19 +0800 Subject: [PATCH 011/512] Make Transitions Bind strongly typed. --- src/Avalonia.Animation/Transition`1.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index 5db3082deb..c097b930a5 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -24,17 +24,7 @@ namespace Avalonia.Animation /// /// Gets the easing class to be used. /// - public Easing Easing - { - get - { - return _easing ?? (_easing = new LinearEasing()); - } - set - { - _easing = value; - } - } + public Easing Easing { get; set; } = new LinearEasing(); /// public AvaloniaProperty Property @@ -61,8 +51,8 @@ namespace Avalonia.Animation /// public virtual IDisposable Apply(Animatable control, object oldValue, object newValue) { - var transition = DoTransition(Timing.GetTransitionsTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue).Select(p => (object)p); - return control.Bind(Property, transition, Data.BindingPriority.Animation); + var transition = DoTransition(Timing.GetTransitionsTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue); + return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } } } \ No newline at end of file From 714606b2ad8bd0d8adb3acc4d30701a8f195d2f7 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sun, 12 Aug 2018 01:26:03 +0800 Subject: [PATCH 012/512] Add PlayState support. Redoing the main algorithm yet again. --- .../ViewModels/AnimationsPageViewModel.cs | 10 +- src/Avalonia.Animation/Animatable.cs | 21 +---- src/Avalonia.Animation/Animation.cs | 87 ++++++++++-------- .../AnimatorStateMachine`1.cs | 92 ++++++++++--------- src/Avalonia.Animation/Animator`1.cs | 21 ++--- src/Avalonia.Animation/Timing.cs | 59 +++--------- 6 files changed, 127 insertions(+), 163 deletions(-) diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs index 626a3e7c77..c76d4db513 100644 --- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs @@ -10,21 +10,21 @@ namespace RenderDemo.ViewModels public AnimationsPageViewModel() { - ToggleGlobalPlayState = ReactiveCommand.Create(()=>TogglePlayState()); + ToggleGlobalPlayState = ReactiveCommand.Create(() => TogglePlayState()); } void TogglePlayState() { - switch (Timing.GetGlobalPlayState()) + switch (Timing.GlobalPlayState) { case PlayState.Run: PlayStateText = "Resume all animations"; - Timing.SetGlobalPlayState(PlayState.Pause); + Timing.GlobalPlayState = PlayState.Pause; break; case PlayState.Pause: PlayStateText = "Pause all animations"; - Timing.SetGlobalPlayState(PlayState.Run); + Timing.GlobalPlayState = PlayState.Run; break; } } @@ -36,5 +36,5 @@ namespace RenderDemo.ViewModels } public ReactiveCommand ToggleGlobalPlayState { get; } - } + } } diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 85317af1a8..303e01aed8 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -23,25 +23,8 @@ namespace Avalonia.Animation /// public Animatable() { - Transitions = new Transitions(); - AnimatableTimer = Timing.AnimationStateTimer - .Select(p => - { - if (this._playState == PlayState.Pause) - { - return (PlayState.Pause, p.Item2); - } - else return p; - }) - .Publish() - .RefCount(); - } - - /// - /// The specific animations timer for this control. - /// - /// - public IObservable<(PlayState, long)> AnimatableTimer; + Transitions = new Transitions(); + } /// /// Defines the property. diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 344b63d15c..da2fc75c0b 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -21,70 +21,81 @@ namespace Avalonia.Animation /// public class Animation : AvaloniaList, IAnimation { - private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> - { - ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) - }; - - public static void RegisterAnimator(Func condition) - where TAnimator : IAnimator - { - Animators.Insert(0, (condition, typeof(TAnimator))); - } - - private static Type GetAnimatorType(AvaloniaProperty property) - { - foreach (var (condition, type) in Animators) - { - if (condition(property)) - { - return type; - } - } - return null; - } public AvaloniaList _animators { get; set; } = new AvaloniaList(); /// - /// Run time of this animation. + /// Gets or sets the active time of this animation. /// public TimeSpan Duration { get; set; } /// - /// Delay time for this animation. - /// - public TimeSpan Delay { get; set; } - - /// - /// The repeat count for this animation. + /// Gets or sets the repeat count for this animation. /// public RepeatCount RepeatCount { get; set; } /// - /// The playback direction for this animation. + /// Gets or sets the playback direction for this animation. /// public PlaybackDirection PlaybackDirection { get; set; } /// - /// The value fill mode for this animation. + /// Gets or sets the value fill mode for this animation. /// public FillMode FillMode { get; set; } /// - /// Easing function to be used. + /// Gets or sets the easing function to be used for this animation. /// public Easing Easing { get; set; } = new LinearEasing(); - + /// - /// Sets the speed multiple for this animation. + /// Gets or sets the speed multiple for this animation. /// public double SpeedRatio { get; set; } = 1d; - /// - /// Sets the behavior for having a delay between repeats for this animation. - /// - public bool DelayBetweenRepeats { get; set; } + /// + /// Gets or sets the delay time for this animation. + /// + /// + /// Describes a delay to be added before the animation starts, and optionally between + /// repeats of the animation if is set. + /// + public TimeSpan Delay { get; set; } + + /// + /// Gets or sets a value indicating whether will be applied between + /// iterations of the animation. + /// + /// + /// If this property is not set, then will only be applied to the first + /// iteration of the animation. + /// + public bool DelayBetweenIterations { get; set; } + + + private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> + { + ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) + }; + + public static void RegisterAnimator(Func condition) + where TAnimator : IAnimator + { + Animators.Insert(0, (condition, typeof(TAnimator))); + } + + private static Type GetAnimatorType(AvaloniaProperty property) + { + foreach (var (condition, type) in Animators) + { + if (condition(property)) + { + return type; + } + } + return null; + } private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control) { diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 38ccce6cb5..47bfa1c321 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -9,7 +9,7 @@ namespace Avalonia.Animation /// Provides statefulness for an iteration of a keyframe animation. /// internal class AnimatorStateMachine : IObservable, IDisposable - { + { T lastInterpValue; T firstKFValue; @@ -34,6 +34,11 @@ namespace Avalonia.Animation internal bool unsubscribe; private bool isDisposed; + private long? internalClock; + + private long? previousClock = null; + private long currentDiscreteTime; + private Easings.Easing EaseFunc; private IObserver targetObserver; private readonly Action onComplete; @@ -55,7 +60,7 @@ namespace Avalonia.Animation speedRatio = animation.SpeedRatio; delayFC = ((animation.Delay.Ticks / Timing.FrameTick.Ticks) * speedRatio); durationFC = ((animation.Duration.Ticks / Timing.FrameTick.Ticks) * speedRatio); - delayBetweenIterations = animation.DelayBetweenRepeats; + delayBetweenIterations = animation.DelayBetweenIterations; switch (animation.RepeatCount.RepeatType) { @@ -72,15 +77,15 @@ namespace Avalonia.Animation } animationDirection = animation.PlaybackDirection; - fillMode = animation.FillMode; + fillMode = animation.FillMode; this.onComplete = onComplete; } - public void Step(PlayState playState, long frameTick, Func Interpolator) + public void Step(long frameTick, Func Interpolator) { try { - InternalStep(playState, frameTick, Interpolator); + InternalStep(frameTick, Interpolator); } catch (Exception e) { @@ -107,8 +112,31 @@ namespace Avalonia.Animation targetObserver.OnNext(lastInterpValue); } - private void InternalStep(PlayState playState, long frameTick, Func Interpolator) + private void InternalStep(long time, Func Interpolator) { + if (Timing.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) + DoComplete(); + + if (!previousClock.HasValue) + { + previousClock = time; + internalClock = 0; + } + else + { + if (Timing.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) + { + previousClock = time; + return; + } + var delta = time - previousClock; + internalClock += delta; + previousClock = time; + } + + // currentDiscreteTime = internalClock.Value; + currentDiscreteTime++; + if (!gotFirstKFValue) { firstKFValue = (T)parent.First().Value; @@ -117,42 +145,23 @@ namespace Avalonia.Animation if (!gotFirstFrameCount) { - firstFrameCount = frameTick; + firstFrameCount = currentDiscreteTime; gotFirstFrameCount = true; } if (isDisposed) throw new InvalidProgramException("This KeyFrames Animation is already disposed."); - if (playState == PlayState.Stop) - DoComplete(); - - // Save state and pause the machine - // if (playState == PlayState.Pause && currentState != KeyFramesStates.Pause) - // { - // savedState = currentState; - // currentState = KeyFramesStates.Pause; - // } - - // // Resume the previous state - // if (playState != PlayState.Pause && currentState == KeyFramesStates.Pause) - // currentState = savedState; - // get the time with the initial fc as point of origin. - double t = (frameTick - firstFrameCount); + double t = (currentDiscreteTime - firstFrameCount); // check if t is within the zeroth iteration - if (t <= (delayFC + durationFC)) - { - currentIteration = 0; - t = t % (delayFC + durationFC + 1); - } - else - { - var totalDur = (double)((delayBetweenIterations ? delayFC : 0) + durationFC + 1); - currentIteration = Math.Floor(t / totalDur); - t = t % totalDur; - } + + double delayEndpoint = delayFC; + double iterationEndpoint = delayEndpoint + durationFC; + + currentIteration = Math.Floor(t / iterationEndpoint); + t = t % iterationEndpoint; // check if it's over the repeat count if (currentIteration > (repeatCount - 1) && !isLooping) @@ -166,19 +175,17 @@ namespace Avalonia.Animation animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false : animationDirection == PlaybackDirection.Reverse ? true : false; - double delayEndpoint = delayFC; - double iterationEndpoint = delayEndpoint + durationFC; - if (delayFC > 0 & t >= 0 & t <= delayEndpoint) + if (delayFC > 0 & t <= delayEndpoint) { - if (currentIteration == 0 || delayBetweenIterations) + if (currentIteration == 0) DoDelay(); - } - else if (t >= delayEndpoint & t <= iterationEndpoint) + else if (t > delayEndpoint & t < iterationEndpoint) { - var interpVal = t / durationFC; - + double k = t - delayFC; + var interpVal = k / (double)durationFC; + if (isCurIterReverse) interpVal = 1 - interpVal; @@ -187,7 +194,7 @@ namespace Avalonia.Animation lastInterpValue = Interpolator(easedTime, neutralValue); targetObserver.OnNext(lastInterpValue); } - else if (t > iterationEndpoint & (currentIteration + 1 > repeatCount & !isLooping)) + else if (t > iterationEndpoint && !isLooping) { DoComplete(); } @@ -198,7 +205,6 @@ namespace Avalonia.Animation targetObserver = observer; return this; } - public void Dispose() { unsubscribe = true; diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 607ccee947..a8b5ce7a27 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -21,7 +21,7 @@ namespace Avalonia.Animation /// private readonly SortedList _convertedKeyframes = new SortedList(); - private bool _isVerfifiedAndConverted; + private bool isVerfifiedAndConverted; /// /// Gets or sets the target property for the keyframe. @@ -31,18 +31,17 @@ namespace Avalonia.Animation public Animator() { // Invalidate keyframes when changed. - this.CollectionChanged += delegate { _isVerfifiedAndConverted = false; }; + this.CollectionChanged += delegate { isVerfifiedAndConverted = false; }; } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IObservable obsMatch, Action onComplete) + public virtual IDisposable Apply(Animation animation, Animatable control, IObservable Match, Action onComplete) { - if (!_isVerfifiedAndConverted) + if (!isVerfifiedAndConverted) VerifyConvertKeyFrames(); - return obsMatch - // Ignore triggers when global timers are paused. - .Where(p => p && Timing.GetGlobalPlayState() != PlayState.Pause) + return Match + .Where(p => p) .Subscribe(_ => { var timerObs = RunKeyFrames(animation, control, onComplete); @@ -101,9 +100,9 @@ namespace Avalonia.Animation { var stateMachine = new AnimatorStateMachine(animation, control, this, onComplete); - Timing.AnimationStateTimer + Timing.AnimationsTimer .TakeWhile(_ => !stateMachine.unsubscribe) - .Subscribe(p => stateMachine.Step(p.Item1, p.Item2, DoInterpolation)); + .Subscribe(p => stateMachine.Step(p, DoInterpolation)); return control.Bind((AvaloniaProperty)Property, stateMachine, BindingPriority.Animation); } @@ -124,7 +123,7 @@ namespace Avalonia.Animation } AddNeutralKeyFramesIfNeeded(); - _isVerfifiedAndConverted = true; + isVerfifiedAndConverted = true; } @@ -133,7 +132,7 @@ namespace Avalonia.Animation bool hasStartKey, hasEndKey; hasStartKey = hasEndKey = false; - // Make start and end keyframe mandatory. + // Check if there's start and end keyframes. foreach (var converted in _convertedKeyframes.Keys) { if (DoubleUtils.AboutEqual(converted, 0.0)) diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index fb61e15f05..c6def06b15 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -16,16 +16,18 @@ namespace Avalonia.Animation public static class Timing { static long _tickStartTimeStamp; - static PlayState _globalState = PlayState.Run; static long TicksPerFrame = Stopwatch.Frequency / FramesPerSecond; + /// + /// Gets or sets the animation play state for all animations + /// + public static PlayState GlobalPlayState { get; set; } = PlayState.Run; /// /// The number of frames per second. /// public const int FramesPerSecond = 60; - /// /// The time span of each frame. /// @@ -36,45 +38,20 @@ namespace Avalonia.Animation /// static Timing() { - _tickStartTimeStamp = Stopwatch.GetTimestamp(); var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); - - AnimationStateTimer = globalTimer + AnimationsTimer = globalTimer .Select(_ => { - return (_globalState, (Stopwatch.GetTimestamp() - _tickStartTimeStamp) - / TicksPerFrame); + return (Stopwatch.GetTimestamp() - _tickStartTimeStamp) + / TicksPerFrame * 2; }) .Publish() .RefCount(); - - TransitionsTimer = globalTimer - .Select(p => p) - .Publish() - .RefCount(); - } - - - /// - /// Sets the animation play state for all animations - /// - public static void SetGlobalPlayState(PlayState playState) - { - Dispatcher.UIThread.VerifyAccess(); - _globalState = playState; } - /// - /// Gets the animation play state for all animations - /// - public static PlayState GetGlobalPlayState() - { - Dispatcher.UIThread.VerifyAccess(); - return _globalState; - } /// /// Gets the animation timer. @@ -84,21 +61,7 @@ namespace Avalonia.Animation /// defined in . /// The parameter passed to a subsciber is the current playstate of the animation. /// - internal static IObservable<(PlayState, long)> AnimationStateTimer - { - get; - } - - /// - /// Gets the transitions timer. - /// - /// - /// The transitions timer increments usually 60 times per second as - /// defined in . - /// The parameter passed to a subsciber is the number of frames since the animation system was - /// initialized. - /// - public static IObservable TransitionsTimer + internal static IObservable AnimationsTimer { get; } @@ -116,10 +79,12 @@ namespace Avalonia.Animation /// public static IObservable GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan)) { + // TODO: Fix this mess. var _duration = (duration.Ticks / FrameTick.Ticks); - var endTime = _duration; + long? endTime = ((Stopwatch.GetTimestamp() - _tickStartTimeStamp) + / TicksPerFrame) + _duration; - return TransitionsTimer + return AnimationsTimer .TakeWhile(x => x < endTime) .Select(x => (double)x / _duration) .StartWith(0.0) From dc4c7cf4ca66e3b59580fc09e51cb40f57130e29 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sun, 12 Aug 2018 13:32:55 +0800 Subject: [PATCH 013/512] Make the algorithm use TimeSpans directly instead of converting to quantized framecounts to avoid/reduce quantization errors in interpolation. --- .../AnimatorStateMachine`1.cs | 81 +++++++++---------- src/Avalonia.Animation/Timing.cs | 30 +++---- 2 files changed, 50 insertions(+), 61 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index 47bfa1c321..e5a6864899 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -13,11 +13,8 @@ namespace Avalonia.Animation T lastInterpValue; T firstKFValue; - private double delayFC; - private double durationFC; private long repeatCount; private double currentIteration; - private long firstFrameCount; private bool isLooping; private bool isRepeating; @@ -34,10 +31,11 @@ namespace Avalonia.Animation internal bool unsubscribe; private bool isDisposed; - private long? internalClock; - - private long? previousClock = null; - private long currentDiscreteTime; + private TimeSpan delayFC; + private TimeSpan durationFC; + private TimeSpan firstFrameCount; + private TimeSpan internalClock; + private TimeSpan? previousClock; private Easings.Easing EaseFunc; private IObserver targetObserver; @@ -58,8 +56,10 @@ namespace Avalonia.Animation neutralValue = (T)targetControl.GetValue(parent.Property); speedRatio = animation.SpeedRatio; - delayFC = ((animation.Delay.Ticks / Timing.FrameTick.Ticks) * speedRatio); - durationFC = ((animation.Duration.Ticks / Timing.FrameTick.Ticks) * speedRatio); + + delayFC = animation.Delay; + durationFC = animation.Duration; + delayBetweenIterations = animation.DelayBetweenIterations; switch (animation.RepeatCount.RepeatType) @@ -81,7 +81,7 @@ namespace Avalonia.Animation this.onComplete = onComplete; } - public void Step(long frameTick, Func Interpolator) + public void Step(TimeSpan frameTick, Func Interpolator) { try { @@ -112,31 +112,28 @@ namespace Avalonia.Animation targetObserver.OnNext(lastInterpValue); } - private void InternalStep(long time, Func Interpolator) + private void DoPlayStatesAndTime(TimeSpan systemTime) { if (Timing.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) DoComplete(); if (!previousClock.HasValue) { - previousClock = time; - internalClock = 0; + previousClock = systemTime; + internalClock = TimeSpan.Zero; } else { if (Timing.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) { - previousClock = time; + previousClock = systemTime; return; } - var delta = time - previousClock; - internalClock += delta; - previousClock = time; + var delta = systemTime - previousClock; + internalClock += delta.Value; + previousClock = systemTime; } - // currentDiscreteTime = internalClock.Value; - currentDiscreteTime++; - if (!gotFirstKFValue) { firstKFValue = (T)parent.First().Value; @@ -145,47 +142,47 @@ namespace Avalonia.Animation if (!gotFirstFrameCount) { - firstFrameCount = currentDiscreteTime; + firstFrameCount = internalClock; gotFirstFrameCount = true; } + } + + private void InternalStep(TimeSpan systemTime, Func Interpolator) + { + DoPlayStatesAndTime(systemTime); if (isDisposed) throw new InvalidProgramException("This KeyFrames Animation is already disposed."); - // get the time with the initial fc as point of origin. - double t = (currentDiscreteTime - firstFrameCount); - - // check if t is within the zeroth iteration + var t = internalClock - firstFrameCount; - double delayEndpoint = delayFC; - double iterationEndpoint = delayEndpoint + durationFC; + var delayEndpoint = delayFC; + var iterationEndpoint = delayEndpoint + durationFC; - currentIteration = Math.Floor(t / iterationEndpoint); - t = t % iterationEndpoint; + currentIteration = (int)Math.Floor((double)t.Ticks / iterationEndpoint.Ticks); + t = TimeSpan.FromTicks(t.Ticks % iterationEndpoint.Ticks); - // check if it's over the repeat count if (currentIteration > (repeatCount - 1) && !isLooping) - { DoComplete(); - } - // check if the current iteration should be reversed or not. + if (t > iterationEndpoint & !isLooping) + DoComplete(); + bool isCurIterReverse = animationDirection == PlaybackDirection.Normal ? false : animationDirection == PlaybackDirection.Alternate ? (currentIteration % 2 == 0) ? false : true : animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false : animationDirection == PlaybackDirection.Reverse ? true : false; - - if (delayFC > 0 & t <= delayEndpoint) + if (delayFC > TimeSpan.Zero & t < delayEndpoint) { if (currentIteration == 0) DoDelay(); } - else if (t > delayEndpoint & t < iterationEndpoint) + else if (t >= delayEndpoint & t <= iterationEndpoint) { - double k = t - delayFC; - var interpVal = k / (double)durationFC; - + var k = t - delayFC; + var interpVal = (double)k.Ticks / durationFC.Ticks; + if (isCurIterReverse) interpVal = 1 - interpVal; @@ -194,10 +191,7 @@ namespace Avalonia.Animation lastInterpValue = Interpolator(easedTime, neutralValue); targetObserver.OnNext(lastInterpValue); } - else if (t > iterationEndpoint && !isLooping) - { - DoComplete(); - } + } public IDisposable Subscribe(IObserver observer) @@ -205,6 +199,7 @@ namespace Avalonia.Animation targetObserver = observer; return this; } + public void Dispose() { unsubscribe = true; diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index c6def06b15..575cedc620 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -15,9 +15,6 @@ namespace Avalonia.Animation /// public static class Timing { - static long _tickStartTimeStamp; - static long TicksPerFrame = Stopwatch.Frequency / FramesPerSecond; - /// /// Gets or sets the animation play state for all animations /// @@ -37,22 +34,18 @@ namespace Avalonia.Animation /// Initializes static members of the class. /// static Timing() - { - _tickStartTimeStamp = Stopwatch.GetTimestamp(); - + { var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); AnimationsTimer = globalTimer .Select(_ => { - return (Stopwatch.GetTimestamp() - _tickStartTimeStamp) - / TicksPerFrame * 2; + return TimeSpan.FromMilliseconds(Environment.TickCount); }) .Publish() .RefCount(); } - /// /// Gets the animation timer. /// @@ -61,7 +54,7 @@ namespace Avalonia.Animation /// defined in . /// The parameter passed to a subsciber is the current playstate of the animation. /// - internal static IObservable AnimationsTimer + internal static IObservable AnimationsTimer { get; } @@ -80,15 +73,16 @@ namespace Avalonia.Animation public static IObservable GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan)) { // TODO: Fix this mess. - var _duration = (duration.Ticks / FrameTick.Ticks); - long? endTime = ((Stopwatch.GetTimestamp() - _tickStartTimeStamp) - / TicksPerFrame) + _duration; + // var _duration = (duration.Ticks / FrameTick.Ticks); + // long? endTime = ((Stopwatch.GetTimestamp() - _tickStartTimeStamp) + // / TicksPerFrame) + _duration; - return AnimationsTimer - .TakeWhile(x => x < endTime) - .Select(x => (double)x / _duration) - .StartWith(0.0) - .Concat(Observable.Return(1.0)); + // return AnimationsTimer + // .TakeWhile(x => x < endTime) + // .Select(x => (double)x / _duration) + // .StartWith(0.0) + // .Concat(Observable.Return(1.0)); + return Observable.Empty(); } } } \ No newline at end of file From b6e6b7db48c8c279b74d8b0b8a20d1dc1316970d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 Aug 2018 15:38:55 +0800 Subject: [PATCH 014/512] Fix Delay and Iteration Delay behaviors. AnimatorStateMachine is functionally complete. --- .../AnimatorStateMachine`1.cs | 104 +++++++++++------- 1 file changed, 65 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimatorStateMachine`1.cs index e5a6864899..0a785ab496 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimatorStateMachine`1.cs @@ -6,7 +6,7 @@ using Avalonia.Data; namespace Avalonia.Animation { /// - /// Provides statefulness for an iteration of a keyframe animation. + /// Provides statefulness for keyframe animations. /// internal class AnimatorStateMachine : IObservable, IDisposable { @@ -17,10 +17,9 @@ namespace Avalonia.Animation private double currentIteration; private bool isLooping; - private bool isRepeating; private bool gotFirstKFValue; private bool gotFirstFrameCount; - private bool delayBetweenIterations; + private bool iterationDelay; private FillMode fillMode; private PlaybackDirection animationDirection; @@ -31,36 +30,34 @@ namespace Avalonia.Animation internal bool unsubscribe; private bool isDisposed; - private TimeSpan delayFC; - private TimeSpan durationFC; + private TimeSpan delay; + private TimeSpan duration; private TimeSpan firstFrameCount; private TimeSpan internalClock; private TimeSpan? previousClock; - private Easings.Easing EaseFunc; + private Easings.Easing easeFunc; private IObserver targetObserver; - private readonly Action onComplete; + private readonly Action onCompleteAction; - public AnimatorStateMachine(Animation animation, Animatable control, Animator animator, Action onComplete) + public AnimatorStateMachine(Animation animation, Animatable control, Animator animator, Action OnComplete) { - if (animation.SpeedRatio <= 0 || DoubleUtils.AboutEqual(animation.SpeedRatio, 0)) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); if (animation.Duration.TotalSeconds <= 0 || DoubleUtils.AboutEqual(animation.Duration.TotalSeconds, 0)) - throw new InvalidOperationException("Animation duration cannot be negative or zero."); - + throw new InvalidOperationException("Duration cannot be negative or zero."); + parent = animator; - EaseFunc = animation.Easing; + easeFunc = animation.Easing; targetControl = control; neutralValue = (T)targetControl.GetValue(parent.Property); speedRatio = animation.SpeedRatio; - delayFC = animation.Delay; - durationFC = animation.Duration; - - delayBetweenIterations = animation.DelayBetweenIterations; + delay = animation.Delay; + duration = animation.Duration; + iterationDelay = animation.DelayBetweenIterations; switch (animation.RepeatCount.RepeatType) { @@ -71,14 +68,13 @@ namespace Avalonia.Animation isLooping = true; break; case RepeatType.Repeat: - isRepeating = true; repeatCount = (long)animation.RepeatCount.Value; break; } animationDirection = animation.PlaybackDirection; fillMode = animation.FillMode; - this.onComplete = onComplete; + onCompleteAction = OnComplete; } public void Step(TimeSpan frameTick, Func Interpolator) @@ -99,7 +95,7 @@ namespace Avalonia.Animation targetControl.SetValue(parent.Property, lastInterpValue, BindingPriority.LocalValue); targetObserver.OnCompleted(); - onComplete?.Invoke(); + onCompleteAction?.Invoke(); Dispose(); } @@ -154,44 +150,74 @@ namespace Avalonia.Animation if (isDisposed) throw new InvalidProgramException("This KeyFrames Animation is already disposed."); - var t = internalClock - firstFrameCount; + var time = internalClock - firstFrameCount; + var delayEndpoint = delay; + var iterationEndpoint = delayEndpoint + duration; - var delayEndpoint = delayFC; - var iterationEndpoint = delayEndpoint + durationFC; + //determine if time is currently in the first iteration. + if (time >= TimeSpan.Zero & time <= iterationEndpoint) + { + currentIteration = 1; + } + else if (time > iterationEndpoint) + { + //Subtract first iteration to properly get the subsequent iteration time + time -= iterationEndpoint; - currentIteration = (int)Math.Floor((double)t.Ticks / iterationEndpoint.Ticks); - t = TimeSpan.FromTicks(t.Ticks % iterationEndpoint.Ticks); + if (!iterationDelay & delayEndpoint > TimeSpan.Zero) + { + delayEndpoint = TimeSpan.Zero; + iterationEndpoint = duration; + } - if (currentIteration > (repeatCount - 1) && !isLooping) - DoComplete(); + //Calculate the current iteration number + currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2; + } + else + { + previousClock = systemTime; + return; + } - if (t > iterationEndpoint & !isLooping) - DoComplete(); - + time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks); + + if (!isLooping) + { + if (currentIteration > repeatCount) + DoComplete(); + + if (time > iterationEndpoint) + DoComplete(); + } + + // Determine if the current iteration should have its normalized time inverted. bool isCurIterReverse = animationDirection == PlaybackDirection.Normal ? false : animationDirection == PlaybackDirection.Alternate ? (currentIteration % 2 == 0) ? false : true : animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false : animationDirection == PlaybackDirection.Reverse ? true : false; - if (delayFC > TimeSpan.Zero & t < delayEndpoint) + if (delayEndpoint > TimeSpan.Zero & time < delayEndpoint) { - if (currentIteration == 0) - DoDelay(); + DoDelay(); } - else if (t >= delayEndpoint & t <= iterationEndpoint) + else { - var k = t - delayFC; - var interpVal = (double)k.Ticks / durationFC.Ticks; + // Offset the delay time + time -= delayEndpoint; + iterationEndpoint -= delayEndpoint; + + // Normalize time + var interpVal = (double)time.Ticks / iterationEndpoint.Ticks; if (isCurIterReverse) interpVal = 1 - interpVal; - var easedTime = EaseFunc.Ease(interpVal); - + // Ease and interpolate + var easedTime = easeFunc.Ease(interpVal); lastInterpValue = Interpolator(easedTime, neutralValue); + targetObserver.OnNext(lastInterpValue); } - } public IDisposable Subscribe(IObserver observer) @@ -199,7 +225,7 @@ namespace Avalonia.Animation targetObserver = observer; return this; } - + public void Dispose() { unsubscribe = true; From 5d49c5f969be55d34408010aed3d1d54a69ed1fc Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 Aug 2018 23:27:02 +0800 Subject: [PATCH 015/512] Rename AnimatorStateMachine to AnimationsEngine to properly reflect the functions of the new algorithm. --- .../{AnimatorStateMachine`1.cs => AnimationsEngine`1.cs} | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename src/Avalonia.Animation/{AnimatorStateMachine`1.cs => AnimationsEngine`1.cs} (96%) diff --git a/src/Avalonia.Animation/AnimatorStateMachine`1.cs b/src/Avalonia.Animation/AnimationsEngine`1.cs similarity index 96% rename from src/Avalonia.Animation/AnimatorStateMachine`1.cs rename to src/Avalonia.Animation/AnimationsEngine`1.cs index 0a785ab496..169f0a7ae0 100644 --- a/src/Avalonia.Animation/AnimatorStateMachine`1.cs +++ b/src/Avalonia.Animation/AnimationsEngine`1.cs @@ -6,9 +6,10 @@ using Avalonia.Data; namespace Avalonia.Animation { /// - /// Provides statefulness for keyframe animations. + /// Handles interpolatoin and time-related functions + /// for keyframe animations. /// - internal class AnimatorStateMachine : IObservable, IDisposable + internal class AnimationsEngine : IObservable, IDisposable { T lastInterpValue; T firstKFValue; @@ -40,7 +41,7 @@ namespace Avalonia.Animation private IObserver targetObserver; private readonly Action onCompleteAction; - public AnimatorStateMachine(Animation animation, Animatable control, Animator animator, Action OnComplete) + public AnimationsEngine(Animation animation, Animatable control, Animator animator, Action OnComplete) { if (animation.SpeedRatio <= 0 || DoubleUtils.AboutEqual(animation.SpeedRatio, 0)) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); From 3d7516cc0c1f82fc7d703f4c3d9add31dc9a0242 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 14 Aug 2018 23:54:48 +0800 Subject: [PATCH 016/512] Implement a new timing engine for Transitions. Transtions are fixed now.Move out some stuff from Timing.cs. --- .../ViewModels/AnimationsPageViewModel.cs | 6 +- src/Avalonia.Animation/Animation.cs | 5 +- src/Avalonia.Animation/AnimationsEngine`1.cs | 4 +- src/Avalonia.Animation/Animator`1.cs | 2 +- src/Avalonia.Animation/Timing.cs | 38 +----------- src/Avalonia.Animation/Transition`1.cs | 5 +- src/Avalonia.Animation/TransitionsEngine.cs | 58 +++++++++++++++++++ 7 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 src/Avalonia.Animation/TransitionsEngine.cs diff --git a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs index c76d4db513..f724baf3c6 100644 --- a/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs +++ b/samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs @@ -15,16 +15,16 @@ namespace RenderDemo.ViewModels void TogglePlayState() { - switch (Timing.GlobalPlayState) + switch (Animation.GlobalPlayState) { case PlayState.Run: PlayStateText = "Resume all animations"; - Timing.GlobalPlayState = PlayState.Pause; + Animation.GlobalPlayState = PlayState.Pause; break; case PlayState.Pause: PlayStateText = "Pause all animations"; - Timing.GlobalPlayState = PlayState.Run; + Animation.GlobalPlayState = PlayState.Run; break; } } diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index da2fc75c0b..204cc9d04d 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -21,6 +21,10 @@ namespace Avalonia.Animation /// public class Animation : AvaloniaList, IAnimation { + /// + /// Gets or sets the animation play state for all animations + /// + public static PlayState GlobalPlayState { get; set; } = PlayState.Run; public AvaloniaList _animators { get; set; } = new AvaloniaList(); @@ -73,7 +77,6 @@ namespace Avalonia.Animation /// public bool DelayBetweenIterations { get; set; } - private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> { ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) diff --git a/src/Avalonia.Animation/AnimationsEngine`1.cs b/src/Avalonia.Animation/AnimationsEngine`1.cs index 169f0a7ae0..cccb3098c0 100644 --- a/src/Avalonia.Animation/AnimationsEngine`1.cs +++ b/src/Avalonia.Animation/AnimationsEngine`1.cs @@ -111,7 +111,7 @@ namespace Avalonia.Animation private void DoPlayStatesAndTime(TimeSpan systemTime) { - if (Timing.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) + if (Animation.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) DoComplete(); if (!previousClock.HasValue) @@ -121,7 +121,7 @@ namespace Avalonia.Animation } else { - if (Timing.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) + if (Animation.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) { previousClock = systemTime; return; diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index a8b5ce7a27..8079ac69b5 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -98,7 +98,7 @@ namespace Avalonia.Animation /// private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) { - var stateMachine = new AnimatorStateMachine(animation, control, this, onComplete); + var stateMachine = new AnimationsEngine(animation, control, this, onComplete); Timing.AnimationsTimer .TakeWhile(_ => !stateMachine.unsubscribe) diff --git a/src/Avalonia.Animation/Timing.cs b/src/Avalonia.Animation/Timing.cs index 575cedc620..b8282c05d0 100644 --- a/src/Avalonia.Animation/Timing.cs +++ b/src/Avalonia.Animation/Timing.cs @@ -15,11 +15,6 @@ namespace Avalonia.Animation /// public static class Timing { - /// - /// Gets or sets the animation play state for all animations - /// - public static PlayState GlobalPlayState { get; set; } = PlayState.Run; - /// /// The number of frames per second. /// @@ -38,14 +33,13 @@ namespace Avalonia.Animation var globalTimer = Observable.Interval(FrameTick, AvaloniaScheduler.Instance); AnimationsTimer = globalTimer - .Select(_ => - { - return TimeSpan.FromMilliseconds(Environment.TickCount); - }) + .Select(_ => GetTickCount()) .Publish() .RefCount(); } + internal static TimeSpan GetTickCount() => TimeSpan.FromMilliseconds(Environment.TickCount); + /// /// Gets the animation timer. /// @@ -58,31 +52,5 @@ namespace Avalonia.Animation { get; } - - /// - /// Gets a timer that fires every frame for the specified duration with delay. - /// - /// - /// An observable that notifies the subscriber of the progress along the transition. - /// - /// - /// The parameter passed to the subscriber is the progress along the transition, with - /// 0 being the start and 1 being the end. The observable is guaranteed to fire 0 - /// immediately on subscribe and 1 at the end of the duration. - /// - public static IObservable GetTransitionsTimer(Animatable control, TimeSpan duration, TimeSpan delay = default(TimeSpan)) - { - // TODO: Fix this mess. - // var _duration = (duration.Ticks / FrameTick.Ticks); - // long? endTime = ((Stopwatch.GetTimestamp() - _tickStartTimeStamp) - // / TicksPerFrame) + _duration; - - // return AnimationsTimer - // .TakeWhile(x => x < endTime) - // .Select(x => (double)x / _duration) - // .StartWith(0.0) - // .Concat(Observable.Return(1.0)); - return Observable.Empty(); - } } } \ No newline at end of file diff --git a/src/Avalonia.Animation/Transition`1.cs b/src/Avalonia.Animation/Transition`1.cs index c097b930a5..346c328809 100644 --- a/src/Avalonia.Animation/Transition`1.cs +++ b/src/Avalonia.Animation/Transition`1.cs @@ -5,6 +5,7 @@ using Avalonia.Metadata; using System; using System.Reactive.Linq; using Avalonia.Animation.Easings; +using Avalonia.Animation.Utils; namespace Avalonia.Animation { @@ -51,8 +52,10 @@ namespace Avalonia.Animation /// public virtual IDisposable Apply(Animatable control, object oldValue, object newValue) { - var transition = DoTransition(Timing.GetTransitionsTimer(control, Duration, TimeSpan.Zero), (T)oldValue, (T)newValue); + var transition = DoTransition(new TransitionsEngine(Duration), (T)oldValue, (T)newValue); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } + + } } \ No newline at end of file diff --git a/src/Avalonia.Animation/TransitionsEngine.cs b/src/Avalonia.Animation/TransitionsEngine.cs new file mode 100644 index 0000000000..4d52b2bd48 --- /dev/null +++ b/src/Avalonia.Animation/TransitionsEngine.cs @@ -0,0 +1,58 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Metadata; +using System; +using System.Reactive.Linq; +using Avalonia.Animation.Easings; +using Avalonia.Animation.Utils; + +namespace Avalonia.Animation +{ + public class TransitionsEngine : IObservable, IDisposable + { + private IObserver observer; + private IDisposable timerSubscription; + private readonly TimeSpan startTime; + private readonly TimeSpan duration; + + public TransitionsEngine(TimeSpan Duration) + { + startTime = Timing.GetTickCount(); + duration = Duration; + + timerSubscription = Timing + .AnimationsTimer + .Subscribe(t => TimerTick(t)); + } + + private void TimerTick(TimeSpan t) + { + var interpVal = (double)(t.Ticks - startTime.Ticks) / duration.Ticks; + + if (interpVal > 1d + || interpVal < 0d) + { + this.Dispose(); + return; + } + + observer?.OnNext(interpVal); + } + + public void Dispose() + { + timerSubscription?.Dispose(); + observer?.OnCompleted(); + } + + public IDisposable Subscribe(IObserver Observer) + { + if (Observer is null) + throw new InvalidProgramException("Can only set the subscription once."); + + observer = Observer; + return this; + } + } +} \ No newline at end of file From 25b00749c58cd69b317305d780f597684309bff4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 3 Sep 2018 09:36:10 +0200 Subject: [PATCH 017/512] Updated portable.xaml submodule to latest develop. --- .../Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index 8abbe09592..31ea8e6900 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit 8abbe09592668efb573ac4d5548ba2d7e464ba78 +Subproject commit 31ea8e6900d859a8d1bf45972954b075d2a59f30 From 66bebd0c1b5a5c4ece8c08c675c0ef926b41e3c9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 16:45:27 +0200 Subject: [PATCH 018/512] Initial --- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 91 ++++++++++--------- .../ExternalRenderTarget.cs | 22 +---- .../FramebufferShimRenderTarget.cs | 27 +----- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 10 +- .../Media/Direct2D1FontCollectionCache.cs | 13 +-- .../Media/DrawingContextImpl.cs | 10 +- .../Media/FormattedTextImpl.cs | 4 +- .../Avalonia.Direct2D1/Media/GeometryImpl.cs | 3 +- .../Media/Imaging/BitmapImpl.cs | 7 -- .../Media/Imaging/D2DBitmapImpl.cs | 28 +++--- .../Imaging/D2DRenderTargetBitmapImpl.cs | 39 +++----- .../Media/Imaging/WicBitmapImpl.cs | 33 +++---- .../Imaging/WicRenderTargetBitmapImpl.cs | 22 ++--- .../Media/Imaging/WriteableWicBitmapImpl.cs | 8 +- .../Media/StreamGeometryImpl.cs | 6 +- .../Avalonia.Direct2D1/RenderTarget.cs | 19 +--- .../SwapChainRenderTarget.cs | 55 +++-------- 17 files changed, 132 insertions(+), 265 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 296edcb2d9..437bae6fd1 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -4,14 +4,12 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using Avalonia.Direct2D1.Media; -using Avalonia.Media; -using Avalonia.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; -using Avalonia.Rendering; +using Avalonia.Media; +using Avalonia.Platform; namespace Avalonia { @@ -31,15 +29,16 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); - private static SharpDX.Direct2D1.Factory s_d2D1Factory; + public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } - private static SharpDX.DirectWrite.Factory s_dwfactory; + public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } - private static SharpDX.WIC.ImagingFactory s_imagingFactory; + public static SharpDX.DirectWrite.Factory1 DirectWriteFactory { get; private set; } - private static SharpDX.DXGI.Device s_dxgiDevice; + public static SharpDX.WIC.ImagingFactory ImagingFactory { get; private set; } + + public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; } - private static SharpDX.Direct2D1.Device s_d2D1Device; private static readonly object s_initLock = new object(); private static bool s_initialized = false; @@ -49,13 +48,14 @@ namespace Avalonia.Direct2D1 lock (s_initLock) { if (s_initialized) + { return; + } #if DEBUG try { - s_d2D1Factory = - - new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, + Direct2D1Factory = new SharpDX.Direct2D1.Factory1( + SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.Error); } catch @@ -63,12 +63,19 @@ namespace Avalonia.Direct2D1 // } #endif - s_dwfactory = new SharpDX.DirectWrite.Factory(); - s_imagingFactory = new SharpDX.WIC.ImagingFactory(); - if (s_d2D1Factory == null) - s_d2D1Factory = new SharpDX.Direct2D1.Factory1(SharpDX.Direct2D1.FactoryType.MultiThreaded, + if (Direct2D1Factory == null) + { + Direct2D1Factory = new SharpDX.Direct2D1.Factory1( + SharpDX.Direct2D1.FactoryType.MultiThreaded, SharpDX.Direct2D1.DebugLevel.None); + } + using (var factory = new SharpDX.DirectWrite.Factory()) + { + DirectWriteFactory = factory.QueryInterface(); + } + + ImagingFactory = new SharpDX.WIC.ImagingFactory(); var featureLevels = new[] { @@ -83,17 +90,18 @@ namespace Avalonia.Direct2D1 using (var d3dDevice = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, featureLevels)) { - s_dxgiDevice = d3dDevice.QueryInterface(); + DxgiDevice = d3dDevice.QueryInterface(); } - using (var factory1 = s_d2D1Factory.QueryInterface()) + using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) { - s_d2D1Device = new SharpDX.Direct2D1.Device(factory1, s_dxgiDevice); + Direct2D1Device = device.QueryInterface(); } + s_initialized = true; } } @@ -101,19 +109,13 @@ namespace Avalonia.Direct2D1 public static void Initialize() { InitializeDirect2D(); - AvaloniaLocator.CurrentMutable - .Bind().ToConstant(s_instance) - .BindToSelf(s_d2D1Factory) - .BindToSelf(s_dwfactory) - .BindToSelf(s_imagingFactory) - .BindToSelf(s_dxgiDevice) - .BindToSelf(s_d2D1Device); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(s_instance); SharpDX.Configuration.EnableReleaseOnFinalizer = true; } public IBitmapImpl CreateBitmap(int width, int height) { - return new WicBitmapImpl(s_imagingFactory, width, height); + return new WicBitmapImpl(width, height); } public IFormattedTextImpl CreateFormattedText( @@ -140,14 +142,22 @@ namespace Avalonia.Direct2D1 if (s is IPlatformHandle nativeWindow) { if (nativeWindow.HandleDescriptor != "HWND") + { throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor); + } + return new HwndRenderTarget(nativeWindow); } if (s is IExternalDirect2DRenderTargetSurface external) - return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory); + { + return new ExternalRenderTarget(external); + } + if (s is IFramebufferPlatformSurface fb) - return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory); + { + return new FramebufferShimRenderTarget(fb); + } } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } @@ -158,19 +168,12 @@ namespace Avalonia.Direct2D1 double dpiX, double dpiY) { - return new WicRenderTargetBitmapImpl( - s_imagingFactory, - s_d2D1Factory, - s_dwfactory, - width, - height, - dpiX, - dpiY); + return new WicRenderTargetBitmapImpl(width, height, dpiX, dpiY); } public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null) { - return new WriteableWicBitmapImpl(s_imagingFactory, width, height, format); + return new WriteableWicBitmapImpl(width, height, format); } public IStreamGeometryImpl CreateStreamGeometry() @@ -180,17 +183,17 @@ namespace Avalonia.Direct2D1 public IBitmapImpl LoadBitmap(string fileName) { - return new WicBitmapImpl(s_imagingFactory, fileName); + return new WicBitmapImpl(fileName); } public IBitmapImpl LoadBitmap(Stream stream) { - return new WicBitmapImpl(s_imagingFactory, stream); + return new WicBitmapImpl(stream); } public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) { - return new WicBitmapImpl(s_imagingFactory, format, data, width, height, stride); + return new WicBitmapImpl(format, data, width, height, stride); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs index 176cedd377..aad50331d2 100644 --- a/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs @@ -1,27 +1,19 @@ -using System; -using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1 { class ExternalRenderTarget : IRenderTarget, ILayerFactory { private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; - private readonly DirectWriteFactory _dwFactory; - private readonly SharpDX.WIC.ImagingFactory _wicFactory; public ExternalRenderTarget( - IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, - DirectWriteFactory dwFactory, - SharpDX.WIC.ImagingFactory wicFactory) + IExternalDirect2DRenderTargetSurface externalRenderTargetProvider) { _externalRenderTargetProvider = externalRenderTargetProvider; - _dwFactory = dwFactory; - _wicFactory = wicFactory; } public void Dispose() @@ -33,7 +25,7 @@ namespace Avalonia.Direct2D1 { var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); _externalRenderTargetProvider.BeforeDrawing(); - return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () => + return new DrawingContextImpl(visualBrushRenderer, null, target, null, () => { try { @@ -48,12 +40,8 @@ namespace Avalonia.Direct2D1 public IRenderTargetBitmapImpl CreateLayer(Size size) { - var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); - return D2DRenderTargetBitmapImpl.CreateCompatible( - _wicFactory, - _dwFactory, - target, - size); + var renderTarget = _externalRenderTargetProvider.GetOrCreateRenderTarget(); + return D2DRenderTargetBitmapImpl.CreateCompatible(renderTarget, size); } } } diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs index 523cfeed46..5ae174083c 100644 --- a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -1,14 +1,9 @@ using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Text; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Direct2D1.Media; -using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Win32.Interop; -using SharpDX.Direct2D1; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -17,22 +12,14 @@ namespace Avalonia.Direct2D1 class FramebufferShimRenderTarget : IRenderTarget { private readonly IFramebufferPlatformSurface _surface; - private readonly ImagingFactory _imagingFactory; - private readonly Factory _d2DFactory; - private readonly SharpDX.DirectWrite.Factory _dwriteFactory; - public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface, - ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory) + public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface) { _surface = surface; - _imagingFactory = imagingFactory; - _d2DFactory = d2dFactory; - _dwriteFactory = dwriteFactory; } public void Dispose() - { - + { } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) @@ -44,7 +31,7 @@ namespace Avalonia.Direct2D1 throw new ArgumentException("Unsupported pixel format: " + locked.Format); } - return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory) + return new FramebufferShim(locked) .CreateDrawingContext(visualBrushRenderer); } @@ -52,10 +39,8 @@ namespace Avalonia.Direct2D1 { private readonly ILockedFramebuffer _target; - public FramebufferShim(ILockedFramebuffer target, - ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory - ) : base(imagingFactory, d2dFactory, dwriteFactory, - target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) + public FramebufferShim(ILockedFramebuffer target) : + base(target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) { _target = target; } @@ -76,10 +61,8 @@ namespace Avalonia.Direct2D1 } Dispose(); _target.Dispose(); - }); } } - } } diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 49402d54b9..589f85f208 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; +using Avalonia.Platform; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -22,7 +16,7 @@ namespace Avalonia.Direct2D1 protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, DxgiDevice, _window.Handle, ref swapChainDesc); + return new SwapChain1(dxgiFactory, Direct2D1Platform.DxgiDevice, _window.Handle, ref swapChainDesc); } protected override Size2F GetWindowDpi() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs index 4199c73c54..d60aa15a5e 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs @@ -7,16 +7,13 @@ namespace Avalonia.Direct2D1.Media internal static class Direct2D1FontCollectionCache { private static readonly ConcurrentDictionary s_cachedCollections; - private static readonly SharpDX.DirectWrite.Factory s_factory; private static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection; static Direct2D1FontCollectionCache() { s_cachedCollections = new ConcurrentDictionary(); - s_factory = AvaloniaLocator.Current.GetService(); - - s_installedFontCollection = s_factory.GetSystemFontCollection(false); + s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false); } public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface) @@ -39,7 +36,7 @@ namespace Avalonia.Direct2D1.Media } return new SharpDX.DirectWrite.TextFormat( - s_factory, + Direct2D1Platform.DirectWriteFactory, fontFamilyName, fontCollection, (SharpDX.DirectWrite.FontWeight)typeface.Weight, @@ -57,9 +54,9 @@ namespace Avalonia.Direct2D1.Media { var assets = FontFamilyLoader.LoadFontAssets(key); - var fontLoader = new DWriteResourceFontLoader(s_factory, assets); + var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets); - return new SharpDX.DirectWrite.FontCollection(s_factory, fontLoader, fontLoader.Key); + return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index ae5dd3ae13..3aec6c6eb1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -24,8 +24,6 @@ namespace Avalonia.Direct2D1.Media private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; - private readonly SharpDX.WIC.ImagingFactory _imagingFactory; - private SharpDX.DirectWrite.Factory _directWriteFactory; /// /// Initializes a new instance of the class. @@ -36,16 +34,12 @@ namespace Avalonia.Direct2D1.Media /// An object to use to create layers. May be null, in which case a /// will created when a new layer is requested. /// - /// The DirectWrite factory. - /// The WIC imaging factory. /// An optional swap chain associated with this drawing context. /// An optional delegate to be called when context is disposed. public DrawingContextImpl( IVisualBrushRenderer visualBrushRenderer, ILayerFactory layerFactory, SharpDX.Direct2D1.RenderTarget renderTarget, - SharpDX.DirectWrite.Factory directWriteFactory, - SharpDX.WIC.ImagingFactory imagingFactory, SharpDX.DXGI.SwapChain1 swapChain = null, Action finishedCallback = null) { @@ -54,8 +48,6 @@ namespace Avalonia.Direct2D1.Media _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; - _directWriteFactory = directWriteFactory; - _imagingFactory = imagingFactory; _renderTarget.BeginDraw(); } @@ -443,7 +435,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, _renderTarget, - new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap), + new D2DBitmapImpl(intermediate.Bitmap), destinationSize); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs index c3fe60e790..09b249e19f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs @@ -21,15 +21,13 @@ namespace Avalonia.Direct2D1.Media { Text = text; - var factory = AvaloniaLocator.Current.GetService(); - var textFormat = Direct2D1FontCollectionCache.GetTextFormat(typeface); textFormat.WordWrapping = wrapping == TextWrapping.Wrap ? DWrite.WordWrapping.Wrap : DWrite.WordWrapping.NoWrap; TextLayout = new DWrite.TextLayout( - factory, + Direct2D1Platform.DirectWriteFactory, Text ?? string.Empty, textFormat, (float)constraint.Width, diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index 120ab71ead..7c8ddaca3f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -53,10 +53,9 @@ namespace Avalonia.Direct2D1.Media public ITransformedGeometryImpl WithTransform(Matrix transform) { - var factory = AvaloniaLocator.Current.GetService(); return new TransformedGeometryImpl( new TransformedGeometry( - factory, + Direct2D1Platform.Direct2D1Factory, GetSourceGeometry(), transform.ToDirect2D()), this); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index d58f023391..30af01283a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -1,19 +1,12 @@ using System; using System.IO; using Avalonia.Platform; -using SharpDX.WIC; using D2DBitmap = SharpDX.Direct2D1.Bitmap; namespace Avalonia.Direct2D1.Media { public abstract class BitmapImpl : IBitmapImpl, IDisposable { - public BitmapImpl(ImagingFactory imagingFactory) - { - WicImagingFactory = imagingFactory; - } - - public ImagingFactory WicImagingFactory { get; } public abstract int PixelWidth { get; } public abstract int PixelHeight { get; } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 6713cb13be..139100490c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,7 +1,6 @@ using System; using System.IO; using SharpDX.Direct2D1; -using WICFactory = SharpDX.WIC.ImagingFactory; using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; using ImageParameters = SharpDX.WIC.ImageParameters; using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; @@ -13,52 +12,51 @@ namespace Avalonia.Direct2D1.Media /// public class D2DBitmapImpl : BitmapImpl { - private Bitmap _direct2D; + private Bitmap _direct2DBitmap; /// /// Initialize a new instance of the class /// with a bitmap backed by GPU memory. /// - /// The image factory to use when saving out this bitmap. /// The GPU bitmap. /// /// This bitmap must be either from the same render target, /// or if the render target is a , /// the device associated with this context, to be renderable. /// - public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap) - : base(imagingFactory) + public D2DBitmapImpl(Bitmap d2DBitmap) { - _direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); + _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - public override int PixelWidth => _direct2D.PixelSize.Width; - public override int PixelHeight => _direct2D.PixelSize.Height; + public override int PixelWidth => _direct2DBitmap.PixelSize.Width; + public override int PixelHeight => _direct2DBitmap.PixelSize.Height; public override void Dispose() { base.Dispose(); - _direct2D.Dispose(); + _direct2DBitmap.Dispose(); } public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - return new OptionalDispose(_direct2D, false); + return new OptionalDispose(_direct2DBitmap, false); } public override void Save(Stream stream) { - using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) - using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null)) + //ToDo: Not supported under Windows 7! + using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)Direct2D1Platform.ImagingFactory, null)) { var parameters = new ImageParameters( new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), - _direct2D.DotsPerInch.Width, - _direct2D.DotsPerInch.Height, + _direct2DBitmap.DotsPerInch.Width, + _direct2DBitmap.DotsPerInch.Height, 0, 0, PixelWidth, PixelHeight); - imageEncoder.WriteFrame(_direct2D, frameEncode, parameters); + imageEncoder.WriteFrame(_direct2DBitmap, frameEncode, parameters); frameEncode.Commit(); encoder.Commit(); } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 2843848fac..7ea303345a 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,35 +1,25 @@ -using System; -using Avalonia.Platform; +using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; -using SharpDX.WIC; using D2DBitmap = SharpDX.Direct2D1.Bitmap; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media.Imaging { public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory { - private readonly DirectWriteFactory _dwriteFactory; - private readonly BitmapRenderTarget _target; + private readonly BitmapRenderTarget _renderTarget; - public D2DRenderTargetBitmapImpl( - ImagingFactory imagingFactory, - DirectWriteFactory dwriteFactory, - BitmapRenderTarget target) - : base(imagingFactory, target.Bitmap) + public D2DRenderTargetBitmapImpl(BitmapRenderTarget renderTarget) + : base(renderTarget.Bitmap) { - _dwriteFactory = dwriteFactory; - _target = target; + _renderTarget = renderTarget; } - public override int PixelWidth => _target.PixelSize.Width; - public override int PixelHeight => _target.PixelSize.Height; + public override int PixelWidth => _renderTarget.PixelSize.Width; + public override int PixelHeight => _renderTarget.PixelSize.Height; public static D2DRenderTargetBitmapImpl CreateCompatible( - ImagingFactory imagingFactory, - DirectWriteFactory dwriteFactory, SharpDX.Direct2D1.RenderTarget renderTarget, Size size) { @@ -37,32 +27,27 @@ namespace Avalonia.Direct2D1.Media.Imaging renderTarget, CompatibleRenderTargetOptions.None, new Size2F((float)size.Width, (float)size.Height)); - return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); + return new D2DRenderTargetBitmapImpl(bitmapRenderTarget); } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl( - visualBrushRenderer, - this, - _target, - _dwriteFactory, - WicImagingFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } public IRenderTargetBitmapImpl CreateLayer(Size size) { - return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size); + return CreateCompatible(_renderTarget, size); } public override void Dispose() { - _target.Dispose(); + _renderTarget.Dispose(); } public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) { - return new OptionalDispose(_target.Bitmap, false); + return new OptionalDispose(_renderTarget.Bitmap, false); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 371dfcfc3e..2c41f1203f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -19,57 +19,52 @@ namespace Avalonia.Direct2D1.Media /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The filename of the bitmap to load. - public WicBitmapImpl(ImagingFactory factory, string fileName) - : base(factory) + public WicBitmapImpl(string fileName) { - using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand)) + using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand)) { - WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); } } /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The stream to read the bitmap from. - public WicBitmapImpl(ImagingFactory factory, Stream stream) - : base(factory) + public WicBitmapImpl(Stream stream) { - using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad)) + using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad)) { - WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); } } /// /// Initializes a new instance of the class. /// - /// The WIC imaging factory to use. /// The width of the bitmap. /// The height of the bitmap. /// Pixel format - public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null) - : base(factory) + public WicBitmapImpl(int width, int height, APixelFormat? pixelFormat = null) { if (!pixelFormat.HasValue) + { pixelFormat = APixelFormat.Bgra8888; + } PixelFormat = pixelFormat; WicImpl = new Bitmap( - factory, + Direct2D1Platform.ImagingFactory, width, height, pixelFormat.Value.ToWic(), BitmapCreateCacheOption.CacheOnLoad); } - public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride) - : base(factory) + public WicBitmapImpl(APixelFormat format, IntPtr data, int width, int height, int stride) { - WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); + WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); PixelFormat = format; using (var l = WicImpl.Lock(BitmapLockFlags.Write)) { @@ -112,14 +107,14 @@ namespace Avalonia.Direct2D1.Media /// The Direct2D bitmap. public override OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) { - FormatConverter converter = new FormatConverter(WicImagingFactory); + FormatConverter converter = new FormatConverter(Direct2D1Platform.ImagingFactory); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } public override void Save(Stream stream) { - using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream)) + using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frame = new BitmapFrameEncode(encoder)) { frame.Initialize(); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index 0eb2608047..aa8b3ead42 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -5,26 +5,20 @@ using System; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX.Direct2D1; -using SharpDX.WIC; -using DirectWriteFactory = SharpDX.DirectWrite.Factory; namespace Avalonia.Direct2D1.Media { public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl { - private readonly DirectWriteFactory _dwriteFactory; - private readonly WicRenderTarget _target; + private readonly WicRenderTarget _renderTarget; public WicRenderTargetBitmapImpl( - ImagingFactory imagingFactory, - Factory d2dFactory, - DirectWriteFactory dwriteFactory, int width, int height, double dpiX, double dpiY, Platform.PixelFormat? pixelFormat = null) - : base(imagingFactory, width, height, pixelFormat) + : base(width, height, pixelFormat) { var props = new RenderTargetProperties { @@ -32,17 +26,16 @@ namespace Avalonia.Direct2D1.Media DpiY = (float)dpiY, }; - _target = new WicRenderTarget( - d2dFactory, + _renderTarget = new WicRenderTarget( + Direct2D1Platform.Direct2D1Factory, WicImpl, props); - - _dwriteFactory = dwriteFactory; } public override void Dispose() { - _target.Dispose(); + _renderTarget.Dispose(); + base.Dispose(); } @@ -51,8 +44,7 @@ namespace Avalonia.Direct2D1.Media public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback) { - return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory, - finishedCallback: finishedCallback); + return new DrawingContextImpl(visualBrushRenderer, null, _renderTarget, finishedCallback: finishedCallback); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index fc931c32db..075fef5ab2 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; @@ -11,8 +7,8 @@ namespace Avalonia.Direct2D1.Media.Imaging { class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl { - public WriteableWicBitmapImpl(ImagingFactory factory, int width, int height, PixelFormat? pixelFormat) - : base(factory, width, height, pixelFormat) + public WriteableWicBitmapImpl(int width, int height, PixelFormat? pixelFormat) + : base(width, height, pixelFormat) { } diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs index 4c1bc3d6f7..a07d5c1c72 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs @@ -31,8 +31,7 @@ namespace Avalonia.Direct2D1.Media /// public IStreamGeometryImpl Clone() { - Factory factory = AvaloniaLocator.Current.GetService(); - var result = new PathGeometry(factory); + var result = new PathGeometry(Direct2D1Platform.Direct2D1Factory); var sink = result.Open(); ((PathGeometry)Geometry).Stream(sink); sink.Close(); @@ -47,8 +46,7 @@ namespace Avalonia.Direct2D1.Media private static Geometry CreateGeometry() { - Factory factory = AvaloniaLocator.Current.GetService(); - return new PathGeometry(factory); + return new PathGeometry(Direct2D1Platform.Direct2D1Factory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs index 6086b0c67c..a21b5571c6 100644 --- a/src/Windows/Avalonia.Direct2D1/RenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/RenderTarget.cs @@ -1,14 +1,10 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; -using SharpDX.Direct2D1; -using DwFactory = SharpDX.DirectWrite.Factory; -using WicFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1 { @@ -25,32 +21,21 @@ namespace Avalonia.Direct2D1 /// The render target. public RenderTarget(SharpDX.Direct2D1.RenderTarget renderTarget) { - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - WicFactory = AvaloniaLocator.Current.GetService(); _renderTarget = renderTarget; } - public Factory Direct2DFactory { get; } - public DwFactory DirectWriteFactory { get; } - public WicFactory WicFactory { get; } - /// /// Creates a drawing context for a rendering session. /// /// An . public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory); + return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget); } public IRenderTargetBitmapImpl CreateLayer(Size size) { - return D2DRenderTargetBitmapImpl.CreateCompatible( - WicFactory, - DirectWriteFactory, - _renderTarget, - size); + return D2DRenderTargetBitmapImpl.CreateCompatible(_renderTarget, size); } public void Dispose() diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 0a23c63498..f820129b8d 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -1,16 +1,14 @@ -using System; +using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; +using Avalonia.Rendering; + using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; + using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Device = SharpDX.Direct2D1.Device; -using Factory = SharpDX.Direct2D1.Factory; -using Factory2 = SharpDX.DXGI.Factory2; -using Avalonia.Rendering; -using Avalonia.Direct2D1.Media; -using Avalonia.Direct2D1.Media.Imaging; +using PixelFormat = SharpDX.Direct2D1.PixelFormat; namespace Avalonia.Direct2D1 { @@ -21,23 +19,6 @@ namespace Avalonia.Direct2D1 private DeviceContext _deviceContext; private SwapChain1 _swapChain; - protected SwapChainRenderTarget() - { - DxgiDevice = AvaloniaLocator.Current.GetService(); - D2DDevice = AvaloniaLocator.Current.GetService(); - Direct2DFactory = AvaloniaLocator.Current.GetService(); - DirectWriteFactory = AvaloniaLocator.Current.GetService(); - WicImagingFactory = AvaloniaLocator.Current.GetService(); - } - - public Factory Direct2DFactory { get; } - public SharpDX.DirectWrite.Factory DirectWriteFactory { get; } - public SharpDX.WIC.ImagingFactory WicImagingFactory { get; } - - protected SharpDX.DXGI.Device DxgiDevice { get; } - - public Device D2DDevice { get; } - /// /// Creates a drawing context for a rendering session. /// @@ -54,13 +35,7 @@ namespace Avalonia.Direct2D1 CreateSwapChain(); } - return new DrawingContextImpl( - visualBrushRenderer, - this, - _deviceContext, - DirectWriteFactory, - WicImagingFactory, - _swapChain); + return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); } public IRenderTargetBitmapImpl CreateLayer(Size size) @@ -70,11 +45,7 @@ namespace Avalonia.Direct2D1 CreateSwapChain(); } - return D2DRenderTargetBitmapImpl.CreateCompatible( - WicImagingFactory, - DirectWriteFactory, - _deviceContext, - size); + return D2DRenderTargetBitmapImpl.CreateCompatible(_deviceContext, size); } public void Dispose() @@ -85,11 +56,11 @@ namespace Avalonia.Direct2D1 private void CreateSwapChain() { - using (var dxgiAdaptor = DxgiDevice.Adapter) - using (var dxgiFactory = dxgiAdaptor.GetParent()) + using (var dxgiAdaptor = Direct2D1Platform.DxgiDevice.Adapter) + using (var dxgiFactory = dxgiAdaptor.GetParent()) { _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; var swapChainDesc = new SwapChainDescription1 { @@ -119,7 +90,7 @@ namespace Avalonia.Direct2D1 new BitmapProperties1( new PixelFormat { - AlphaMode = AlphaMode.Ignore, + AlphaMode = AlphaMode.Premultiplied, Format = Format.B8G8R8A8_UNorm }, _savedDpi.Width, @@ -131,7 +102,7 @@ namespace Avalonia.Direct2D1 } } - protected abstract SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc); + protected abstract SwapChain1 CreateSwapChain(SharpDX.DXGI.Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc); protected abstract Size2F GetWindowDpi(); From e76b694ebdcc4b2ca79532cb30183ea8aa98fc79 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 17:02:22 +0200 Subject: [PATCH 019/512] InterpolationMode.HighQuality support --- .../Media/DrawingContextImpl.cs | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 3aec6c6eb1..b842f26edb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -22,6 +22,7 @@ namespace Avalonia.Direct2D1.Media private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly ILayerFactory _layerFactory; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; + private readonly DeviceContext _deviceContext; private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly Action _finishedCallback; @@ -48,7 +49,17 @@ namespace Avalonia.Direct2D1.Media _renderTarget = renderTarget; _swapChain = swapChain; _finishedCallback = finishedCallback; - _renderTarget.BeginDraw(); + + if (_renderTarget is DeviceContext deviceContext) + { + _deviceContext = deviceContext; + } + else + { + _deviceContext = _renderTarget.QueryInterface(); + } + + _deviceContext.BeginDraw(); } /// @@ -56,14 +67,14 @@ namespace Avalonia.Direct2D1.Media /// public Matrix Transform { - get { return _renderTarget.Transform.ToAvalonia(); } - set { _renderTarget.Transform = value.ToDirect2D(); } + get { return _deviceContext.Transform.ToAvalonia(); } + set { _deviceContext.Transform = value.ToDirect2D(); } } /// public void Clear(Color color) { - _renderTarget.Clear(color.ToDirect2D()); + _deviceContext.Clear(color.ToDirect2D()); } /// @@ -72,10 +83,13 @@ namespace Avalonia.Direct2D1.Media public void Dispose() { foreach (var layer in _layerPool) + { layer.Dispose(); + } + try { - _renderTarget.EndDraw(); + _deviceContext.EndDraw(); _swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None); _finishedCallback?.Invoke(); @@ -96,29 +110,32 @@ namespace Avalonia.Direct2D1.Media /// The bitmap interpolation mode. public void DrawImage(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { - using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) + using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) { var interpolationMode = GetInterpolationMode(bitmapInterpolationMode); - _renderTarget.DrawBitmap( + _deviceContext.DrawBitmap( d2d.Value, destRect.ToSharpDX(), (float)opacity, interpolationMode, - sourceRect.ToSharpDX()); + sourceRect.ToSharpDX(), + null); } } - private static SharpDX.Direct2D1.BitmapInterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode) + private static InterpolationMode GetInterpolationMode(BitmapInterpolationMode interpolationMode) { switch (interpolationMode) { case BitmapInterpolationMode.LowQuality: - return SharpDX.Direct2D1.BitmapInterpolationMode.NearestNeighbor; + return InterpolationMode.NearestNeighbor; case BitmapInterpolationMode.MediumQuality: + return InterpolationMode.Linear; case BitmapInterpolationMode.HighQuality: + return InterpolationMode.HighQualityCubic; case BitmapInterpolationMode.Default: - return SharpDX.Direct2D1.BitmapInterpolationMode.Linear; + return InterpolationMode.Linear; default: throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); } @@ -133,17 +150,17 @@ namespace Avalonia.Direct2D1.Media /// The rect in the output to draw to. public void DrawImage(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { - using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_renderTarget)) - using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value)) + using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) + using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) - using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) + using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_deviceContext.Factory, destRect.ToDirect2D())) { if (d2dOpacityMask.PlatformBrush != null) { d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D(); } - _renderTarget.FillGeometry( + _deviceContext.FillGeometry( geometry, sourceBrush, d2dOpacityMask.PlatformBrush); @@ -163,11 +180,11 @@ namespace Avalonia.Direct2D1.Media var size = new Rect(p1, p2).Size; using (var d2dBrush = CreateBrush(pen.Brush, size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) { - _renderTarget.DrawLine( + _deviceContext.DrawLine( p1.ToSharpDX(), p2.ToSharpDX(), d2dBrush.PlatformBrush, @@ -193,7 +210,7 @@ namespace Avalonia.Direct2D1.Media if (d2dBrush.PlatformBrush != null) { var impl = (GeometryImpl)geometry; - _renderTarget.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush); + _deviceContext.FillGeometry(impl.Geometry, d2dBrush.PlatformBrush); } } } @@ -201,12 +218,12 @@ namespace Avalonia.Direct2D1.Media if (pen != null) { using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (d2dBrush.PlatformBrush != null) { var impl = (GeometryImpl)geometry; - _renderTarget.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); + _deviceContext.DrawGeometry(impl.Geometry, d2dBrush.PlatformBrush, (float)pen.Thickness, d2dStroke); } } } @@ -221,13 +238,13 @@ namespace Avalonia.Direct2D1.Media public void DrawRectangle(Pen pen, Rect rect, float cornerRadius) { using (var brush = CreateBrush(pen.Brush, rect.Size)) - using (var d2dStroke = pen.ToDirect2DStrokeStyle(_renderTarget)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) { if (brush.PlatformBrush != null) { if (cornerRadius == 0) { - _renderTarget.DrawRectangle( + _deviceContext.DrawRectangle( rect.ToDirect2D(), brush.PlatformBrush, (float)pen.Thickness, @@ -235,7 +252,7 @@ namespace Avalonia.Direct2D1.Media } else { - _renderTarget.DrawRoundedRectangle( + _deviceContext.DrawRoundedRectangle( new RoundedRectangle { Rect = rect.ToDirect2D(), RadiusX = cornerRadius, RadiusY = cornerRadius }, brush.PlatformBrush, (float)pen.Thickness, @@ -258,7 +275,7 @@ namespace Avalonia.Direct2D1.Media var impl = (FormattedTextImpl)text; using (var brush = CreateBrush(foreground, impl.Size)) - using (var renderer = new AvaloniaTextRenderer(this, _renderTarget, brush.PlatformBrush)) + using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush)) { if (brush.PlatformBrush != null) { @@ -282,11 +299,11 @@ namespace Avalonia.Direct2D1.Media { if (cornerRadius == 0) { - _renderTarget.FillRectangle(rect.ToDirect2D(), b.PlatformBrush); + _deviceContext.FillRectangle(rect.ToDirect2D(), b.PlatformBrush); } else { - _renderTarget.FillRoundedRectangle( + _deviceContext.FillRoundedRectangle( new RoundedRectangle { Rect = new RawRectangleF( @@ -312,7 +329,7 @@ namespace Avalonia.Direct2D1.Media else { var platform = AvaloniaLocator.Current.GetService(); - var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height); + var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var pixelSize = size * (dpi / 96); return platform.CreateRenderTargetBitmap( (int)pixelSize.Width, @@ -329,12 +346,12 @@ namespace Avalonia.Direct2D1.Media /// A disposable used to undo the clip rectangle. public void PushClip(Rect clip) { - _renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); + _deviceContext.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); } public void PopClip() { - _renderTarget.PopAxisAlignedClip(); + _deviceContext.PopAxisAlignedClip(); } readonly Stack _layers = new Stack(); @@ -355,8 +372,8 @@ namespace Avalonia.Direct2D1.Media Opacity = (float)opacity, }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); } @@ -374,7 +391,7 @@ namespace Avalonia.Direct2D1.Media var layer = _layers.Pop(); if (layer != null) { - _renderTarget.PopLayer(); + _deviceContext.PopLayer(); _layerPool.Push(layer); } } @@ -395,21 +412,21 @@ namespace Avalonia.Direct2D1.Media if (solidColorBrush != null) { - return new SolidColorBrushImpl(solidColorBrush, _renderTarget); + return new SolidColorBrushImpl(solidColorBrush, _deviceContext); } else if (linearGradientBrush != null) { - return new LinearGradientBrushImpl(linearGradientBrush, _renderTarget, destinationSize); + return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize); } else if (radialGradientBrush != null) { - return new RadialGradientBrushImpl(radialGradientBrush, _renderTarget, destinationSize); + return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize); } else if (imageBrush?.Source != null) { return new ImageBrushImpl( imageBrush, - _renderTarget, + _deviceContext, (BitmapImpl)imageBrush.Source.PlatformImpl.Item, destinationSize); } @@ -422,7 +439,7 @@ namespace Avalonia.Direct2D1.Media if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1) { using (var intermediate = new BitmapRenderTarget( - _renderTarget, + _deviceContext, CompatibleRenderTargetOptions.None, intermediateSize.ToSharpDX())) { @@ -434,7 +451,7 @@ namespace Avalonia.Direct2D1.Media return new ImageBrushImpl( visualBrush, - _renderTarget, + _deviceContext, new D2DBitmapImpl(intermediate.Bitmap), destinationSize); } @@ -446,7 +463,7 @@ namespace Avalonia.Direct2D1.Media } } - return new SolidColorBrushImpl(null, _renderTarget); + return new SolidColorBrushImpl(null, _deviceContext); } public void PushGeometryClip(IGeometryImpl clip) @@ -458,8 +475,8 @@ namespace Avalonia.Direct2D1.Media Opacity = 1, GeometricMask = ((GeometryImpl)clip).Geometry }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); @@ -479,8 +496,8 @@ namespace Avalonia.Direct2D1.Media Opacity = 1, OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush }; - var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); - _renderTarget.PushLayer(ref parameters, layer); + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext); + _deviceContext.PushLayer(ref parameters, layer); _layers.Push(layer); } From 0646106f8fe9d6ead0e433024ed909405cd93c6a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Wed, 5 Sep 2018 19:53:57 +0200 Subject: [PATCH 020/512] Introduce ResizeBuffers to prevent SwapChain recreation --- .../SwapChainRenderTarget.cs | 105 ++++++++++-------- 1 file changed, 61 insertions(+), 44 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index f820129b8d..94d407ed0b 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -7,11 +7,13 @@ using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; - namespace Avalonia.Direct2D1 { + + using AlphaMode = SharpDX.Direct2D1.AlphaMode; + using DeviceContext = SharpDX.Direct2D1.DeviceContext; + using PixelFormat = SharpDX.Direct2D1.PixelFormat; + public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory { private Size2 _savedSize; @@ -32,7 +34,8 @@ namespace Avalonia.Direct2D1 { _savedSize = size; _savedDpi = dpi; - CreateSwapChain(); + + Resize(); } return new DrawingContextImpl(visualBrushRenderer, this, _deviceContext, _swapChain); @@ -42,7 +45,7 @@ namespace Avalonia.Direct2D1 { if (_deviceContext == null) { - CreateSwapChain(); + CreateDeviceContext(); } return D2DRenderTargetBitmapImpl.CreateCompatible(_deviceContext, size); @@ -51,54 +54,68 @@ namespace Avalonia.Direct2D1 public void Dispose() { _deviceContext?.Dispose(); + _swapChain?.Dispose(); } + private void Resize() + { + _deviceContext?.Dispose(); + _deviceContext = null; + + _swapChain?.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); + + CreateDeviceContext(); + } + private void CreateSwapChain() { - using (var dxgiAdaptor = Direct2D1Platform.DxgiDevice.Adapter) - using (var dxgiFactory = dxgiAdaptor.GetParent()) + var swapChainDescription = new SwapChainDescription1 { - _deviceContext?.Dispose(); - _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; - - var swapChainDesc = new SwapChainDescription1 + Width = _savedSize.Width, + Height = _savedSize.Height, + Format = Format.B8G8R8A8_UNorm, + SampleDescription = new SampleDescription { - Width = _savedSize.Width, - Height = _savedSize.Height, - Format = Format.B8G8R8A8_UNorm, - Stereo = false, - SampleDescription = new SampleDescription + Count = 1, + Quality = 0, + }, + Usage = Usage.RenderTargetOutput, + BufferCount = 1, + SwapEffect = SwapEffect.Discard, + }; + + using (var dxgiAdapter = Direct2D1Platform.DxgiDevice.Adapter) + using (var dxgiFactory = dxgiAdapter.GetParent()) + { + _swapChain = CreateSwapChain(dxgiFactory, swapChainDescription); + } + } + + private void CreateDeviceContext() + { + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = _savedDpi }; + + if (_swapChain == null) + { + CreateSwapChain(); + } + + using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) + using (var d2dBackBuffer = new Bitmap1( + _deviceContext, + dxgiBackBuffer, + new BitmapProperties1( + new PixelFormat { - Count = 1, - Quality = 0, + AlphaMode = AlphaMode.Premultiplied, + Format = Format.B8G8R8A8_UNorm }, - Usage = Usage.RenderTargetOutput, - BufferCount = 1, - Scaling = Scaling.Stretch, - SwapEffect = SwapEffect.Discard, - Flags = 0, - }; - - _swapChain?.Dispose(); - _swapChain = CreateSwapChain(dxgiFactory, swapChainDesc); - - using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) - using (var d2dBackBuffer = new Bitmap1( - _deviceContext, - dxgiBackBuffer, - new BitmapProperties1( - new PixelFormat - { - AlphaMode = AlphaMode.Premultiplied, - Format = Format.B8G8R8A8_UNorm - }, - _savedDpi.Width, - _savedDpi.Height, - BitmapOptions.Target | BitmapOptions.CannotDraw))) - { - _deviceContext.Target = d2dBackBuffer; - } + _savedSize.Width, + _savedSize.Height, + BitmapOptions.Target | BitmapOptions.CannotDraw))) + { + _deviceContext.Target = d2dBackBuffer; } } From e7dc15239288ff4e466f585406765269e1cba9c4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 6 Sep 2018 00:21:24 -0500 Subject: [PATCH 021/512] Fix case where timing lands on exactly a new keyframe. --- src/Avalonia.Animation/Animator`1.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 8079ac69b5..e674ac84e1 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -86,10 +86,18 @@ namespace Avalonia.Animation double t0 = firstCue.Key; double t1 = lastCue.Key; - var intraframeTime = (t - t0) / (t1 - t0); - var firstFrameData = (firstCue.Value.frame.GetTypedValue(), firstCue.Value.isNeutral); - var lastFrameData = (lastCue.Value.frame.GetTypedValue(), lastCue.Value.isNeutral); - return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData)); + if (t0 != t1) + { + var intraframeTime = (t - t0) / (t1 - t0); + var firstFrameData = (firstCue.Value.frame.GetTypedValue(), firstCue.Value.isNeutral); + var lastFrameData = (lastCue.Value.frame.GetTypedValue(), lastCue.Value.isNeutral); + return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData)); + } + else + { + var frameData = (firstCue.Value.frame.GetTypedValue(), firstCue.Value.isNeutral); + return (0.0, new KeyFramePair(frameData, frameData)); + } } From 5f4a38842c37693a783712ba2116efbdeeec7217 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Sep 2018 13:26:17 +0800 Subject: [PATCH 022/512] Finally fixed the flickering heart bug! Simplify the Keyframe selection and removed all LINQ code. Use the Cue property on AnimatorKeyFrame as the time index and integrate the IsNeutral value as an internal property. --- src/Avalonia.Animation/AnimatorKeyFrame.cs | 1 + src/Avalonia.Animation/Animator`1.cs | 61 ++++++++++++++-------- src/Avalonia.Animation/Cue.cs | 5 +- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs index 0276c6fa92..0dc15da49c 100644 --- a/src/Avalonia.Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs @@ -29,6 +29,7 @@ namespace Avalonia.Animation Cue = cue; } + internal bool isNeutral; public Type AnimatorType { get; } public Cue Cue { get; } public AvaloniaProperty Property { get; private set; } diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 8079ac69b5..7631a11d9c 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 readonly SortedList _convertedKeyframes = new SortedList(); + private readonly List _convertedKeyframes = new List(); private bool isVerfifiedAndConverted; @@ -58,41 +58,49 @@ namespace Avalonia.Animation /// The time parameter, relative to the total animation time protected (double IntraKFTime, KeyFramePair KFPair) GetKFPairAndIntraKFTime(double t) { - KeyValuePair firstCue, lastCue; + AnimatorKeyFrame firstCue, lastCue ; int kvCount = _convertedKeyframes.Count; if (kvCount > 2) { if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0) { - firstCue = _convertedKeyframes.First(); - lastCue = _convertedKeyframes.Skip(1).First(); + firstCue = _convertedKeyframes[0]; + lastCue = _convertedKeyframes[1]; } else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0) { - firstCue = _convertedKeyframes.Skip(kvCount - 2).First(); - lastCue = _convertedKeyframes.Last(); + firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2]; + lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1]; } else { - firstCue = _convertedKeyframes.Last(j => j.Key <= t); - lastCue = _convertedKeyframes.First(j => j.Key >= t); + (double time, int index) maxval = (0.0d, 0); + for (int i = 0; i < _convertedKeyframes.Count; i++) + { + var comp = _convertedKeyframes[i].Cue.CueValue; + if (t >= comp) + { + maxval = (comp, i); + } + } + firstCue = _convertedKeyframes[maxval.index]; + lastCue = _convertedKeyframes[maxval.index + 1]; } } else { - firstCue = _convertedKeyframes.First(); - lastCue = _convertedKeyframes.Last(); + firstCue = _convertedKeyframes[0]; + lastCue = _convertedKeyframes[1]; } - double t0 = firstCue.Key; - double t1 = lastCue.Key; + double t0 = firstCue.Cue.CueValue; + double t1 = lastCue.Cue.CueValue; var intraframeTime = (t - t0) / (t1 - t0); - var firstFrameData = (firstCue.Value.frame.GetTypedValue(), firstCue.Value.isNeutral); - var lastFrameData = (lastCue.Value.frame.GetTypedValue(), lastCue.Value.isNeutral); + var firstFrameData = (firstCue.GetTypedValue(), firstCue.isNeutral); + var lastFrameData = (lastCue.GetTypedValue(), lastCue.isNeutral); return (intraframeTime, new KeyFramePair(firstFrameData, lastFrameData)); } - /// /// Runs the KeyFrames Animation. /// @@ -113,16 +121,25 @@ namespace Avalonia.Animation protected abstract T DoInterpolation(double time, T neutralValue); /// - /// Verifies and converts keyframe values according to this class's target type. + /// Verifies, converts and sorts keyframe values according to this class's target type. /// private void VerifyConvertKeyFrames() { foreach (AnimatorKeyFrame keyframe in this) { - _convertedKeyframes.Add(keyframe.Cue.CueValue, (keyframe, false)); + _convertedKeyframes.Add(keyframe); } AddNeutralKeyFramesIfNeeded(); + + var copy = _convertedKeyframes.ToList().OrderBy(p => p.Cue.CueValue); + _convertedKeyframes.Clear(); + + foreach (AnimatorKeyFrame keyframe in copy) + { + _convertedKeyframes.Add(keyframe); + } + isVerfifiedAndConverted = true; } @@ -133,13 +150,13 @@ namespace Avalonia.Animation hasStartKey = hasEndKey = false; // Check if there's start and end keyframes. - foreach (var converted in _convertedKeyframes.Keys) + foreach (var frame in _convertedKeyframes) { - if (DoubleUtils.AboutEqual(converted, 0.0)) + if (DoubleUtils.AboutEqual(frame.Cue.CueValue, 0.0)) { hasStartKey = true; } - else if (DoubleUtils.AboutEqual(converted, 1.0)) + else if (DoubleUtils.AboutEqual(frame.Cue.CueValue, 1.0)) { hasEndKey = true; } @@ -153,12 +170,12 @@ namespace Avalonia.Animation { if (!hasStartKey) { - _convertedKeyframes.Add(0.0d, (new AnimatorKeyFrame { Value = default(T) }, true)); + _convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true }); } if (!hasEndKey) { - _convertedKeyframes.Add(1.0d, (new AnimatorKeyFrame { Value = default(T) }, true)); + _convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(1.0d)) { Value = default(T), isNeutral = true }); } } } diff --git a/src/Avalonia.Animation/Cue.cs b/src/Avalonia.Animation/Cue.cs index 5a95c108e3..de77475c2c 100644 --- a/src/Avalonia.Animation/Cue.cs +++ b/src/Avalonia.Animation/Cue.cs @@ -7,7 +7,7 @@ using System.Text; namespace Avalonia.Animation { /// - /// A Cue object for . + /// Determines the time index for a . /// [TypeConverter(typeof(CueTypeConverter))] public readonly struct Cue : IEquatable, IEquatable @@ -84,5 +84,4 @@ namespace Avalonia.Animation return Cue.Parse((string)value, culture); } } - -} +} \ No newline at end of file From 8a800cadfa1917d6a34bd023b3ee07ce7e2cbeac Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Sep 2018 19:52:47 +0800 Subject: [PATCH 023/512] Fix nits & polish up some code --- src/Avalonia.Animation/Animatable.cs | 25 +-- src/Avalonia.Animation/Animation.cs | 2 - src/Avalonia.Animation/AnimationsEngine`1.cs | 208 +++++++++---------- src/Avalonia.Animation/Animator`1.cs | 11 +- src/Avalonia.Animation/TransitionsEngine.cs | 30 ++- 5 files changed, 123 insertions(+), 153 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 4176bf01e5..3e030bf765 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -14,15 +14,7 @@ namespace Avalonia.Animation /// Base class for control which can have property transitions. /// public class Animatable : AvaloniaObject - { - /// - /// Initializes this object. - /// - public Animatable() - { - Transitions = new Transitions(); - } - + { /// /// Defines the property. /// @@ -42,27 +34,25 @@ namespace Avalonia.Animation { get { return _playState; } set { SetAndRaise(PlayStateProperty, ref _playState, value); } - } - /// /// Defines the property. /// - public static readonly DirectProperty> TransitionsProperty = - AvaloniaProperty.RegisterDirect>( + public static readonly DirectProperty TransitionsProperty = + AvaloniaProperty.RegisterDirect( nameof(Transitions), o => o.Transitions, (o, v) => o.Transitions = v); - private IEnumerable _transitions = new AvaloniaList(); + private Transitions _transitions; /// /// Gets or sets the property transitions for the control. /// - public IEnumerable Transitions + public Transitions Transitions { - get { return _transitions; } + get { return _transitions ?? (_transitions = new Transitions()); } set { SetAndRaise(TransitionsProperty, ref _transitions, value); } } @@ -83,6 +73,5 @@ namespace Avalonia.Animation } } } - } -} +} \ No newline at end of file diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index e23caf95f1..2c359ecac3 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -22,8 +22,6 @@ namespace Avalonia.Animation /// public static PlayState GlobalPlayState { get; set; } = PlayState.Run; - public AvaloniaList _animators { get; set; } = new AvaloniaList(); - /// /// Gets or sets the active time of this animation. /// diff --git a/src/Avalonia.Animation/AnimationsEngine`1.cs b/src/Avalonia.Animation/AnimationsEngine`1.cs index cccb3098c0..156056d765 100644 --- a/src/Avalonia.Animation/AnimationsEngine`1.cs +++ b/src/Avalonia.Animation/AnimationsEngine`1.cs @@ -1,7 +1,9 @@ using System; using System.Linq; +using System.Reactive.Linq; using Avalonia.Animation.Utils; using Avalonia.Data; +using Avalonia.Reactive; namespace Avalonia.Animation { @@ -9,182 +11,184 @@ namespace Avalonia.Animation /// Handles interpolatoin and time-related functions /// for keyframe animations. /// - internal class AnimationsEngine : IObservable, IDisposable + internal class AnimationsEngine : SingleSubscriberObservableBase { - T lastInterpValue; - T firstKFValue; - - private long repeatCount; - private double currentIteration; - - private bool isLooping; - private bool gotFirstKFValue; - private bool gotFirstFrameCount; - private bool iterationDelay; - - private FillMode fillMode; - private PlaybackDirection animationDirection; - private Animator parent; - private Animatable targetControl; - private T neutralValue; - private double speedRatio; - internal bool unsubscribe; - private bool isDisposed; - - private TimeSpan delay; - private TimeSpan duration; - private TimeSpan firstFrameCount; - private TimeSpan internalClock; - private TimeSpan? previousClock; - - private Easings.Easing easeFunc; - private IObserver targetObserver; - private readonly Action onCompleteAction; - - public AnimationsEngine(Animation animation, Animatable control, Animator animator, Action OnComplete) + private T _lastInterpValue; + private T _firstKFValue; + private long _repeatCount; + private double _currentIteration; + private bool _isLooping; + private bool _gotFirstKFValue; + private bool _gotFirstFrameCount; + private bool _iterationDelay; + private FillMode _fillMode; + private PlaybackDirection _animationDirection; + private Animator _parent; + private Animatable _targetControl; + private T _neutralValue; + private double _speedRatio; + private TimeSpan _delay; + private TimeSpan _duration; + private TimeSpan _firstFrameCount; + private TimeSpan _internalClock; + private TimeSpan? _previousClock; + private Easings.Easing _easeFunc; + private Action _onCompleteAction; + private Func _interpolator; + private IDisposable _timerSubscription; + + public AnimationsEngine(Animation animation, Animatable control, Animator animator, Action OnComplete, Func Interpolator) { if (animation.SpeedRatio <= 0 || DoubleUtils.AboutEqual(animation.SpeedRatio, 0)) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); if (animation.Duration.TotalSeconds <= 0 || DoubleUtils.AboutEqual(animation.Duration.TotalSeconds, 0)) throw new InvalidOperationException("Duration cannot be negative or zero."); - - parent = animator; - easeFunc = animation.Easing; - targetControl = control; - neutralValue = (T)targetControl.GetValue(parent.Property); - speedRatio = animation.SpeedRatio; + _parent = animator; + _easeFunc = animation.Easing; + _targetControl = control; + _neutralValue = (T)_targetControl.GetValue(_parent.Property); + + _speedRatio = animation.SpeedRatio; - delay = animation.Delay; - duration = animation.Duration; - iterationDelay = animation.DelayBetweenIterations; + _delay = animation.Delay; + _duration = animation.Duration; + _iterationDelay = animation.DelayBetweenIterations; switch (animation.RepeatCount.RepeatType) { case RepeatType.None: - repeatCount = 1; + _repeatCount = 1; break; case RepeatType.Loop: - isLooping = true; + _isLooping = true; break; case RepeatType.Repeat: - repeatCount = (long)animation.RepeatCount.Value; + _repeatCount = (long)animation.RepeatCount.Value; break; } - animationDirection = animation.PlaybackDirection; - fillMode = animation.FillMode; - onCompleteAction = OnComplete; + _animationDirection = animation.PlaybackDirection; + _fillMode = animation.FillMode; + _onCompleteAction = OnComplete; + _interpolator = Interpolator; + } + + protected override void Unsubscribed() + { + _timerSubscription?.Dispose(); } - public void Step(TimeSpan frameTick, Func Interpolator) + protected override void Subscribed() + { + _timerSubscription = Timing.AnimationsTimer + .Subscribe(p => this.Step(p)); + } + + public void Step(TimeSpan frameTick) { try { - InternalStep(frameTick, Interpolator); + InternalStep(frameTick); } catch (Exception e) { - targetObserver?.OnError(e); + PublishError(e); } } private void DoComplete() { - if (fillMode == FillMode.Forward || fillMode == FillMode.Both) - targetControl.SetValue(parent.Property, lastInterpValue, BindingPriority.LocalValue); + if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) + _targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue); - targetObserver.OnCompleted(); - onCompleteAction?.Invoke(); - Dispose(); + _onCompleteAction?.Invoke(); + PublishCompleted(); } private void DoDelay() { - if (fillMode == FillMode.Backward || fillMode == FillMode.Both) - if (currentIteration == 0) - targetObserver.OnNext(firstKFValue); + if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both) + if (_currentIteration == 0) + PublishNext(_firstKFValue); else - targetObserver.OnNext(lastInterpValue); + PublishNext(_lastInterpValue); } private void DoPlayStatesAndTime(TimeSpan systemTime) { - if (Animation.GlobalPlayState == PlayState.Stop || targetControl.PlayState == PlayState.Stop) + if (Animation.GlobalPlayState == PlayState.Stop || _targetControl.PlayState == PlayState.Stop) DoComplete(); - if (!previousClock.HasValue) + if (!_previousClock.HasValue) { - previousClock = systemTime; - internalClock = TimeSpan.Zero; + _previousClock = systemTime; + _internalClock = TimeSpan.Zero; } else { - if (Animation.GlobalPlayState == PlayState.Pause || targetControl.PlayState == PlayState.Pause) + if (Animation.GlobalPlayState == PlayState.Pause || _targetControl.PlayState == PlayState.Pause) { - previousClock = systemTime; + _previousClock = systemTime; return; } - var delta = systemTime - previousClock; - internalClock += delta.Value; - previousClock = systemTime; + var delta = systemTime - _previousClock; + _internalClock += delta.Value; + _previousClock = systemTime; } - if (!gotFirstKFValue) + if (!_gotFirstKFValue) { - firstKFValue = (T)parent.First().Value; - gotFirstKFValue = true; + _firstKFValue = (T)_parent.First().Value; + _gotFirstKFValue = true; } - if (!gotFirstFrameCount) + if (!_gotFirstFrameCount) { - firstFrameCount = internalClock; - gotFirstFrameCount = true; + _firstFrameCount = _internalClock; + _gotFirstFrameCount = true; } } - private void InternalStep(TimeSpan systemTime, Func Interpolator) + private void InternalStep(TimeSpan systemTime) { DoPlayStatesAndTime(systemTime); - - if (isDisposed) - throw new InvalidProgramException("This KeyFrames Animation is already disposed."); - - var time = internalClock - firstFrameCount; - var delayEndpoint = delay; - var iterationEndpoint = delayEndpoint + duration; + + var time = _internalClock - _firstFrameCount; + var delayEndpoint = _delay; + var iterationEndpoint = delayEndpoint + _duration; //determine if time is currently in the first iteration. if (time >= TimeSpan.Zero & time <= iterationEndpoint) { - currentIteration = 1; + _currentIteration = 1; } else if (time > iterationEndpoint) { //Subtract first iteration to properly get the subsequent iteration time time -= iterationEndpoint; - if (!iterationDelay & delayEndpoint > TimeSpan.Zero) + if (!_iterationDelay & delayEndpoint > TimeSpan.Zero) { delayEndpoint = TimeSpan.Zero; - iterationEndpoint = duration; + iterationEndpoint = _duration; } //Calculate the current iteration number - currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2; + _currentIteration = (int)Math.Floor((double)time.Ticks / iterationEndpoint.Ticks) + 2; } else { - previousClock = systemTime; + _previousClock = systemTime; return; } time = TimeSpan.FromTicks(time.Ticks % iterationEndpoint.Ticks); - if (!isLooping) + if (!_isLooping) { - if (currentIteration > repeatCount) + if (_currentIteration > _repeatCount) DoComplete(); if (time > iterationEndpoint) @@ -192,10 +196,10 @@ namespace Avalonia.Animation } // Determine if the current iteration should have its normalized time inverted. - bool isCurIterReverse = animationDirection == PlaybackDirection.Normal ? false : - animationDirection == PlaybackDirection.Alternate ? (currentIteration % 2 == 0) ? false : true : - animationDirection == PlaybackDirection.AlternateReverse ? (currentIteration % 2 == 0) ? true : false : - animationDirection == PlaybackDirection.Reverse ? true : false; + bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false : + _animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true : + _animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false : + _animationDirection == PlaybackDirection.Reverse ? true : false; if (delayEndpoint > TimeSpan.Zero & time < delayEndpoint) { @@ -214,23 +218,11 @@ namespace Avalonia.Animation interpVal = 1 - interpVal; // Ease and interpolate - var easedTime = easeFunc.Ease(interpVal); - lastInterpValue = Interpolator(easedTime, neutralValue); + var easedTime = _easeFunc.Ease(interpVal); + _lastInterpValue = _interpolator(easedTime, _neutralValue); - targetObserver.OnNext(lastInterpValue); + PublishNext(_lastInterpValue); } } - - public IDisposable Subscribe(IObserver observer) - { - targetObserver = observer; - return this; - } - - public void Dispose() - { - unsubscribe = true; - isDisposed = true; - } } } \ No newline at end of file diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 051b1e50cf..77d761ce4c 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -32,12 +32,12 @@ namespace Avalonia.Animation } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IObservable Match, Action onComplete) + public virtual IDisposable Apply(Animation animation, Animatable control, IObservable match, Action onComplete) { if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); - return Match + return match .Where(p => p) .Subscribe(_ => { @@ -103,12 +103,7 @@ namespace Avalonia.Animation /// private IDisposable RunKeyFrames(Animation animation, Animatable control, Action onComplete) { - var stateMachine = new AnimationsEngine(animation, control, this, onComplete); - - Timing.AnimationsTimer - .TakeWhile(_ => !stateMachine.unsubscribe) - .Subscribe(p => stateMachine.Step(p, DoInterpolation)); - + var stateMachine = new AnimationsEngine(animation, control, this, onComplete, DoInterpolation); return control.Bind((AvaloniaProperty)Property, stateMachine, BindingPriority.Animation); } diff --git a/src/Avalonia.Animation/TransitionsEngine.cs b/src/Avalonia.Animation/TransitionsEngine.cs index 4d52b2bd48..34863e4146 100644 --- a/src/Avalonia.Animation/TransitionsEngine.cs +++ b/src/Avalonia.Animation/TransitionsEngine.cs @@ -6,12 +6,15 @@ using System; using System.Reactive.Linq; using Avalonia.Animation.Easings; using Avalonia.Animation.Utils; +using Avalonia.Reactive; namespace Avalonia.Animation { - public class TransitionsEngine : IObservable, IDisposable + /// + /// Handles the timing and lifetime of a . + /// + public class TransitionsEngine : SingleSubscriberObservableBase { - private IObserver observer; private IDisposable timerSubscription; private readonly TimeSpan startTime; private readonly TimeSpan duration; @@ -20,10 +23,6 @@ namespace Avalonia.Animation { startTime = Timing.GetTickCount(); duration = Duration; - - timerSubscription = Timing - .AnimationsTimer - .Subscribe(t => TimerTick(t)); } private void TimerTick(TimeSpan t) @@ -33,26 +32,23 @@ namespace Avalonia.Animation if (interpVal > 1d || interpVal < 0d) { - this.Dispose(); + PublishCompleted(); return; } - observer?.OnNext(interpVal); + PublishNext(interpVal); } - - public void Dispose() + + protected override void Unsubscribed() { timerSubscription?.Dispose(); - observer?.OnCompleted(); } - public IDisposable Subscribe(IObserver Observer) + protected override void Subscribed() { - if (Observer is null) - throw new InvalidProgramException("Can only set the subscription once."); - - observer = Observer; - return this; + timerSubscription = Timing + .AnimationsTimer + .Subscribe(t => TimerTick(t)); } } } \ No newline at end of file From 98ee428d6f4c31e5e7bfa0c2cda49a11a4a86459 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 14:05:56 +0200 Subject: [PATCH 024/512] Merge fix --- .../SwapChainRenderTarget.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs index 000ce23cd2..ffa0f5c8bc 100644 --- a/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs @@ -1,19 +1,13 @@ -using Avalonia.Direct2D1.Media; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; using SharpDX.DXGI; -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Device = SharpDX.Direct2D1.Device; -using Factory = SharpDX.Direct2D1.Factory; -using Factory2 = SharpDX.DXGI.Factory2; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; - -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using DeviceContext = SharpDX.Direct2D1.DeviceContext; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; namespace Avalonia.Direct2D1 { @@ -109,9 +103,9 @@ namespace Avalonia.Direct2D1 _deviceContext, dxgiBackBuffer, new BitmapProperties1( - new PixelFormat + new SharpDX.Direct2D1.PixelFormat { - AlphaMode = AlphaMode.Premultiplied, + AlphaMode = SharpDX.Direct2D1.AlphaMode.Premultiplied, Format = Format.B8G8R8A8_UNorm }, _savedSize.Width, From 45ca6af21cdd15031228f4c5bbbe8c9876f606ec Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Sep 2018 21:29:45 +0800 Subject: [PATCH 025/512] Remove Epsilon equality checking since the animations dont require such precision. --- src/Avalonia.Animation/AnimationsEngine`1.cs | 4 ++-- src/Avalonia.Animation/Animator`1.cs | 8 ++++---- src/Avalonia.Animation/Utils/DoubleUtils.cs | 16 ---------------- src/Avalonia.Animation/Utils/EasingUtils.cs | 2 +- 4 files changed, 7 insertions(+), 23 deletions(-) delete mode 100644 src/Avalonia.Animation/Utils/DoubleUtils.cs diff --git a/src/Avalonia.Animation/AnimationsEngine`1.cs b/src/Avalonia.Animation/AnimationsEngine`1.cs index 156056d765..e724fe52d5 100644 --- a/src/Avalonia.Animation/AnimationsEngine`1.cs +++ b/src/Avalonia.Animation/AnimationsEngine`1.cs @@ -39,10 +39,10 @@ namespace Avalonia.Animation public AnimationsEngine(Animation animation, Animatable control, Animator animator, Action OnComplete, Func Interpolator) { - if (animation.SpeedRatio <= 0 || DoubleUtils.AboutEqual(animation.SpeedRatio, 0)) + if (animation.SpeedRatio <= 0) throw new InvalidOperationException("Speed ratio cannot be negative or zero."); - if (animation.Duration.TotalSeconds <= 0 || DoubleUtils.AboutEqual(animation.Duration.TotalSeconds, 0)) + if (animation.Duration.TotalSeconds <= 0) throw new InvalidOperationException("Duration cannot be negative or zero."); _parent = animator; diff --git a/src/Avalonia.Animation/Animator`1.cs b/src/Avalonia.Animation/Animator`1.cs index 77d761ce4c..2c95571dc8 100644 --- a/src/Avalonia.Animation/Animator`1.cs +++ b/src/Avalonia.Animation/Animator`1.cs @@ -59,12 +59,12 @@ namespace Avalonia.Animation int kvCount = _convertedKeyframes.Count; if (kvCount > 2) { - if (DoubleUtils.AboutEqual(t, 0.0) || t < 0.0) + if (t <= 0.0) { firstCue = _convertedKeyframes[0]; lastCue = _convertedKeyframes[1]; } - else if (DoubleUtils.AboutEqual(t, 1.0) || t > 1.0) + else if (t >= 1.0) { firstCue = _convertedKeyframes[_convertedKeyframes.Count - 2]; lastCue = _convertedKeyframes[_convertedKeyframes.Count - 1]; @@ -143,11 +143,11 @@ namespace Avalonia.Animation // Check if there's start and end keyframes. foreach (var frame in _convertedKeyframes) { - if (DoubleUtils.AboutEqual(frame.Cue.CueValue, 0.0)) + if (frame.Cue.CueValue == 0.0d) { hasStartKey = true; } - else if (DoubleUtils.AboutEqual(frame.Cue.CueValue, 1.0)) + else if (frame.Cue.CueValue == 1.0d) { hasEndKey = true; } diff --git a/src/Avalonia.Animation/Utils/DoubleUtils.cs b/src/Avalonia.Animation/Utils/DoubleUtils.cs deleted file mode 100644 index d2e74376a3..0000000000 --- a/src/Avalonia.Animation/Utils/DoubleUtils.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; - -namespace Avalonia.Animation.Utils -{ - internal static class DoubleUtils - { - internal static bool AboutEqual(double x, double y) - { - double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15; - return Math.Abs(x - y) <= epsilon; - } - } -} diff --git a/src/Avalonia.Animation/Utils/EasingUtils.cs b/src/Avalonia.Animation/Utils/EasingUtils.cs index d07ec3cdf4..1a7688cace 100644 --- a/src/Avalonia.Animation/Utils/EasingUtils.cs +++ b/src/Avalonia.Animation/Utils/EasingUtils.cs @@ -13,6 +13,6 @@ namespace Avalonia.Animation.Utils /// /// Half of /// - internal static double HALFPI = Math.PI / 2d; + internal const double HALFPI = Math.PI / 2d; } } From 024e0da1da398b765422344cbb768f1efef4cfe9 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 6 Sep 2018 15:47:02 +0200 Subject: [PATCH 026/512] Direct3DInterop fixes --- .../Direct3DInteropSample/MainWindow.cs | 276 ++++++++++-------- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 14 +- .../Media/Imaging/D2DBitmapImpl.cs | 29 +- .../Imaging/D2DRenderTargetBitmapImpl.cs | 5 +- .../Media/Imaging/WriteableWicBitmapImpl.cs | 5 +- 5 files changed, 176 insertions(+), 153 deletions(-) diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 19c31a3af1..065f1a285a 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -1,81 +1,83 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + using Avalonia; using Avalonia.Controls; +using Avalonia.Direct2D1; using Avalonia.Direct2D1.Media; using Avalonia.Markup.Xaml; using Avalonia.Platform; using Avalonia.Rendering; + using SharpDX; using SharpDX.D3DCompiler; using SharpDX.Direct2D1; using SharpDX.Direct3D; using SharpDX.Direct3D11; using SharpDX.DXGI; -using SharpDX.WIC; -using SharpDX.Mathematics; + using AlphaMode = SharpDX.Direct2D1.AlphaMode; using Buffer = SharpDX.Direct3D11.Buffer; -using DeviceContext = SharpDX.Direct3D11.DeviceContext; -using Factory1 = SharpDX.DXGI.Factory1; +using DeviceContext = SharpDX.Direct2D1.DeviceContext; +using Factory2 = SharpDX.DXGI.Factory2; using InputElement = SharpDX.Direct3D11.InputElement; using Matrix = SharpDX.Matrix; using PixelFormat = SharpDX.Direct2D1.PixelFormat; +using Resource = SharpDX.Direct3D11.Resource; namespace Direct3DInteropSample { - class MainWindow : Window + public class MainWindow : Window { - private SharpDX.Direct3D11.Device _d3dDevice; - private SharpDX.DXGI.Device _dxgiDevice; - Texture2D backBuffer = null; - RenderTargetView renderView = null; - Texture2D depthBuffer = null; - DepthStencilView depthView = null; + Texture2D _backBuffer; + RenderTargetView _renderView; + Texture2D _depthBuffer; + DepthStencilView _depthView; private readonly SwapChain _swapChain; - private SwapChainDescription _desc; + private SwapChainDescription1 _desc; private Matrix _proj = Matrix.Identity; - private Matrix _view; + private readonly Matrix _view; private Buffer _contantBuffer; - private SharpDX.Direct2D1.Device _d2dDevice; - private SharpDX.Direct2D1.DeviceContext _d2dContext; - private RenderTarget _d2dRenderTarget; - private MainWindowViewModel _model; + private DeviceContext _deviceContext; + private readonly MainWindowViewModel _model; public MainWindow() { - _dxgiDevice = AvaloniaLocator.Current.GetService(); - _d3dDevice = _dxgiDevice.QueryInterface(); - _d2dDevice = AvaloniaLocator.Current.GetService(); DataContext = _model = new MainWindowViewModel(); - _desc = new SwapChainDescription() + + _desc = new SwapChainDescription1() { BufferCount = 1, - ModeDescription = - new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, - new Rational(60, 1), Format.R8G8B8A8_UNorm), - IsWindowed = true, - OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero, + Width = (int)ClientSize.Width, + Height = (int)ClientSize.Height, + Format = Format.R8G8B8A8_UNorm, SampleDescription = new SampleDescription(1, 0), SwapEffect = SwapEffect.Discard, Usage = Usage.RenderTargetOutput }; - _swapChain = new SwapChain(new Factory1(), _d3dDevice, _desc); + using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent()) + { + _swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc); + } - _d2dContext = new SharpDX.Direct2D1.DeviceContext(_d2dDevice, DeviceContextOptions.None) + _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) { DotsPerInch = new Size2F(96, 96) }; CreateMesh(); + _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY); + this.GetObservable(ClientSizeProperty).Subscribe(Resize); + Resize(ClientSize); + AvaloniaXamlLoader.Load(this); + Background = Avalonia.Media.Brushes.Transparent; } @@ -83,29 +85,32 @@ namespace Direct3DInteropSample protected override void HandlePaint(Rect rect) { var viewProj = Matrix.Multiply(_view, _proj); - var context = _d3dDevice.ImmediateContext; + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; + // Clear views - context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0); - context.ClearRenderTargetView(renderView, Color.White); + context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0); + context.ClearRenderTargetView(_renderView, Color.White); // Update WorldViewProj Matrix - var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) * - Matrix.RotationZ((float) _model.RotationZ) - * Matrix.Scaling((float) _model.Zoom) * viewProj; + var worldViewProj = Matrix.RotationX((float)_model.RotationX) * Matrix.RotationY((float)_model.RotationY) + * Matrix.RotationZ((float)_model.RotationZ) + * Matrix.Scaling((float)_model.Zoom) + * viewProj; worldViewProj.Transpose(); context.UpdateSubresource(ref worldViewProj, _contantBuffer); // Draw the cube context.Draw(36, 0); base.HandlePaint(rect); + // Present! _swapChain.Present(0, PresentFlags.None); } - - void CreateMesh() + private void CreateMesh() { - var device = _d3dDevice; + var device = Direct2D1Platform.Direct3D11Device; + // Compile Vertex and Pixel shaders var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0"); var vertexShader = new VertexShader(device, vertexShaderByteCode); @@ -114,63 +119,72 @@ namespace Direct3DInteropSample var pixelShader = new PixelShader(device, pixelShaderByteCode); var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode); + + var inputElements = new[] + { + new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), + new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) + }; + // Layout from VertexShader input signature - var layout = new InputLayout(device, signature, new[] - { - new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), - new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) - }); - + var layout = new InputLayout( + device, + signature, + inputElements); + // Instantiate Vertex buiffer from vertex data - var vertices = Buffer.Create(device, BindFlags.VertexBuffer, new[] - { - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - - new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom - new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f,-1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f,-1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - }); + var vertices = Buffer.Create( + device, + BindFlags.VertexBuffer, + new[] + { + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), + + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left + new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), + + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), + }); // Create Constant Buffer _contantBuffer = new Buffer(device, Utilities.SizeOf(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0); - var context = _d3dDevice.ImmediateContext; + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; // Prepare All the stages context.InputAssembler.InputLayout = layout; @@ -181,63 +195,73 @@ namespace Direct3DInteropSample context.PixelShader.Set(pixelShader); } - void Resize(Size size) + private void Resize(Size size) { - Utilities.Dispose(ref _d2dRenderTarget); - Utilities.Dispose(ref backBuffer); - Utilities.Dispose(ref renderView); - Utilities.Dispose(ref depthBuffer); - Utilities.Dispose(ref depthView); - var context = _d3dDevice.ImmediateContext; + Utilities.Dispose(ref _deviceContext); + Utilities.Dispose(ref _backBuffer); + Utilities.Dispose(ref _renderView); + Utilities.Dispose(ref _depthBuffer); + Utilities.Dispose(ref _depthView); + var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; + // Resize the backbuffer - _swapChain.ResizeBuffers(_desc.BufferCount, (int)size.Width, (int)size.Height, Format.Unknown, SwapChainFlags.None); + _swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); // Get the backbuffer from the swapchain - backBuffer = Texture2D.FromSwapChain(_swapChain, 0); + _backBuffer = Resource.FromSwapChain(_swapChain, 0); // Renderview on the backbuffer - renderView = new RenderTargetView(_d3dDevice, backBuffer); + _renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _backBuffer); // Create the depth buffer - depthBuffer = new Texture2D(_d3dDevice, new Texture2DDescription() - { - Format = Format.D32_Float_S8X24_UInt, - ArraySize = 1, - MipLevels = 1, - Width = (int)size.Width, - Height = (int)size.Height, - SampleDescription = new SampleDescription(1, 0), - Usage = ResourceUsage.Default, - BindFlags = BindFlags.DepthStencil, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None - }); + _depthBuffer = new Texture2D( + Direct2D1Platform.Direct3D11Device, + new Texture2DDescription() + { + Format = Format.D32_Float_S8X24_UInt, + ArraySize = 1, + MipLevels = 1, + Width = (int)size.Width, + Height = (int)size.Height, + SampleDescription = new SampleDescription(1, 0), + Usage = ResourceUsage.Default, + BindFlags = BindFlags.DepthStencil, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None + }); // Create the depth buffer view - depthView = new DepthStencilView(_d3dDevice, depthBuffer); + _depthView = new DepthStencilView(Direct2D1Platform.Direct3D11Device, _depthBuffer); // Setup targets and viewport for rendering context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f)); - context.OutputMerger.SetTargets(depthView, renderView); + context.OutputMerger.SetTargets(_depthView, _renderView); // Setup new projection matrix with correct aspect ratio _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f); using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) { - _d2dRenderTarget = new RenderTarget(AvaloniaLocator.Current.GetService() - , dxgiBackBuffer, new RenderTargetProperties + var renderTarget = new SharpDX.Direct2D1.RenderTarget( + Direct2D1Platform.Direct2D1Factory, + dxgiBackBuffer, + new RenderTargetProperties { DpiX = 96, DpiY = 96, Type = RenderTargetType.Default, - PixelFormat = new PixelFormat(Format.Unknown, AlphaMode.Premultiplied) + PixelFormat = new PixelFormat( + Format.Unknown, + AlphaMode.Premultiplied) }); - } + _deviceContext = renderTarget.QueryInterface(); + + renderTarget.Dispose(); + } } - class D3DRenderTarget: IRenderTarget + private class D3DRenderTarget : IRenderTarget { private readonly MainWindow _window; @@ -245,16 +269,14 @@ namespace Direct3DInteropSample { _window = window; } + public void Dispose() { - } public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) { - return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget, - AvaloniaLocator.Current.GetService(), - AvaloniaLocator.Current.GetService()); + return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 437bae6fd1..38c49924bc 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -29,6 +29,8 @@ namespace Avalonia.Direct2D1 { private static readonly Direct2D1Platform s_instance = new Direct2D1Platform(); + public static SharpDX.Direct3D11.Device Direct3D11Device { get; private set; } + public static SharpDX.Direct2D1.Factory1 Direct2D1Factory { get; private set; } public static SharpDX.Direct2D1.Device1 Direct2D1Device { get; private set; } @@ -88,14 +90,12 @@ namespace Avalonia.Direct2D1 SharpDX.Direct3D.FeatureLevel.Level_9_1, }; - using (var d3dDevice = new SharpDX.Direct3D11.Device( + Direct3D11Device = new SharpDX.Direct3D11.Device( SharpDX.Direct3D.DriverType.Hardware, - SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | - SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, - featureLevels)) - { - DxgiDevice = d3dDevice.QueryInterface(); - } + SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport, + featureLevels); + + DxgiDevice = Direct3D11Device.QueryInterface(); using (var device = new SharpDX.Direct2D1.Device(Direct2D1Factory, DxgiDevice)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 5711a79c2e..b65e32bbbb 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -1,19 +1,20 @@ using System; using System.IO; using SharpDX.Direct2D1; -using ImageParameters = SharpDX.WIC.ImageParameters; -using ImagingFactory2 = SharpDX.WIC.ImagingFactory2; -using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder; -using WICFactory = SharpDX.WIC.ImagingFactory; namespace Avalonia.Direct2D1.Media { + using SharpDX.WIC; + + using Bitmap = SharpDX.Direct2D1.Bitmap; + using PixelFormat = SharpDX.Direct2D1.PixelFormat; + /// /// A Direct2D Bitmap implementation that uses a GPU memory bitmap as its image. /// public class D2DBitmapImpl : BitmapImpl { - private Bitmap _direct2DBitmap; + private readonly Bitmap _direct2DBitmap; /// /// Initialize a new instance of the class @@ -29,7 +30,7 @@ namespace Avalonia.Direct2D1.Media { _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); } - + public override int PixelWidth => _direct2DBitmap.PixelSize.Width; public override int PixelHeight => _direct2DBitmap.PixelSize.Height; @@ -47,18 +48,12 @@ namespace Avalonia.Direct2D1.Media public override void Save(Stream stream) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) - using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder)) - //ToDo: Not supported under Windows 7! - using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)Direct2D1Platform.ImagingFactory, null)) + using (var frame = new BitmapFrameEncode(encoder)) + using (var bitmapSource = _direct2DBitmap.QueryInterface()) { - var parameters = new ImageParameters( - new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied), - _direct2DBitmap.DotsPerInch.Width, - _direct2DBitmap.DotsPerInch.Height, - 0, 0, PixelWidth, PixelHeight); - - imageEncoder.WriteFrame(_direct2DBitmap, frameEncode, parameters); - frameEncode.Commit(); + frame.Initialize(); + frame.WriteSource(bitmapSource); + frame.Commit(); encoder.Commit(); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 7ea303345a..3646d9e9e1 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -1,4 +1,7 @@ -using Avalonia.Platform; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Platform; using Avalonia.Rendering; using SharpDX; using SharpDX.Direct2D1; diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs index 075fef5ab2..5ca78ef278 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; using Avalonia.Platform; using SharpDX.WIC; using PixelFormat = Avalonia.Platform.PixelFormat; From 06a4fe7312b7b7759902b855c90616797cae683a Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 6 Sep 2018 23:15:56 +0800 Subject: [PATCH 027/512] Minor fix on TransitionsEngine --- src/Avalonia.Animation/TransitionsEngine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Animation/TransitionsEngine.cs b/src/Avalonia.Animation/TransitionsEngine.cs index 34863e4146..81b5b28820 100644 --- a/src/Avalonia.Animation/TransitionsEngine.cs +++ b/src/Avalonia.Animation/TransitionsEngine.cs @@ -16,12 +16,11 @@ namespace Avalonia.Animation public class TransitionsEngine : SingleSubscriberObservableBase { private IDisposable timerSubscription; - private readonly TimeSpan startTime; - private readonly TimeSpan duration; + private TimeSpan startTime; + private TimeSpan duration; public TransitionsEngine(TimeSpan Duration) { - startTime = Timing.GetTickCount(); duration = Duration; } @@ -46,6 +45,7 @@ namespace Avalonia.Animation protected override void Subscribed() { + startTime = Timing.GetTickCount(); timerSubscription = Timing .AnimationsTimer .Subscribe(t => TimerTick(t)); From a0d2d7f70b4cccf2984c317a48b66040f003521a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 7 Sep 2018 01:48:57 +0200 Subject: [PATCH 028/512] Added failing test for #1865. And similar test for Grid. --- .../DockPanelTests.cs | 24 +++++++++++++++++ .../Avalonia.Controls.UnitTests/GridTests.cs | 26 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs index 3de67839a7..59f047abae 100644 --- a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs @@ -58,5 +58,29 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(50, 350, 500, 50), target.Children[3].Bounds); Assert.Equal(new Rect(50, 50, 500, 300), target.Children[4].Bounds); } + + [Fact] + public void Changing_Child_Dock_Invalidates_Measure() + { + Border child; + var target = new DockPanel + { + Children = + { + (child = new Border + { + [DockPanel.DockProperty] = Dock.Left, + }), + } + }; + + target.Measure(Size.Infinity); + target.Arrange(new Rect(target.DesiredSize)); + Assert.True(target.IsMeasureValid); + + DockPanel.SetDock(child, Dock.Right); + + Assert.False(target.IsMeasureValid); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index 4c79b7775b..5799cb91c4 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -154,5 +154,31 @@ namespace Avalonia.Controls.UnitTests GridAssert.ChildrenHeight(rowGrid, 200, 300, 300); GridAssert.ChildrenWidth(columnGrid, 200, 300, 300); } + + [Fact] + public void Changing_Child_Column_Invalidates_Measure() + { + Border child; + var target = new Grid + { + ColumnDefinitions = new ColumnDefinitions("*,*"), + Children = + { + (child = new Border + { + [Grid.ColumnProperty] = 0, + }), + } + }; + + target.Measure(Size.Infinity); + target.Arrange(new Rect(target.DesiredSize)); + Assert.True(target.IsMeasureValid); + + Grid.SetColumn(child, 1); + + Assert.False(target.IsMeasureValid); + } + } } From a6a80c205ce351cfa8d61905b4b69cec050a1e62 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 7 Sep 2018 01:50:30 +0200 Subject: [PATCH 029/512] Make attached panel properties invalidate parent layout. Fixes #1865. --- src/Avalonia.Controls/Canvas.cs | 26 +----------------- src/Avalonia.Controls/DockPanel.cs | 4 +-- src/Avalonia.Controls/Grid.cs | 5 ++++ src/Avalonia.Controls/Panel.cs | 42 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/Canvas.cs b/src/Avalonia.Controls/Canvas.cs index 5c9a97cb27..e16a0b074b 100644 --- a/src/Avalonia.Controls/Canvas.cs +++ b/src/Avalonia.Controls/Canvas.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls static Canvas() { ClipToBoundsProperty.OverrideDefaultValue(false); - AffectsCanvasArrange(LeftProperty, TopProperty, RightProperty, BottomProperty); + AffectsParentArrange(LeftProperty, TopProperty, RightProperty, BottomProperty); } /// @@ -207,29 +207,5 @@ namespace Avalonia.Controls return finalSize; } - - /// - /// Marks a property on a child as affecting the canvas' arrangement. - /// - /// The properties. - private static void AffectsCanvasArrange(params AvaloniaProperty[] properties) - { - foreach (var property in properties) - { - property.Changed.Subscribe(AffectsCanvasArrangeInvalidate); - } - } - - /// - /// Calls on the parent of the control whose - /// property changed, if that parent is a canvas. - /// - /// The event args. - private static void AffectsCanvasArrangeInvalidate(AvaloniaPropertyChangedEventArgs e) - { - var control = e.Sender as IControl; - var canvas = control?.VisualParent as Canvas; - canvas?.InvalidateArrange(); - } } } diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs index 66e84c1110..e147fe1a52 100644 --- a/src/Avalonia.Controls/DockPanel.cs +++ b/src/Avalonia.Controls/DockPanel.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls /// static DockPanel() { - AffectsArrange(DockProperty); + AffectsParentMeasure(DockProperty); } /// @@ -173,4 +173,4 @@ namespace Avalonia.Controls return arrangeSize; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 5f194bdd71..1a07ccaf7e 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -48,6 +48,11 @@ namespace Avalonia.Controls private RowDefinitions _rowDefinitions; + static Grid() + { + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); + } + /// /// Gets or sets the columns definitions for the grid. /// diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index a2cb013300..c0d211effb 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -72,6 +72,32 @@ namespace Avalonia.Controls base.Render(context); } + /// + /// Marks a property on a child as affecting the parent panel's arrangement. + /// + /// The properties. + protected static void AffectsParentArrange(params AvaloniaProperty[] properties) + where TPanel : class, IPanel + { + foreach (var property in properties) + { + property.Changed.Subscribe(AffectsParentArrangeInvalidate); + } + } + + /// + /// Marks a property on a child as affecting the parent panel's measurement. + /// + /// The properties. + protected static void AffectsParentMeasure(params AvaloniaProperty[] properties) + where TPanel : class, IPanel + { + foreach (var property in properties) + { + property.Changed.Subscribe(AffectsParentMeasureInvalidate); + } + } + /// /// Called when the collection changes. /// @@ -116,5 +142,21 @@ namespace Avalonia.Controls InvalidateMeasure(); } + + private static void AffectsParentArrangeInvalidate(AvaloniaPropertyChangedEventArgs e) + where TPanel : class, IPanel + { + var control = e.Sender as IControl; + var panel = control?.VisualParent as TPanel; + panel?.InvalidateArrange(); + } + + private static void AffectsParentMeasureInvalidate(AvaloniaPropertyChangedEventArgs e) + where TPanel : class, IPanel + { + var control = e.Sender as IControl; + var panel = control?.VisualParent as TPanel; + panel?.InvalidateMeasure(); + } } } From fad2e317ba413b0624de7600ebc1bdeaa02a3272 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 7 Sep 2018 02:13:57 +0200 Subject: [PATCH 030/512] Make AffectsMeasure/Arrange/Render typed. --- src/Avalonia.Controls/Border.cs | 4 ++-- src/Avalonia.Controls/Decorator.cs | 2 +- src/Avalonia.Controls/DrawingPresenter.cs | 6 ++--- src/Avalonia.Controls/Image.cs | 3 +-- .../Presenters/ContentPresenter.cs | 4 ++-- .../Presenters/ScrollContentPresenter.cs | 2 +- .../Presenters/TextPresenter.cs | 2 +- .../Primitives/AccessText.cs | 2 +- src/Avalonia.Controls/Primitives/Track.cs | 2 +- src/Avalonia.Controls/Shapes/Shape.cs | 5 ++--- src/Avalonia.Controls/StackPanel.cs | 4 ++-- src/Avalonia.Controls/TabControl.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 9 ++++---- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Controls/WrapPanel.cs | 2 +- src/Avalonia.Layout/Layoutable.cs | 22 +++++++++++-------- src/Avalonia.Visuals/Visual.cs | 12 +++++----- 17 files changed, 45 insertions(+), 40 deletions(-) diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs index 5f84421c64..8b2a45b090 100644 --- a/src/Avalonia.Controls/Border.cs +++ b/src/Avalonia.Controls/Border.cs @@ -43,8 +43,8 @@ namespace Avalonia.Controls /// static Border() { - AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); - AffectsMeasure(BorderThicknessProperty); + AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); + AffectsMeasure(BorderThicknessProperty); } /// diff --git a/src/Avalonia.Controls/Decorator.cs b/src/Avalonia.Controls/Decorator.cs index 389cf66d34..15651b918e 100644 --- a/src/Avalonia.Controls/Decorator.cs +++ b/src/Avalonia.Controls/Decorator.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls /// static Decorator() { - AffectsMeasure(ChildProperty, PaddingProperty); + AffectsMeasure(ChildProperty, PaddingProperty); ChildProperty.Changed.AddClassHandler(x => x.ChildChanged); } diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs index af3665fabc..34ce598218 100644 --- a/src/Avalonia.Controls/DrawingPresenter.cs +++ b/src/Avalonia.Controls/DrawingPresenter.cs @@ -8,8 +8,8 @@ namespace Avalonia.Controls { static DrawingPresenter() { - AffectsMeasure(DrawingProperty); - AffectsRender(DrawingProperty); + AffectsMeasure(DrawingProperty); + AffectsRender(DrawingProperty); } public static readonly StyledProperty DrawingProperty = @@ -56,4 +56,4 @@ namespace Avalonia.Controls } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index f146e3571c..802b700a07 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -25,8 +25,7 @@ namespace Avalonia.Controls static Image() { - AffectsRender(SourceProperty); - AffectsRender(StretchProperty); + AffectsRender(SourceProperty, StretchProperty); } /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 6badf91367..8d703cfc1c 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -90,8 +90,8 @@ namespace Avalonia.Controls.Presenters /// static ContentPresenter() { - AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); - AffectsMeasure(BorderThicknessProperty, PaddingProperty); + AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); + AffectsMeasure(BorderThicknessProperty, PaddingProperty); ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); ContentTemplateProperty.Changed.AddClassHandler(x => x.ContentChanged); TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 2ef7941b55..c05c1672f8 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -72,7 +72,7 @@ namespace Avalonia.Controls.Presenters { ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); ChildProperty.Changed.AddClassHandler(x => x.ChildChanged); - AffectsArrange(OffsetProperty); + AffectsArrange(OffsetProperty); } /// diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index a30d9bfc48..f73a335de5 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -38,7 +38,7 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender(PasswordCharProperty); + AffectsRender(PasswordCharProperty); } public TextPresenter() diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 32a0efc440..5adc8d2448 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Primitives /// static AccessText() { - AffectsRender(ShowAccessKeyProperty); + AffectsRender(ShowAccessKeyProperty); } /// diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index 648fe5f4b0..8ff3ced770 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives ThumbProperty.Changed.AddClassHandler(x => x.ThumbChanged); IncreaseButtonProperty.Changed.AddClassHandler(x => x.ButtonChanged); DecreaseButtonProperty.Changed.AddClassHandler(x => x.ButtonChanged); - AffectsArrange(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty); + AffectsArrange(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty); } public double Minimum diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 604051ef28..f77c43acd0 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -30,11 +30,10 @@ namespace Avalonia.Controls.Shapes private Geometry _renderedGeometry; bool _calculateTransformOnArrange = false; - static Shape() { - AffectsMeasure(StretchProperty, StrokeThicknessProperty); - AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); + AffectsMeasure(StretchProperty, StrokeThicknessProperty); + AffectsRender(FillProperty, StrokeProperty, StrokeDashArrayProperty); } public Geometry DefiningGeometry diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 645cdbd926..df0c113cc0 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -29,8 +29,8 @@ namespace Avalonia.Controls /// static StackPanel() { - AffectsMeasure(SpacingProperty); - AffectsMeasure(OrientationProperty); + AffectsMeasure(SpacingProperty); + AffectsMeasure(OrientationProperty); } /// diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 70cf8b4e05..3aae256858 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -44,7 +44,7 @@ namespace Avalonia.Controls { SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); FocusableProperty.OverrideDefaultValue(false); - AffectsMeasure(TabStripPlacementProperty); + AffectsMeasure(TabStripPlacementProperty); } /// diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index e91d2e8fa7..541e55625a 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -99,10 +99,11 @@ namespace Avalonia.Controls static TextBlock() { ClipToBoundsProperty.OverrideDefaultValue(true); - AffectsRender(ForegroundProperty); - AffectsRender(FontWeightProperty); - AffectsRender(FontSizeProperty); - AffectsRender(FontStyleProperty); + AffectsRender( + ForegroundProperty, + FontWeightProperty, + FontSizeProperty, + FontStyleProperty); } /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 1161ded25f..5f8eac1fe3 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -59,7 +59,7 @@ namespace Avalonia.Controls /// static TopLevel() { - AffectsMeasure(ClientSizeProperty); + AffectsMeasure(ClientSizeProperty); } /// diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs index 8ee0636124..597734d400 100644 --- a/src/Avalonia.Controls/WrapPanel.cs +++ b/src/Avalonia.Controls/WrapPanel.cs @@ -30,7 +30,7 @@ namespace Avalonia.Controls /// static WrapPanel() { - AffectsMeasure(OrientationProperty); + AffectsMeasure(OrientationProperty); } /// diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 54bdbb5d48..b8b24e6d31 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -140,7 +140,7 @@ namespace Avalonia.Layout /// static Layoutable() { - AffectsMeasure( + AffectsMeasure( IsVisibleProperty, WidthProperty, HeightProperty, @@ -427,11 +427,12 @@ namespace Avalonia.Layout /// After a call to this method in a control's static constructor, any change to the /// property will cause to be called on the element. /// - protected static void AffectsMeasure(params AvaloniaProperty[] properties) + protected static void AffectsMeasure(params AvaloniaProperty[] properties) + where T : class, ILayoutable { foreach (var property in properties) { - property.Changed.Subscribe(AffectsMeasureInvalidate); + property.Changed.Subscribe(AffectsMeasureInvalidate); } } @@ -443,11 +444,12 @@ namespace Avalonia.Layout /// After a call to this method in a control's static constructor, any change to the /// property will cause to be called on the element. /// - protected static void AffectsArrange(params AvaloniaProperty[] properties) + protected static void AffectsArrange(params AvaloniaProperty[] properties) + where T : class, ILayoutable { foreach (var property in properties) { - property.Changed.Subscribe(AffectsArrangeInvalidate); + property.Changed.Subscribe(AffectsArrangeInvalidate); } } @@ -636,9 +638,10 @@ namespace Avalonia.Layout /// Calls on the control on which a property changed. /// /// The event args. - private static void AffectsMeasureInvalidate(AvaloniaPropertyChangedEventArgs e) + private static void AffectsMeasureInvalidate(AvaloniaPropertyChangedEventArgs e) + where T : class, ILayoutable { - ILayoutable control = e.Sender as ILayoutable; + var control = e.Sender as T; control?.InvalidateMeasure(); } @@ -646,9 +649,10 @@ namespace Avalonia.Layout /// Calls on the control on which a property changed. /// /// The event args. - private static void AffectsArrangeInvalidate(AvaloniaPropertyChangedEventArgs e) + private static void AffectsArrangeInvalidate(AvaloniaPropertyChangedEventArgs e) + where T : class, ILayoutable { - ILayoutable control = e.Sender as ILayoutable; + var control = e.Sender as T; control?.InvalidateArrange(); } diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 81e1a93a6f..c2db20306e 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -100,7 +100,7 @@ namespace Avalonia /// static Visual() { - AffectsRender( + AffectsRender( BoundsProperty, ClipProperty, ClipToBoundsProperty, @@ -320,11 +320,12 @@ namespace Avalonia /// on the control which when changed should cause a redraw. This is similar to WPF's /// FrameworkPropertyMetadata.AffectsRender flag. /// - protected static void AffectsRender(params AvaloniaProperty[] properties) + protected static void AffectsRender(params AvaloniaProperty[] properties) + where T : class, IVisual { foreach (var property in properties) { - property.Changed.Subscribe(AffectsRenderInvalidate); + property.Changed.Subscribe(AffectsRenderInvalidate); } } @@ -416,9 +417,10 @@ namespace Avalonia /// Called when a property changes that should invalidate the visual. /// /// The event args. - private static void AffectsRenderInvalidate(AvaloniaPropertyChangedEventArgs e) + private static void AffectsRenderInvalidate(AvaloniaPropertyChangedEventArgs e) + where T : class, IVisual { - (e.Sender as Visual)?.InvalidateVisual(); + (e.Sender as T)?.InvalidateVisual(); } /// From 576cc915731892c2c6f55b928ed0588d5ecac889 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 7 Sep 2018 02:30:00 +0200 Subject: [PATCH 031/512] Make Pseudoclass method typed. --- src/Avalonia.Controls/Button.cs | 2 +- src/Avalonia.Controls/ButtonSpinner.cs | 6 +++--- src/Avalonia.Controls/ContentControl.cs | 4 ++-- src/Avalonia.Controls/Expander.cs | 10 +++++----- src/Avalonia.Controls/Primitives/ScrollBar.cs | 4 ++-- .../Primitives/ToggleButton.cs | 6 +++--- src/Avalonia.Controls/Primitives/Track.cs | 2 ++ src/Avalonia.Controls/ProgressBar.cs | 6 +++--- src/Avalonia.Controls/Slider.cs | 2 ++ src/Avalonia.Input/InputElement.cs | 6 +++--- src/Avalonia.Styling/StyledElement.cs | 20 +++++++++++-------- 11 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index fa69d72d67..24b2af7996 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -80,7 +80,7 @@ namespace Avalonia.Controls FocusableProperty.OverrideDefaultValue(typeof(Button), true); CommandProperty.Changed.Subscribe(CommandChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); - PseudoClass(IsPressedProperty, ":pressed"); + PseudoClass + + + + + + Hello world! + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs new file mode 100644 index 0000000000..749f83c1ab --- /dev/null +++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs @@ -0,0 +1,53 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using System; +using Avalonia.Input; + +namespace ControlCatalog +{ + public class DecoratedWindow : Window + { + public DecoratedWindow() + { + this.InitializeComponent(); + this.AttachDevTools(); + } + + void SetupSide(string name, StandardCursorType cursor, WindowEdge edge) + { + var ctl = this.FindControl(name); + ctl.Cursor = new Cursor(cursor); + ctl.PointerPressed += delegate + { + PlatformImpl?.BeginResizeDrag(edge); + }; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + this.FindControl("TitleBar").PointerPressed += delegate + { + PlatformImpl?.BeginMoveDrag(); + }; + SetupSide("Left", StandardCursorType.LeftSide, WindowEdge.West); + SetupSide("Right", StandardCursorType.RightSide, WindowEdge.East); + SetupSide("Top", StandardCursorType.TopSide, WindowEdge.North); + SetupSide("Bottom", StandardCursorType.BottomSize, WindowEdge.South); + SetupSide("TopLeft", StandardCursorType.TopLeftCorner, WindowEdge.NorthWest); + SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast); + SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest); + SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast); + this.FindControl + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ButtonPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonPage.xaml.cs new file mode 100644 index 0000000000..ebcbeae925 --- /dev/null +++ b/samples/ControlCatalog/Pages/ButtonPage.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class ButtonPage : UserControl + { + public ButtonPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml new file mode 100644 index 0000000000..fba15f6e77 --- /dev/null +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml @@ -0,0 +1,24 @@ + + + + ButtonSpinner + The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element. + + + AllowSpin + ShowButtonSpinner + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs new file mode 100644 index 0000000000..1f753ab3ea --- /dev/null +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs @@ -0,0 +1,54 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class ButtonSpinnerPage : UserControl + { + public ButtonSpinnerPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void OnSpin(object sender, SpinEventArgs e) + { + var spinner = (ButtonSpinner)sender; + var txtBox = (TextBlock)spinner.Content; + + int value = Array.IndexOf(_mountains, txtBox.Text); + if (e.Direction == SpinDirection.Increase) + value++; + else + value--; + + if (value < 0) + value = _mountains.Length - 1; + else if (value >= _mountains.Length) + value = 0; + + txtBox.Text = _mountains[value]; + } + + private readonly string[] _mountains = new[] + { + "Everest", + "K2 (Mount Godwin Austen)", + "Kangchenjunga", + "Lhotse", + "Makalu", + "Cho Oyu", + "Dhaulagiri", + "Manaslu", + "Nanga Parbat", + "Annapurna" + }; + } +} diff --git a/samples/ControlCatalog/Pages/CalendarPage.xaml b/samples/ControlCatalog/Pages/CalendarPage.xaml new file mode 100644 index 0000000000..c47fd766fb --- /dev/null +++ b/samples/ControlCatalog/Pages/CalendarPage.xaml @@ -0,0 +1,47 @@ + + + Calendar + A calendar control for selecting dates + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CalendarPage.xaml.cs b/samples/ControlCatalog/Pages/CalendarPage.xaml.cs new file mode 100644 index 0000000000..e3e9a3444e --- /dev/null +++ b/samples/ControlCatalog/Pages/CalendarPage.xaml.cs @@ -0,0 +1,28 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using System; + +namespace ControlCatalog.Pages +{ + public class CalendarPage : UserControl + { + public CalendarPage() + { + this.InitializeComponent(); + + var today = DateTime.Today; + var cal1 = this.FindControl("DisplayDatesCalendar"); + cal1.DisplayDateStart = today.AddDays(-25); + cal1.DisplayDateEnd = today.AddDays(25); + + var cal2 = this.FindControl("BlackoutDatesCalendar"); + cal2.BlackoutDates.AddDatesInPast(); + cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6))); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml b/samples/ControlCatalog/Pages/CanvasPage.xaml new file mode 100644 index 0000000000..d6c138a4f7 --- /dev/null +++ b/samples/ControlCatalog/Pages/CanvasPage.xaml @@ -0,0 +1,35 @@ + + + Canvas + A panel which lays out its children by explicit coordinates + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/CanvasPage.xaml.cs b/samples/ControlCatalog/Pages/CanvasPage.xaml.cs new file mode 100644 index 0000000000..f43d974c18 --- /dev/null +++ b/samples/ControlCatalog/Pages/CanvasPage.xaml.cs @@ -0,0 +1,18 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class CanvasPage : UserControl + { + public CanvasPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml b/samples/ControlCatalog/Pages/CarouselPage.xaml new file mode 100644 index 0000000000..cf9b13c00c --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml @@ -0,0 +1,41 @@ + + + Carousel + An items control that displays its items as pages that fill the control. + + + + + + + + + + + + + + + + Transition + + None + Slide + Crossfade + + + + + Orientation + + Horizontal + Vertical + + + + + diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs new file mode 100644 index 0000000000..5e24bfb58f --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs @@ -0,0 +1,51 @@ +using System; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class CarouselPage : UserControl + { + private Carousel _carousel; + private Button _left; + private Button _right; + private DropDown _transition; + private DropDown _orientation; + + public CarouselPage() + { + this.InitializeComponent(); + _left.Click += (s, e) => _carousel.Previous(); + _right.Click += (s, e) => _carousel.Next(); + _transition.SelectionChanged += TransitionChanged; + _orientation.SelectionChanged += TransitionChanged; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + _carousel = this.FindControl("carousel"); + _left = this.FindControl + + + + + Modal to window + + + + diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs new file mode 100644 index 0000000000..8b3e810f0a --- /dev/null +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -0,0 +1,46 @@ +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +#pragma warning disable 4014 + +namespace ControlCatalog.Pages +{ + public class DialogsPage : UserControl + { + public DialogsPage() + { + this.InitializeComponent(); + this.FindControl