惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
雷峰网
雷峰网
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Spread Privacy
Spread Privacy
H
Hacker News: Front Page
PCI Perspectives
PCI Perspectives
Webroot Blog
Webroot Blog
罗磊的独立博客
H
Heimdal Security Blog
TaoSecurity Blog
TaoSecurity Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Last Week in AI
Last Week in AI
美团技术团队
Help Net Security
Help Net Security
The Hacker News
The Hacker News
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
The Register - Security
The Register - Security
IT之家
IT之家
WordPress大学
WordPress大学
Jina AI
Jina AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Help Net Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
NISL@THU
NISL@THU
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
Scott Helme
Scott Helme
V
Vulnerabilities – Threatpost
B
Blog
T
Tenable Blog
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
S
Security Affairs
小众软件
小众软件
Hacker News: Ask HN
Hacker News: Ask HN
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
A
Arctic Wolf
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence

博客园 - HotSky

Chrome启用CDP(chrome-devtools-protocol)进行远程操控 WPF 让ScrollViewer支持按住鼠标中键拖拽滚动内容 WPF 让ScrollViewer支持鼠标中键滚动缩放内容 Nodejs使用 nodejs中写sql需要用in时的写法 C#城市最短路径 Sqlite PDF附录A: 内容流操作码 WPF FPS类 生命模拟 C# Sql帮助类,可扩展 WPF WriteableBitmap通过GDI+绘制帮助类 Alpha混合 Bmp读写二值图 WPF支持任意快捷键+鼠标组合的绑定类 WPF阻止窗体被系统缩放,使用显示器DPI WPF DataGrid自动增长序号列 C#访问或修改私有类、函数、变量、属性 WPF一个简单的属性编辑控件
C#Animation
HotSky · 2025-04-02 · via 博客园 - HotSky
Animation
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1.DX
{
    #region EasingFunction
    public interface IEasingFunction
    {
        double Ease(double normalizedTime);
    }
    public class CustomerEasingFunction : IEasingFunction
    {
        private Func<double, double> _func;
        public double Ease(double normalizedTime) => _func.Invoke(normalizedTime);
        public CustomerEasingFunction(Func<double, double> func) {  _func = func; }
    }
    public class LinearEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime;
    }

    public class InSineEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Cos(normalizedTime * Math.PI / 2);
    }

    public class OutSineEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => Math.Sin(normalizedTime * Math.PI / 2);
    }

    public class InOutSineEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => -(Math.Cos(Math.PI * normalizedTime) - 1) / 2;
    }

    public class InQuadEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime * normalizedTime;
    }

    public class OutQuadEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - (1 - normalizedTime) * (1 - normalizedTime);
    }

    public class InOutQuadEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 2 * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 2) / 2;
        }
    }

    public class InCubicEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime * normalizedTime * normalizedTime;
    }

    public class OutCubicEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Pow(1 - normalizedTime, 3);
    }

    public class InOutCubicEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 4 * normalizedTime * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 3) / 2;
        }
    }

    public class InQuartEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime * normalizedTime * normalizedTime * normalizedTime;
    }

    public class OutQuartEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Pow(1 - normalizedTime, 4);
    }

    public class InOutQuartEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 8 * normalizedTime * normalizedTime * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 4) / 2;
        }
    }

    public class InQuintEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => Math.Pow(normalizedTime, 5);
    }

    public class OutQuintEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Pow(1 - normalizedTime, 5);
    }

    public class InOutQuintEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 16 * normalizedTime * normalizedTime * normalizedTime * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 5) / 2;
        }
    }

    public class InExpoEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) =>
            normalizedTime == 0 ? 0 : Math.Pow(2, 10 * normalizedTime - 10);
    }

    public class OutExpoEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) =>
            normalizedTime == 1 ? 1 : 1 - Math.Pow(2, -10 * normalizedTime);
    }

    public class InOutExpoEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return normalizedTime < 0.5
                ? Math.Pow(2, 20 * normalizedTime - 10) / 2
                : (2 - Math.Pow(2, -20 * normalizedTime + 10)) / 2;
        }
    }

    public class InCircEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Sqrt(1 - Math.Pow(normalizedTime, 2));
    }

    public class OutCircEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => Math.Sqrt(1 - Math.Pow(normalizedTime - 1, 2));
    }

    public class InOutCircEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? (1 - Math.Sqrt(1 - Math.Pow(2 * normalizedTime, 2))) / 2
                : (Math.Sqrt(1 - Math.Pow(-2 * normalizedTime + 2, 2)) + 1) / 2;
        }
    }

    public class InBackEasingFunction : IEasingFunction
    {
        private const double c1 = 1.70158;
        private const double c3 = c1 + 1;

        public double Ease(double normalizedTime) =>
            c3 * normalizedTime * normalizedTime * normalizedTime - c1 * normalizedTime * normalizedTime;
    }

    public class OutBackEasingFunction : IEasingFunction
    {
        private const double c1 = 1.70158;
        private const double c3 = c1 + 1;

        public double Ease(double normalizedTime) =>
            1 + c3 * Math.Pow(normalizedTime - 1, 3) + c1 * Math.Pow(normalizedTime - 1, 2);
    }

    public class InOutBackEasingFunction : IEasingFunction
    {
        private const double c1 = 1.70158;
        private const double c2 = c1 * 1.525;

        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? (Math.Pow(2 * normalizedTime, 2) * ((c2 + 1) * 2 * normalizedTime - c2)) / 2
                : (Math.Pow(2 * normalizedTime - 2, 2) * ((c2 + 1) * (2 * normalizedTime - 2) + c2) + 2) / 2;
        }
    }

    public class InElasticEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return -Math.Pow(2, 10 * normalizedTime - 10) * Math.Sin((normalizedTime * 10 - 10.75) * (2 * Math.PI) / 3);
        }
    }

    public class OutElasticEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return Math.Pow(2, -10 * normalizedTime) * Math.Sin((normalizedTime * 10 - 0.75) * (2 * Math.PI) / 3) + 1;
        }
    }

    public class InOutElasticEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return normalizedTime < 0.5
                ? -(Math.Pow(2, 20 * normalizedTime - 10) * Math.Sin((20 * normalizedTime - 11.125) * (2 * Math.PI) / 4.5)) / 2
                : Math.Pow(2, -20 * normalizedTime + 10) * Math.Sin((20 * normalizedTime - 11.125) * (2 * Math.PI) / 4.5) / 2 + 1;
        }
    }

    public class InBounceEasingFunction : IEasingFunction
    {
        private readonly OutBounceEasingFunction _outBounce = new OutBounceEasingFunction();

        public double Ease(double normalizedTime) =>
            1 - _outBounce.Ease(1 - normalizedTime);
    }

    public class OutBounceEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            double n1 = 7.5625;
            double d1 = 2.75;

            if (normalizedTime < 1 / d1)
            {
                return n1 * normalizedTime * normalizedTime;
            }
            else if (normalizedTime < 2 / d1)
            {
                return n1 * (normalizedTime -= 1.5 / d1) * normalizedTime + 0.75;
            }
            else if (normalizedTime < 2.5 / d1)
            {
                return n1 * (normalizedTime -= 2.25 / d1) * normalizedTime + 0.9375;
            }
            else
            {
                return n1 * (normalizedTime -= 2.625 / d1) * normalizedTime + 0.984375;
            }
        }
    }

    public class InOutBounceEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? (1 - new OutBounceEasingFunction().Ease(1 - 2 * normalizedTime)) / 2
                : (1 + new OutBounceEasingFunction().Ease(2 * normalizedTime - 1)) / 2;
        }
    }
    #endregion

    public enum AnimationStatus
    {
        Stopped,
        Playing,
        Paused
    }

    public abstract class Animation
    {
        // 新增字段
        public TimeSpan ActualStartTime { get; private set; }
        public bool BeginTimePassed { get; private set; }
        public TimeSpan StartTime { get; private set; }
        protected TimeSpan _pausedTime;
        protected TimeSpan _totalPausedDuration;
        protected double Progress { get; private set; }
        protected int CurrentLoop;

        public int Loop { get; set; } = 1;
        public bool KeepEnd { get; set; }
        public AnimationStatus Status { get; set; } = AnimationStatus.Stopped;
        public IEasingFunction EasingFunction { get; set; } = new LinearEasingFunction();
        public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(1);
        public double SpeedMultiplier { get; private set; } = 1.0;
        public TimeSpan BeginTime { get; set; }

        public abstract Action<Animation> Started { get; set; }
        public abstract Action<Animation> Paused { get; set; }
        public abstract Action<Animation> Stopped { get; set; }
        public abstract Action<Animation> Progressing { get; set; }

        public virtual void Begin(TimeSpan timeSpan)
        {
            if (Status == AnimationStatus.Playing) return;

            Status = AnimationStatus.Playing;
            CurrentLoop = 1;
            StartTime = timeSpan;
            _totalPausedDuration = TimeSpan.Zero;
        }

        public virtual void Resume(TimeSpan timeSpan)
        {
            if (Status != AnimationStatus.Paused) return;

            _totalPausedDuration += timeSpan - _pausedTime;
            Status = AnimationStatus.Playing;
        }

        public virtual void Pause(TimeSpan timeSpan)
        {
            if (Status != AnimationStatus.Playing) return;

            Status = AnimationStatus.Paused;
            _pausedTime = timeSpan;
        }

        public virtual void Stop()
        {
            Status = AnimationStatus.Stopped;
            BeginTimePassed = false;
        }

        public virtual void Update(TimeSpan timeSpan)
        {
            if (Status != AnimationStatus.Playing) return;

            var elapsed = timeSpan - StartTime - _totalPausedDuration;
            var scaledElapsed = elapsed.TotalMilliseconds * SpeedMultiplier;

            // 处理开始时间偏移
            if (!BeginTimePassed)
            {
                if (scaledElapsed >= BeginTime.TotalMilliseconds)
                {
                    ActualStartTime = timeSpan - TimeSpan.FromMilliseconds(scaledElapsed - BeginTime.TotalMilliseconds);
                    BeginTimePassed = true;
                }
            }

            var effectiveElapsed = timeSpan - ActualStartTime - _totalPausedDuration;
            var scaledEffectiveElapsed = effectiveElapsed.TotalMilliseconds * SpeedMultiplier;
            var progress = scaledEffectiveElapsed / Duration.TotalMilliseconds;

            // 处理速度影响下的循环逻辑
            while (progress >= 1 && ShouldContinueLoop())
            {
                CurrentLoop++;
                ActualStartTime = timeSpan - TimeSpan.FromMilliseconds(
                    (scaledEffectiveElapsed % Duration.TotalMilliseconds) / SpeedMultiplier);
                progress = (timeSpan - ActualStartTime - _totalPausedDuration).TotalMilliseconds * SpeedMultiplier
                         / Duration.TotalMilliseconds;
            }
            progress = EasingFunction.Ease(progress);

            Progress = progress;
            OnUpdate(timeSpan);
            if (!ShouldContinueLoop() && progress >= 1)
            {
                Stop();
                return;
            }
        }

        protected virtual bool ShouldContinueLoop()
        {
            return Loop == -1 || CurrentLoop < Loop;
        }

        public void SetSpeedMultiplier(double multiplier, TimeSpan currentTime)
        {
            if (multiplier <= 0) throw new ArgumentOutOfRangeException();
            if (multiplier == SpeedMultiplier) return;

            if (Status == AnimationStatus.Playing)
            {
                // 重新计算起始时间保持进度连续
                var elapsed = currentTime - StartTime - _totalPausedDuration;
                var scaledElapsed = elapsed.TotalMilliseconds * SpeedMultiplier;
                var progress = scaledElapsed / Duration.TotalMilliseconds;

                var newElapsed = progress * Duration.TotalMilliseconds / multiplier;
                StartTime = currentTime - TimeSpan.FromMilliseconds(newElapsed);
                _totalPausedDuration = TimeSpan.Zero;
            }

            SpeedMultiplier = multiplier;
        }
        
        protected virtual void OnUpdate(TimeSpan timeSpan)
        {
        }
    }

    public class DoubleAnimation : Animation
    {
        public double From { get; set; }
        public double To { get; set; }
        public double Value { get; private set; }
        public override Action<Animation> Started { get; set; }
        public override Action<Animation> Paused { get; set; }
        public override Action<Animation> Stopped { get; set; }
        public override Action<Animation> Progressing { get; set; }

        public override void Begin(TimeSpan timeSpan)
        {
            base.Begin(timeSpan);
            Value = From;
            Started?.Invoke(this);
        }
        public override void Pause(TimeSpan timeSpan)
        {
            base.Pause(timeSpan);
            Paused?.Invoke(this);
        }
        public override void Stop()
        {
            base.Stop();
            Stopped?.Invoke(this);
        }

        protected override void OnUpdate(TimeSpan timeSpan)
        {
            Value = From + (To - From) * Progress;
            Progressing?.Invoke(this);
        }
    }

    public class KeyFrame<T>
    {
        public T Value { get; }
        public TimeSpan Time { get; }

        public KeyFrame(T value, TimeSpan time)
        {
            Value = value;
            Time = time;
        }
    }

    public class KeyFramesAnimation<T> : Animation
    {
        public override Action<Animation> Started { get; set; }
        public override Action<Animation> Paused { get; set; }
        public override Action<Animation> Stopped { get; set; }
        public override Action<Animation> Progressing { get; set; }

        private List<KeyFrame<T>> _keyFrames = new List<KeyFrame<T>>();
        public List<KeyFrame<T>> KeyFrames { get { return _keyFrames; } }

        public KeyFrame<T> CurrentFrame { get; private set; }
        public T Value { get => CurrentFrame ==null ? default(T) : CurrentFrame.Value; }

        public override void Begin(TimeSpan timeSpan)
        {
            base.Begin(timeSpan);
            if (Duration.Equals(new TimeSpan()))
                Duration = _keyFrames.Last().Time; // 自动设置总时长为最后一帧时间
            CurrentFrame = null;
            Started?.Invoke(this);
        }
        public override void Pause(TimeSpan timeSpan)
        {
            base.Pause(timeSpan);
            Paused?.Invoke(this);
        }
        public override void Stop()
        {
            base.Stop();
            Stopped?.Invoke(this);
        }

        protected override void OnUpdate(TimeSpan timeSpan)
        {
            double per = Progress;
            if(Loop != CurrentLoop)
            {
                while (per > 1.0)
                    per -= 1.0;
            }
            else if(per > 1.0)
                per = 1.0;
            List<double> values = new List<double>();
            foreach (var item in KeyFrames)
            {
                values.Add(item.Time.TotalMilliseconds / Duration.TotalMilliseconds);
            }
            int index = 0;
            for (int i = 0; i < values.Count && per > values[i]; i++)
                index = i;
            CurrentFrame = KeyFrames[index];
            Progressing?.Invoke(this);
        }
    }

    public class Storyboard
    {
        private TimeSpan _startTime;
        private TimeSpan _pausedTime;
        private TimeSpan _totalPausedDuration;
        private bool _hasBegun;

        public List<Animation> Animations { get; } = new List<Animation>();
        public AnimationStatus Status { get; private set; } = AnimationStatus.Stopped;
        public double SpeedMultiplier { get; private set; } = 1.0;
        public int Loop { get; set; } = 1;
        public int CurrentLoop { get; set; }
        public TimeSpan Duration { get; set; }

        public Action<Storyboard> Began;
        public Action<Storyboard> Paused;
        public Action<Storyboard> Resumed;
        public Action<Storyboard> Completed;
        public Action<Storyboard> Progressed;

        public void AddAnimation(Animation animation)
        {
            if (Status != AnimationStatus.Stopped)
                throw new InvalidOperationException("Cannot add animation while storyboard is running");

            Animations.Add(animation);
        }

        public void Begin(TimeSpan globalTime)
        {
            Begin(globalTime, 1);
        }
        public void Begin(TimeSpan globalTime, int loop)
        {
            Status = AnimationStatus.Playing;
            _startTime = globalTime;
            _totalPausedDuration = TimeSpan.Zero;
            _hasBegun = true;
            CurrentLoop = loop;

            foreach (var animation in Animations)
            {
                animation.Begin(globalTime);
            }

            Began?.Invoke(this);
        }

        public void Pause(TimeSpan globalTime)
        {
            if (Status != AnimationStatus.Playing) return;

            Status = AnimationStatus.Paused;
            _pausedTime = globalTime;

            foreach (var animation in Animations)
            {
                animation.Pause(globalTime);
            }

            Paused?.Invoke(this);
        }

        public void Resume(TimeSpan globalTime)
        {
            if (Status != AnimationStatus.Paused) return;

            Status = AnimationStatus.Playing;
            _totalPausedDuration += globalTime - _pausedTime;

            foreach (var animation in Animations)
            {
                animation.Resume(globalTime);
            }

            Resumed?.Invoke(this);
        }

        public void Stop()
        {
            if (Status == AnimationStatus.Stopped) return;

            Status = AnimationStatus.Stopped;

            foreach (var animation in Animations)
            {
                animation.Stop();
            }

            Completed?.Invoke(this);
        }

        public void Update(TimeSpan globalTime)
        {
            if (Status != AnimationStatus.Playing) return;

            var elapsed = globalTime - _startTime - _totalPausedDuration;
            var scaledElapsed = elapsed.TotalMilliseconds * SpeedMultiplier;
            var progress = scaledElapsed / Duration.TotalMilliseconds;

            // 处理速度影响下的循环逻辑
            bool newLoop = false;
            while (progress >= 1 && (Loop == -1 || CurrentLoop < Loop))
            {
                newLoop = true;
                CurrentLoop++;
                _startTime = globalTime - TimeSpan.FromMilliseconds(
                    (scaledElapsed % Duration.TotalMilliseconds) / SpeedMultiplier);
                progress = (globalTime - _startTime - _totalPausedDuration).TotalMilliseconds * SpeedMultiplier
                         / Duration.TotalMilliseconds;
            }

            bool allCompleted = true;
            foreach (var animation in Animations)
            {
                if (animation.Status == AnimationStatus.Stopped) continue;

                // 考虑BeginTime的实际开始时间
                var animStartTime = animation.BeginTimePassed ?
                    animation.ActualStartTime :
                    animation.StartTime + animation.BeginTime;

                var animElapsed = TimeSpan.FromMilliseconds(scaledElapsed) -
                                (animStartTime - animation.StartTime);

                //animation.Update(animStartTime + animElapsed);
                animation.Update(globalTime);

                if (animation.Status != AnimationStatus.Stopped)
                    allCompleted = false;
            }

            Progressed?.Invoke(this);

            if (progress >= 1 && _hasBegun && CurrentLoop == Loop)
            {
                Stop();
            }
            else if(allCompleted && CurrentLoop != Loop)
            {
                foreach (var animation in Animations)
                    animation.Begin(globalTime);
            }
        }

        public void SetSpeedMultiplier(double multiplier, TimeSpan currentTimespan)
        {
            if (multiplier <= 0) throw new ArgumentOutOfRangeException();
            SpeedMultiplier = multiplier;

            foreach (var animation in Animations)
            {
                animation.SetSpeedMultiplier(multiplier, currentTimespan);
            }
        }

        public void Seek(TimeSpan position)
        {
            if (Status == AnimationStatus.Playing)
                throw new InvalidOperationException("Cannot seek while playing");

            var simulatedTime = _startTime + position;
            foreach (var animation in Animations)
            {
                animation.Stop();
                animation.Begin(simulatedTime);
                animation.Update(simulatedTime);
                animation.Stop();
            }
        }
    }

}