UI/EditorTextEntry.cs

A custom TextEntry UI control for the editor, extending Sandbox.UI.TextEntry. It tracks whether input came from native text events versus button events, commits text on blur via an OnCommit callback, allows shift+left/right to move the caret, and converts ButtonEvent names into typed characters when needed.

Native Interop
using Sandbox.UI;

namespace BlockParty;

public sealed class EditorTextEntry : TextEntry
{
	[Parameter] public Action<string> OnCommit { get; set; }

	private bool _receivedNativeText;

	protected override void OnBlur( PanelEvent e )
	{
		base.OnBlur( e );
		OnCommit?.Invoke( Text );
	}

	public override void OnButtonEvent( ButtonEvent e )
	{
		if ( e.Pressed ) _receivedNativeText = false;
		base.OnButtonEvent( e );
	}

	public override void OnKeyTyped( char character )
	{
		_receivedNativeText = true;
		base.OnKeyTyped( character );
	}

	public override void OnButtonTyped( ButtonEvent e )
	{
		if ( e.HasShift && e.Button is "left" or "right" )
		{
			Label.MoveCaretPos( e.Button == "left" ? -1 : 1, true );
			e.StopPropagation = true;
			return;
		}

		base.OnButtonTyped( e );
		if ( !_receivedNativeText && !e.HasCtrl && !e.HasAlt && TryGetCharacter( e, out char character ) )
			base.OnKeyTyped( character );
	}

	private static bool TryGetCharacter( ButtonEvent e, out char character )
	{
		character = default;
		if ( e.Button.Length == 1 )
		{
			char raw = e.Button[0];
			if ( raw is >= 'a' and <= 'z' )
			{
				character = e.HasShift ? char.ToUpperInvariant( raw ) : raw;
				return true;
			}
			if ( raw is >= '0' and <= '9' )
			{
				character = e.HasShift ? ")!@#$%^&*("[raw - '0'] : raw;
				return true;
			}
		}

		character = e.Button switch
		{
			"space" => ' ',
			"comma" => e.HasShift ? '<' : ',',
			"period" => e.HasShift ? '>' : '.',
			"slash" => e.HasShift ? '?' : '/',
			"backslash" => e.HasShift ? '|' : '\\',
			"semicolon" => e.HasShift ? ':' : ';',
			"apostrophe" => e.HasShift ? '"' : '\'',
			"minus" => e.HasShift ? '_' : '-',
			"equal" => e.HasShift ? '+' : '=',
			"lbracket" => e.HasShift ? '{' : '[',
			"rbracket" => e.HasShift ? '}' : ']',
			"backquote" => e.HasShift ? '~' : '`',
			_ => default,
		};
		return character != default;
	}
}