UI/Speechbubble.razor

A UI PanelComponent Razor file that displays a speech bubble above a Player showing their chat message character-by-character. It binds to a Player component in the parent, computes visibility based on chat state, and progressively reveals the Message over time in OnUpdate.

Networking
@using System
@using Sandbox
@using Sandbox.UI
@namespace BrickJam.UI
@inherits PanelComponent

<root>
	@if ( Visible )
	{
		<div class="border">
			<label class="label">@Shown</label>
		</div>
	}
</root>

@code {
	private const float Delay = 0.05f;

	private Player player;
	private Player Player => player ??= GetComponentInParent<Player>();

	private bool Visible => Player.IsValid() && Player.HasChatMessage && !(Player == Player.Local);

	private string Message => Player?.ChatMessage ?? string.Empty;

	private int textIndex;
	private TimeUntil nextIndex = Delay;
	private string lastMessage = "";

	private string Shown => Message.Substring( 0, Math.Clamp( textIndex, 0, Message.Length ) );

	protected override void OnUpdate()
	{
		if ( Message != lastMessage )
		{
			lastMessage = Message;
			textIndex = 0;
			nextIndex = Delay;
		}

		if ( textIndex < Message.Length && nextIndex )
		{
			nextIndex = Delay;
			textIndex++;
		}
	}

	protected override int BuildHash() => HashCode.Combine( Visible, textIndex, Message );
}

<style>
	Speechbubble {
		position: absolute;
		background-color: rgba(white, 0);
		justify-content: center;
		flex-wrap: wrap-reverse;
		transition: transform 1s ease-in-out;
		transform-origin: bottom;
		width: 100%;
		height: 100%;

		.border {
			background-color: rgba(white, 0);
			border-image: url(ui/speech.png) fill;
			transform: scale(1);
			padding: 5px;
			padding-bottom: 0px;
			flex-grow: 0;
			color: white;

			.label {
				bottom: 15px;
				right: 15px;
				margin-right: -20px;
				margin-bottom: -15px;
				font-size: 32px;
				font-family: "alagard";
				font-weight: bold;
				text-shadow: 2px 2px 0px black;
			}
		}

		&:intro {
			transform: scale(0);
		}

		&:outro {
			transform: scale(0);
		}
	}
</style>