namespace XFESystem.Desktop; [Flags] public enum DesktopChromeAnimationChanges { None = 0, Minimize = 1, Close = 2, TitleHint = 4, Resize = 8 } /// Monotonic reversible window affordances. Stable states never request another frame. public sealed class DesktopChromeAnimation { public const int DurationMilliseconds = 180; private readonly Channel _minimize = new(), _close = new(), _title = new(), _resize = new(); public double MinimizeProgress => _minimize.Progress; public double CloseProgress => _close.Progress; public double TitleHintProgress => _title.Progress; public double ResizeProgress => _resize.Progress; public bool IsAnimating => _minimize.IsAnimating || _close.IsAnimating || _title.IsAnimating || _resize.IsAnimating; public DesktopChromeAnimationChanges SetTargets(long monotonicMs, bool minimize, bool close, bool titleHint, bool resize) { var changed = Advance(monotonicMs); _minimize.SetTarget(minimize); _close.SetTarget(close); _title.SetTarget(titleHint); _resize.SetTarget(resize); return changed; } public DesktopChromeAnimationChanges Advance(long monotonicMs) { var changed = DesktopChromeAnimationChanges.None; if (_minimize.Advance(monotonicMs)) changed |= DesktopChromeAnimationChanges.Minimize; if (_close.Advance(monotonicMs)) changed |= DesktopChromeAnimationChanges.Close; if (_title.Advance(monotonicMs)) changed |= DesktopChromeAnimationChanges.TitleHint; if (_resize.Advance(monotonicMs)) changed |= DesktopChromeAnimationChanges.Resize; return changed; } private sealed class Channel { private double _phase; private bool _target, _initialized; private long _lastMs; public double Progress => _phase * _phase * (3 - 2 * _phase); public bool IsAnimating => _target ? _phase < 1 : _phase > 0; public void SetTarget(bool target) => _target = target; public bool Advance(long nowMs) { if (!_initialized) { _initialized = true; _lastMs = nowMs; return false; } if (nowMs <= _lastMs) return false; long elapsed = unchecked(nowMs - _lastMs); _lastMs = nowMs; double step = (elapsed < 0 ? DurationMilliseconds : Math.Min(elapsed, DurationMilliseconds)) / (double)DurationMilliseconds; double previous = _phase; _phase = Math.Clamp(_phase + (_target ? step : -step), 0, 1); return previous != _phase; } } }