(Feat) Add Main Menu + Music Manger
This commit is contained in:
@@ -6,7 +6,11 @@
|
||||
#include "Components/Button.h"
|
||||
#include "Components/WidgetSwitcher.h"
|
||||
#include "ConfirmDialogWidget.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "KeyCaptureWidget.h"
|
||||
#include "UserSettings/EnhancedInputUserSettings.h"
|
||||
#include "SettingsTabWidget.h"
|
||||
#include "SurvivalUserSettings.h"
|
||||
|
||||
@@ -19,7 +23,8 @@ namespace
|
||||
constexpr int32 TabIndexGameplay = 0;
|
||||
constexpr int32 TabIndexDisplay = 1;
|
||||
constexpr int32 TabIndexGraphics = 2;
|
||||
constexpr int32 TabIndexAudio = 3;
|
||||
constexpr int32 TabIndexControls = 3;
|
||||
constexpr int32 TabIndexAudio = 4;
|
||||
}
|
||||
|
||||
USettingsMenuWidget::USettingsMenuWidget(const FObjectInitializer& ObjectInitializer)
|
||||
@@ -62,16 +67,27 @@ void USettingsMenuWidget::NativeConstruct()
|
||||
GraphicsTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleGraphicsTabClicked);
|
||||
}
|
||||
|
||||
if (ControlsTabButton)
|
||||
{
|
||||
ControlsTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleControlsTabClicked);
|
||||
}
|
||||
|
||||
if (AudioTabButton)
|
||||
{
|
||||
AudioTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleAudioTabClicked);
|
||||
}
|
||||
|
||||
if (ResetDialog)
|
||||
// Chaque onglet recoit un pointeur vers nous : c'est par la qu'il demandera
|
||||
// une confirmation, sans avoir a fabriquer sa propre boite.
|
||||
if (TabSwitcher)
|
||||
{
|
||||
ResetDialog->OnConfirmed.AddDynamic(this, &USettingsMenuWidget::HandleResetConfirmed);
|
||||
ResetDialog->OnCancelled.AddDynamic(this, &USettingsMenuWidget::HandleResetCancelled);
|
||||
ResetDialog->Close();
|
||||
for (int32 Index = 0; Index < TabSwitcher->GetNumWidgets(); ++Index)
|
||||
{
|
||||
if (USettingsTabWidget* Tab = Cast<USettingsTabWidget>(TabSwitcher->GetWidgetAtIndex(Index)))
|
||||
{
|
||||
Tab->SetOwningMenu(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTabButtonStates();
|
||||
@@ -89,6 +105,20 @@ void USettingsMenuWidget::NativeDestruct()
|
||||
ResetButton->OnClicked.RemoveDynamic(this, &USettingsMenuWidget::HandleResetClicked);
|
||||
}
|
||||
|
||||
// La boite et le voile vivent au viewport, pas dans notre arbre : ils ne
|
||||
// disparaitraient pas avec nous.
|
||||
if (ResetDialog)
|
||||
{
|
||||
ResetDialog->RemoveFromParent();
|
||||
ResetDialog = nullptr;
|
||||
}
|
||||
|
||||
if (KeyCapture)
|
||||
{
|
||||
KeyCapture->RemoveFromParent();
|
||||
KeyCapture = nullptr;
|
||||
}
|
||||
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
@@ -96,6 +126,9 @@ void USettingsMenuWidget::Open()
|
||||
{
|
||||
SetVisibility(ESlateVisibility::Visible);
|
||||
|
||||
// L'ecran a pu etre referme sur un autre onglet que celui d'ouverture.
|
||||
UpdateTabButtonStates();
|
||||
|
||||
// L'ecran a pu etre ouvert, referme, puis rouvert apres un changement fait
|
||||
// ailleurs : on relit toujours plutot que de faire confiance a l'affichage.
|
||||
if (USettingsTabWidget* ActiveTab = GetActiveTab())
|
||||
@@ -109,12 +142,18 @@ void USettingsMenuWidget::Open()
|
||||
|
||||
void USettingsMenuWidget::Close()
|
||||
{
|
||||
if (ResetDialog)
|
||||
// Fermer l'ecran avec une question en suspens vaut un refus : sinon le
|
||||
// demandeur -- typiquement un changement de resolution en attente de
|
||||
// confirmation -- resterait dans un etat non tranche.
|
||||
if (bResetDialogOpen)
|
||||
{
|
||||
ResetDialog->Close();
|
||||
FinishConfirmation(false);
|
||||
}
|
||||
|
||||
bResetDialogOpen = false;
|
||||
if (bKeyCaptureOpen)
|
||||
{
|
||||
CloseKeyCapture();
|
||||
}
|
||||
|
||||
SetVisibility(ESlateVisibility::Collapsed);
|
||||
|
||||
@@ -124,6 +163,20 @@ void USettingsMenuWidget::Close()
|
||||
{
|
||||
Settings->SaveSettings();
|
||||
}
|
||||
|
||||
// Les touches remappees vivent ailleurs : Enhanced Input les serialise pour
|
||||
// son compte, dans son propre fichier. Deux systemes de persistance, mais un
|
||||
// seul moment ou l'on ecrit.
|
||||
if (const ULocalPlayer* LocalPlayer = GetOwningLocalPlayer())
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
|
||||
{
|
||||
if (UEnhancedInputUserSettings* InputSettings = Subsystem->GetUserSettings())
|
||||
{
|
||||
InputSettings->SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FReply USettingsMenuWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
|
||||
@@ -132,12 +185,18 @@ FReply USettingsMenuWidget::NativeOnKeyDown(const FGeometry& InGeometry, const F
|
||||
|
||||
if (Key == EKeys::Escape)
|
||||
{
|
||||
// La boite de confirmation gere Echap elle-meme quand elle a le focus.
|
||||
// Si elle l'a perdu, on annule ici plutot que de fermer tout l'ecran en
|
||||
// laissant la question affichee.
|
||||
// La boite et le voile gerent Echap eux-memes quand ils ont le focus.
|
||||
// S'ils l'ont perdu, on annule ici plutot que de fermer tout l'ecran en
|
||||
// laissant la question ou le voile affiche.
|
||||
if (bKeyCaptureOpen)
|
||||
{
|
||||
CloseKeyCapture();
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
if (bResetDialogOpen)
|
||||
{
|
||||
HandleResetCancelled();
|
||||
FinishConfirmation(false);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
@@ -165,6 +224,9 @@ void USettingsMenuWidget::ShowTab(int32 Index)
|
||||
|
||||
TabSwitcher->SetActiveWidgetIndex(Index);
|
||||
|
||||
// L'onglet a change, donc le bouton a griser aussi.
|
||||
UpdateTabButtonStates();
|
||||
|
||||
if (USettingsTabWidget* ActiveTab = GetActiveTab())
|
||||
{
|
||||
ActiveTab->RefreshFromSettings();
|
||||
@@ -174,26 +236,24 @@ void USettingsMenuWidget::ShowTab(int32 Index)
|
||||
void USettingsMenuWidget::UpdateTabButtonStates()
|
||||
{
|
||||
const int32 PageCount = TabSwitcher ? TabSwitcher->GetNumWidgets() : 0;
|
||||
const int32 ActiveIndex = TabSwitcher ? TabSwitcher->GetActiveWidgetIndex() : INDEX_NONE;
|
||||
|
||||
if (GameplayTabButton)
|
||||
// Un bouton est cliquable si sa page existe ET qu'on n'y est pas deja.
|
||||
// Griser l'onglet courant lui sert d'indicateur de position : sans ca, rien
|
||||
// ne distingue visuellement l'onglet affiche des autres.
|
||||
auto ApplyState = [PageCount, ActiveIndex](UButton* Button, int32 TabIndex)
|
||||
{
|
||||
GameplayTabButton->SetIsEnabled(PageCount > TabIndexGameplay);
|
||||
}
|
||||
if (Button)
|
||||
{
|
||||
Button->SetIsEnabled(PageCount > TabIndex && ActiveIndex != TabIndex);
|
||||
}
|
||||
};
|
||||
|
||||
if (DisplayTabButton)
|
||||
{
|
||||
DisplayTabButton->SetIsEnabled(PageCount > TabIndexDisplay);
|
||||
}
|
||||
|
||||
if (GraphicsTabButton)
|
||||
{
|
||||
GraphicsTabButton->SetIsEnabled(PageCount > TabIndexGraphics);
|
||||
}
|
||||
|
||||
if (AudioTabButton)
|
||||
{
|
||||
AudioTabButton->SetIsEnabled(PageCount > TabIndexAudio);
|
||||
}
|
||||
ApplyState(GameplayTabButton, TabIndexGameplay);
|
||||
ApplyState(DisplayTabButton, TabIndexDisplay);
|
||||
ApplyState(GraphicsTabButton, TabIndexGraphics);
|
||||
ApplyState(ControlsTabButton, TabIndexControls);
|
||||
ApplyState(AudioTabButton, TabIndexAudio);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleBackClicked()
|
||||
@@ -201,20 +261,66 @@ void USettingsMenuWidget::HandleBackClicked()
|
||||
OnCloseRequested.Broadcast();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetClicked()
|
||||
UConfirmDialogWidget* USettingsMenuWidget::EnsureDialog()
|
||||
{
|
||||
// Sans boite assignee on ne bloque pas le joueur : on reinitialise.
|
||||
if (!ResetDialog)
|
||||
if (ResetDialog)
|
||||
{
|
||||
HandleResetConfirmed();
|
||||
return;
|
||||
return ResetDialog;
|
||||
}
|
||||
|
||||
bResetDialogOpen = true;
|
||||
ResetDialog->Open(ResetDialogMessage);
|
||||
if (!ResetDialogClass)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResetDialog = CreateWidget<UConfirmDialogWidget>(GetOwningPlayer(), ResetDialogClass);
|
||||
if (!ResetDialog)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResetDialog->OnConfirmed.AddDynamic(this, &USettingsMenuWidget::HandleDialogConfirmed);
|
||||
ResetDialog->OnCancelled.AddDynamic(this, &USettingsMenuWidget::HandleDialogCancelled);
|
||||
ResetDialog->AddToViewport(DialogZOrder);
|
||||
|
||||
return ResetDialog;
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetConfirmed()
|
||||
bool USettingsMenuWidget::RequestConfirmation(const FText& MessageOrFormat, float CountdownSeconds, const FOnSettingsConfirmationResult& OnResult)
|
||||
{
|
||||
UConfirmDialogWidget* Dialog = EnsureDialog();
|
||||
if (!Dialog)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USettingsMenuWidget : ResetDialogClass n'est pas assignee, la question ne peut pas etre posee."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Une seule boite pour tout l'ecran : si une question etait deja posee, la
|
||||
// nouvelle l'ecrase. On repond "non" a l'ancienne pour que son demandeur ne
|
||||
// reste pas suspendu a une reponse qui n'arrivera plus.
|
||||
if (PendingConfirmation.IsBound())
|
||||
{
|
||||
const FOnSettingsConfirmationResult Superseded = PendingConfirmation;
|
||||
PendingConfirmation.Unbind();
|
||||
Superseded.ExecuteIfBound(false);
|
||||
}
|
||||
|
||||
PendingConfirmation = OnResult;
|
||||
bResetDialogOpen = true;
|
||||
|
||||
if (CountdownSeconds > 0.f)
|
||||
{
|
||||
Dialog->OpenWithCountdown(MessageOrFormat, CountdownSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialog->Open(MessageOrFormat);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::FinishConfirmation(bool bConfirmed)
|
||||
{
|
||||
bResetDialogOpen = false;
|
||||
|
||||
@@ -223,26 +329,51 @@ void USettingsMenuWidget::HandleResetConfirmed()
|
||||
ResetDialog->Close();
|
||||
}
|
||||
|
||||
// Detache AVANT d'appeler : le demandeur a parfaitement le droit de reposer
|
||||
// une question dans son propre handler.
|
||||
const FOnSettingsConfirmationResult Answered = PendingConfirmation;
|
||||
PendingConfirmation.Unbind();
|
||||
|
||||
SetKeyboardFocus();
|
||||
|
||||
Answered.ExecuteIfBound(bConfirmed);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleDialogConfirmed()
|
||||
{
|
||||
FinishConfirmation(true);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleDialogCancelled()
|
||||
{
|
||||
FinishConfirmation(false);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetClicked()
|
||||
{
|
||||
FOnSettingsConfirmationResult Callback;
|
||||
Callback.BindDynamic(this, &USettingsMenuWidget::HandleResetAnswered);
|
||||
|
||||
// Sans boite configuree on ne bloque pas le joueur : on reinitialise.
|
||||
if (!RequestConfirmation(ResetDialogMessage, 0.f, Callback))
|
||||
{
|
||||
HandleResetAnswered(true);
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetAnswered(bool bConfirmed)
|
||||
{
|
||||
if (!bConfirmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Uniquement l'onglet visible : un "Reinitialiser" qui remettrait aussi la
|
||||
// resolution et le volume serait une mauvaise surprise.
|
||||
if (USettingsTabWidget* ActiveTab = GetActiveTab())
|
||||
{
|
||||
ActiveTab->ResetToDefaults();
|
||||
}
|
||||
|
||||
SetKeyboardFocus();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleResetCancelled()
|
||||
{
|
||||
bResetDialogOpen = false;
|
||||
|
||||
if (ResetDialog)
|
||||
{
|
||||
ResetDialog->Close();
|
||||
}
|
||||
|
||||
SetKeyboardFocus();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleGameplayTabClicked()
|
||||
@@ -260,9 +391,127 @@ void USettingsMenuWidget::HandleGraphicsTabClicked()
|
||||
ShowTab(TabIndexGraphics);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleControlsTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexControls);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleAudioTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexAudio);
|
||||
}
|
||||
|
||||
bool USettingsMenuWidget::RequestKeyCapture(const FText& ActionLabel, const FOnSettingsKeyCaptureResult& OnResult)
|
||||
{
|
||||
if (!KeyCaptureClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USettingsMenuWidget : KeyCaptureClass n'est pas assignee, aucun remappage possible."));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cree au premier remappage seulement, puis reutilise -- comme la boite.
|
||||
if (!KeyCapture)
|
||||
{
|
||||
KeyCapture = CreateWidget<UKeyCaptureWidget>(GetOwningPlayer(), KeyCaptureClass);
|
||||
if (!KeyCapture)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyCapture->OnKeyCaptured.AddDynamic(this, &USettingsMenuWidget::HandleKeyCaptured);
|
||||
KeyCapture->OnCaptureCancelled.AddDynamic(this, &USettingsMenuWidget::HandleKeyCaptureCancelled);
|
||||
KeyCapture->OnConflictAnswered.AddDynamic(this, &USettingsMenuWidget::HandleKeyConflictAnswered);
|
||||
|
||||
// Au-dessus de la boite : une confirmation de conflit s'ouvre APRES la
|
||||
// capture, mais le voile doit couvrir l'ecran pendant qu'il attend.
|
||||
KeyCapture->AddToViewport(DialogZOrder + 1);
|
||||
}
|
||||
|
||||
// Une capture deja en cours est abandonnee : son demandeur recoit une touche
|
||||
// invalide plutot que de rester suspendu.
|
||||
if (PendingKeyCapture.IsBound())
|
||||
{
|
||||
const FOnSettingsKeyCaptureResult Superseded = PendingKeyCapture;
|
||||
PendingKeyCapture.Unbind();
|
||||
Superseded.ExecuteIfBound(FKey());
|
||||
}
|
||||
|
||||
PendingKeyCapture = OnResult;
|
||||
bKeyCaptureOpen = true;
|
||||
KeyCapture->BeginCapture(ActionLabel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::ShowKeyCaptureConflict(const FText& Message, const FOnSettingsConfirmationResult& OnAnswer)
|
||||
{
|
||||
if (!KeyCapture || !bKeyCaptureOpen)
|
||||
{
|
||||
// Le voile a ete referme entre-temps : on repond "non" plutot que de
|
||||
// laisser le demandeur suspendu.
|
||||
OnAnswer.ExecuteIfBound(false);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingConflictAnswer = OnAnswer;
|
||||
KeyCapture->ShowConflict(Message);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::CloseKeyCapture()
|
||||
{
|
||||
bKeyCaptureOpen = false;
|
||||
|
||||
if (KeyCapture)
|
||||
{
|
||||
KeyCapture->EndCapture();
|
||||
}
|
||||
|
||||
// On detache tout : si quelqu'un attendait encore une reponse, il la recoit
|
||||
// negative plutot que jamais.
|
||||
const FOnSettingsKeyCaptureResult AbandonedCapture = PendingKeyCapture;
|
||||
const FOnSettingsConfirmationResult AbandonedConflict = PendingConflictAnswer;
|
||||
PendingKeyCapture.Unbind();
|
||||
PendingConflictAnswer.Unbind();
|
||||
|
||||
SetKeyboardFocus();
|
||||
|
||||
AbandonedCapture.ExecuteIfBound(FKey());
|
||||
AbandonedConflict.ExecuteIfBound(false);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleKeyCaptured(FKey CapturedKey)
|
||||
{
|
||||
// Le voile RESTE ouvert : c'est l'onglet qui decide s'il y a conflit, et
|
||||
// donc s'il faut le basculer sur la question ou le refermer.
|
||||
const FOnSettingsKeyCaptureResult Answered = PendingKeyCapture;
|
||||
PendingKeyCapture.Unbind();
|
||||
|
||||
Answered.ExecuteIfBound(CapturedKey);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleKeyCaptureCancelled()
|
||||
{
|
||||
// Echap pendant la capture : rien a arbitrer, on referme.
|
||||
CloseKeyCapture();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleKeyConflictAnswered(bool bConfirmed)
|
||||
{
|
||||
// Detache AVANT d'appeler : le demandeur applique le remappage dans son
|
||||
// handler et pourrait vouloir rouvrir une capture derriere.
|
||||
const FOnSettingsConfirmationResult Answered = PendingConflictAnswer;
|
||||
PendingConflictAnswer.Unbind();
|
||||
|
||||
bKeyCaptureOpen = false;
|
||||
|
||||
if (KeyCapture)
|
||||
{
|
||||
KeyCapture->EndCapture();
|
||||
}
|
||||
|
||||
SetKeyboardFocus();
|
||||
|
||||
Answered.ExecuteIfBound(bConfirmed);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
Reference in New Issue
Block a user