Editor/DoubleFloatSlider.cs
using Editor;
using Sandbox;
using System;

namespace ExtendedBox;

public sealed class DoubleFloatSlider : Widget
{
    private float _valueA;
    private float _valueB;


    private const float BackgroundOffsetX = 2.5f;
    private const float ThumbWidth = 5f;

    private const float ThumbHeight = 12f;


    public float Minimum { get; set; }

    public float Maximum { get; set; }

    public Action? OnValueEdited { get; set; }

    public Color HighlightColor { get; set; } = Theme.TextLight;


    public Action<Rect, float, float>? SliderPaint { get; set; }

    public Action? EditingStarted { get; set; }
    public Action? EditingFinished { get; set; }


    public float ValueA
    {
        get
        {
            return _valueA;
        }
        set
        {
            float v = ((Step > 0f) ? value.SnapToGrid(Step) : value);
            v = v.Clamp(Minimum, Maximum);
            if(_valueA != v)
            {
                _valueA = v;
                Update();
            }
        }
    }

    public float ValueB
    {
        get
        {
            return _valueB;
        }
        set
        {
            float v = ((Step > 0f) ? value.SnapToGrid(Step) : value);
            v = v.Clamp(Minimum, Maximum);
            if(_valueB != v)
            {
                _valueB = v;
                Update();
            }
        }
    }


    public float DeltaValueA
    {
        get
        {
            return ValueA.LerpInverse(Minimum, Maximum);
        }
        set
        {
            float value2 = Minimum.LerpTo(Maximum, value);
            ValueA = value2;
        }
    }

    public float DeltaValueB
    {
        get
        {
            return ValueB.LerpInverse(Minimum, Maximum);
        }
        set
        {
            float value2 = Minimum.LerpTo(Maximum, value);
            ValueB = value2;
        }
    }


    public float Step { get; set; } = 0.01f;


    private bool? _editingA = true;


    public DoubleFloatSlider(Widget parent)
        : base(parent)
    {
        Minimum = 0f;
        Maximum = 100f;
        ValueA = 25f;
        ValueB = 50f;

        base.MinimumSize = Theme.RowHeight;
        base.MaximumSize = new Vector2(4096f, Theme.RowHeight);
        Cursor = CursorShape.Arrow;
    }

    protected override void OnMouseEnter()
    {
        base.OnMouseEnter();
        Update();
    }

    protected override void OnMouseLeave()
    {
        base.OnMouseLeave();
        Update();
    }

    internal void PaintSlider(Rect rect, float deltaA, float deltaB)
    {
        float backgroundBorderRadius = 3f;
        float thumbBorderRadius = 2f;

        float backgroundHeight = 5f;
        float halfHeight = rect.Height / 2f;
        float width = rect.Width;
        float backgroundWidth = width - BackgroundOffsetX * 2;

        Color fillerColor = HighlightColor;
        Paint.Antialiasing = true;
        Paint.ClearPen();

        //background
        var backgroundPoistionY = halfHeight - backgroundHeight * 0.5f;

        Color backgroundColor = fillerColor.WithAlpha(0.2f);
        Paint.SetBrush(in backgroundColor);
        var backgroundRect = new Rect(BackgroundOffsetX, backgroundPoistionY, in backgroundWidth, in backgroundHeight);
        Paint.DrawRect(in backgroundRect, backgroundBorderRadius);

        var thumbAPositionX = backgroundWidth * deltaA;
        var thumbBPositionX = backgroundWidth * deltaB;

        var thumpPositionY = halfHeight - ThumbHeight / 2f;

        var minThumbPosition = Math.Min(thumbAPositionX, thumbBPositionX);
        var maxThumbPosition = Math.Max(thumbAPositionX, thumbBPositionX);

        //filler
        Paint.SetBrush(in fillerColor);
        var fillerRect = new Rect(minThumbPosition, backgroundPoistionY, maxThumbPosition - minThumbPosition + ThumbWidth, in backgroundHeight);
        Paint.DrawRect(in fillerRect, backgroundBorderRadius);


        //thumb
        var thumbColor = Color.Black.WithAlpha(0.2f);
        PenStyle thumbStyle = PenStyle.Solid;

        Paint.SetBrush(in fillerColor);
        Paint.SetPen(in thumbColor, 1f, in thumbStyle);

        var thumbARect = new Rect(thumbAPositionX, in thumpPositionY, ThumbWidth, ThumbHeight);
        Paint.DrawRect(in thumbARect, thumbBorderRadius);

        var thumbBRect = new Rect(thumbBPositionX, in thumpPositionY, ThumbWidth, ThumbHeight);
        Paint.DrawRect(in thumbBRect, thumbBorderRadius);
    }

    protected override void OnPaint()
    {
        base.OnPaint();

        if(SliderPaint == null)
            PaintSlider(base.LocalRect, DeltaValueA, DeltaValueB);
        else
            SliderPaint(base.LocalRect, DeltaValueA, DeltaValueB);
    }

    private void UpdateClosestFromLocalPosition(float position)
    {
        if(ReadOnly)
            return;

        float v = (position - BackgroundOffsetX) / (base.Width - BackgroundOffsetX * 2);

        if(!_editingA.HasValue)
            _editingA = Math.Abs(DeltaValueA - v) < Math.Abs(DeltaValueB - v);

        if(_editingA.Value)
            DeltaValueA = v.Clamp(0f, 1f);
        else
            DeltaValueB = v.Clamp(0f, 1f);

        OnValueEdited?.Invoke();
        SignalValuesChanged();
    }

    protected override void OnMousePress(MouseEvent e)
    {
        if(!ReadOnly)
        {
            e.Accepted = true;
            if(e.LeftMouseButton)
            {
                EditingStarted?.Invoke();
                UpdateClosestFromLocalPosition(e.LocalPosition.x);
            }
        }
    }

    protected override void OnMouseMove(MouseEvent e)
    {
        if(!ReadOnly)
        {
            e.Accepted = true;
            if(e.ButtonState.Contains(MouseButtons.Left))
            {
                UpdateClosestFromLocalPosition(e.LocalPosition.x);
            }
        }
    }

    protected override void OnMouseReleased(MouseEvent e)
    {
        base.OnMouseReleased(e);
        e.Accepted = true;
        if(e.LeftMouseButton)
        {
            _editingA = null;
            EditingFinished?.Invoke();
        }
    }
}