Code/SboxSuperuser/UI/AdminMenuRenderer.ModuleSettings.cs

Part of the ImGui admin menu renderer. Draws the settings form for a module command: fetches current values from the server, builds labeled fields per command argument, and sends an apply command when values change.

using Sandbox;
using SboxSuperuser.Engine.Vendor.ImGui;

namespace SboxSuperuser.UI;

internal sealed partial class AdminMenuRenderer
{
	readonly Dictionary<string, SettingsFields> _settingsFields = new();

	void DrawModuleSettings( AdminMenuElementRow element, string key )
	{
		var command = FindCommand( element.Command );
		if ( command is null ) return;
		var reply = Server.Live( key, element.StatusCommand, Array.Empty<string>(), DateTimeOffset.UtcNow );
		ImGui.Spacing();
		ImGui.BeginGroup();
		ImGui.SeparatorText( Label( element.Text ) );
		Caption( command.Description );
		ImGui.Spacing();
		if ( reply is not { Status: CommandStatus.Success } )
		{
			if ( reply is null ) Caption( "Loading current settings…" );
			else ColoredWrapped( reply.Message, AdminMenuTheme.Tone( reply.Status ) );
			ImGui.EndGroup();
			return;
		}
		if ( !_settingsFields.TryGetValue( key, out var fields ) )
		{
			fields = new SettingsFields( command, element );
			_settingsFields.Add( key, fields );
			SeedSettings( command, reply, fields );
		}
		fields.Refresh( reply );
		ImGui.PushID( command.Name );
		if ( ImGui.BeginTable( "fields", 2, ImGuiTableFlags.SizingStretchProp ) )
		{
			ImGui.TableSetupColumn( "Setting", ImGuiTableColumnFlags.WidthStretch, 0.56f );
			ImGui.TableSetupColumn( "Value", ImGuiTableColumnFlags.WidthStretch, 0.44f );
			for ( var i = 0; i < command.Arguments.Length; i++ ) DrawSetting( command.Arguments[i], fields, i );
			ImGui.EndTable();
		}
		ImGui.Spacing();
		DrawSettingsActions( element, command, reply, fields );
		ImGui.PopID();
		ImGui.EndGroup();
	}

	void SeedSettings( AdminCommandRow command, AdminReply reply, SettingsFields fields )
	{
		for ( var i = 0; i < command.Arguments.Length; i++ )
			_commandValues[fields.Keys[i]] = reply.Data.GetValueOrDefault( command.Arguments[i].Name, "" );
	}

	void DrawSetting( AdminCommandArgumentRow argument, SettingsFields fields, int index )
	{
		ImGui.PushID( argument.Name );
		ImGui.TableNextRow();
		ImGui.TableNextColumn();
		ImGui.AlignTextToFramePadding();
		ImGui.TextUnformatted( fields.Labels[index] );
		ImGui.SetItemTooltip( "{0}", argument.Description );
		Caption( fields.Defaults[index] );
		ImGui.TableNextColumn();
		var valueKey = fields.Keys[index];
		var value = _commandValues.GetValueOrDefault( valueKey, "" );
		if ( argument.Kind == CommandArgumentKind.Boolean )
		{
			var enabled = string.Equals( value, "true", StringComparison.OrdinalIgnoreCase );
			if ( ImGui.Checkbox( "##value", ref enabled ) ) value = enabled ? "true" : "false";
		}
		else Field( "##value", fields.Hints[index], ref value, 256, ImGui.GetContentRegionAvail().x );
		if ( value != _commandValues.GetValueOrDefault( valueKey, "" ) ) fields.Error = null;
		_commandValues[valueKey] = value;
		Caption( fields.Current[index] );
		ImGui.PopID();
	}

	void DrawSettingsActions( AdminMenuElementRow element, AdminCommandRow command, AdminReply reply, SettingsFields fields )
	{
		var missing = false;
		for ( var i = 0; i < fields.Keys.Length; i++ ) missing |= string.IsNullOrWhiteSpace( _commandValues.GetValueOrDefault( fields.Keys[i], "" ) );
		var changed = false;
		for ( var i = 0; i < command.Arguments.Length; i++ )
			changed |= _commandValues.GetValueOrDefault( fields.Keys[i], "" ) != reply.Data.GetValueOrDefault( command.Arguments[i].Name, "" );
		ImGui.BeginDisabled( Server.Running || missing || !changed );
		if ( ImGui.Button( "Apply changes", new Vector2( 130 * Scale, 0 ) ) )
		{
			fields.Error = BuildArguments( command, out var values );
			if ( fields.Error is null ) RunCommand( $"Apply {element.Text}?", command.Name, values, confirm: command.Mutation,
				details: command.Arguments.Zip( values ).Select( pair => (pair.First.Name, pair.Second) ).ToArray() );
		}
		ImGui.EndDisabled();
		ImGui.SameLine();
		if ( ImGui.Button( "Use current" ) ) SeedSettings( command, reply, fields );
		if ( element.Arguments.Length == command.Arguments.Length )
		{
			ImGui.SameLine();
			if ( ImGui.Button( "Defaults" ) )
				for ( var i = 0; i < command.Arguments.Length; i++ ) _commandValues[fields.Keys[i]] = element.Arguments[i];
		}
		if ( fields.Error is not null ) ColoredWrapped( fields.Error, AdminMenuTheme.Warning );
		else if ( !changed ) Caption( "Up to date" );
		if ( Server.LastCommand == command.Name && Server.LastResult is { } result ) ColoredWrapped( result.Message, AdminMenuTheme.Tone( result.Status ) );
	}

	sealed class SettingsFields
	{
		internal readonly string[] Keys, Labels, Defaults, Hints, Current;
		readonly AdminCommandRow _command;
		AdminReply _reply;
		internal string Error;

		internal SettingsFields( AdminCommandRow command, AdminMenuElementRow element )
		{
			_command = command;
			var count = command.Arguments.Length;
			Keys = new string[count]; Labels = new string[count]; Defaults = new string[count];
			Hints = new string[count]; Current = new string[count];
			for ( var i = 0; i < count; i++ )
			{
				var argument = command.Arguments[i];
				Keys[i] = command.Name + "/" + argument.Name;
				var label = argument.Name.Replace( '-', ' ' );
				Labels[i] = char.ToUpperInvariant( label[0] ) + label[1..];
				Defaults[i] = i < element.Arguments.Length ? "Default: " + element.Arguments[i] : "";
				Hints[i] = Hint( argument );
			}
		}

		internal void Refresh( AdminReply reply )
		{
			if ( ReferenceEquals( _reply, reply ) ) return;
			_reply = reply;
			for ( var i = 0; i < Current.Length; i++ )
				Current[i] = "Current: " + reply.Data.GetValueOrDefault( _command.Arguments[i].Name, "—" );
		}
	}
}