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.
@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&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();
}
}