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

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
博客园_首页
WordPress大学
WordPress大学
博客园 - 聂微东
P
Privacy International News Feed
Forbes - Security
Forbes - Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Last Week in AI
Last Week in AI
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
NISL@THU
NISL@THU
美团技术团队
T
Tailwind CSS Blog
Jina AI
Jina AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
C
Cisco Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Hacker News
The Hacker News
B
Blog
P
Palo Alto Networks Blog
L
Lohrmann on Cybersecurity
有赞技术团队
有赞技术团队
The Register - Security
The Register - Security
S
Securelist
A
Arctic Wolf
MyScale Blog
MyScale Blog
H
Help Net Security
N
Netflix TechBlog - Medium
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
T
Threatpost
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Security Latest
Security Latest
T
Tor Project blog
V
Vulnerabilities – Threatpost
V
V2EX
AI
AI
Hugging Face - Blog
Hugging Face - Blog
大猫的无限游戏
大猫的无限游戏
博客园 - Franky
Simon Willison's Weblog
Simon Willison's Weblog
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Troy Hunt's Blog
Schneier on Security
Schneier on Security
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
Google Online Security Blog
Google Online Security Blog
Know Your Adversary
Know Your Adversary

博客园 - 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();
            }
        }
    }

}