Editor/FXParticleControlWidget.cs
using Editor;
using fxbox;
using Sandbox;

namespace Editor;

[CustomEditor(typeof(FXParticleFloat))]
public class FXParticleFloatControlWidget : ControlWidget
{
    public Color HighlightColor { get; set; }
    public string Label { get; set; }

    Layout ControlArea;
    SerializedObject Target;
    Button ModeSwitchButton;
    Button ToggleButton;
    
    SerializedProperty ValueProperty;
    SerializedObject ValueTarget;

    public FXParticleFloatControlWidget(SerializedProperty property) : this(property, "f", Theme.Green)
    {
    }

    public FXParticleFloatControlWidget(SerializedProperty property, string label, Color color) : base(property)
    {
        SetSizeMode(SizeMode.Ignore, SizeMode.Default);

        if (!property.TryGetAsObject(out Target))
            return;

        Label = label;
        HighlightColor = color;

        Layout = Layout.Row();
        Layout.Spacing = 3;

        Layout.AddStretchCell();

        // Add toggle button for UseParameter
        ToggleButton = new Button();
        ToggleButton.Text = "P";
        ToggleButton.ToolTip = "Toggle Parameter Mode";
        ToggleButton.FixedWidth = Theme.RowHeight;
        ToggleButton.Pressed = () => ToggleParameterMode();
        ToggleButton.OnPaintOverride = PaintToggleButton;
        Layout.Add(ToggleButton);

        ModeSwitchButton = new Button();
        ModeSwitchButton.Text = "Mode";
        ModeSwitchButton.OnPaintOverride = PaintButton;
        ModeSwitchButton.Pressed = () => OpenPopup(ModeSwitchButton.ScreenRect);
        ModeSwitchButton.FixedWidth = Theme.RowHeight;

        Layout.Add(ModeSwitchButton);

        ControlArea = Layout.AddRow(1);
        ControlArea.Spacing = 2;

        Target.OnPropertyChanged += (p) =>
        {
            if (p.Name == "UseParameter")
            {
                Rebuild();
                return;
            }
            
            if (!Target.GetProperty("UseParameter").GetValue<bool>())
            {
                // Only rebuild for Value changes when not using parameters
                ValueProperty = Target.GetProperty("Value");
                if (ValueProperty.TryGetAsObject(out ValueTarget))
                {
                    if (p.Name == "Type" || p.Name == "Evaluation")
                    {
                        Rebuild();
                    }
                }
            }
        };

        Rebuild();
    }

    private void ToggleParameterMode()
    {
        var useParam = Target.GetProperty("UseParameter");
        useParam.SetValue(!useParam.GetValue<bool>());
        Rebuild();
    }

    private bool PaintToggleButton()
    {
        Paint.Antialiasing = true;

        var useParam = Target.GetProperty("UseParameter").GetValue<bool>();

        if (Paint.HasPressed)
        {
            Paint.SetBrushAndPen(Theme.ControlBackground.Lighten(0.3f));
            Paint.DrawRect(Paint.LocalRect.Shrink(0.5f), Theme.ControlRadius);
            Paint.Pen = useParam ? Theme.Blue.Lighten(0.4f) : Theme.TextControl.Lighten(0.4f);
        }
        else if (Paint.HasMouseOver)
        {
            Paint.SetBrushAndPen(Theme.TextControl.Lighten(0.2f).WithAlpha(0.1f));
            Paint.DrawRect(Paint.LocalRect.Shrink(1), Theme.ControlRadius);
            Paint.Pen = useParam ? Theme.Blue.Lighten(0.5f) : Theme.TextControl.Lighten(0.5f);
        }
        else
        {
            Paint.Pen = useParam ? Theme.Blue : Theme.TextControl.WithAlpha(0.5f);
        }

        Paint.DrawIcon(Paint.LocalRect, "tune", 15, TextFlag.Center);

        return true;
    }

    private void OpenPopup(Rect parentRect)
    {
        // Only show popup if not in parameter mode
        if (Target.GetProperty("UseParameter").GetValue<bool>())
            return;

        ValueProperty = Target.GetProperty("Value");
        if (!ValueProperty.TryGetAsObject(out ValueTarget))
            return;

        var popup = new FXParticleFloatConfigPopup(ValueTarget, this);
        popup.Position = parentRect.BottomRight;
        popup.AdjustSize();
        popup.Position -= new Vector2(popup.Width, 0);

        popup.Show();
        popup.ConstrainToScreen();
    }

    bool PaintButton()
    {
        Paint.Antialiasing = true;

        var useParam = Target.GetProperty("UseParameter").GetValue<bool>();
        
        // Disable button appearance if in parameter mode
        if (useParam)
        {
            Paint.Pen = Theme.TextControl.WithAlpha(0.2f);
            Paint.DrawIcon(Paint.LocalRect, "block", 11, TextFlag.Center);
            return true;
        }

        ValueProperty = Target.GetProperty("Value");
        if (!ValueProperty.TryGetAsObject(out ValueTarget))
            return true;

        if (Paint.HasPressed)
        {
            Paint.SetBrushAndPen(Theme.ControlBackground.Lighten(0.3f));
            Paint.DrawRect(Paint.LocalRect.Shrink(0.5f), Theme.ControlRadius);
            Paint.Pen = Theme.TextControl.Lighten(0.4f);
        }
        else if (Paint.HasMouseOver)
        {
            Paint.SetBrushAndPen(Theme.TextControl.Lighten(0.2f).WithAlpha(0.1f));
            Paint.DrawRect(Paint.LocalRect.Shrink(1), Theme.ControlRadius);
            Paint.Pen = Theme.TextControl.Lighten(0.5f);
        }
        else
        {
            Paint.Pen = Theme.TextControl.WithAlpha(0.5f);
        }

        var type = ValueTarget.GetProperty("Type").GetValue<ParticleFloat.ValueType>();
        var eval = ValueTarget.GetProperty("Evaluation").GetValue<ParticleFloat.EvaluationType>();

        var icon = "people";
        float iconSize = 15;

        if (type == ParticleFloat.ValueType.Constant)
        {
            icon = "radio_button_unchecked";
            Paint.Pen = Paint.Pen.WithAlpha(0.3f);
            iconSize = 11;
        }
        else
        {
            if (eval == ParticleFloat.EvaluationType.Seed)
            {
                icon = "scatter_plot";
            }

            if (eval == ParticleFloat.EvaluationType.Life)
            {
                icon = "play_arrow";
            }

            if (eval == ParticleFloat.EvaluationType.Frame)
            {
                icon = "casino";
            }
        }

        Paint.DrawIcon(Paint.LocalRect, icon, iconSize, TextFlag.Center);

        return true;
    }

    void Rebuild()
    {
        ControlArea.Clear(true);

        var useParam = Target.GetProperty("UseParameter").GetValue<bool>();

        if (useParam)
        {
            // Parameter mode - show parameter selector and multiplier
            var paramName = Target.GetProperty("ParameterName");
            var multiplier = Target.GetProperty("Multiplier");

            var paramControl = new ParameterNameControlWidget(paramName);
            ControlArea.Add(paramControl, 1);

            var multControl = new FloatControlWidget(multiplier)
            {
                HighlightColor = Theme.Highlight,
                Label = "×"
            };
            ControlArea.Add(multControl);
        }
        else
        {
            // Normal mode - show particle float controls
            ValueProperty = Target.GetProperty("Value");
            if (ValueProperty.TryGetAsObject(out ValueTarget))
            {
                var type = ValueTarget.GetProperty("Type").GetValue<ParticleFloat.ValueType>();
                var eval = ValueTarget.GetProperty("Evaluation").GetValue<ParticleFloat.EvaluationType>();
                RebuildForType(type, eval);
            }
        }

        Update();
    }

    void RebuildForType(ParticleFloat.ValueType type, ParticleFloat.EvaluationType eval)
    {
        ModeSwitchButton.ToolTip = GetTypeName(type, eval);

        if (type == ParticleFloat.ValueType.Constant)
        {
            var control = new FloatControlWidget(ValueTarget.GetProperty("ConstantValue"))
            {
                HighlightColor = HighlightColor,
                Label = Label
            };
            ControlArea.Add(control);
        }

        if (type == ParticleFloat.ValueType.Range)
        {
            var controlA = new FloatControlWidget(ValueTarget.GetProperty("ConstantA"))
            {
                HighlightColor = HighlightColor,
                Label = Label
            };
            ControlArea.Add(controlA);

            var controlB = new FloatControlWidget(ValueTarget.GetProperty("ConstantB"))
            {
                HighlightColor = HighlightColor,
                Label = Label
            };
            ControlArea.Add(controlB);
        }

        if (type == ParticleFloat.ValueType.Curve)
        {
            var controlA = new CurveControlWidget(ValueTarget.GetProperty("CurveA"))
            {
                HighlightColor = HighlightColor
            };
            ControlArea.Add(controlA);
        }

        if (type == ParticleFloat.ValueType.CurveRange)
        {
            var controlA = new CurveRangeControlWidget(ValueTarget.GetProperty("CurveRange"))
            {
                HighlightColor = HighlightColor
            };
            ControlArea.Add(controlA);
        }
    }

    string GetTypeName(ParticleFloat.ValueType type, ParticleFloat.EvaluationType eval)
    {
        switch (type)
        {
            case ParticleFloat.ValueType.Constant:
                return "Constant value";
            case ParticleFloat.ValueType.Curve:
                {
                    switch (eval)
                    {
                        case ParticleFloat.EvaluationType.Seed:
                            return "Random from curve per particle";
                        case ParticleFloat.EvaluationType.Frame:
                            return "Random from curve";
                        case ParticleFloat.EvaluationType.Life:
                            return "Curve over lifetime";
                        default:
                            return "Unknown";
                    }
                }
            case ParticleFloat.ValueType.CurveRange:
                switch (eval)
                {
                    case ParticleFloat.EvaluationType.Seed:
                        return "Random from range, per particle";
                    case ParticleFloat.EvaluationType.Frame:
                        return "Random from range (per frame)";
                    case ParticleFloat.EvaluationType.Life:
                        return "path between curve over lifetime, per particle";
                    default:
                        return "Unknown";
                }
            case ParticleFloat.ValueType.Range:
                {
                    switch (eval)
                    {
                        case ParticleFloat.EvaluationType.Seed:
                            return "Between range, per particle";
                        case ParticleFloat.EvaluationType.Frame:
                            return "Random between range (per frame)";
                        case ParticleFloat.EvaluationType.Life:
                            return "Lerp between range over lifetime";
                        default:
                            return "Unknown";
                    }
                }
        }

        return "Unknown Combo";
    }

    protected override void OnPaint()
    {
    }
}

file class FXParticleFloatConfigPopup : PopupWidget
{
    SerializedObject SerializedObject;
    SerializedProperty Type;
    SerializedProperty Eval;

    public FXParticleFloatConfigPopup(SerializedObject target, Widget parent) : base(parent)
    {
        Layout = Layout.Column();
        Layout.Spacing = 8;
        Layout.Margin = 16;

        SerializedObject = target;
        Type = target.GetProperty("Type");
        Eval = target.GetProperty("Evaluation");

        AddQuickModes(Type.GetValue<ParticleFloat.ValueType>(), Eval.GetValue<ParticleFloat.EvaluationType>());
    }

    void AddQuickModes(ParticleFloat.ValueType type, ParticleFloat.EvaluationType eval)
    {
        var grid = new GridLayout();
        grid.Spacing = 8;

        grid.AddCell(0, 0, MakeQuickMode("radio_button_unchecked", "Constant", "Value is constant. It stays the same. It doesn't change", ParticleFloat.ValueType.Constant, ParticleFloat.EvaluationType.Life));
        grid.AddCell(1, 0, MakeQuickMode("casino", "Random", "Choose a value between two constants every frame", ParticleFloat.ValueType.Range, ParticleFloat.EvaluationType.Frame));

        grid.AddCell(0, 1, MakeQuickMode("hdr_strong", "Range", "Choose a value between two constants", ParticleFloat.ValueType.Range, ParticleFloat.EvaluationType.Seed));
        grid.AddCell(1, 1, MakeQuickMode("animation", "Lerp", "Lerp between two values over the lifetime of the particle", ParticleFloat.ValueType.Range, ParticleFloat.EvaluationType.Life));

        grid.AddCell(0, 2, MakeQuickMode("show_chart", "Curve", "Get the value by querying a curve over the particle's lifetime", ParticleFloat.ValueType.Curve, ParticleFloat.EvaluationType.Life));
        grid.AddCell(1, 2, MakeQuickMode("area_chart", "Curve with Range", "Choose a path between two curves - over the lifetime of the particle", ParticleFloat.ValueType.CurveRange, ParticleFloat.EvaluationType.Life));

        Layout.Add(grid);
    }

    private Widget MakeQuickMode(string icon, string label, string description, ParticleFloat.ValueType t, ParticleFloat.EvaluationType e)
    {
        var b = new Widget();

        b.Cursor = CursorShape.Finger;
        b.Layout = new IconTitleDescriptionLayout(icon, label, description);
        b.Layout.Margin = new Sandbox.UI.Margin(8, 4);
        b.SetStyles("color: #ffffff;");

        bool isCurrent = t == Type.GetValue<ParticleFloat.ValueType>() && e == Eval.GetValue<ParticleFloat.EvaluationType>();

        b.MouseClick += () =>
        {
            Type.SetValue(t);
            Eval.SetValue(e);
            Close();
        };

        b.OnPaintOverride = () =>
        {
            if (isCurrent || Paint.HasMouseOver)
            {
                Paint.SetBrushAndPen(Theme.Blue.Darken(0.5f).WithAlpha(0.5f));
                Paint.DrawRect(Paint.LocalRect, 4);
            }

            return true;
        };

        return b;
    }
}

file class IconTitleDescriptionLayout : GridLayout
{
    public IconTitleDescriptionLayout(string icon, string title, string description)
    {
        VerticalSpacing = 0;
        HorizontalSpacing = 8;

        var iconLabel = AddCell(0, 0, new IconButton(icon) { Background = Color.Transparent, IconSize = 33, FixedSize = 40, TransparentForMouseEvents = true }, ySpan: 2);
        var titleLabel = AddCell(1, 0, new Label(title));
        var descLabel = AddCell(1, 1, new Label(description) { WordWrap = true });

        titleLabel.SetStyles("font-size: 13px; font-family: Poppins; font-weight: bold;");
        descLabel.SetStyles("font-size: 9px; font-family: Poppins;");
        descLabel.SetEffectOpacity(0.5f);
    }
}

[CustomEditor(typeof(FXParticleVector))]
public class FXParticleVectorControlWidget : ControlWidget
{
    private SerializedObject Target;
    private Button ToggleButton;
    private Layout ControlArea;

    public FXParticleVectorControlWidget(SerializedProperty property) : base(property)
    {
        SetSizeMode(SizeMode.Ignore, SizeMode.Default);

        if (!property.TryGetAsObject(out Target))
            return;

        Layout = Layout.Row();
        Layout.Spacing = 3;

        Layout.AddStretchCell();

        // Add toggle button for UseParameter
        ToggleButton = new Button();
        ToggleButton.Text = "P";
        ToggleButton.ToolTip = "Toggle Parameter Mode";
        ToggleButton.FixedWidth = Theme.RowHeight;
        ToggleButton.Pressed = () => ToggleParameterMode();
        ToggleButton.OnPaintOverride = PaintToggleButton;
        Layout.Add(ToggleButton);

        ControlArea = Layout.AddRow(1);
        ControlArea.Spacing = 2;

        Target.OnPropertyChanged += (p) =>
        {
            if (p.Name == "UseParameter")
            {
                Rebuild();
            }
        };

        Rebuild();
    }

    private void ToggleParameterMode()
    {
        var useParam = Target.GetProperty("UseParameter");
        useParam.SetValue(!useParam.GetValue<bool>());
        Rebuild();
    }

    private bool PaintToggleButton()
    {
        Paint.Antialiasing = true;

        var useParam = Target.GetProperty("UseParameter").GetValue<bool>();

        if (Paint.HasPressed)
        {
            Paint.SetBrushAndPen(Theme.ControlBackground.Lighten(0.3f));
            Paint.DrawRect(Paint.LocalRect.Shrink(0.5f), Theme.ControlRadius);
            Paint.Pen = useParam ? Theme.Blue.Lighten(0.4f) : Theme.TextControl.Lighten(0.4f);
        }
        else if (Paint.HasMouseOver)
        {
            Paint.SetBrushAndPen(Theme.TextControl.Lighten(0.2f).WithAlpha(0.1f));
            Paint.DrawRect(Paint.LocalRect.Shrink(1), Theme.ControlRadius);
            Paint.Pen = useParam ? Theme.Blue.Lighten(0.5f) : Theme.TextControl.Lighten(0.5f);
        }
        else
        {
            Paint.Pen = useParam ? Theme.Blue : Theme.TextControl.WithAlpha(0.5f);
        }

        Paint.DrawIcon(Paint.LocalRect, "tune", 15, TextFlag.Center);

        return true;
    }

    void Rebuild()
    {
        ControlArea.Clear(true);

        var useParam = Target.GetProperty("UseParameter").GetValue<bool>();

        if (useParam)
        {
            // Parameter mode - show parameter selector and multiplier
            var paramName = Target.GetProperty("ParameterName");
            var multiplier = Target.GetProperty("Multiplier");

            var paramControl = new VectorParameterNameControlWidget(paramName);
            ControlArea.Add(paramControl, 1);

            var multControl = new FloatControlWidget(multiplier)
            {
                HighlightColor = Theme.Highlight,
                Label = "×"
            };
            ControlArea.Add(multControl);
        }
        else
        {
            // Normal mode - show vector control
            var valueProp = Target.GetProperty("Value");
            var control = ControlWidget.Create(valueProp);
            if (control != null)
            {
                ControlArea.Add(control, 1);
            }
        }

        Update();
    }

    protected override void OnPaint()
    {
    }
}

[CustomEditor(typeof(FXParticleColor))]
public class FXParticleColorControlWidget : ControlWidget
{
    private SerializedObject Target;
    private Button ToggleButton;
    private Layout ControlArea;

    public FXParticleColorControlWidget(SerializedProperty property) : base(property)
    {
        SetSizeMode(SizeMode.Ignore, SizeMode.Default);

        if (!property.TryGetAsObject(out Target))
            return;

        Layout = Layout.Row();
        Layout.Spacing = 3;

        Layout.AddStretchCell();

        // Add toggle button for UseParameter
        ToggleButton = new Button();
        ToggleButton.Text = "P";
        ToggleButton.ToolTip = "Toggle Parameter Mode";
        ToggleButton.FixedWidth = Theme.RowHeight;
        ToggleButton.Pressed = () => ToggleParameterMode();
        ToggleButton.OnPaintOverride = PaintToggleButton;
        Layout.Add(ToggleButton);

        ControlArea = Layout.AddRow(1);
        ControlArea.Spacing = 2;

        Target.OnPropertyChanged += (p) =>
        {
            if (p.Name == "UseParameter")
            {
                Rebuild();
            }
        };

        Rebuild();
    }

    private void ToggleParameterMode()
    {
        var useParam = Target.GetProperty("UseParameter");
        useParam.SetValue(!useParam.GetValue<bool>());
        Rebuild();
    }

    private bool PaintToggleButton()
    {
        Paint.Antialiasing = true;

        var useParam = Target.GetProperty("UseParameter").GetValue<bool>();

        if (Paint.HasPressed)
        {
            Paint.SetBrushAndPen(Theme.ControlBackground.Lighten(0.3f));
            Paint.DrawRect(Paint.LocalRect.Shrink(0.5f), Theme.ControlRadius);
            Paint.Pen = useParam ? Theme.Blue.Lighten(0.4f) : Theme.TextControl.Lighten(0.4f);
        }
        else if (Paint.HasMouseOver)
        {
            Paint.SetBrushAndPen(Theme.TextControl.Lighten(0.2f).WithAlpha(0.1f));
            Paint.DrawRect(Paint.LocalRect.Shrink(1), Theme.ControlRadius);
            Paint.Pen = useParam ? Theme.Blue.Lighten(0.5f) : Theme.TextControl.Lighten(0.5f);
        }
        else
        {
            Paint.Pen = useParam ? Theme.Blue : Theme.TextControl.WithAlpha(0.5f);
        }

        Paint.DrawIcon(Paint.LocalRect, "tune", 15, TextFlag.Center);

        return true;
    }

    void Rebuild()
    {
        ControlArea.Clear(true);

        var useParam = Target.GetProperty("UseParameter").GetValue<bool>();

        if (useParam)
        {
            // Parameter mode - show parameter selector and multiplier
            var paramName = Target.GetProperty("ParameterName");
            var multiplier = Target.GetProperty("Multiplier");

            var paramControl = new ColorParameterNameControlWidget(paramName);
            ControlArea.Add(paramControl, 1);

            var multControl = new FloatControlWidget(multiplier)
            {
                HighlightColor = Theme.Highlight,
                Label = "×"
            };
            ControlArea.Add(multControl);
        }
        else
        {
            // Normal mode - show color control
            var valueProp = Target.GetProperty("Value");
            var control = ControlWidget.Create(valueProp);
            if (control != null)
            {
                ControlArea.Add(control, 1);
            }
        }

        Update();
    }

    protected override void OnPaint()
    {
    }
}