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

XFESystem

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

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

public enum DesktopPointerShape { Arrow, Text, Resize }

/// <summary>
/// Small tapered line cursors with native-pixel antialiasing and a translucent
/// accent-colored halo. Cached masks contain coverage only, so the background
/// always comes from the canvas and changing themes needs no raster rebuild.
/// </summary>
public static class DesktopPointerRenderer
{
    private const double HaloRadius = 5.5;
    private const int HaloPeakAlpha = 38;
    private const int SamplesPerAxis = 4;
    private const int MaskCapacity = 24;
    private static readonly Dictionary<(DesktopPointerShape Shape, int ScalePercent), CoverageMask> Masks = new(MaskCapacity);
    private static readonly (DesktopPointerShape Shape, int ScalePercent)[] MaskOrder = new (DesktopPointerShape, int)[MaskCapacity];
    private static int nextMaskSlot;
    private sealed record CoverageMask(int Width, int Height, byte[] Halo, byte[] Core);
    private readonly record struct Segment(double X1, double Y1, double X2, double Y2, double Radius1, double Radius2);

    // Coordinates are relative to the hot spot, in layout units. Extra padding
    // contains all halo and AA samples, including the outermost fractional edge.
    public static DesktopBounds GetBounds(int x, int y, DesktopPointerShape shape)
    {
        var relative = RelativeBounds(shape);
        return new(x + relative.X, y + relative.Y, relative.Width, relative.Height);
    }

    public static void Draw(IDesktopCanvas canvas, int x, int y, DesktopPointerShape shape, uint accent)
    {
        ArgumentNullException.ThrowIfNull(canvas);
        if (canvas is IDesktopCoverageCanvas coverage)
        {
            int percent = Math.Clamp(canvas.ScalePercent, 50, 400);
            double scale = percent / 100.0;
            var mask = Mask(shape, percent);
            var bounds = GetBounds(x, y, shape);
            int left = (int)Math.Floor(bounds.X * scale), top = (int)Math.Floor(bounds.Y * scale);
            coverage.BlendCoverage(accent & 0xFFFFFF, left, top, mask.Width, mask.Height, mask.Halo);
            coverage.BlendCoverage(accent & 0xFFFFFF, left, top, mask.Width, mask.Height, mask.Core);
            return;
        }

        // A legacy canvas cannot blend without knowing the background. Draw
        // only the narrow core instead of inventing an opaque glow background.
        var fallback = Mask(shape, 100);
        var logical = GetBounds(x, y, shape);
        for (int row = 0; row < fallback.Height; row++)
            for (int col = 0; col < fallback.Width;)
            {
                if (fallback.Core[row * fallback.Width + col] < 128) { col++; continue; }
                int start = col++;
                while (col < fallback.Width && fallback.Core[row * fallback.Width + col] >= 128) col++;
                canvas.Fill(accent, logical.X + start, logical.Y + row, col - start, 1);
            }
    }

    private static DesktopBounds RelativeBounds(DesktopPointerShape shape) => shape switch
    {
        DesktopPointerShape.Arrow => new(-7, -7, 23, 23),
        DesktopPointerShape.Text => new(-10, -13, 21, 29),
        DesktopPointerShape.Resize => new(-11, -11, 23, 23),
        _ => throw new ArgumentOutOfRangeException(nameof(shape))
    };

    private static CoverageMask Mask(DesktopPointerShape shape, int scalePercent)
    {
        lock (Masks)
        {
            var key = (shape, scalePercent);
            if (Masks.TryGetValue(key, out var cached)) return cached;
            var result = BuildMask(shape, scalePercent);
            // Always retain a new family. Evicted immutable masks remain valid
            // for a Draw already holding them; hits do not allocate or reorder.
            if (Masks.Count == MaskCapacity) Masks.Remove(MaskOrder[nextMaskSlot]);
            Masks.Add(key, result);
            MaskOrder[nextMaskSlot] = key;
            nextMaskSlot = (nextMaskSlot + 1) % MaskCapacity;
            return result;
        }
    }

    private static CoverageMask BuildMask(DesktopPointerShape shape, int scalePercent)
    {
        var bounds = RelativeBounds(shape);
        double scale = scalePercent / 100.0;
        int width = (int)Math.Ceiling(bounds.Width * scale), height = (int)Math.Ceiling(bounds.Height * scale);
        var core = new byte[width * height];
        var halo = new byte[width * height];
        Segment[] segments = shape switch
        {
            DesktopPointerShape.Arrow =>
            [
                new(0, 0, 8.3, 0.7, 0.95, 0.16),
                new(0, 0, 0.7, 8.3, 0.95, 0.16),
                new(0.2, 0.2, 5.8, 5.8, 0.80, 0.28)
            ],
            DesktopPointerShape.Text =>
            [
                new(0, -5.5, 0, 7.5, 0.78, 0.78),
                new(0, -5.5, -3.7, -5.5, 0.80, 0.16),
                new(0, -5.5, 3.7, -5.5, 0.80, 0.16),
                new(0, 7.5, -3.7, 7.5, 0.80, 0.16),
                new(0, 7.5, 3.7, 7.5, 0.80, 0.16)
            ],
            DesktopPointerShape.Resize =>
            [
                new(-4.6, -4.6, 4.6, 4.6, 0.64, 0.64),
                new(-4.6, -4.6, -4.3, 0, 0.88, 0.16),
                new(-4.6, -4.6, 0, -4.3, 0.88, 0.16),
                new(4.6, 4.6, 4.3, 0, 0.88, 0.16),
                new(4.6, 4.6, 0, 4.3, 0.88, 0.16)
            ],
            _ => throw new ArgumentOutOfRangeException(nameof(shape))
        };
        const int sampleCount = SamplesPerAxis * SamplesPerAxis;
        for (int row = 0; row < height; row++)
            for (int col = 0; col < width; col++)
            {
                int covered = 0;
                double glow = 0;
                for (int sy = 0; sy < SamplesPerAxis; sy++)
                    for (int sx = 0; sx < SamplesPerAxis; sx++)
                    {
                        double x = bounds.X + (col + (sx + 0.5) / SamplesPerAxis) / scale;
                        double y = bounds.Y + (row + (sy + 0.5) / SamplesPerAxis) / scale;
                        double distance = double.MaxValue;
                        foreach (var segment in segments) distance = Math.Min(distance, SignedDistance(segment, x, y));
                        if (distance <= 0) covered++;
                        double strength = Math.Clamp(1 - Math.Max(0, distance) / HaloRadius, 0, 1);
                        glow += strength * strength;
                    }
                int at = row * width + col;
                core[at] = (byte)((covered * 255 + sampleCount / 2) / sampleCount);
                halo[at] = (byte)Math.Round(HaloPeakAlpha * glow / sampleCount);
            }
        return new CoverageMask(width, height, halo, core);
    }

    private static double SignedDistance(Segment segment, double x, double y)
    {
        double dx = segment.X2 - segment.X1, dy = segment.Y2 - segment.Y1;
        double lengthSquared = dx * dx + dy * dy;
        double t = lengthSquared == 0 ? 0 : Math.Clamp(((x - segment.X1) * dx + (y - segment.Y1) * dy) / lengthSquared, 0, 1);
        double px = x - (segment.X1 + t * dx), py = y - (segment.Y1 + t * dy);
        return Math.Sqrt(px * px + py * py) - (segment.Radius1 + t * (segment.Radius2 - segment.Radius1));
    }
}