Internal/StatefulDrawPanel.cs
using System;
using Sandbox;
using Sandbox.Rendering;
using Sandbox.UI;

namespace Goo.Internal;

internal sealed class StatefulDrawPanel : Panel, IPanelDraw, IStatefulHost, IStatefulEventHost
{
    StateController? _state;

    internal Action<MousePanelEvent>? _onClick;
    internal Action<MousePanelEvent>? _onMouseEnter;
    internal Action<MousePanelEvent>? _onMouseLeave;
    internal Action<MousePanelEvent>? _onMouseDown;
    internal Action<MousePanelEvent>? _onMouseUp;
    internal Action<MousePanelEvent>? _onMouseMove;
    internal bool _userSetPointerEvents;
    internal Action? _requestRebuild;
    public Action? RequestRebuild { set => _requestRebuild = value; }

    internal Action<DragEvent>?  _onDragStart;
    internal Action<DragEvent>?  _onDrag;
    internal Action<DragEvent>?  _onDragEnd;
    internal Action<PanelEvent>? _onDragEnter;
    internal Action<PanelEvent>? _onDragLeave;
    internal Action<PanelEvent>? _onDrop;
    bool _wantsDrag;

    // User custom-draw state.
    internal DrawState _drawState = DrawState.Empty;

    public void ApplyStateVariants(
        Color? baseBg,  Color? baseFg,
        Color? hoverBg, Color? activeBg, Color? focusBg,
        Color? hoverFg, Color? activeFg, Color? focusFg,
        int? transitionMs)
    {
        _state ??= new StateController(this);
        _state.ApplyVariants(
            baseBg, baseFg,
            hoverBg, activeBg, focusBg,
            hoverFg, activeFg, focusFg,
            transitionMs);
    }

    public void ApplyEvents(in BlobEvents events)
    {
        _onClick      = events.OnClick;
        _onMouseEnter = events.OnMouseEnter;
        _onMouseLeave = events.OnMouseLeave;
        _onMouseDown  = events.OnMouseDown;
        _onMouseUp    = events.OnMouseUp;
        _onMouseMove  = events.OnMouseMove;
        _onDragStart = events.OnDragStart;
        _onDrag      = events.OnDrag;
        _onDragEnd   = events.OnDragEnd;
        _onDragEnter = events.OnDragEnter;
        _onDragLeave = events.OnDragLeave;
        _onDrop      = events.OnDrop;
        _wantsDrag   = events.WantsDrag;
    }

    public bool HasEventHandlers =>
        _onClick != null || _onMouseEnter != null || _onMouseLeave != null ||
        _onMouseDown != null || _onMouseUp != null || _onMouseMove != null ||
        _onDragStart != null || _onDrag != null || _onDragEnd != null ||
        _onDragEnter != null || _onDragLeave != null || _onDrop != null;

    public bool UserSetPointerEvents
    {
        get => _userSetPointerEvents;
        set => _userSetPointerEvents = value;
    }

    // IPanelDraw paint pass for the user Material recipe and Draw callback. BackgroundColor goes through the engine's batched background path instead.
    public void Draw(CommandList cl)
    {
        var rect = Box.Rect;

        if (_drawState.Material is Material userMat)
        {
            if (!_drawState.Uniforms.IsDefaultOrEmpty)
                foreach (var u in _drawState.Uniforms)
                    ApplyUniform(cl, u);
            cl.DrawQuad(rect, userMat, Color.White);
        }

        _drawState.Draw?.Invoke(cl, rect);
    }

    static void ApplyUniform(CommandList cl, in UniformValue u)
    {
        switch (u.Kind)
        {
            case UniformKind.Color:   cl.Attributes.Set(u.Name, u.ColorValue); break;
            case UniformKind.Float:   cl.Attributes.Set(u.Name, u.FloatValue); break;
            case UniformKind.Int:     cl.Attributes.Set(u.Name, u.IntValue); break;
            case UniformKind.Bool:    cl.Attributes.Set(u.Name, u.BoolValue ? 1 : 0); break;
            case UniformKind.Vec2:    cl.Attributes.Set(u.Name, new Vector2(u.VecValue.x, u.VecValue.y)); break;
            case UniformKind.Vec3:    cl.Attributes.Set(u.Name, new Vector3(u.VecValue.x, u.VecValue.y, u.VecValue.z)); break;
            case UniformKind.Vec4:    cl.Attributes.Set(u.Name, u.VecValue); break;
            case UniformKind.Texture: if (u.TextureValue is not null) cl.Attributes.Set(u.Name, u.TextureValue); break;
        }
    }

    protected override void OnClick(MousePanelEvent e)
    {
        base.OnClick(e);
        EventDispatch.Fire(_onClick, e, _requestRebuild);
    }

    protected override void OnRightClick(MousePanelEvent e)
    {
        base.OnRightClick(e);
        EventDispatch.Fire(_onClick, e, _requestRebuild);
    }

    protected override void OnMiddleClick(MousePanelEvent e)
    {
        base.OnMiddleClick(e);
        EventDispatch.Fire(_onClick, e, _requestRebuild);
    }

    protected override void OnMouseOver(MousePanelEvent e)
    {
        base.OnMouseOver(e);
        EventDispatch.Fire(_onMouseEnter, e, _requestRebuild);
    }

    protected override void OnMouseOut(MousePanelEvent e)
    {
        base.OnMouseOut(e);
        EventDispatch.Fire(_onMouseLeave, e, _requestRebuild);
    }

    protected override void OnMouseDown(MousePanelEvent e)
    {
        base.OnMouseDown(e);
        EventDispatch.Fire(_onMouseDown, e, _requestRebuild);
    }

    protected override void OnMouseUp(MousePanelEvent e)
    {
        base.OnMouseUp(e);
        EventDispatch.Fire(_onMouseUp, e, _requestRebuild);
    }

    protected override void OnMouseMove(MousePanelEvent e)
    {
        base.OnMouseMove(e);
        EventDispatch.Fire(_onMouseMove, e, _requestRebuild);
    }

    public override bool WantsDrag => _wantsDrag;

    protected override void OnDragStart( DragEvent e ) => _onDragStart?.Invoke( e );
    protected override void OnDrag( DragEvent e ) => _onDrag?.Invoke( e );
    protected override void OnDragEnd( DragEvent e ) => _onDragEnd?.Invoke( e );

    protected override void OnDragEnter( PanelEvent e )
    {
        base.OnDragEnter( e );
        _onDragEnter?.Invoke( e );
    }

    protected override void OnDragLeave( PanelEvent e )
    {
        base.OnDragLeave( e );
        _onDragLeave?.Invoke( e );
    }

    protected override void OnDrop( PanelEvent e )
    {
        base.OnDrop( e );
        _onDrop?.Invoke( e );
    }
}