UI/Controls/ClientInputControl.cs
namespace Sandbox.UI;
[CustomEditor( typeof( ClientInput ) )]
public partial class ClientInputControl : BaseControl
{
Panel _preview;
InputHint _inputHint;
IconPanel _fallbackIcon;
Label _bindLabel;
public override bool SupportsMultiEdit => true;
public ClientInputControl()
{
_preview = AddChild<Panel>( "preview" );
_inputHint = _preview.AddChild<InputHint>( "hint" );
_fallbackIcon = _preview.AddChild<IconPanel>( "fallback" );
_fallbackIcon.Text = "keyboard";
_bindLabel = AddChild<Label>( "bind-label" );
}
public override void Rebuild()
{
if ( Property == null ) return;
var action = Property.GetValue<ClientInput>().Action;
var outputCount = GetLinkedOutputs().Count( IsConnected );
if ( string.IsNullOrWhiteSpace( action ) )
{
_inputHint.Action = null;
_inputHint.SetClass( "hidden", true );
_fallbackIcon.SetClass( "hidden", false );
_bindLabel.Text = outputCount > 0
? $"{outputCount} Connected Output{(outputCount == 1 ? "" : "s")}"
: "No Binding";
SetClass( "no-binding", outputCount == 0 );
return;
}
_inputHint.Action = action;
_inputHint.SetClass( "hidden", false );
_fallbackIcon.SetClass( "hidden", true );
SetClass( "no-binding", false );
var match = Input.GetActions().FirstOrDefault( a => a.Name == action );
var label = match != null ? (match.Title ?? match.Name) : action;
_bindLabel.Text = outputCount > 0 ? $"{label} + {outputCount}" : label;
}
protected override void OnClick( MousePanelEvent e )
{
base.OnClick( e );
var menu = new Sandbox.UI.Menu();
menu.AddOption( "No Key Binding", "", () => OnBindChanged( "" ) );
var outputs = GetLinkedOutputs().ToArray();
if ( outputs.Length > 0 )
{
var sub = menu.AddMenu( "Linked Outputs", "cable" );
foreach ( var output in outputs )
{
var connected = IsConnected( output );
var source = output;
sub.AddOption(
$"{output.Component.GameObject.Name}: {output.Title}",
connected ? "check_box" : "check_box_outline_blank",
() => SetConnected( source, !connected )
);
}
}
menu.AddSeparator();
var grouped = Input.GetActions()
.GroupBy( a => a.GroupName ?? "" )
.OrderBy( g => g.Key );
foreach ( var group in grouped )
{
if ( string.IsNullOrWhiteSpace( group.Key ) )
{
foreach ( var action in group )
{
var a = action;
menu.AddOption( ActionLabel( a ), "", () => OnBindChanged( a.Name ) );
}
}
else
{
var groupActions = group.ToList();
var sub = menu.AddMenu( group.Key, "" );
foreach ( var action in groupActions )
{
var a = action;
sub.AddOption( ActionLabel( a ), "", () => OnBindChanged( a.Name ) );
}
}
}
menu.Open( this, Popup.PositionMode.UnderMouse );
}
IEnumerable<SignalOutputDescription> GetLinkedOutputs()
{
var visitedRoots = new HashSet<GameObject>();
foreach ( var target in GetTargetComponents() )
{
if ( !visitedRoots.Add( target.GameObject.Root ) ) continue;
foreach ( var output in SignalSystem.GetContraptionOutputs( target.GameObject ) )
{
if ( CanConnect( output ) ) yield return output;
}
}
}
bool CanConnect( SignalOutputDescription output )
{
return GetTargetInputs().Any( input => SignalSystem.AreCompatible( output, input ) );
}
IEnumerable<SignalInputDescription> GetTargetInputs()
{
foreach ( var target in GetTargetComponents() )
{
foreach ( var input in SignalSystem.GetInputs( target ) )
{
if ( string.Equals( input.Id, Property.Name, StringComparison.OrdinalIgnoreCase ) )
yield return input;
}
}
}
IEnumerable<Component> GetTargetComponents()
{
foreach ( var target in Property.Parent?.Targets ?? Enumerable.Empty<object>() )
{
if ( target is Component component && component.IsValid() )
yield return component;
}
}
bool IsConnected( SignalOutputDescription output )
{
return GetTargetInputs().Any( input => SignalSystem.IsConnected( output, input ) );
}
void SetConnected( SignalOutputDescription output, bool connected )
{
foreach ( var input in GetTargetInputs() )
SignalSystem.SetConnected( output, input, connected );
Rebuild();
}
string ActionLabel( InputAction a )
{
var title = !string.IsNullOrEmpty( a.Title ) ? a.Title : a.Name;
var origin = Input.GetButtonOrigin( a.Name );
return origin != null ? $"{title} ({origin})" : title;
}
void OnBindChanged( string value )
{
var current = Property.GetValue<ClientInput>();
current.Action = value;
Property.SetValue( current );
foreach ( var target in Property.Parent?.Targets ?? Enumerable.Empty<object>() )
{
if ( target is Component component )
GameManager.ChangeProperty( component, Property.Name, current );
}
Rebuild();
}
}