SoundBoardPanel.razor

A Blazor/Razor UI component for a soundboard panel. It renders board tabs, pads grid, voice meter tiers, controls (loop, kill all), and reacts to manager/tracker events and screen size to update UI state and layout.

NetworkingFile Access
@using System;
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace SSoundboard

<root class="sb @LayoutClass @(SoundBoardManager.ActiveVoiceCount > 0 ? "has-voices" : "")">
	<div class="sb-shell">
		<header class="sb-topbar">
			<div class="sb-brand-block">
				<span class="sb-status-led @(SoundBoardManager.ActiveVoiceCount > 0 ? "is-live" : "")"></span>
				<div class="sb-brand-copy">
					<span class="sb-eyebrow">SAD_PHO AUDIO TOY</span>
					<span class="sb-title">S&amp;Soundboard</span>
				</div>
			</div>

			<div class="sb-voice-chip">
				<div class="sb-voice-chip-head">
					<span class="sb-voice-chip-label">VOICES</span>
					<span class="sb-voice-chip-value" style="color: @ActiveVoiceColor">@SoundBoardManager.ActiveVoiceCount</span>
				</div>
				<div class="sb-voice-meter-stack">
					@for ( var tier = 0; tier < VoiceMeterTierCount; tier++ )
					{
						var state = GetVoiceMeterTier( tier );
						<div class="sb-voice-meter-tier">
							<div class="sb-voice-meter-fill @(state.IsActive ? "is-active" : "") @(state.IsFull ? "is-full" : "") @(state.IsChaos ? "is-chaos" : "")"
								 style="width: @(state.FillPercent)%; background-color: @state.Color"></div>
						</div>
					}
				</div>
			</div>
		</header>

		<nav class="seg solid sb-tabs">
			@foreach ( var board in SoundBoardData.Boards )
			{
				<button class="seg-btn @(ActiveBoardId == board.Id ? "active" : "")"
						onmousedown="@(() => SetBoard( board.Id ))">
					@board.Icon @board.Label (@board.Pads.Count)
				</button>
			}
		</nav>

		<section class="sb-board">
			<div @ref="_boardScroll" class="sb-board-scroll">
				<div class="tile-grid sb-pad-grid">
					@foreach ( var pad in ActivePads )
					{
						<div class="tile @(SoundBoardManager.IsPadPlaying( pad.SoundId ) ? "active" : "")"
							 onmousedown="@(() => PlayPad( pad ))">
							<div class="tile-swatch" style="background-color: @pad.Color">
								<div class="tile-vignette"></div>
							</div>
							<div class="tile-label">@ShortLabel( pad.Label )</div>
						</div>
					}
				</div>
			</div>
		</section>

		<footer class="sb-controls">
			<button class="chip @(SoundBoardManager.LoopEnabled ? "active primary" : "")"
					onmousedown="@ToggleLoop">
				LOOP @(SoundBoardManager.LoopEnabled ? "ON" : "OFF")
			</button>

			<div class="sb-now-playing">
				<span class="sb-now-playing-dot @(SoundBoardManager.ActiveVoiceCount > 0 ? "is-live" : "")"></span>
				<span class="sb-now-playing-text">@SoundBoardManager.StatusText</span>
			</div>

			<button class="btn danger sb-kill-btn" onmousedown="@KillAll">☠ KILL ALL</button>
		</footer>
	</div>
</root>

@code
{
	string ActiveBoardId { get; set; } = "main";
	int _screenWidth;
	int _screenHeight;
	Panel _boardScroll;

	IEnumerable<SoundPad> ActivePads => SoundBoardData.Boards.FirstOrDefault( b => b.Id == ActiveBoardId )?.Pads ?? Enumerable.Empty<SoundPad>();

	string LayoutClass
	{
		get
		{
			if ( _screenWidth < 720 )
				return "is-tight";

			if ( _screenWidth < 1000 )
				return "is-compact";

			if ( _screenWidth < 1400 )
				return "is-medium";

			return "is-wide";
		}
	}

	const int VoicesPerTier = 7;
	const int VoiceMeterTierCount = 4;

	string ActiveVoiceColor => SoundBoardManager.ActiveVoiceCount == 0
		? "#7cf9ff"
		: TierColor( ActiveVoiceTierIndex );

	int ActiveVoiceTierIndex => SoundBoardManager.ActiveVoiceCount == 0
		? 0
		: Math.Min( (SoundBoardManager.ActiveVoiceCount - 1) / VoicesPerTier, VoiceMeterTierCount - 1 );

	VoiceMeterTierState GetVoiceMeterTier( int tierIndex )
	{
		var count = SoundBoardManager.ActiveVoiceCount;
		var tierStart = tierIndex * VoicesPerTier;
		var tierEnd = (tierIndex + 1) * VoicesPerTier;
		var color = TierColor( tierIndex );

		if ( count <= tierStart )
			return new VoiceMeterTierState( 0, color, false, false, false );

		if ( count >= tierEnd )
		{
			var chaos = tierIndex == VoiceMeterTierCount - 1 && count > tierEnd;
			return new VoiceMeterTierState( 100, color, chaos, true, chaos );
		}

		var fill = (int)Math.Round( (count - tierStart) * 100f / VoicesPerTier );
		return new VoiceMeterTierState( fill, color, true, false, false );
	}

	static string TierColor( int tierIndex ) => tierIndex switch
	{
		0 => "#7cf9ff",
		1 => "#8b5cff",
		2 => "#ffd34a",
		3 => "#ff5d6a",
		_ => "#ff5d6a"
	};

	readonly record struct VoiceMeterTierState( int FillPercent, string Color, bool IsActive, bool IsFull, bool IsChaos );

	protected override void OnStart()
	{
		_screenWidth = (int)Screen.Width;
		_screenHeight = (int)Screen.Height;
		SoundPlayTracker.EnsureLoaded();
		SoundBoardManager.StateChanged += OnStateChanged;
		SoundPlayTracker.Changed += OnTrendingChanged;
	}

	protected override void OnDestroy()
	{
		SoundBoardManager.StateChanged -= OnStateChanged;
		SoundPlayTracker.Changed -= OnTrendingChanged;
	}

	void OnTrendingChanged() => StateHasChanged();

	protected override void OnUpdate()
	{
		var width = (int)Screen.Width;
		var height = (int)Screen.Height;

		if ( width == _screenWidth && height == _screenHeight )
			return;

		_screenWidth = width;
		_screenHeight = height;
		StateHasChanged();
	}

	void OnStateChanged() => StateHasChanged();

	void SetBoard( string boardId )
	{
		ActiveBoardId = boardId;
		StateHasChanged();
	}

	void PlayPad( SoundPad pad ) => SoundBoardManager.Play( pad );

	void ToggleLoop() => SoundBoardManager.ToggleLoop();

	void KillAll() => SoundBoardManager.KillAll();

	protected override void OnMouseWheel( Vector2 delta )
	{
		if ( _boardScroll is not null && _boardScroll.TryScroll( delta ) )
			return;

		base.OnMouseWheel( delta );
	}

	string ShortLabel( string label )
	{
		if ( string.IsNullOrWhiteSpace( label ) )
			return "???";

		var max = _screenWidth < 720 ? 9 : _screenWidth < 1000 ? 11 : _screenWidth < 1400 ? 13 : 16;
		if ( label.Length <= max )
			return label;

		return label[..(max - 1)] + "…";
	}

	int PlayingPadsHash()
	{
		var hash = new HashCode();
		foreach ( var pad in ActivePads )
		{
			if ( SoundBoardManager.IsPadPlaying( pad.SoundId ) )
				hash.Add( pad.SoundId );
		}

		return hash.ToHashCode();
	}

	int TrendingPadsHash()
	{
		var hash = new HashCode();
		var trending = SoundBoardData.Boards.FirstOrDefault( b => b.Id == "trending" );
		if ( trending is null )
			return hash.ToHashCode();

		foreach ( var pad in trending.Pads )
			hash.Add( pad.SoundId );

		return hash.ToHashCode();
	}

	protected override int BuildHash()
	{
		var hash = new HashCode();
		hash.Add( ActiveBoardId );
		hash.Add( SoundBoardManager.LoopEnabled );
		hash.Add( SoundBoardManager.StatusText );
		hash.Add( SoundBoardManager.ActiveVoiceCount );
		hash.Add( SoundBoardManager.LastPlayedLabel );
		hash.Add( PlayingPadsHash() );
		hash.Add( TrendingPadsHash() );
		hash.Add( _screenWidth );
		hash.Add( _screenHeight );
		return hash.ToHashCode();
	}
}