Partial class of the ImGui admin menu renderer. Draws module-supplied menu entries, floating windows, and their element list (headings, text, buttons, forms, catalog, settings and live command output).
using Sandbox;
using SboxSuperuser.Core.Menu;
using SboxSuperuser.Engine.Vendor.ImGui;
namespace SboxSuperuser.UI;
internal sealed partial class AdminMenuRenderer
{
// Window titles and labels come from modules, so they never reach ImGui as IDs or format strings unescaped.
private static string Label( string text ) => text.Replace( "#", "#" );
private static string WindowKey( AdminMenuWindowRow window ) => MenuLayout.ModuleWindowName( window.ModuleId, window.Title );
/// <summary>The Modules menu: one entry per window the viewer's modules added, shown only when there are any.</summary>
/// <summary>The Modules tab, then one tab for each menu name modules added that isn't one of Superuser's own.</summary>
private void DrawModulesMenu()
{
var windows = Server.MenuWindows?.MenuWindows;
if ( windows is not { Length: > 0 } ) return;
foreach ( var menu in windows.Select( window => window.Menu ).Distinct().Where( menu => !IsBuiltInMenu( menu ) )
.OrderBy( menu => menu == AdminMenuWindow.DefaultMenu ? 1 : 0 ).ThenBy( menu => menu, StringComparer.OrdinalIgnoreCase ) )
{
if ( !ImGui.BeginMenu( menu ) ) continue;
DrawModuleItems( menu );
ImGui.EndMenu();
}
}
/// <summary>Module windows listed in a menu, each module under its own separator.</summary>
private void DrawModuleItems( string menu )
{
var windows = Server.MenuWindows?.MenuWindows;
if ( windows is null ) return;
foreach ( var module in windows.Where( window => window.Menu == menu ).GroupBy( window => window.ModuleId ) )
{
ImGui.SeparatorText( module.Key );
foreach ( var window in module ) WindowItem( Label( window.Title ) + "##" + WindowKey( window ), WindowKey( window ) );
}
}
private static bool IsBuiltInMenu( string menu ) => menu is "Moderation" or "Server";
private void DrawModuleWindows()
{
foreach ( var window in Server.MenuWindows?.MenuWindows ?? Array.Empty<AdminMenuWindowRow>() ) DrawModuleWindow( window );
}
private void DrawModuleWindow( AdminMenuWindowRow window )
{
var key = WindowKey( window );
if ( !BeginWindow( key, Label( window.Title ) + "###" + key, new Vector2( 220, 90 ), new Vector2( 560, 520 ), new Vector2( 340, 220 ), out var expanded ) ) return;
if ( expanded )
{
ImGui.PushID( key );
for ( var index = 0; index < window.Elements.Length; index++ )
{
ImGui.PushID( index );
DrawModuleElement( window.Elements[ index ], $"{key}/{index}" );
ImGui.PopID();
}
ImGui.PopID();
}
ImGui.End();
}
private void DrawModuleElement( AdminMenuElementRow element, string key )
{
switch ( element.Kind )
{
case AdminMenuElementKind.Heading:
ImGui.SeparatorText( Label( element.Text ) );
break;
case AdminMenuElementKind.Text:
ImGui.TextWrapped( "{0}", element.Text );
break;
case AdminMenuElementKind.Separator:
ImGui.Separator();
break;
case AdminMenuElementKind.Button:
DrawModuleButton( element );
break;
case AdminMenuElementKind.Form:
if ( FindCommand( element.Command ) is { } command )
{
ImGui.BeginChild( "form", Vector2.Zero, ImGuiChildFlags.Borders | ImGuiChildFlags.AutoResizeY | ImGuiChildFlags.AlwaysUseWindowPadding );
DrawCommandForm( command );
ImGui.EndChild();
}
break;
case AdminMenuElementKind.Catalog:
DrawModuleCatalog( element, key );
break;
case AdminMenuElementKind.Settings:
DrawModuleSettings( element, key );
break;
case AdminMenuElementKind.Live:
DrawModuleLive( element, key );
break;
}
}
private void DrawModuleButton( AdminMenuElementRow element )
{
var command = FindCommand( element.Command );
ImGui.BeginDisabled( command is null || Server.Running );
if ( ImGui.Button( Label( element.Text ) ) && command is not null )
{
var details = command.Arguments.Zip( element.Arguments ).Select( pair => (pair.First.Name, pair.Second) ).ToArray();
RunCommand( $"{element.Text}?", command.Name, element.Arguments, confirm: command.Mutation, details: details );
}
ImGui.EndDisabled();
if ( command is not null ) ImGui.SetItemTooltip( "{0}", command.Description );
}
// A live element shows a read-only command's reply: its message, then its rows, then any other fields.
private void DrawModuleLive( AdminMenuElementRow element, string key )
{
var reply = Server.Live( key, element.Command, element.Arguments, DateTimeOffset.UtcNow );
if ( reply is null )
{
Caption( "Loading..." );
return;
}
if ( reply.Status != CommandStatus.Success )
{
ColoredWrapped( reply.Message, AdminMenuTheme.Tone( reply.Status ) );
}
else DrawCommandResult( reply );
if ( ImGui.SmallButton( "Refresh" ) ) Server.RefreshLive();
}
private AdminCommandRow FindCommand( string name ) => Server.Commands?.Commands.FirstOrDefault( command => command.Name == name );
}