Files
sbox-public/game/addons/tools/Code/Widgets/ControlWidgets/GenericControlWidget.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

237 lines
6.0 KiB
C#

namespace Editor;
/// <summary>
/// This is a callback control widget, which is used to edit classes.
/// It shows key properties, and an edit button. On clicking the edit button
/// it'll show a propery sheet popup.
/// </summary>
public class GenericControlWidget : ControlObjectWidget
{
public override bool SupportsMultiEdit => true;
public override bool IsWideMode => _isInlineEditor;
public override bool IncludeLabel => !_isInlineEditor;
bool _isInlineEditor;
RealTimeSince _visibilityDebounce = 0;
Widget PlaceholderWidget;
Dictionary<SerializedProperty, ControlWidget> KeyPropertyWidgets;
public GenericControlWidget( SerializedProperty property ) : base( property, true )
{
PaintBackground = false;
if ( property.TryGetAttribute<InlineEditorAttribute>( out var inlineEditor ) )
{
_isInlineEditor = true;
BuildInlineEditor( inlineEditor );
return;
}
Layout = Layout.Row();
Layout.Spacing = 2;
KeyPropertyWidgets = new();
var keys = SerializedObject?.Where( x => x.HasAttribute<KeyPropertyAttribute>() ).ToArray();
if ( keys is not null && keys.Length > 0 )
{
foreach ( var key in keys )
{
var widget = Layout.Add( ControlSheetRow.CreateEditor( key ) );
widget.Visible = key.ShouldShow();
KeyPropertyWidgets.Add( key, widget );
}
// Only show the full edit button if we have editable properties that aren't showing on the front bar.
var propertiesThatArentKeys = SerializedObject?.Where( x => x.IsEditable && x.ShouldShow() && !x.IsMethod && !x.HasAttribute<KeyPropertyAttribute>() ).ToArray();
if ( propertiesThatArentKeys.Length > 0 )
{
var row = Layout.AddColumn();
var popupButton = new IconButton( "edit_note" );
popupButton.OnClick = OpenPopup;
popupButton.ToolTip = $"Edit";
popupButton.Background = Theme.ControlBackground;
popupButton.Foreground = Theme.Text;
popupButton.IconSize = 16;
row.Add( popupButton );
row.AddStretchCell( 1 );
}
}
else
{
PlaceholderWidget = new Widget( this );
PlaceholderWidget.Size = Theme.RowHeight;
PlaceholderWidget.VerticalSizeMode = SizeMode.CanGrow;
PlaceholderWidget.HorizontalSizeMode = SizeMode.Flexible;
PlaceholderWidget.OnPaintOverride = PaintPlaceholder;
PlaceholderWidget.MouseClick = OpenPopup;
PlaceholderWidget.Cursor = CursorShape.Finger;
PlaceholderWidget.ToolTip = $"Edit";
Layout.Add( PlaceholderWidget, 1 );
}
}
StickyPopup Popup;
public override void OnDestroyed()
{
Popup?.Destroy();
Popup = null;
base.OnDestroyed();
}
protected void OpenPopup()
{
if ( Popup.IsValid() )
{
Popup?.Destroy();
Popup = null;
return;
}
var obj = SerializedObject;
// if it's nullable, create for the actual value rather than the nullable container
if ( SerializedProperty.IsNullable )
{
// best way to do this?
obj = SerializedProperty.GetValue<object>().GetSerialized();
obj.ParentProperty = SerializedProperty;
}
if ( obj is null )
{
Log.Error( "Cannot create ControlSheet for a null object" );
return;
}
//
// Create a popup for the control sheet editor
//
{
var popup = new StickyPopup( null )
{
Owner = this,
MinimumWidth = Width,
Position = ScreenRect.BottomLeft
};
var editor = EditorUtility.OpenControlSheet( obj, this, false );
popup.Layout.Add( editor );
popup.OnPaintOverride = PaintPopupBackground;
popup.Visible = true;
popup.Focus( true );
Popup = popup;
//
// Clear any unrelated popups
//
Popup.DestroyUnrelatedPopups();
}
}
bool PaintPopupBackground()
{
Paint.ClearPen();
Paint.SetBrushLinear( 0, Vector2.Down * 256, Theme.SurfaceBackground.Lighten( 0.2f ).WithAlpha( 0.98f ), Theme.SurfaceBackground.WithAlpha( 0.95f ) );
Paint.DrawRect( Paint.LocalRect );
Paint.ClearBrush();
Paint.SetPen( Color.Black.WithAlpha( 0.33f ), 2, PenStyle.Solid );
Paint.DrawRect( Paint.LocalRect.Shrink( 0, -10, 1, 1 ), 4 );
return true;
}
bool PaintPlaceholder()
{
var type = SerializedProperty.IsNullable ? SerializedProperty.NullableType : SerializedProperty.PropertyType;
var value = SerializedProperty.GetValue<object>();
type = value?.GetType() ?? type;
var displayInfo = DisplayInfo.ForType( type );
string labelText = displayInfo.Name;
// If the type has a custom ToString(), use that instead of the type name.
if ( type.GetMethod( "ToString" ).DeclaringType != typeof( object ) )
{
labelText = value?.ToString() ?? labelText;
}
Theme.DrawDropdown( LocalRect, labelText, displayInfo.Icon ?? "edit_note", Popup.IsValid(), IsControlDisabled );
return true;
}
/// <summary>
/// The property has [InlineEditor], so we want to unfold this and show it inline.
/// </summary>
void BuildInlineEditor( InlineEditorAttribute attribute )
{
Layout = Layout.Column();
if ( attribute.Label )
{
Layout.Add( ControlSheet.CreateLabel( SerializedProperty ) );
}
var cs = new ControlSheet();
cs.AddObject( SerializedObject );
cs.Margin = 0;
Layout.Add( cs );
}
protected override void OnContextMenu( ContextMenuEvent e )
{
if ( !_isInlineEditor ) return;
e.Accepted = true;
var property = SerializedProperty;
var menu = new ContextMenu( this );
menu.AddOption( $"Copy {property.DisplayName}", "content_copy", () =>
{
var str = ToClipboardString();
EditorUtility.Clipboard.Copy( str );
} );
menu.AddOption( $"Paste as {property.DisplayName}", "content_paste", () =>
{
var str = EditorUtility.Clipboard.Paste();
FromClipboardString( str );
} );
menu.AddOption( "Reset to Default", "restart_alt", () =>
{
property.Parent.NoteStartEdit( property );
property.SetValue( property.GetDefault() );
property.Parent.NoteFinishEdit( property );
} );
menu.OpenAt( e.ScreenPosition, false );
}
[EditorEvent.Frame]
public void UpdateVisibility()
{
if ( KeyPropertyWidgets is null )
return;
if ( _visibilityDebounce < 0.2f )
return;
_visibilityDebounce = Random.Shared.Float( 0, 0.1f );
foreach ( var kvp in KeyPropertyWidgets )
{
kvp.Value.Visible = kvp.Key.ShouldShow();
}
}
}