268 lines
7.5 KiB
C++
268 lines
7.5 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "SettingRowWidget.h"
|
|
|
|
#include "Components/Button.h"
|
|
#include "Components/ProgressBar.h"
|
|
#include "Components/Slider.h"
|
|
#include "Components/TextBlock.h"
|
|
|
|
void USettingRowWidget::NativeOnInitialized()
|
|
{
|
|
Super::NativeOnInitialized();
|
|
|
|
// NativeOnInitialized et non NativeConstruct : l'onglet parent appelle
|
|
// SetupAs... depuis SON NativeConstruct, et rien ne garantit l'ordre entre
|
|
// le construct du parent et celui de ses enfants. Initialize, lui, tourne
|
|
// une seule fois a la creation de l'arbre, avant tout le monde.
|
|
if (LeftArrowButton)
|
|
{
|
|
LeftArrowButton->OnClicked.AddDynamic(this, &USettingRowWidget::HandleLeftClicked);
|
|
}
|
|
|
|
if (RightArrowButton)
|
|
{
|
|
RightArrowButton->OnClicked.AddDynamic(this, &USettingRowWidget::HandleRightClicked);
|
|
}
|
|
|
|
if (ValueSlider)
|
|
{
|
|
ValueSlider->OnValueChanged.AddDynamic(this, &USettingRowWidget::HandleSliderValueChanged);
|
|
}
|
|
|
|
// La valeur est POSEE SUR le curseur : si elle restait cliquable, elle
|
|
// avalerait le clic au milieu de la course et le glissement ne partirait
|
|
// jamais. Force ici plutot que dans le WBP : c'est le genre de case qu'on
|
|
// oublie et dont le symptome (une zone morte au centre) est illisible.
|
|
if (ValueText)
|
|
{
|
|
ValueText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
}
|
|
}
|
|
|
|
void USettingRowWidget::SetupAsChoice(const FText& Label, const TArray<FText>& Options, int32 Index)
|
|
{
|
|
bNumeric = false;
|
|
ChoiceLabels = Options;
|
|
StepCount = FMath::Max(Options.Num(), 1);
|
|
ArrowIndexDelta = 1;
|
|
MaxSelectableIndex = INDEX_NONE;
|
|
CurrentIndex = FMath::Clamp(Index, 0, StepCount - 1);
|
|
|
|
if (LabelText)
|
|
{
|
|
LabelText->SetText(Label);
|
|
}
|
|
|
|
// Une jauge sur "Maintien / Bascule" ne veut rien dire : deux positions et
|
|
// rien a doser. On replie l'empilement plutot que d'imposer un second WBP.
|
|
if (ValueBar)
|
|
{
|
|
ValueBar->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
|
|
if (ValueSlider)
|
|
{
|
|
ValueSlider->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
void USettingRowWidget::SetupAsNumber(const FText& Label, float Min, float Max, float Step, float ArrowStep, float Value, const FText& Suffix)
|
|
{
|
|
bNumeric = true;
|
|
ChoiceLabels.Reset();
|
|
NumericMin = Min;
|
|
NumericStep = FMath::Max(Step, KINDA_SMALL_NUMBER);
|
|
NumericSuffix = Suffix;
|
|
|
|
// +1 : les deux extremites comptent. 70 a 120 par 1 fait 51 paliers, pas 50.
|
|
StepCount = FMath::Max(FMath::FloorToInt((Max - Min) / NumericStep) + 1, 1);
|
|
|
|
// Le bond des fleches ne vit qu'en nombre de paliers fins : tout le reste du
|
|
// widget raisonne en index, y compris le curseur.
|
|
ArrowIndexDelta = FMath::Max(FMath::RoundToInt(ArrowStep / NumericStep), 1);
|
|
MaxSelectableIndex = INDEX_NONE;
|
|
|
|
if (LabelText)
|
|
{
|
|
LabelText->SetText(Label);
|
|
}
|
|
|
|
// HitTestInvisible : le remplissage est un decor pose SOUS le curseur, il ne
|
|
// doit surtout pas intercepter le clic qui devrait lancer le glissement.
|
|
if (ValueBar)
|
|
{
|
|
ValueBar->SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
}
|
|
|
|
if (ValueSlider)
|
|
{
|
|
ValueSlider->SetVisibility(ESlateVisibility::Visible);
|
|
|
|
// La course du curseur est graduee en INDEX, pas dans l'unite du
|
|
// reglage : 0 a 10 pour un FOV de 70 a 120 par pas de 5. Le glissement
|
|
// ne peut donc produire qu'un palier valide, et le meme curseur sert a
|
|
// n'importe quelle plage sans conversion.
|
|
ValueSlider->SetMinValue(0.f);
|
|
ValueSlider->SetMaxValue(static_cast<float>(FMath::Max(StepCount - 1, 1)));
|
|
ValueSlider->SetStepSize(1.f);
|
|
}
|
|
|
|
SetNumericValueSilently(Value);
|
|
}
|
|
|
|
float USettingRowWidget::GetNumericValue() const
|
|
{
|
|
return NumericMin + CurrentIndex * NumericStep;
|
|
}
|
|
|
|
void USettingRowWidget::SetIndexSilently(int32 NewIndex)
|
|
{
|
|
// Borne sur StepCount et non sur MaxSelectable : le code a le droit
|
|
// d'afficher un etat que le joueur ne peut pas choisir.
|
|
CurrentIndex = FMath::Clamp(NewIndex, 0, StepCount - 1);
|
|
Refresh();
|
|
}
|
|
|
|
void USettingRowWidget::SetMaxSelectableIndex(int32 InMaxSelectableIndex)
|
|
{
|
|
MaxSelectableIndex = InMaxSelectableIndex;
|
|
Refresh();
|
|
}
|
|
|
|
int32 USettingRowWidget::MaxSelectable() const
|
|
{
|
|
return MaxSelectableIndex == INDEX_NONE
|
|
? StepCount - 1
|
|
: FMath::Clamp(MaxSelectableIndex, 0, StepCount - 1);
|
|
}
|
|
|
|
void USettingRowWidget::SetNumericValueSilently(float Value)
|
|
{
|
|
SetIndexSilently(FMath::RoundToInt((Value - NumericMin) / NumericStep));
|
|
}
|
|
|
|
void USettingRowWidget::HandleLeftClicked()
|
|
{
|
|
StepIndex(ArrowTargetIndex(-1) - CurrentIndex);
|
|
}
|
|
|
|
void USettingRowWidget::HandleRightClicked()
|
|
{
|
|
StepIndex(ArrowTargetIndex(1) - CurrentIndex);
|
|
}
|
|
|
|
int32 USettingRowWidget::ArrowTargetIndex(int32 Direction) const
|
|
{
|
|
// Pas de bond a arrondir : une ligne de choix, ou un reglage dont les deux
|
|
// pas sont identiques.
|
|
if (ArrowIndexDelta <= 1)
|
|
{
|
|
return CurrentIndex + Direction;
|
|
}
|
|
|
|
// On vise le multiple du bond suivant, pas CurrentIndex + bond : arrive a 37
|
|
// au curseur, la fleche droite doit donner 40 et la gauche 35. Sinon les
|
|
// fleches perdent leur reperage des qu'on a glisse une fois.
|
|
if (Direction > 0)
|
|
{
|
|
return ((CurrentIndex / ArrowIndexDelta) + 1) * ArrowIndexDelta;
|
|
}
|
|
|
|
// Division entiere arrondie au superieur, pour que 35 redescende bien a 30
|
|
// et pas a 35 (ce qui n'aurait rien fait).
|
|
return ((CurrentIndex + ArrowIndexDelta - 1) / ArrowIndexDelta - 1) * ArrowIndexDelta;
|
|
}
|
|
|
|
void USettingRowWidget::HandleSliderValueChanged(float NewValue)
|
|
{
|
|
if (bRefreshingSlider)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// On passe par StepIndex plutot que d'ecrire CurrentIndex : le glissement
|
|
// et les fleches doivent produire exactement le meme effet, y compris le
|
|
// grisage des fleches et la diffusion du delegue.
|
|
StepIndex(FMath::RoundToInt(NewValue) - CurrentIndex);
|
|
|
|
// Puis on recale la poignee sur le cran, meme si l'index n'a pas bouge :
|
|
// StepIndex sort sans rien faire dans ce cas, et la poignee resterait
|
|
// plantee entre deux paliers.
|
|
Refresh();
|
|
}
|
|
|
|
void USettingRowWidget::StepIndex(int32 Delta)
|
|
{
|
|
const int32 NewIndex = FMath::Clamp(CurrentIndex + Delta, 0, MaxSelectable());
|
|
|
|
// Pas de bouclage, volontairement : passer de 100 a 5 de sensibilite d'un
|
|
// clic de trop rendrait le jeu injouable sans qu'on comprenne pourquoi.
|
|
if (NewIndex == CurrentIndex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CurrentIndex = NewIndex;
|
|
Refresh();
|
|
|
|
OnIndexChanged.Broadcast(CurrentIndex);
|
|
}
|
|
|
|
void USettingRowWidget::Refresh()
|
|
{
|
|
if (ValueText)
|
|
{
|
|
if (bNumeric)
|
|
{
|
|
FNumberFormattingOptions Format;
|
|
Format.MaximumFractionalDigits = 0;
|
|
|
|
const FText Number = FText::AsNumber(GetNumericValue(), &Format);
|
|
|
|
// FormatOrdered plutot qu'une concatenation : selon la langue, l'unite
|
|
// ne se colle pas forcement du meme cote ni avec la meme espace.
|
|
ValueText->SetText(NumericSuffix.IsEmpty()
|
|
? Number
|
|
: FText::FormatOrdered(NSLOCTEXT("Reglages", "ValeurAvecUnite", "{0}{1}"), Number, NumericSuffix));
|
|
}
|
|
else if (ChoiceLabels.IsValidIndex(CurrentIndex))
|
|
{
|
|
ValueText->SetText(ChoiceLabels[CurrentIndex]);
|
|
}
|
|
}
|
|
|
|
if (ValueBar)
|
|
{
|
|
// Un seul palier : la jauge n'a aucun sens, on la remplit entierement
|
|
// plutot que de diviser par zero.
|
|
ValueBar->SetPercent(StepCount > 1
|
|
? static_cast<float>(CurrentIndex) / static_cast<float>(StepCount - 1)
|
|
: 1.f);
|
|
}
|
|
|
|
if (ValueSlider && bNumeric)
|
|
{
|
|
// Le drapeau evite qu'un repositionnement venant du code soit relu comme
|
|
// un geste du joueur.
|
|
bRefreshingSlider = true;
|
|
ValueSlider->SetValue(static_cast<float>(CurrentIndex));
|
|
bRefreshingSlider = false;
|
|
}
|
|
|
|
// Les fleches se grisent aux extremites : le joueur voit qu'il est au bout
|
|
// au lieu de cliquer dans le vide.
|
|
if (LeftArrowButton)
|
|
{
|
|
LeftArrowButton->SetIsEnabled(CurrentIndex > 0);
|
|
}
|
|
|
|
if (RightArrowButton)
|
|
{
|
|
RightArrowButton->SetIsEnabled(CurrentIndex < MaxSelectable());
|
|
}
|
|
}
|