UI/ReplayReactionTooltip.cs
using System;
using Sandbox;
using Sandbox.Rendering;

namespace BlockParty;

/// <summary>Reaction names drawn after the CRT command list, at screen resolution. Owned by the
/// replay stage so rebuilding/seeking or leaving removes both the tooltip and its render hook.</summary>
public sealed class ReplayReactionTooltip : Component
{
	public GameStage Stage { get; set; }
	private readonly CommandList _commands = new( "Replay reaction tooltip" );
	private CameraComponent _camera;
	private string _text;
	private float _hoverTime;

	public void Show( string name )
	{
		if ( _text == name ) return;
		_text = name;
		_hoverTime = 0f;
	}

	public void Hide()
	{
		_text = null;
		_hoverTime = 0f;
		_commands.Reset();
	}

	protected override void OnPreRender()
	{
		_commands.Reset();
		var manager = GameManager.Instance;
		if ( manager?.Camera != _camera )
		{
			Detach();
			_camera = manager?.Camera;
			if ( _camera.IsValid() )
				_camera.AddCommandList( _commands, Sandbox.Rendering.Stage.AfterUI, 10000 );
		}
		if ( string.IsNullOrWhiteSpace( _text ) || manager?.Stage != Stage || !manager.IsReplaying
			|| manager.IsTransitioning || manager.IsPlacingReaction || manager.IsHijacked || manager.IsGifPreviewActive
			|| Stage.MenuOpen || Stage.IsCharacterHelpVisible || Stage.LeaveConfirmOpen || Stage.ReplayImportOpen ) return;
		_hoverTime += Time.Delta;
		if ( _hoverTime < 0.32f || Screen.Width <= 0 || Screen.Height <= 0 ) return;

		float scale = Screen.Height / 1080f;
		float pad = 12f * scale, border = 3f * scale, edge = 8f * scale;
		var text = new TextRendering.Scope( _text, Color.White, 22f * scale, "m6x11plus" );
		var size = text.Measure();
		float available = MathF.Max( 1f, Screen.Width - 2f * (edge + pad + border) );
		if ( size.x > available )
		{
			text.FontSize *= available / size.x;
			size = text.Measure();
		}
		float width = size.x + 2f * (pad + border), height = size.y + 2f * (6f * scale + border);
		float x = Math.Clamp( Mouse.Position.x + edge, edge, MathF.Max( edge, Screen.Width - edge - width ) );
		float y = Mouse.Position.y - edge - height;
		if ( y < edge ) y = Mouse.Position.y + edge;
		y = Math.Clamp( y, edge, MathF.Max( edge, Screen.Height - edge - height ) );
		x = MathF.Round( x ); y = MathF.Round( y );
		using ( var paint = Painter.Begin( _commands ) )
		{
			paint.Fill = Color.White;
			paint.Rect( new Rect( x, y, width, height ) );
			paint.Fill = new Color( 2f / 255f, 74f / 255f, 84f / 255f );
			paint.Rect( new Rect( x + border, y + border, width - 2f * border, height - 2f * border ) );
		}
		_commands.DrawText( text, new Rect( x + pad + border, y + 6f * scale + border, size.x, size.y ), TextFlag.LeftTop );
	}

	private void Detach()
	{
		if ( _camera.IsValid() ) _camera.RemoveCommandList( _commands );
		_camera = null;
	}
	protected override void OnDisabled() { Hide(); Detach(); }
	protected override void OnDestroy() { Hide(); Detach(); }
}