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()
{
}
}