Files
Unreal_EmberWild/Source/Survival_projet/Private/SettingsDisplayWidget.cpp
T

507 lines
15 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "SettingsDisplayWidget.h"
#include "Kismet/KismetSystemLibrary.h"
#include "SettingRowWidget.h"
#include "SettingsMenuWidget.h"
#include "SurvivalUserSettings.h"
#define LOCTEXT_NAMESPACE "Reglages"
namespace SettingsDisplayTab
{
// Ordre des modes dans la ligne. Il doit correspondre a MakeWindowMode et a
// FindDisplayModeIndex : trois listes a garder alignees, d'ou leur voisinage.
constexpr int32 ModeFullscreen = 0;
constexpr int32 ModeBorderless = 1;
constexpr int32 ModeWindowed = 2;
EWindowMode::Type MakeWindowMode(int32 Index)
{
switch (Index)
{
case ModeBorderless: return EWindowMode::WindowedFullscreen;
case ModeWindowed: return EWindowMode::Windowed;
default: return EWindowMode::Fullscreen;
}
}
int32 FindDisplayModeIndex(EWindowMode::Type Mode)
{
switch (Mode)
{
case EWindowMode::WindowedFullscreen: return ModeBorderless;
case EWindowMode::Windowed: return ModeWindowed;
default: return ModeFullscreen;
}
}
/**
* Limites de FPS proposees. 0 = illimitee, c'est la convention de
* SetFrameRateLimit et non une valeur sentinelle a nous.
*/
const TArray<float>& FrameRateLimits()
{
static const TArray<float> Limits = { 30.f, 60.f, 90.f, 120.f, 144.f, 165.f, 240.f, 0.f };
return Limits;
}
int32 FindFrameRateIndex(float Limit)
{
const TArray<float>& Limits = FrameRateLimits();
// Comparaison a la tolerance pres : la valeur relue du .ini a pu passer
// par une conversion et ne plus etre strictement egale.
for (int32 Index = 0; Index < Limits.Num(); ++Index)
{
if (FMath::IsNearlyEqual(Limits[Index], Limit, 0.5f))
{
return Index;
}
}
// Valeur exotique posee a la main dans le .ini : on retombe sur
// "Illimitee" plutot que d'afficher n'importe quoi.
return Limits.Num() - 1;
}
}
USettingsDisplayWidget::USettingsDisplayWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Dans le constructeur, pour la meme raison que ConflictMessage : ecrit dans
// NativeConstruct, ce texte ecraserait a chaque ouverture celui regle dans le
// Blueprint.
VideoConfirmMessage = LOCTEXT("VideoConfirm",
"Garder ce mode d'affichage ?\nRetour automatique dans {0} s.");
}
void USettingsDisplayWidget::NativeConstruct()
{
Super::NativeConstruct();
using namespace SurvivalSettingsRange;
if (DisplayModeRow)
{
DisplayModeRow->SetupAsChoice(LOCTEXT("ModeAffichage", "Mode d'affichage"),
{ LOCTEXT("PleinEcran", "Plein écran"),
LOCTEXT("SansBordure", "Sans bordure"),
LOCTEXT("Fenetre", "Fenêtré") }, 0);
DisplayModeRow->OnIndexChanged.AddDynamic(this, &USettingsDisplayWidget::HandleDisplayModeChanged);
}
if (ResolutionRow)
{
ResolutionRow->OnIndexChanged.AddDynamic(this, &USettingsDisplayWidget::HandleResolutionChanged);
}
if (FrameRateLimitRow)
{
TArray<FText> Labels;
Labels.Reserve(SettingsDisplayTab::FrameRateLimits().Num());
for (const float Limit : SettingsDisplayTab::FrameRateLimits())
{
Labels.Add(FMath::IsNearlyZero(Limit)
? LOCTEXT("Illimitee", "Illimitée")
: FText::AsNumber(FMath::RoundToInt(Limit)));
}
FrameRateLimitRow->SetupAsChoice(LOCTEXT("LimiteFPS", "Limite de FPS"), Labels, 0);
FrameRateLimitRow->OnIndexChanged.AddDynamic(this, &USettingsDisplayWidget::HandleFrameRateLimitChanged);
}
if (VSyncRow)
{
VSyncRow->SetupAsChoice(LOCTEXT("VSync", "Synchronisation verticale"),
{ LOCTEXT("Non", "Non"), LOCTEXT("Oui", "Oui") }, 0);
VSyncRow->OnIndexChanged.AddDynamic(this, &USettingsDisplayWidget::HandleVSyncChanged);
}
if (ResolutionScaleRow)
{
ResolutionScaleRow->SetupAsNumber(LOCTEXT("EchelleResolution", "Échelle de résolution"),
ResolutionScaleMin, ResolutionScaleMax, ResolutionScaleStep, ResolutionScaleArrowStep,
ResolutionScaleMax, LOCTEXT("Pourcent", " %"));
ResolutionScaleRow->OnIndexChanged.AddDynamic(this, &USettingsDisplayWidget::HandleResolutionScaleChanged);
}
if (BrightnessRow)
{
BrightnessRow->SetupAsNumber(LOCTEXT("Luminosite", "Luminosité"),
BrightnessMin, BrightnessMax, BrightnessStep, BrightnessArrowStep,
BrightnessMin, LOCTEXT("Pourcent", " %"));
BrightnessRow->OnIndexChanged.AddDynamic(this, &USettingsDisplayWidget::HandleBrightnessChanged);
}
RebuildResolutionList();
RefreshFromSettings();
}
void USettingsDisplayWidget::RebuildResolutionList()
{
AvailableResolutions.Reset();
// La liste depend du mode : en fenetre on veut des tailles qui tiennent dans
// l'ecran, pas les modes exclusifs de la carte graphique.
const USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
const bool bWindowed = Settings && Settings->GetFullscreenMode() == EWindowMode::Windowed;
if (bWindowed)
{
UKismetSystemLibrary::GetConvenientWindowedResolutions(AvailableResolutions);
}
else
{
UKismetSystemLibrary::GetSupportedFullscreenResolutions(AvailableResolutions);
}
if (AvailableResolutions.Num() == 0)
{
// Ecran non interroge (editeur, machine sans mode expose) : on propose au
// moins la resolution courante pour ne pas afficher une ligne vide.
UE_LOG(LogTemp, Warning, TEXT("USettingsDisplayWidget : aucune resolution retournee par le systeme, repli sur la resolution courante."));
AvailableResolutions.Add(Settings ? Settings->GetScreenResolution() : FIntPoint(1920, 1080));
}
FilterResolutions(Settings ? Settings->GetScreenResolution() : FIntPoint(1920, 1080));
if (!ResolutionRow)
{
return;
}
TArray<FText> Labels;
Labels.Reserve(AvailableResolutions.Num());
for (const FIntPoint& Resolution : AvailableResolutions)
{
// FString::Printf et non FText::AsNumber : ce dernier formate selon la
// locale et insererait un separateur de milliers -- "1 920 x 1 080".
// Une resolution s'ecrit "1920x1080", sans espace ni separateur : c'est
// une reference technique, pas une quantite.
Labels.Add(FText::FromString(FString::Printf(TEXT("%dx%d"), Resolution.X, Resolution.Y)));
}
ResolutionRow->SetupAsChoice(LOCTEXT("Resolution", "Résolution"),
Labels, FindCurrentResolutionIndex(Settings));
}
void USettingsDisplayWidget::FilterResolutions(const FIntPoint& CurrentResolution)
{
// Des PLAGES et non un rapport exact : les resolutions commerciales ne
// tombent pas juste. En 16:9, 1366x768 vaut 1.779 ; en "21:9", 2560x1080
// vaut 2.370, 3440x1440 vaut 2.389 et 3840x1600 vaut 2.400 -- trois valeurs
// differentes pour un format qu'on annonce pourtant comme un seul.
constexpr float WideMin = 1.74f;
constexpr float WideMax = 1.81f;
constexpr float UltrawideMin = 2.30f;
constexpr float UltrawideMax = 2.42f;
TArray<FIntPoint> Kept;
Kept.Reserve(AvailableResolutions.Num());
for (const FIntPoint& Resolution : AvailableResolutions)
{
if (Resolution.X < MinimumResolution.X || Resolution.Y < MinimumResolution.Y)
{
continue;
}
const float Ratio = static_cast<float>(Resolution.X) / static_cast<float>(Resolution.Y);
const bool bIsWide = Ratio >= WideMin && Ratio <= WideMax;
const bool bIsUltrawide = bAllowUltrawide && Ratio >= UltrawideMin && Ratio <= UltrawideMax;
// Tout le reste degage : 4:3, 5:4, 16:10, et le 32:9 des ecrans doubles,
// qui deformerait le HUD sans qu'on ait rien prevu pour lui.
if (!bIsWide && !bIsUltrawide)
{
continue;
}
Kept.Add(Resolution);
}
// La resolution courante doit rester selectionnable meme si elle ne passe pas
// le filtre : sinon un joueur en 16:10 verrait la ligne afficher autre chose
// que ce qu'il utilise vraiment.
if (!Kept.Contains(CurrentResolution) && CurrentResolution.X > 0 && CurrentResolution.Y > 0)
{
Kept.Add(CurrentResolution);
}
// Un ecran qui ne proposerait aucun 16:9 vaut mieux non filtre que vide.
if (Kept.IsEmpty())
{
UE_LOG(LogTemp, Warning, TEXT("USettingsDisplayWidget : aucune resolution ne passe le filtre, la liste complete est conservee."));
return;
}
// Tri croissant par surface : la plus petite en haut, la native en bas.
Kept.Sort([](const FIntPoint& A, const FIntPoint& B)
{
return static_cast<int64>(A.X) * A.Y < static_cast<int64>(B.X) * B.Y;
});
AvailableResolutions = MoveTemp(Kept);
}
int32 USettingsDisplayWidget::FindCurrentResolutionIndex(const USurvivalUserSettings* Settings) const
{
if (!Settings)
{
return 0;
}
const FIntPoint Current = Settings->GetScreenResolution();
const int32 Found = AvailableResolutions.IndexOfByKey(Current);
// Resolution absente de la liste (fenetre redimensionnee a la main) : on
// retombe sur la plus grande, en fin de liste.
return Found != INDEX_NONE ? Found : AvailableResolutions.Num() - 1;
}
int32 USettingsDisplayWidget::FindLargestResolutionIndex() const
{
int32 BestIndex = INDEX_NONE;
int64 BestArea = -1;
// On compare la SURFACE et non l'index : rien ne garantit que le systeme
// renvoie ses modes triés, et un 2560x1080 ultralarge ne doit pas passer
// devant un 1920x1200.
for (int32 Index = 0; Index < AvailableResolutions.Num(); ++Index)
{
const int64 Area = static_cast<int64>(AvailableResolutions[Index].X) * AvailableResolutions[Index].Y;
if (Area > BestArea)
{
BestArea = Area;
BestIndex = Index;
}
}
return BestIndex;
}
void USettingsDisplayWidget::RefreshFromSettings()
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings)
{
return;
}
if (DisplayModeRow)
{
DisplayModeRow->SetIndexSilently(SettingsDisplayTab::FindDisplayModeIndex(Settings->GetFullscreenMode()));
}
if (ResolutionRow)
{
ResolutionRow->SetIndexSilently(FindCurrentResolutionIndex(Settings));
}
if (FrameRateLimitRow)
{
FrameRateLimitRow->SetIndexSilently(SettingsDisplayTab::FindFrameRateIndex(Settings->GetFrameRateLimit()));
}
if (VSyncRow)
{
VSyncRow->SetIndexSilently(Settings->IsVSyncEnabled() ? 1 : 0);
}
if (ResolutionScaleRow)
{
// On lit la valeur BRUTE en pourcentage et non la normalisee : la plage
// autorisee par le moteur ne commence pas a 50 %, une valeur normalisee
// ne correspondrait donc pas a ce que la ligne affiche.
float CurrentNormalized = 0.f;
float CurrentValue = 100.f;
float MinValue = 0.f;
float MaxValue = 100.f;
Settings->GetResolutionScaleInformationEx(CurrentNormalized, CurrentValue, MinValue, MaxValue);
ResolutionScaleRow->SetNumericValueSilently(CurrentValue);
}
if (BrightnessRow)
{
BrightnessRow->SetNumericValueSilently(Settings->GetBrightness());
}
}
void USettingsDisplayWidget::ResetToDefaults()
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings)
{
return;
}
// On ne touche PAS a la resolution ni au mode d'affichage : les remettre
// "par defaut" pourrait basculer sur un mode que l'ecran ne gere pas, et le
// joueur n'a pas demande ca en cliquant Reinitialiser.
Settings->SetFrameRateLimit(0.f);
Settings->SetVSyncEnabled(false);
Settings->SetResolutionScaleNormalized(1.f);
Settings->SetSurvivalDisplayToDefaults();
Settings->ApplyNonResolutionSettings();
RefreshFromSettings();
}
void USettingsDisplayWidget::HandleDisplayModeChanged(int32 NewIndex)
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings)
{
return;
}
Settings->SetFullscreenMode(SettingsDisplayTab::MakeWindowMode(NewIndex));
// Passer en fenetre change la liste des tailles proposables.
RebuildResolutionList();
// En plein ecran ou sans bordure, on bascule d'office sur la plus grande
// resolution disponible -- c'est-a-dire celle de l'ecran.
//
// Sans ca, un jeu lance en Standalone dans une fenetre de 1280x720 garderait
// cette resolution en plein ecran : l'image serait etiree sur tout l'ecran et
// visiblement floue, alors que le joueur vient de demander le contraire. En
// fenetre on ne touche a rien, la taille choisie est celle qu'il veut.
if (NewIndex != SettingsDisplayTab::ModeWindowed)
{
const int32 LargestIndex = FindLargestResolutionIndex();
if (AvailableResolutions.IsValidIndex(LargestIndex))
{
Settings->SetScreenResolution(AvailableResolutions[LargestIndex]);
if (ResolutionRow)
{
ResolutionRow->SetIndexSilently(LargestIndex);
}
}
}
ApplyVideoModeAndConfirm();
}
void USettingsDisplayWidget::HandleResolutionChanged(int32 NewIndex)
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings || !AvailableResolutions.IsValidIndex(NewIndex))
{
return;
}
Settings->SetScreenResolution(AvailableResolutions[NewIndex]);
ApplyVideoModeAndConfirm();
}
void USettingsDisplayWidget::ApplyVideoModeAndConfirm()
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings)
{
return;
}
// ApplyResolutionSettings et non ApplySettings : on ne veut surtout pas
// repousser la qualite graphique au passage, ce serait une saccade a chaque
// clic de fleche.
Settings->ApplyResolutionSettings(/*bCheckForCommandLineOverrides=*/false);
if (!OwningMenu.IsValid())
{
// Sans ecran parent, personne ne peut poser la question. On valide, au
// prix du filet de securite -- mieux que de laisser un etat en suspens.
Settings->ConfirmVideoMode();
return;
}
FOnSettingsConfirmationResult Callback;
Callback.BindDynamic(this, &USettingsDisplayWidget::HandleVideoConfirmAnswered);
if (!OwningMenu->RequestConfirmation(VideoConfirmMessage, VideoConfirmSeconds, Callback))
{
Settings->ConfirmVideoMode();
}
}
void USettingsDisplayWidget::HandleVideoConfirmAnswered(bool bConfirmed)
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings)
{
return;
}
if (bConfirmed)
{
// Ce mode devient la reference vers laquelle RevertVideoMode ramenera la
// prochaine fois.
Settings->ConfirmVideoMode();
}
else
{
Settings->RevertVideoMode();
Settings->ApplyResolutionSettings(/*bCheckForCommandLineOverrides=*/false);
RebuildResolutionList();
}
RefreshFromSettings();
}
void USettingsDisplayWidget::HandleFrameRateLimitChanged(int32 NewIndex)
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings || !SettingsDisplayTab::FrameRateLimits().IsValidIndex(NewIndex))
{
return;
}
Settings->SetFrameRateLimit(SettingsDisplayTab::FrameRateLimits()[NewIndex]);
Settings->ApplyNonResolutionSettings();
}
void USettingsDisplayWidget::HandleVSyncChanged(int32 NewIndex)
{
if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
{
Settings->SetVSyncEnabled(NewIndex == 1);
Settings->ApplyNonResolutionSettings();
}
}
void USettingsDisplayWidget::HandleResolutionScaleChanged(int32 NewIndex)
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings || !ResolutionScaleRow)
{
return;
}
Settings->SetResolutionScaleValueEx(ResolutionScaleRow->GetNumericValue());
Settings->ApplyNonResolutionSettings();
}
void USettingsDisplayWidget::HandleBrightnessChanged(int32 NewIndex)
{
USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
if (!Settings || !BrightnessRow)
{
return;
}
Settings->SetBrightness(BrightnessRow->GetNumericValue());
// ApplySurvivalSettings et non ApplyNonResolutionSettings : la luminosite
// est a nous, elle ne passe pas par le volet video du moteur.
Settings->ApplySurvivalSettings();
}
#undef LOCTEXT_NAMESPACE