DiamondPanel.Buttons.cs
using System;
using Sandbox;
namespace Diamonds;
/// <summary>Button hover/click sounds and the title menu's keyboard/controller selection.</summary>
public sealed partial class DiamondPanel
{
public enum UiButton { None, TitlePlay, TitleLeaderboard, TitleInstructions, TitleQuit, Instructions, Options, Leave, Restart, Close, Tab1, Tab2, Tab3, DisableFluid, LeaderboardMode }
const string HoverSound = "sfx/hover.sound", ClickSound = "sfx/click.sound";
const int TitleButtonCount = 4;
// Hotload leaves new fields at their defaults, so PLAY (0) must be the default selection.
int titleSelection;
UiButton hoveredButton;
Vector2 lastHoverMouse;
/// <summary>The highlighted title button, 0 (PLAY) to 3 (QUIT).</summary>
public int TitleSelection => titleSelection;
void PlayUiSound( string path )
{
if ( IsMuted ) return;
var sound = Sound.Play( path );
if ( sound is null ) return;
sound.SpacialBlend = 0;
sound.Volume *= DiamondSettings.EffectsGain;
}
void PlayClickSound()
{
PlayUiSound( ClickSound );
DiamondHaptics.MenuConfirm();
}
/// <summary>
/// Plays the hover sound when the mouse moves onto a button, and lets a moving mouse select title buttons.
/// A button appearing under a still mouse is neither announced nor selected, so keyboard/controller selection stays put.
/// </summary>
public void UpdateButtonHover()
{
var mouse = MousePosition;
var hovered = HoveredButton();
bool moved = mouse != lastHoverMouse;
if ( moved && hovered != hoveredButton && hovered != UiButton.None ) PlayUiSound( HoverSound );
if ( moved && TitleScreen && hovered is >= UiButton.TitlePlay and <= UiButton.TitleQuit ) titleSelection = hovered - UiButton.TitlePlay;
hoveredButton = hovered;
lastHoverMouse = mouse;
}
/// <summary>Moves the title selection up/down (clamped, never wrapping onto QUIT), playing the hover sound, and confirms it.</summary>
public void UpdateTitleMenu( bool up, bool down, bool confirm )
{
if ( !TitleScreen || ModalOpen || TransitionPending ) return;
int previous = titleSelection;
if ( up ) titleSelection = Math.Max( 0, titleSelection - 1 );
if ( down ) titleSelection = Math.Min( TitleButtonCount - 1, titleSelection + 1 );
// Stepping sounds like a hover; pressing into either end doesn't.
if ( titleSelection != previous )
{
PlayUiSound( HoverSound );
DiamondHaptics.MenuStep();
}
if ( confirm ) ActivateTitleButton( titleSelection );
}
void ActivateTitleButton( int index )
{
titleSelection = Math.Clamp( index, 0, TitleButtonCount - 1 );
PlayClickSound();
switch ( titleSelection )
{
case 0: BeginTransition( toGame: true ); break;
case 1: OpenLeaderboard(); break;
case 2: OpenTutorial(); break;
default: QuitRequested?.Invoke(); break;
}
}
/// <summary>The clickable button under the mouse, mirroring each click handler's hit test.</summary>
UiButton HoveredButton()
{
if ( TransitionPending ) return UiButton.None;
var size = Box.Rect.Size;
var mouse = MousePosition;
if ( LeaderboardOpen )
{
var frame = LeaderboardFrame( size );
if ( TutorialClose( frame ).IsInside( mouse ) ) return UiButton.Close;
return LeaderboardOtherTitle.IsInside( LeaderboardLocal( mouse, frame ) ) ? UiButton.LeaderboardMode : UiButton.None;
}
if ( OptionsOpen )
{
var frame = OptionsFrame( size );
if ( TutorialClose( frame, OptionsWidth ).IsInside( mouse ) ) return UiButton.Close;
return DraggingSlider == OptionsSlider.None && DisableFluidRow.IsInside( OptionsLocal( mouse, frame ) ) ? UiButton.DisableFluid : UiButton.None;
}
if ( TutorialOpen )
{
var frame = TutorialFrame( size );
if ( TutorialClose( frame ).IsInside( mouse ) ) return UiButton.Close;
float modalScale = TutorialScale( frame );
if ( modalScale <= 0 ) return UiButton.None;
var local = (mouse - TutorialOrigin( frame )) / modalScale;
for ( int i = 0; i < 3; i++ )
if ( TutorialTab( i ).IsInside( local ) ) return UiButton.Tab1 + i;
return UiButton.None;
}
float scale = MathF.Min( size.x / CanvasWidth, size.y / CanvasHeight );
if ( scale <= 0 ) return UiButton.None;
var point = (mouse - (size - new Vector2( CanvasWidth, CanvasHeight ) * scale) * 0.5f) / scale;
if ( TitleScreen )
{
point -= TitleShakeOffset;
for ( int i = 0; i < TitleButtonCount; i++ )
if ( TitleMenuButton( i ).IsInside( point ) ) return UiButton.TitlePlay + i;
return UiButton.None;
}
if ( Board is { GameOver: true } && RestartOverlayFade < 1 && GameOverRestartButton.IsInside( point ) ) return UiButton.Restart;
if ( TutorialButton( size ).IsInside( point ) ) return UiButton.Instructions;
if ( OptionsButton( size ).IsInside( point ) ) return UiButton.Options;
if ( LeaveButton( size ).IsInside( point ) ) return UiButton.Leave;
return UiButton.None;
}
}