XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Desktop;

/// <summary>Theme-aware small window affordances; animation time/state belongs to DesktopSession.</summary>
public static class DesktopWindowChromeRenderer
{
    public const int AnimationSteps = 32;
    private static readonly Dictionary<uint, uint> CloseInkCache = [];

    // Keep the animated band centered and clear of both caption buttons.
    public static DesktopBounds TitleDamage(DesktopBounds bounds)
    {
        int width = bounds.Width * 2 / 3;
        return new(bounds.X + (bounds.Width - width) / 2 - 1, bounds.Y, width + 2, 32);
    }
    public static DesktopBounds CaptionDamage(DesktopBounds bounds, bool close) =>
        new(bounds.X + bounds.Width - (close ? 32 : 64), bounds.Y, 32, 32);
    public static DesktopBounds ResizeDamage(DesktopBounds bounds) =>
        new(bounds.X + bounds.Width - 20, bounds.Y + bounds.Height - 20, 20, 20);

    /// <remarks>
    /// Draw after restoring/redrawing the scene below this chrome. Progress is
    /// reversible 0..1 input, quantized to a bounded family of native AA masks;
    /// alpha is never composited repeatedly over the previous animation frame.
    /// </remarks>
    public static void Draw(IDesktopCanvas canvas, DesktopBounds bounds, DesktopTheme theme, bool isActive,
        double minimizeProgress, double closeProgress, double titleHintProgress, double resizeProgress,
        bool isMaximizedOrSnapped = false, bool minimizePressed = false, bool closePressed = false)
    {
        ArgumentNullException.ThrowIfNull(canvas); ArgumentNullException.ThrowIfNull(theme);
        if (bounds.Width < 128 || bounds.Height < 64) return;
        int minPhase = Phase(minimizeProgress), closePhase = Phase(closeProgress);
        int titlePhase = Phase(titleHintProgress), resizePhase = Phase(resizeProgress);
        double centerX = bounds.X + bounds.Width / 2d, centerY = bounds.Y + 16;
        uint ink = isActive ? theme.AccentStroke : theme.Muted;
        double titleWidth = bounds.Width * 2 / 3;
        DesktopChromeRenderer.RoundedAlphaFill(canvas, 0xFFFFFF, centerX - titleWidth / 2, bounds.Y, titleWidth, 30,
            0, 0, 10, 10, (byte)((titlePhase * 40 + AnimationSteps / 2) / AnimationSteps));
        DesktopChromeRenderer.RoundedAlphaBorder(canvas, theme.AccentStroke, centerX - titleWidth / 2, bounds.Y, titleWidth, 30,
            0, 0, 10, 10, .75, (byte)((titlePhase * 32 + AnimationSteps / 2) / AnimationSteps));
        DesktopChromeRenderer.ThickLine(canvas, ink, centerX - 12, bounds.Y + 13, centerX + 12, bounds.Y + 13, 2.5);
        DesktopChromeRenderer.ThickLine(canvas, ink, centerX - 12, bounds.Y + 19, centerX + 12, bounds.Y + 19, 2.5);

        double minCenter = bounds.X + bounds.Width - 48;
        double angle = minPhase * Math.PI / (2 * AnimationSteps);
        double length = minimizePressed ? 5.0 : 5.5;
        double dx = Math.Cos(angle) * length, dy = Math.Sin(angle) * length;
        DesktopChromeRenderer.ThickLine(canvas, theme.AccentStroke, minCenter - dx, centerY - dy,
            minCenter + dx, centerY + dy, minimizePressed ? 3.2 : 2.8);

        double closeCenter = bounds.X + bounds.Width - 16, circle = closePressed ? 10.0 : 10.5;
        DesktopChromeRenderer.RoundedAlphaFill(canvas, 0xFFFFFF, closeCenter - circle, centerY - circle,
            circle * 2, circle * 2, circle, circle, circle, circle);
        // An X repeats every quarter turn. Canonicalizing that symmetry avoids
        // redundant cache entries while preserving the complete 180-degree spin.
        angle = Math.PI / 4 + (closePhase % (AnimationSteps / 2)) * Math.PI / AnimationSteps;
        length = closePressed ? 5.8 : 6.2;
        dx = Math.Cos(angle) * length; dy = Math.Sin(angle) * length;
        double thickness = closePressed ? 3.1 : 2.6;
        uint closeInk = CloseInk(theme.AccentStroke);
        DesktopChromeRenderer.ThickLine(canvas, closeInk, closeCenter - dx, centerY - dy,
            closeCenter + dx, centerY + dy, thickness);
        DesktopChromeRenderer.ThickLine(canvas, closeInk, closeCenter + dy, centerY - dx,
            closeCenter - dy, centerY + dx, thickness);

        if (isMaximizedOrSnapped) return;
        double right = bounds.X + bounds.Width, bottom = bounds.Y + bounds.Height;
        // The highlight occupies exactly the quarter-corner's footprint,
        // rather than a separate square extending into the window content.
        DesktopChromeRenderer.RoundedAlphaFill(canvas, 0xFFFFFF, right - 18, bottom - 18, 18, 18,
            7, 0, 18, 0, (byte)((resizePhase * 44 + AnimationSteps / 2) / AnimationSteps));
        DesktopChromeRenderer.RoundedAlphaBorder(canvas, theme.AccentStroke, right - 18, bottom - 18, 18, 18,
            7, 0, 18, 0, .75, (byte)((resizePhase * 32 + AnimationSteps / 2) / AnimationSteps));
        DesktopChromeRenderer.QuarterArc(canvas, ink, right - 18, bottom - 18, 15.5, 2.5);
    }

    private static int Phase(double value)
    {
        if (!double.IsFinite(value)) return 0;
        return (int)Math.Round(Math.Clamp(value, 0, 1) * AnimationSteps);
    }

    private static uint CloseInk(uint accent)
    {
        lock (CloseInkCache)
        {
            if (CloseInkCache.TryGetValue(accent, out uint cached)) return cached;
            uint result = accent;
            for (int amount = 0; DesktopTheme.Contrast(result, 0xFFFFFF) < 3 && amount <= 100; amount += 10)
                result = DesktopTheme.Mix(accent, 0, amount);
            if (CloseInkCache.Count < 32) CloseInkCache.Add(accent, result);
            return result;
        }
    }
}