(Feat) Add Animation Fps Charater
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SettingsMenuWidget.h"
|
||||
|
||||
#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"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Reglages"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Ordre des pages dans le WidgetSwitcher. Un bouton dont la page manque se
|
||||
// grise tout seul, ce qui reste vrai si un onglet vient a etre retire.
|
||||
constexpr int32 TabIndexGameplay = 0;
|
||||
constexpr int32 TabIndexDisplay = 1;
|
||||
constexpr int32 TabIndexGraphics = 2;
|
||||
constexpr int32 TabIndexControls = 3;
|
||||
constexpr int32 TabIndexAudio = 4;
|
||||
}
|
||||
|
||||
USettingsMenuWidget::USettingsMenuWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
// Sans ce drapeau le widget ne peut pas prendre le focus clavier, et
|
||||
// NativeOnKeyDown ne serait jamais appele. Il ne se change qu'a la
|
||||
// construction, d'ou sa presence ici et pas dans Open().
|
||||
SetIsFocusable(true);
|
||||
|
||||
ResetDialogMessage = LOCTEXT("ResetConfirm", "Réinitialiser cet onglet ?");
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
if (BackButton)
|
||||
{
|
||||
BackButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleBackClicked);
|
||||
}
|
||||
|
||||
if (ResetButton)
|
||||
{
|
||||
ResetButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleResetClicked);
|
||||
}
|
||||
|
||||
if (GameplayTabButton)
|
||||
{
|
||||
GameplayTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleGameplayTabClicked);
|
||||
}
|
||||
|
||||
if (DisplayTabButton)
|
||||
{
|
||||
DisplayTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleDisplayTabClicked);
|
||||
}
|
||||
|
||||
if (GraphicsTabButton)
|
||||
{
|
||||
GraphicsTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleGraphicsTabClicked);
|
||||
}
|
||||
|
||||
if (ControlsTabButton)
|
||||
{
|
||||
ControlsTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleControlsTabClicked);
|
||||
}
|
||||
|
||||
if (AudioTabButton)
|
||||
{
|
||||
AudioTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleAudioTabClicked);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
for (int32 Index = 0; Index < TabSwitcher->GetNumWidgets(); ++Index)
|
||||
{
|
||||
if (USettingsTabWidget* Tab = Cast<USettingsTabWidget>(TabSwitcher->GetWidgetAtIndex(Index)))
|
||||
{
|
||||
Tab->SetOwningMenu(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTabButtonStates();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::NativeDestruct()
|
||||
{
|
||||
if (BackButton)
|
||||
{
|
||||
BackButton->OnClicked.RemoveDynamic(this, &USettingsMenuWidget::HandleBackClicked);
|
||||
}
|
||||
|
||||
if (ResetButton)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
ActiveTab->RefreshFromSettings();
|
||||
}
|
||||
|
||||
// Le focus doit etre repris a CHAQUE ouverture, sinon Echap reste sans effet.
|
||||
SetKeyboardFocus();
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::Close()
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
FinishConfirmation(false);
|
||||
}
|
||||
|
||||
if (bKeyCaptureOpen)
|
||||
{
|
||||
CloseKeyCapture();
|
||||
}
|
||||
|
||||
SetVisibility(ESlateVisibility::Collapsed);
|
||||
|
||||
// La sauvegarde a lieu ici et pas a chaque clic de fleche : ecrire le .ini
|
||||
// vingt fois pendant qu'on cherche la bonne sensibilite ne sert a rien.
|
||||
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
|
||||
{
|
||||
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)
|
||||
{
|
||||
const FKey Key = InKeyEvent.GetKey();
|
||||
|
||||
if (Key == EKeys::Escape)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
FinishConfirmation(false);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
OnCloseRequested.Broadcast();
|
||||
|
||||
// Handled : sans ca la touche continue de remonter et le menu de pause
|
||||
// la consommerait une seconde fois.
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
return Super::NativeOnKeyDown(InGeometry, InKeyEvent);
|
||||
}
|
||||
|
||||
USettingsTabWidget* USettingsMenuWidget::GetActiveTab() const
|
||||
{
|
||||
return TabSwitcher ? Cast<USettingsTabWidget>(TabSwitcher->GetActiveWidget()) : nullptr;
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::ShowTab(int32 Index)
|
||||
{
|
||||
if (!TabSwitcher || Index < 0 || Index >= TabSwitcher->GetNumWidgets())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TabSwitcher->SetActiveWidgetIndex(Index);
|
||||
|
||||
// L'onglet a change, donc le bouton a griser aussi.
|
||||
UpdateTabButtonStates();
|
||||
|
||||
if (USettingsTabWidget* ActiveTab = GetActiveTab())
|
||||
{
|
||||
ActiveTab->RefreshFromSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::UpdateTabButtonStates()
|
||||
{
|
||||
const int32 PageCount = TabSwitcher ? TabSwitcher->GetNumWidgets() : 0;
|
||||
const int32 ActiveIndex = TabSwitcher ? TabSwitcher->GetActiveWidgetIndex() : INDEX_NONE;
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (Button)
|
||||
{
|
||||
Button->SetIsEnabled(PageCount > TabIndex && ActiveIndex != TabIndex);
|
||||
}
|
||||
};
|
||||
|
||||
ApplyState(GameplayTabButton, TabIndexGameplay);
|
||||
ApplyState(DisplayTabButton, TabIndexDisplay);
|
||||
ApplyState(GraphicsTabButton, TabIndexGraphics);
|
||||
ApplyState(ControlsTabButton, TabIndexControls);
|
||||
ApplyState(AudioTabButton, TabIndexAudio);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleBackClicked()
|
||||
{
|
||||
OnCloseRequested.Broadcast();
|
||||
}
|
||||
|
||||
UConfirmDialogWidget* USettingsMenuWidget::EnsureDialog()
|
||||
{
|
||||
if (ResetDialog)
|
||||
{
|
||||
return ResetDialog;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (ResetDialog)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleGameplayTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexGameplay);
|
||||
}
|
||||
|
||||
void USettingsMenuWidget::HandleDisplayTabClicked()
|
||||
{
|
||||
ShowTab(TabIndexDisplay);
|
||||
}
|
||||
|
||||
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