Code/XGUI/Elements/Razor/ColourWidget.razor
@using System;
@using System.Collections.Generic;
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Razor;

@namespace XGUI
@inherits Panel

<root class="colour-widget">
    <label>Select Colour</label>

    <div class="colour-picker">
        <div class="colour-map" @ref="ColourMap">
            @* <div class="colour-map-overlay main" style="background-color: @GetHueColor();"></div> *@
            <div class="colour-map-overlay white" style="background: @GenerateWhiteToHueGradient();"></div>
            <div class="colour-map-overlay black"></div>
            <div class="picker-thumb" style="left: @($"{SaturationPct}%"); top: @($"{(100-ValuePct)}%")"></div>
        </div>

        <div class="hue-slider" @ref="HueSlider" style="cursor: pointer;">
            <div class="hue-thumb" style="left: @($"{HuePct}%")"></div>
        </div>

        <div class="preview-swatch" style="background-color: @CurrentColor.Hex"></div>
    </div>

    <div class="swatch-grid">
        @foreach (var colour in WebColours)
        {
            <div class="swatch" style="background-color:@colour.Hex;" @onclick=@(() => OnSwatchClick(colour))></div>
        }
    </div>
</root>

@code {
    public Action<Color> OnChange { get; set; }

    private Panel ColourMap { get; set; }
    private Panel HueSlider { get; set; }
    private Color CurrentColor { get; set; } = Color.White;

    private float Hue { get; set; } = 0f;
    private float Saturation { get; set; } = 1f;
    private float Value { get; set; } = 1f;

    private float HuePct => Hue / 360f * 100f;
    private float SaturationPct => Saturation * 100f;
    private float ValuePct => Value * 100f;

    private Panel ActivePanel { get; set; }

    protected override void OnAfterTreeRender(bool firstTime)
    {
        base.OnAfterTreeRender(firstTime);

        if (firstTime)
        {
            AddClass("colour-widget");
        }
    }

    protected override void OnMouseDown(MousePanelEvent e)
    {
        base.OnMouseDown(e);
        if (e.Target == ColourMap)
        {
            ActivePanel = ColourMap;
            UpdateColorFromMap(e.LocalPosition);
        }
        else if (e.Target == HueSlider)
        {
            ActivePanel = HueSlider;
            UpdateHueFromSlider(e.LocalPosition);
        }
    }

    protected override void OnMouseMove(MousePanelEvent e)
    {
        base.OnMouseMove(e);

        if (ActivePanel == null) return;

        if (ActivePanel == ColourMap)
        {
            UpdateColorFromMap(e.LocalPosition);
        }
        else if (ActivePanel == HueSlider)
        {
            UpdateHueFromSlider(e.LocalPosition);
        }
    }

    protected override void OnMouseUp(MousePanelEvent e)
    {
        base.OnMouseUp(e);
        ActivePanel = null;
    }

    private void UpdateColorFromMap(Vector2 pos)
    {
        var bounds = ColourMap.Box.Rect;
        Saturation = Math.Clamp(pos.x / bounds.Width, 0, 1);
        Value = Math.Clamp(1 - (pos.y / bounds.Height), 0, 1);
        UpdateCurrentColor();
    }

    private void UpdateHueFromSlider(Vector2 pos)
    {
        var bounds = HueSlider.Box.Rect;
        Hue = Math.Clamp(pos.x / bounds.Width * 360f, 0, 360);
        UpdateCurrentColor();
    }

    private void UpdateCurrentColor()
    {
        var hsv = new ColorHsv(Hue, Saturation, Value, 1);
        CurrentColor = hsv.ToColor();
        OnChange?.Invoke(CurrentColor);
        StateHasChanged();
    }

    private string GetHueColor()
    {
        var hsv = new ColorHsv(Hue, 1, 1, 1);
        return hsv.ToColor().Hex;
    }

    private void OnSwatchClick(Color color)
    {
        CurrentColor = color;
        var hsv = color.ToHsv();
        Hue = hsv.Hue;
        Saturation = hsv.Saturation;
        Value = hsv.Value;
        OnChange?.Invoke(CurrentColor);
        StateHasChanged();
    }

    private string GenerateWhiteToHueGradient()
    {
        var hueColor = GetHueColor();
        var hueColorObj = Color.Parse(hueColor).Value;

        var gradientStops = new List<string>();
        const int steps = 10; // 11 points (0% to 100%)

        for (int i = 0; i <= steps; i++)
        {
            float t = i / (float)steps;

            // Calculate intermediate color using linear interpolation
            var r = (byte)Math.Round(255 * (1 - t) + hueColorObj.r * t * 255);
            var g = (byte)Math.Round(255 * (1 - t) + hueColorObj.g * t * 255);
            var b = (byte)Math.Round(255 * (1 - t) + hueColorObj.b * t * 255);

            // For the intermediate stops, set alpha based on position (full alpha at left, zero at right)
            var a = 255;

            // Add the color stop to the gradient
            var hex = $"#{r:X2}{g:X2}{b:X2}{a:X2}";
            gradientStops.Add(hex);
        }

        return $"linear-gradient(to right, {string.Join(", ", gradientStops)})";
    }

    public List<Color> WebColours = new List<Color>
    {
        Color.FromRgb(0xFF0000), // Red
        Color.FromRgb(0x800000), // Maroon
        Color.FromRgb(0xFFA500), // Orange
        Color.FromRgb(0xFFFF00), // Yellow
        Color.FromRgb(0x808000), // Olive
        Color.FromRgb(0x00FF00), // Lime Green
        Color.FromRgb(0x008000), // Green
        Color.FromRgb(0x00FFFF), // Aqua
        Color.FromRgb(0x008080), // Teal
        Color.FromRgb(0x0000FF), // Blue
        Color.FromRgb(0x000080), // Navy Blue
        Color.FromRgb(0xFF00FF), // Fuchsia
        Color.FromRgb(0x800080), // Purple
        Color.FromRgb(0xFFFFFF), // White
        Color.FromRgb(0xC0C0C0), // Silver
        Color.FromRgb(0x808080), // Gray
        Color.FromRgb(0x000000), // Black
    };
}