An inline editor UI component for enum-valued ports in the Editor. It builds a list of enum options from a CLR enum type, draws a compact dropdown chip showing the current option label, and opens a context menu to pick a different enum value.
using Editor.Prism.Core;
using Editor.Prism.Ui.Adapters;
namespace Editor.Prism.Ui.InlineEditors;
/// <summary>
/// An enum-valued port, drawn as a compact dropdown chip.
/// <para>
/// The shader type system has no enums — a sampler filter or a blend mode is an <c>int</c> by the time
/// it reaches the compiler — so the choice is discovered from the CLR property the port's inline value
/// is bound to. That is also where the pretty names come from, so <c>[Title]</c> on an enum member
/// shows up on the card for free.
/// </para>
/// </summary>
public sealed class EnumInlineEditor : PrismInlineEditor
{
readonly List<(object Value, string Label)> _options = new();
/// <summary>Bind an enum editor to a port and the enum type behind it.</summary>
public EnumInlineEditor( Plug plug, PrismPlugIn port, Type enumType ) : base( plug, port )
{
EnumType = enumType;
BuildOptions();
}
/// <summary>The enum type the bound property declares.</summary>
public Type EnumType { get; }
/// <summary>The label of the current value.</summary>
public string Text
{
get
{
var current = Value;
foreach ( var option in _options )
{
if ( Equals( option.Value, current ) ) return option.Label;
}
return current?.ToString() ?? ( _options.Count > 0 ? _options[0].Label : "—" );
}
}
/// <inheritdoc/>
protected override float MeasureWidth() =>
MathF.Min( 140f,
MathF.Max( 42f, PrismPaint.MeasureText( Text, PrismTheme.InlineValueSize, PrismTheme.InlineValueWeight ) + 24f ) );
/// <inheritdoc/>
protected override void OnPaintPill( Rect pill )
{
var hovered = Paint.HasMouseOver;
PrismPaint.Pill( pill, hovered ? PrismTheme.Elevated.Lighten( 0.14f ) : PrismTheme.Elevated,
PrismTheme.BorderSubtle.WithAlpha( hovered ? 0.9f : 0.55f ), PrismTheme.RadiusChip );
Paint.ClearPen();
Paint.SetBrush( TypeColor.WithAlpha( hovered ? 1f : 0.8f ) );
Paint.DrawRect( new Rect( pill.Left, pill.Top, 2f, pill.Height ), 1f );
PrismPaint.Text( pill.Shrink( 6f, 0f, 14f, 0f ), Text,
hovered ? PrismTheme.TextPrimary : PrismTheme.TextSecondary,
PrismTheme.InlineValueSize, PrismTheme.InlineValueWeight, TextFlag.LeftCenter );
Paint.SetPen( PrismTheme.TextMuted );
Paint.DrawIcon( pill.Shrink( 0f, 0f, 2f, 0f ), "expand_more", 12f, TextFlag.RightCenter );
}
/// <inheritdoc/>
protected override void OnMousePressed( GraphicsMouseEvent e )
{
if ( !ShouldDraw || !e.LeftMouseButton ) return;
if ( !PillRect.Grow( 3f ).IsInside( e.LocalPosition ) ) return;
e.Accepted = true;
PrismLog.Guard( "Open enum menu", () =>
{
if ( _options.Count == 0 ) return;
var menu = new Menu();
var current = Value;
foreach ( var option in _options )
{
var value = option.Value;
var entry = menu.AddOption( option.Label, null, () => Commit( value ) );
entry.Checkable = true;
entry.Checked = Equals( value, current );
}
var view = Plug?.Node?.GraphicsView;
var pill = PillRect;
var screen = view is null
? Application.CursorPosition
: view.ToScreen( view.FromScene( ToScene( new Vector2( pill.Left, pill.Bottom + 2f ) ) ) );
menu.OpenAt( screen );
} );
}
void BuildOptions()
{
if ( EnumType is null || !EnumType.IsEnum ) return;
PrismLog.Guard( "Read enum values", () =>
{
var values = Enum.GetValues( EnumType );
var names = Enum.GetNames( EnumType );
DisplayInfo[] display = null;
PrismLog.Guard( "Read enum display info", () => display = DisplayInfo.ForEnumValues( EnumType ) );
for ( int i = 0; i < values.Length; i++ )
{
var label = display is not null && i < display.Length && !string.IsNullOrEmpty( display[i].Name )
? display[i].Name
: names[i];
_options.Add( (values.GetValue( i ), label) );
}
} );
}
}