UI/SettingsPanel.razor

A Razor UI panel for game settings. It displays audio sliders (master, music, sfx), toggles for mute, auto-sell and impact effects, a reset-progress danger action, and persists changes to a PumpSettings singleton.

File Access
@using System
@using Sandbox
@using Sandbox.UI
@namespace DesertPump
@inherits Panel

<root class="settingspanel">

	<div class="scrim" onclick="@Close"></div>

	<div class="sheet">

		<div class="head">
			<div class="title"><i>settings</i> SETTINGS</div>
			<div class="closebtn" onclick="@Close"><i>close</i></div>
		</div>

		<div class="group">
			<div class="grouptitle">AUDIO</div>

			<div class="row">
				<div class="label">Master volume</div>
				<SliderControl class="simple" Value:bind=@Master Min="@(0f)" Max="@(1f)" Step="@(0.05f)"></SliderControl>
				<div class="value">@Percent( Master )</div>
			</div>

			<div class="row">
				<div class="label">Music</div>
				<SliderControl class="simple" Value:bind=@Music Min="@(0f)" Max="@(1f)" Step="@(0.05f)"></SliderControl>
				<div class="value">@Percent( Music )</div>
			</div>

			<div class="row">
				<div class="label">Sound effects</div>
				<SliderControl class="simple" Value:bind=@Sfx Min="@(0f)" Max="@(1f)" Step="@(0.05f)"></SliderControl>
				<div class="value">@Percent( Sfx )</div>
			</div>

			<div class="row toggle @( Settings.Muted ? "on" : "" )" onclick="@ToggleMute">
				<div class="label">Mute everything</div>
				<div class="switch"><div class="knob"></div></div>
			</div>
		</div>

		<div class="group">
			<div class="grouptitle">GAME</div>

			<div class="row toggle @( Settings.AutoSell ? "on" : "" )" onclick="@ToggleAutoSell">
				<div class="label">
					Auto-sell when the tank fills
					<span class="sub">Hands off, but you'll miss the coin sound</span>
				</div>
				<div class="switch"><div class="knob"></div></div>
			</div>

			<div class="row toggle @( Settings.ShowFloatingNumbers ? "on" : "" )" onclick="@ToggleFloaters">
				@* Still stored as ShowFloatingNumbers so old saves keep working, but it
				   governs the whole per-action effects layer now, and the label has to
				   say so or turning it off looks like it broke half the screen. *@
				<div class="label">
					Impact effects
					<span class="sub">Floating numbers, water, coins, rings and screen shake</span>
				</div>
				<div class="switch"><div class="knob"></div></div>
			</div>
		</div>

		<div class="group danger">
			<div class="grouptitle">DANGER</div>

			<div class="row">
				<div class="label">
					Reset progress
					<span class="sub">Every pump, upgrade and coin. Settings are kept.</span>
				</div>

				@if ( confirmingReset )
				{
					<div class="resetrow">
						<div class="resetbtn confirm" onclick="@DoReset">Yes, wipe it</div>
						<div class="resetbtn" onclick="@( () => confirmingReset = false )">Cancel</div>
					</div>
				}
				else
				{
					<div class="resetbtn" onclick="@( () => confirmingReset = true )">Reset</div>
				}
			</div>
		</div>

		<div class="done" onclick="@Close">DONE</div>

	</div>

</root>

@code
{
	/// <summary>Raised when the player dismisses the settings.</summary>
	public Action OnClose { get; set; }

	static PumpSettings Settings => PumpSettings.Current;

	bool confirmingReset;

	// Slider bindings. Each setter persists, so a change survives even if the game
	// is killed rather than closed.
	float Master
	{
		get => Settings.MasterVolume;
		set => Apply( () => Settings.MasterVolume = value, Settings.MasterVolume == value );
	}

	float Music
	{
		get => Settings.MusicVolume;
		set => Apply( () => Settings.MusicVolume = value, Settings.MusicVolume == value );
	}

	float Sfx
	{
		get => Settings.SfxVolume;
		set => Apply( () => Settings.SfxVolume = value, Settings.SfxVolume == value );
	}

	void Apply( Action change, bool unchanged )
	{
		if ( unchanged ) return;

		change();
		Settings.Save();
		StateHasChanged();
	}

	void ToggleMute()
	{
		Settings.Muted = !Settings.Muted;
		Settings.Save();
		StateHasChanged();
	}

	void ToggleAutoSell()
	{
		Settings.AutoSell = !Settings.AutoSell;
		Settings.Save();
		StateHasChanged();
	}

	void ToggleFloaters()
	{
		Settings.ShowFloatingNumbers = !Settings.ShowFloatingNumbers;
		Settings.Save();
		StateHasChanged();
	}

	void DoReset()
	{
		DesertPumpGame.Current?.ResetProgress();
		confirmingReset = false;
		StateHasChanged();
	}

	void Close()
	{
		confirmingReset = false;
		OnClose?.Invoke();
	}

	static string Percent( float value ) => $"{value * 100f:0}%";

	protected override int BuildHash() => HashCode.Combine(
		Settings.MasterVolume, Settings.MusicVolume, Settings.SfxVolume,
		Settings.Muted, Settings.AutoSell, Settings.ShowFloatingNumbers, confirmingReset );
}