UI/BlackScreen.razor

A UI Panel component (Razor) that shows a full-screen black fade transition. It listens to BlackScreenBus.OnTrigger to start a sequence of start, black, and end phases, animating opacity over time and updating style opacity each Tick.

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

<root />

@code {
	private enum State { Idle, Starting, Black, Ending }

	private State state = State.Idle;
	private TimeUntil endTransition;
	private float phaseDuration = 1f;
	private float startTransition, blackTransition, endingTransition;
	private float opacity;

	public BlackScreen()
	{
		BlackScreenBus.OnTrigger += Begin;
	}

	public override void OnDeleted()
	{
		base.OnDeleted();
		BlackScreenBus.OnTrigger -= Begin;
	}

	private void Begin( float black, float start, float end )
	{
		startTransition = start;
		blackTransition = black;
		endingTransition = end;
		state = State.Starting;
		SetPhase( start );
	}

	private void SetPhase( float duration )
	{
		phaseDuration = MathF.Max( duration, 0.0001f );
		endTransition = duration;
	}

	private float Fraction => 1f - ((float)endTransition / phaseDuration).Clamp( 0f, 1f );

	public override void Tick()
	{
		opacity = state switch
		{
			State.Starting => MathE.Slerp( 0f, 1f, Fraction ),
			State.Black => 1f,
			State.Ending => MathE.Slerp( 1f, 0f, Fraction ),
			_ => 0f
		};

		Style.Opacity = opacity;

		if ( state != State.Idle && endTransition <= 0f )
		{
			switch ( state )
			{
				case State.Starting: state = State.Black; SetPhase( blackTransition ); break;
				case State.Black: state = State.Ending; SetPhase( endingTransition ); break;
				default: state = State.Idle; break;
			}
		}
	}

	protected override int BuildHash() => HashCode.Combine( opacity );
}

<style>
	BlackScreen {
		position: absolute;
		top: 0; left: 0;
		width: 100%;
		height: 100%;
		background-color: black;
		opacity: 0;
		pointer-events: none;
		z-index: 50;
	}
</style>