added main and pause menus

This commit is contained in:
2026-06-05 20:00:33 +03:00
parent 0792f7cdfd
commit 61d5a57d8d
53 changed files with 1402 additions and 49 deletions
@@ -0,0 +1,61 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "AudioSettingsTab.h"
#include "Components/Slider.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Global/AudioSettingsSubsystem.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
void UAudioSettingsTab::NativeConstruct()
{
Super::NativeConstruct();
MasterSlider->OnValueChanged.AddUniqueDynamic(this, &UAudioSettingsTab::OnMasterChanged);
MusicSlider->OnValueChanged.AddUniqueDynamic(this, &UAudioSettingsTab::OnMusicChanged);
SfxSlider->OnValueChanged.AddUniqueDynamic(this, &UAudioSettingsTab::OnSfxChanged);
RefreshFromSettings();
}
void UAudioSettingsTab::RefreshFromSettings()
{
const UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
if (!Settings)
return;
MasterSlider->SetValue(Settings->GetGlobalVolume());
MusicSlider->SetValue(Settings->GetMusicVolume());
SfxSlider->SetValue(Settings->GetSfxVolume());
}
void UAudioSettingsTab::OnMasterChanged(float Value)
{
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
Settings->SetGlobalVolume(Value);
ApplyLive();
}
void UAudioSettingsTab::OnMusicChanged(float Value)
{
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
Settings->SetMusicVolume(Value);
ApplyLive();
}
void UAudioSettingsTab::OnSfxChanged(float Value)
{
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
Settings->SetSfxVolume(Value);
ApplyLive();
}
void UAudioSettingsTab::ApplyLive()
{
if (const UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(this))
{
if (UAudioSettingsSubsystem* Audio = GameInstance->GetSubsystem<UAudioSettingsSubsystem>())
Audio->ApplyVolumes();
}
}
@@ -0,0 +1,44 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "AudioSettingsTab.generated.h"
class USlider;
// Audio settings tab. Master / Music / SFX volume sliders. Changes apply live through
// UAudioSettingsSubsystem (routed via the configured SoundMix) and persist on close.
UCLASS(Abstract)
class NAKEDDESIRE_API UAudioSettingsTab : public UCommonUserWidget
{
GENERATED_BODY()
public:
void RefreshFromSettings();
protected:
virtual void NativeConstruct() override;
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<USlider> MasterSlider;
UPROPERTY(meta = (BindWidget))
TObjectPtr<USlider> MusicSlider;
UPROPERTY(meta = (BindWidget))
TObjectPtr<USlider> SfxSlider;
UFUNCTION()
void OnMasterChanged(float Value);
UFUNCTION()
void OnMusicChanged(float Value);
UFUNCTION()
void OnSfxChanged(float Value);
void ApplyLive();
};
@@ -0,0 +1,82 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "GameplaySettingsTab.h"
#include "Components/CheckBox.h"
#include "Components/ComboBoxString.h"
#include "Kismet/KismetInternationalizationLibrary.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
namespace
{
// Index-aligned: combo display name → UE culture code. Add a row here when a new
// translation ships, and make sure the culture is in DefaultGame.ini CulturesToStage.
struct FLanguageOption
{
const TCHAR* DisplayName;
const TCHAR* CultureCode;
};
constexpr FLanguageOption LanguageOptions[] = {
{ TEXT("English"), TEXT("en") },
{ TEXT("Українська"), TEXT("uk") },
{ TEXT("日本語"), TEXT("ja") }
};
}
void UGameplaySettingsTab::NativeConstruct()
{
Super::NativeConstruct();
CensorshipCheckBox->OnCheckStateChanged.AddUniqueDynamic(this, &UGameplaySettingsTab::OnCensorshipChanged);
LanguageCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGameplaySettingsTab::OnLanguageChanged);
RefreshFromSettings();
}
void UGameplaySettingsTab::RefreshFromSettings()
{
const UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
if (!Settings)
return;
CensorshipCheckBox->SetIsChecked(Settings->GetIsCensorshipEnabled());
// Language: match the active culture (e.g. "uk-UA") against our supported codes by prefix.
LanguageCombo->ClearOptions();
const FString CurrentCulture = UKismetInternationalizationLibrary::GetCurrentLanguage();
int32 SelectedIndex = 0;
for (int32 i = 0; i < static_cast<int32>(UE_ARRAY_COUNT(LanguageOptions)); ++i)
{
LanguageCombo->AddOption(LanguageOptions[i].DisplayName);
if (CurrentCulture.StartsWith(LanguageOptions[i].CultureCode))
SelectedIndex = i;
}
LanguageCombo->SetSelectedIndex(SelectedIndex);
}
void UGameplaySettingsTab::OnCensorshipChanged(bool bIsChecked)
{
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
if (!Settings)
return;
Settings->SetIsCensorshipEnabled(bIsChecked);
// Broadcasts OnSettingsChanged so the censorship meshes update immediately.
Settings->ApplyNonResolutionSettings();
}
void UGameplaySettingsTab::OnLanguageChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
{
// Skip the programmatic SetSelectedIndex during RefreshFromSettings.
if (SelectionType == ESelectInfo::Direct)
return;
const int32 Index = LanguageCombo->GetSelectedIndex();
if (Index < 0 || Index >= static_cast<int32>(UE_ARRAY_COUNT(LanguageOptions)))
return;
// Applies immediately (all LOCTEXT re-evaluates) and persists to the game config.
UKismetInternationalizationLibrary::SetCurrentLanguageAndLocale(LanguageOptions[Index].CultureCode, /*SaveToConfig*/ true);
}
@@ -0,0 +1,39 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "GameplaySettingsTab.generated.h"
class UCheckBox;
class UComboBoxString;
// Gameplay settings tab. Censorship toggle (§ compliance) — applies live through
// UNakedDesireUserSettings::OnSettingsChanged, which UCensorshipComponent listens to —
// plus the UI/text language selector (English / Ukrainian / Japanese).
UCLASS(Abstract)
class NAKEDDESIRE_API UGameplaySettingsTab : public UCommonUserWidget
{
GENERATED_BODY()
public:
// Re-reads the current values into the controls. Called by the settings screen on show.
void RefreshFromSettings();
protected:
virtual void NativeConstruct() override;
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UCheckBox> CensorshipCheckBox;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UComboBoxString> LanguageCombo;
UFUNCTION()
void OnCensorshipChanged(bool bIsChecked);
UFUNCTION()
void OnLanguageChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
};
@@ -0,0 +1,137 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "GraphicsSettingsTab.h"
#include "Components/CheckBox.h"
#include "Components/ComboBoxString.h"
#include "Kismet/KismetSystemLibrary.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
namespace
{
// Combo order → EWindowMode. Index-aligned with the WindowModeCombo entries below.
constexpr EWindowMode::Type WindowModes[] = {
EWindowMode::Fullscreen,
EWindowMode::WindowedFullscreen,
EWindowMode::Windowed
};
FString ResolutionToText(const FIntPoint& Resolution)
{
return FString::Printf(TEXT("%d x %d"), Resolution.X, Resolution.Y);
}
}
void UGraphicsSettingsTab::NativeConstruct()
{
Super::NativeConstruct();
QualityCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnQualityChanged);
WindowModeCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnWindowModeChanged);
ResolutionCombo->OnSelectionChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnResolutionChanged);
VSyncCheckBox->OnCheckStateChanged.AddUniqueDynamic(this, &UGraphicsSettingsTab::OnVSyncChanged);
RefreshFromSettings();
}
void UGraphicsSettingsTab::RefreshFromSettings()
{
const UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
if (!Settings)
return;
// Quality presets (Scalability levels 0-3).
QualityCombo->ClearOptions();
QualityCombo->AddOption(TEXT("Low"));
QualityCombo->AddOption(TEXT("Medium"));
QualityCombo->AddOption(TEXT("High"));
QualityCombo->AddOption(TEXT("Epic"));
const int32 Quality = FMath::Clamp(Settings->GetOverallScalabilityLevel(), 0, 3);
QualityCombo->SetSelectedIndex(Quality);
// Window mode.
WindowModeCombo->ClearOptions();
WindowModeCombo->AddOption(TEXT("Fullscreen"));
WindowModeCombo->AddOption(TEXT("Borderless Windowed"));
WindowModeCombo->AddOption(TEXT("Windowed"));
const EWindowMode::Type CurrentMode = Settings->GetFullscreenMode();
int32 ModeIndex = 0;
for (int32 i = 0; i < UE_ARRAY_COUNT(WindowModes); ++i)
{
if (WindowModes[i] == CurrentMode)
{
ModeIndex = i;
break;
}
}
WindowModeCombo->SetSelectedIndex(ModeIndex);
// Resolutions.
ResolutionCombo->ClearOptions();
AvailableResolutions.Reset();
UKismetSystemLibrary::GetSupportedFullscreenResolutions(AvailableResolutions);
const FIntPoint CurrentResolution = Settings->GetScreenResolution();
int32 ResolutionIndex = INDEX_NONE;
for (int32 i = 0; i < AvailableResolutions.Num(); ++i)
{
ResolutionCombo->AddOption(ResolutionToText(AvailableResolutions[i]));
if (AvailableResolutions[i] == CurrentResolution)
ResolutionIndex = i;
}
// Current resolution may not be in the supported list (e.g. windowed) — surface it anyway.
if (ResolutionIndex == INDEX_NONE)
{
ResolutionCombo->AddOption(ResolutionToText(CurrentResolution));
AvailableResolutions.Add(CurrentResolution);
ResolutionIndex = AvailableResolutions.Num() - 1;
}
ResolutionCombo->SetSelectedIndex(ResolutionIndex);
VSyncCheckBox->SetIsChecked(Settings->IsVSyncEnabled());
}
void UGraphicsSettingsTab::OnQualityChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
{
if (SelectionType == ESelectInfo::Direct)
return;
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
if (!Settings)
return;
Settings->SetOverallScalabilityLevel(QualityCombo->GetSelectedIndex());
}
void UGraphicsSettingsTab::OnWindowModeChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
{
if (SelectionType == ESelectInfo::Direct)
return;
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
if (!Settings)
return;
const int32 Index = FMath::Clamp(WindowModeCombo->GetSelectedIndex(), 0, static_cast<int32>(UE_ARRAY_COUNT(WindowModes)) - 1);
Settings->SetFullscreenMode(WindowModes[Index]);
}
void UGraphicsSettingsTab::OnResolutionChanged(FString SelectedItem, ESelectInfo::Type SelectionType)
{
if (SelectionType == ESelectInfo::Direct)
return;
UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings();
if (!Settings)
return;
const int32 Index = ResolutionCombo->GetSelectedIndex();
if (AvailableResolutions.IsValidIndex(Index))
Settings->SetScreenResolution(AvailableResolutions[Index]);
}
void UGraphicsSettingsTab::OnVSyncChanged(bool bIsChecked)
{
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
Settings->SetVSyncEnabled(bIsChecked);
}
@@ -0,0 +1,53 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "GraphicsSettingsTab.generated.h"
class UComboBoxString;
class UCheckBox;
// Graphics settings tab. Overall quality, window mode, resolution and VSync, backed by the
// inherited UGameUserSettings. Changes stage into the settings object; the settings screen's
// Apply button calls ApplySettings to commit resolution/quality changes.
UCLASS(Abstract)
class NAKEDDESIRE_API UGraphicsSettingsTab : public UCommonUserWidget
{
GENERATED_BODY()
public:
void RefreshFromSettings();
protected:
virtual void NativeConstruct() override;
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UComboBoxString> QualityCombo;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UComboBoxString> WindowModeCombo;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UComboBoxString> ResolutionCombo;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UCheckBox> VSyncCheckBox;
// Index-aligned with ResolutionCombo entries.
TArray<FIntPoint> AvailableResolutions;
UFUNCTION()
void OnQualityChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
UFUNCTION()
void OnWindowModeChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
UFUNCTION()
void OnResolutionChanged(FString SelectedItem, ESelectInfo::Type SelectionType);
UFUNCTION()
void OnVSyncChanged(bool bIsChecked);
};
@@ -0,0 +1,72 @@
// © 2025 Naked People Team. All Rights Reserved.
#include "SettingsScreenWidget.h"
#include "AudioSettingsTab.h"
#include "GameplaySettingsTab.h"
#include "GraphicsSettingsTab.h"
#include "Components/Button.h"
#include "Components/WidgetSwitcher.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
void USettingsScreenWidget::NativeOnActivated()
{
Super::NativeOnActivated();
GameplayTabButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::ShowGameplayTab);
AudioTabButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::ShowAudioTab);
GraphicsTabButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::ShowGraphicsTab);
ApplyButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::OnApplyClicked);
BackButton->OnClicked.AddUniqueDynamic(this, &USettingsScreenWidget::OnBackClicked);
// Pull live values into every tab so the controls reflect the current state.
GameplayTab->RefreshFromSettings();
AudioTab->RefreshFromSettings();
GraphicsTab->RefreshFromSettings();
ShowGameplayTab();
}
void USettingsScreenWidget::NativeOnDeactivated()
{
Super::NativeOnDeactivated();
// Persist whatever the player changed, even if they didn't hit Apply.
PersistSettings();
}
void USettingsScreenWidget::ShowGameplayTab()
{
TabSwitcher->SetActiveWidget(GameplayTab);
}
void USettingsScreenWidget::ShowAudioTab()
{
TabSwitcher->SetActiveWidget(AudioTab);
}
void USettingsScreenWidget::ShowGraphicsTab()
{
TabSwitcher->SetActiveWidget(GraphicsTab);
}
void USettingsScreenWidget::OnApplyClicked()
{
// Commits resolution + scalability changes staged by the graphics tab.
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
Settings->ApplySettings(false);
PersistSettings();
}
void USettingsScreenWidget::OnBackClicked()
{
DeactivateWidget();
}
void USettingsScreenWidget::PersistSettings()
{
if (UNakedDesireUserSettings* Settings = UNakedDesireUserSettings::GetNakedDesireUserSettings())
Settings->SaveSettings();
}
@@ -0,0 +1,71 @@
// © 2025 Naked People Team. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "CommonActivatableWidget.h"
#include "SettingsScreenWidget.generated.h"
class UButton;
class UWidgetSwitcher;
class UGameplaySettingsTab;
class UAudioSettingsTab;
class UGraphicsSettingsTab;
// Settings screen with Gameplay / Audio / Graphics tabs. Pushed onto a menu's ModalStack
// (assign as SettingsWidgetClass on the main / pause menus). Apply commits + persists;
// closing the screen also persists pending changes.
UCLASS(Abstract)
class NAKEDDESIRE_API USettingsScreenWidget : public UCommonActivatableWidget
{
GENERATED_BODY()
protected:
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
private:
UPROPERTY(meta = (BindWidget))
TObjectPtr<UWidgetSwitcher> TabSwitcher;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> GameplayTabButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> AudioTabButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> GraphicsTabButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> ApplyButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UButton> BackButton;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UGameplaySettingsTab> GameplayTab;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UAudioSettingsTab> AudioTab;
UPROPERTY(meta = (BindWidget))
TObjectPtr<UGraphicsSettingsTab> GraphicsTab;
UFUNCTION()
void ShowGameplayTab();
UFUNCTION()
void ShowAudioTab();
UFUNCTION()
void ShowGraphicsTab();
UFUNCTION()
void OnApplyClicked();
UFUNCTION()
void OnBackClicked();
void PersistSettings();
};