UI/MenuSystem/Pages/OpiumSlider.razor
@using System
@namespace Sandbox.UI
@inherits BaseControl

<root class="slidercontrol">

    <div class="inner">

        @if (ShowRange)
        {
            <div class="values">
                <div class="left">@Min.ToString( NumberFormat )</div>
               <div class="right">@Max.ToString( NumberFormat )</div>
            </div>
        }

        <div @ref="TrackPanel" class="track">
            <div class="track-active" style="width: @(SliderPosition)%;"></div>
            <div @ref="ThumbPanel" style="left: @(SliderPosition)%;" class="thumb">

                @if (this.HasActive)
                {
                    <div class="value-tooltip">
                        <label>@Value.ToString( NumberFormat )</label>
                        <div class="tail"></div>
                    </div>
                }

            </div>
        </div>

    </div>

    @if ( ShowTextEntry )
    {
        <div class="entry">
            <TextEntry @ref="TextEntryPanel" Value:bind="@Value" Numeric="@true" NumberFormat="@NumberFormat"></TextEntry>
        </div>
    }

</root>

@code
{
    public Action<float> OnValueChanged { get; set; }

    /// <summary>
    /// The right side of the slider.
    /// </summary>
    public float Max { get; set; } = 100;


    /// <summary>
    /// The left side of the slider.
    /// </summary>
    public float Min { get; set; } = 0;

    /// <summary>
    /// If set to 1, value will be rounded to 1's
    /// If set to 10, value will be rounded to 10's
    /// If set to 0.1, value will be rounded to 0.1's
    /// </summary>
    public float Step { get; set; } = 1.0f;

    /// <summary>
    /// Show the range values above the slider
    /// </summary>
    public bool ShowRange { get; set; } = false;

    /// <summary>
    /// When changing the value show the tooltip
    /// </summary>
    public bool ShowValueTooltip { get; set; } = true;

    /// <summary>
    /// When changing the value show the tooltip
    /// </summary>
    public bool ShowTextEntry { get; set; } = false;

    /// <summary>
    /// How to display numbers in this control
    /// </summary>
    public string NumberFormat { get; set; } = "0.###";

    float _value;

    public float Value
    {
        get => _value;

        set
        {
            if (_value == value)
                return;

            _value = MathX.Clamp( _value, Min, Max );

            _value = value;
            StateHasChanged();
        }
    }

    Panel TrackPanel { get; set; }
    Panel ThumbPanel { get; set; }
    TextEntry TextEntryPanel { get; set; }

    public OpiumSlider()
    {

    }

    public OpiumSlider( float min, float max, float step = 1.0f )
    {
        Min = min;
        Max = max;
        Step = step;
    }

    /// <summary>
    /// Convert a screen position to a value. The value is clamped, but not snapped.
    /// </summary>
    public virtual float ScreenPosToValue( Vector2 pos )
    {
        var normalized = MathX.LerpInverse(pos.x, TrackPanel.Box.Left, TrackPanel.Box.Right, true);
        var scaled = MathX.LerpTo( Min, Max, normalized, true );
        return Step > 0 ? scaled.SnapToGrid( Step ) : scaled;
    }

    /// <summary>
    /// If we move the mouse while we're being pressed then set the value
    /// </summary>
    protected override void OnMouseMove( MousePanelEvent e )
    {
        base.OnMouseMove( e );

        if ( !HasActive || e.MouseButton == MouseButtons.Middle ) return;

        Value = ScreenPosToValue( Mouse.Position );
        OnValueChanged?.Invoke( Value );
        e.StopPropagation();
    }

    /// <summary>
    /// On mouse press jump to that position
    /// </summary>
    protected override void OnMouseDown( MousePanelEvent e )
    {
        base.OnMouseDown( e );

        Value = ScreenPosToValue( Mouse.Position );
        OnValueChanged?.Invoke( Value );
        e.StopPropagation();

        TextEntryPanel?.Blur();
    }

    protected override void OnMiddleClick( MousePanelEvent e )
    {
        base.OnMiddleClick( e );
        e.StopPropagation();
    }

    float SliderPosition => MathX.LerpInverse(Value, Min, Max, true) * 100.0f;
}