Files
sbox-public/game/addons/tools/Code/Widgets/ControlWidgets/ParticleFloatControlWidget.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

323 lines
9.3 KiB
C#

namespace Editor;
[CustomEditor( typeof( ParticleFloat ) )]
public class ParticleFloatControlWidget : ControlWidget
{
public Color HighlightColor { get; set; }
public string Label { get; set; }
Layout ControlArea;
SerializedObject Target;
Button ModeSwitchButton;
public ParticleFloatControlWidget( SerializedProperty property ) : this( property, "f", Theme.Green )
{
}
public ParticleFloatControlWidget( 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();
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 != "Type" && p.Name != "Evaluation" ) return;
Rebuild();
};
Rebuild();
}
private void OpenPopup( Rect parentRect )
{
var popup = new ParticleFloatConfigPopup( Target, this );
popup.Position = parentRect.BottomRight;
popup.AdjustSize();
popup.Position -= new Vector2( popup.Width, 0 );
popup.Show();
popup.ConstrainToScreen();
}
bool PaintButton()
{
Paint.Antialiasing = 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 = Target.GetProperty( "Type" ).GetValue<ParticleFloat.ValueType>();
var eval = Target.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";
}
}
var iconRect = Paint.DrawIcon( Paint.LocalRect, icon, iconSize, TextFlag.Center );
return true;
}
void Rebuild()
{
RebuildForType( Target.GetProperty( "Type" ).GetValue<ParticleFloat.ValueType>(), Target.GetProperty( "Evaluation" ).GetValue<ParticleFloat.EvaluationType>() );
}
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";
}
void RebuildForType( ParticleFloat.ValueType type, ParticleFloat.EvaluationType eval )
{
ControlArea.Clear( true );
ModeSwitchButton.ToolTip = GetTypeName( type, eval );
if ( type == ParticleFloat.ValueType.Constant )
{
var control = new FloatControlWidget( Target.GetProperty( "ConstantValue" ) ) { HighlightColor = HighlightColor, Label = Label };
ControlArea.Add( control );
}
if ( type == ParticleFloat.ValueType.Range )
{
var controlA = new FloatControlWidget( Target.GetProperty( "ConstantA" ) ) { HighlightColor = HighlightColor, Label = Label };
ControlArea.Add( controlA );
var controlB = new FloatControlWidget( Target.GetProperty( "ConstantB" ) ) { HighlightColor = HighlightColor, Label = Label };
ControlArea.Add( controlB );
}
if ( type == ParticleFloat.ValueType.Curve )
{
var controlA = new CurveControlWidget( Target.GetProperty( "CurveA" ) ) { HighlightColor = HighlightColor };
ControlArea.Add( controlA );
}
if ( type == ParticleFloat.ValueType.CurveRange )
{
var controlA = new CurveRangeControlWidget( Target.GetProperty( "CurveRange" ) ) { HighlightColor = HighlightColor };
ControlArea.Add( controlA );
}
Update();
}
protected override void OnPaint()
{
}
}
/// <summary>
/// A popup that lets you choose between modes for a Particle Float Controller.
/// We can offer more configuration here, eventually.. but for now we're keeping it simple.
/// </summary>
file class ParticleFloatConfigPopup : PopupWidget
{
SerializedObject SerializedObject;
SerializedProperty Type;
SerializedProperty Eval;
public ParticleFloatConfigPopup( 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>() );
// tODO - make this window fucking awesome
// var dropDown = Create( Type );
// dropDown.FixedWidth = 100;
// Layout.Add( dropDown );
// var evalDropDown = Create( Eval );
// evalDropDown.FixedWidth = 100;
// Layout.Add( evalDropDown );
}
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 );
}
}