// Reglages par onglets. Voir LPSettingsSubsystem.h. #include "LPSettingsSubsystem.h" #include "Blueprint/UserWidget.h" #include "Components/Button.h" #include "Components/ButtonSlot.h" #include "Components/CanvasPanel.h" #include "Components/CanvasPanelSlot.h" #include "Components/HorizontalBox.h" #include "Components/HorizontalBoxSlot.h" #include "Components/Image.h" #include "Components/NativeWidgetHost.h" #include "Components/Overlay.h" #include "Components/OverlaySlot.h" #include "Components/ScrollBox.h" #include "Components/SizeBox.h" #include "Components/TextBlock.h" #include "Components/PanelWidget.h" #include "Components/VerticalBox.h" #include "Components/VerticalBoxSlot.h" #include "Engine/GameInstance.h" #include "Engine/Texture2D.h" #include "LPKeyboardSubsystem.h" #include "LifePlay.h" #include "Styling/CoreStyle.h" #include "UObject/TextProperty.h" namespace LPSettingsUi { const TCHAR* TabNames[] = { TEXT("General"), TEXT("Audio"), TEXT("Video"), TEXT("Keyboard"), TEXT("Lewd"), TEXT("Misc") }; constexpr int32 TabGeneral = 0; constexpr int32 TabAudio = 1; constexpr int32 TabVideo = 2; constexpr int32 TabKeyboard = 3; constexpr int32 TabLewd = 4; constexpr int32 TabMisc = 5; FString VolumeLabel(const FString& Text) { if (Text.Equals(TEXT("Volume - Sex Moans"), ESearchCase::IgnoreCase) || Text.Equals(TEXT("Volume - Sex Moan"), ESearchCase::IgnoreCase)) { return TEXT("Volume - SFX"); } if (Text.Equals(TEXT("Volume - Sex"), ESearchCase::IgnoreCase)) { return TEXT("Volume - Dialogue"); } return Text; } void RenameVolumes(UWidget* Widget) { if (!Widget) { return; } if (FTextProperty* Setting = CastField(Widget->GetClass()->FindPropertyByName(TEXT("SettingText")))) { const FString Next = VolumeLabel(Setting->GetPropertyValue_InContainer(Widget).ToString()); if (Next != Setting->GetPropertyValue_InContainer(Widget).ToString()) { Setting->SetPropertyValue_InContainer(Widget, FText::FromString(Next)); } } if (UTextBlock* Block = Cast(Widget)) { const FString Next = VolumeLabel(Block->GetText().ToString()); if (Next != Block->GetText().ToString()) { Block->SetText(FText::FromString(Next)); } } if (UUserWidget* User = Cast(Widget)) { RenameVolumes(User->GetRootWidget()); } if (UPanelWidget* Panel = Cast(Widget)) { for (int32 Index = 0; Index < Panel->GetChildrenCount(); ++Index) { RenameVolumes(Panel->GetChildAt(Index)); } } } bool IsOpen(const UWidget* Widget) { if (!Widget) { return false; } const ESlateVisibility Visibility = Widget->GetVisibility(); return Visibility != ESlateVisibility::Collapsed && Visibility != ESlateVisibility::Hidden; } FString RowText(const UWidget* Widget) { FString Text; if (!Widget) { return Text; } if (const FTextProperty* Setting = CastField(Widget->GetClass()->FindPropertyByName(TEXT("SettingText")))) { Text += Setting->GetPropertyValue_InContainer(Widget).ToString(); } Text += TEXT(" ") + Widget->GetToolTipText().ToString(); TArray Pending; Pending.Add(const_cast(Widget)); while (Pending.Num() > 0) { UWidget* Current = Pending.Pop(EAllowShrinking::No); if (const UTextBlock* Block = Cast(Current)) { Text += TEXT(" ") + Block->GetText().ToString(); } if (const UUserWidget* User = Cast(Current)) { if (UWidget* Root = User->GetRootWidget()) { Pending.Add(Root); } } if (const UPanelWidget* Panel = Cast(Current)) { for (int32 Index = 0; Index < Panel->GetChildrenCount(); ++Index) { Pending.Add(Panel->GetChildAt(Index)); } } } return Text; } int32 CategoryOf(const UWidget* Widget) { if (Widget && Widget->GetFName() == TEXT("BackButton")) { return -1; } if (Widget && Widget->GetName().StartsWith(TEXT("LPColor"))) { return TabVideo; } const FString Text = RowText(Widget).ToLower(); if (Text.Contains(TEXT("volume")) || Text.Contains(TEXT("audio")) || Text.Contains(TEXT("sound")) || Text.Contains(TEXT("music"))) { return TabAudio; } if (Text.Contains(TEXT("fps")) || Text.Contains(TEXT("screen")) || Text.Contains(TEXT("shadow")) || Text.Contains(TEXT("quality")) || Text.Contains(TEXT("fullscreen")) || Text.Contains(TEXT("window")) || Text.Contains(TEXT("toon")) || Text.Contains(TEXT("depth of field")) || Text.Contains(TEXT("censor")) || Text.Contains(TEXT("morph")) || Text.Contains(TEXT("dog")) || Text.Contains(TEXT("jiggle")) || Text.Contains(TEXT("resolution")) || Text.Contains(TEXT("vsync")) || Text.Contains(TEXT("brightness"))) { return TabVideo; } if (Text.Contains(TEXT("sex")) || Text.Contains(TEXT("condom")) || Text.Contains(TEXT("squirt")) || Text.Contains(TEXT("pregnan")) || Text.Contains(TEXT("underwear")) || Text.Contains(TEXT("orientation")) || Text.Contains(TEXT("trans")) || Text.Contains(TEXT("relationship")) || Text.Contains(TEXT("moan")) || Text.Contains(TEXT("lewd"))) { return TabLewd; } if (Text.Contains(TEXT("difficult")) || Text.Contains(TEXT("clock")) || Text.Contains(TEXT("camera")) || Text.Contains(TEXT("scene")) || Text.Contains(TEXT("pause")) || Text.Contains(TEXT("timeout")) || Text.Contains(TEXT("tutorial")) || Text.Contains(TEXT("marker")) || Text.Contains(TEXT("building")) || Text.Contains(TEXT("animation")) || Text.Contains(TEXT("action filter")) || Text.Contains(TEXT("dye")) || Text.Contains(TEXT("stat")) || Text.Contains(TEXT("child")) || Text.Contains(TEXT("redress"))) { return TabGeneral; } return TabMisc; } UTextBlock* FirstText(UWidget* Widget) { if (!Widget) { return nullptr; } if (UTextBlock* Text = Cast(Widget)) { return Text; } if (UUserWidget* User = Cast(Widget)) { if (UTextBlock* Text = FirstText(User->GetRootWidget())) { return Text; } } if (UPanelWidget* Panel = Cast(Widget)) { for (int32 Index = 0; Index < Panel->GetChildrenCount(); ++Index) { if (UTextBlock* Text = FirstText(Panel->GetChildAt(Index))) { return Text; } } } return nullptr; } UImage* RoundIcon(UWidget* Owner, const TCHAR* TexturePath, float Size) { UImage* Image = NewObject(Owner); if (UTexture2D* Texture = LoadObject(nullptr, TexturePath)) { FSlateBrush Brush; Brush.SetResourceObject(Texture); Brush.ImageSize = FVector2D(Size, Size); Image->SetBrush(Brush); } return Image; } } void ULPSettingsSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPSettingsSubsystem::Tick), 0.5f); } void ULPSettingsSubsystem::Deinitialize() { FTSTicker::GetCoreTicker().RemoveTicker(TickHandle); Super::Deinitialize(); } bool ULPSettingsSubsystem::Tick(float DeltaTime) { (void)DeltaTime; PlaceSettingsButton(); UScrollBox* Scroll = nullptr; for (TObjectIterator It; It; ++It) { UScrollBox* Candidate = *It; if (!Candidate || Candidate->GetFName() != TEXT("SettingsScrollBox") || Candidate->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) { continue; } const UUserWidget* MenuOwner = Candidate->GetTypedOuter(); if (!MenuOwner || MenuOwner->HasAnyFlags(RF_ClassDefaultObject) || MenuOwner->GetGameInstance() != GetGameInstance()) { continue; } Scroll = Candidate; break; } if (!Scroll) { return true; } SettingsList = Scroll; const bool bOpen = LPSettingsUi::IsOpen(Scroll); if (TabBar.IsValid()) { TabBar->SetVisibility(bOpen ? ESlateVisibility::Visible : ESlateVisibility::Collapsed); } if (KeyboardHost.IsValid() && !bOpen) { KeyboardHost->SetVisibility(ESlateVisibility::Collapsed); bKeyboardPage = false; } if (!bOpen) { return true; } EnsureTabs(Scroll); if (Scroll->GetChildrenCount() != LastChildCount) { LastChildCount = Scroll->GetChildrenCount(); ApplyRows(); } return true; } void ULPSettingsSubsystem::OpenSettings() { if (UButton* Source = SourceSettings.Get()) { Source->OnClicked.Broadcast(); } } void ULPSettingsSubsystem::PlaceSettingsButton() { UVerticalBox* Menu = nullptr; UButton* TitleMods = nullptr; UButton* Original = nullptr; UButton* Ours = nullptr; for (TObjectIterator It; It; ++It) { UVerticalBox* Box = *It; if (!Box || Box->GetFName() != TEXT("MainMenuOptions") || Box->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject)) { continue; } const UUserWidget* MenuOwner = Box->GetTypedOuter(); if (!MenuOwner || MenuOwner->HasAnyFlags(RF_ClassDefaultObject) || MenuOwner->GetGameInstance() != GetGameInstance()) { continue; } UButton* FoundTitle = nullptr; for (int32 Index = 0; Index < Box->GetChildrenCount(); ++Index) { if (UButton* Child = Cast(Box->GetChildAt(Index))) { if (Child->GetFName() == TEXT("ModManagerTitleButton")) { FoundTitle = Child; break; } } } if (FoundTitle) { Menu = Box; TitleMods = FoundTitle; break; } } if (!Menu || !TitleMods) { return; } for (int32 Index = 0; Index < Menu->GetChildrenCount(); ++Index) { UButton* Child = Cast(Menu->GetChildAt(Index)); if (!Child) { continue; } if (Child->GetFName() == TEXT("SettingsButton")) { Original = Child; } else if (Child->GetFName() == TEXT("LPSettingsTitleButton")) { Ours = Child; } } if (Original) { SourceSettings = Original; } if (!Ours) { Ours = NewObject(Menu, TEXT("LPSettingsTitleButton")); Ours->SetStyle(TitleMods->GetStyle()); UOverlay* Icon = NewObject(Ours); UImage* Circle = LPSettingsUi::RoundIcon(Icon, TEXT("/Game/Heavy/New/whitecircle.whitecircle"), 50.f); UImage* Gear = LPSettingsUi::RoundIcon(Icon, TEXT("/Game/Heavy/New/settings.settings"), 42.f); if (UOverlaySlot* Slot = Cast(Icon->AddChild(Circle))) { Slot->SetHorizontalAlignment(HAlign_Fill); Slot->SetVerticalAlignment(VAlign_Fill); } if (UOverlaySlot* Slot = Cast(Icon->AddChild(Gear))) { Slot->SetHorizontalAlignment(HAlign_Fill); Slot->SetVerticalAlignment(VAlign_Fill); Slot->SetPadding(FMargin(6.f)); } USizeBox* IconFrame = NewObject(Ours); IconFrame->SetWidthOverride(50.f); IconFrame->SetHeightOverride(50.f); IconFrame->AddChild(Icon); UTextBlock* Label = NewObject(Ours); Label->SetText(FText::FromString(TEXT("Settings"))); if (const UTextBlock* Sample = LPSettingsUi::FirstText(TitleMods)) { Label->SetFont(Sample->GetFont()); Label->SetColorAndOpacity(Sample->GetColorAndOpacity()); Label->SetShadowOffset(Sample->GetShadowOffset()); } else { Label->SetFont(FCoreStyle::GetDefaultFontStyle("Regular", 26)); } UHorizontalBox* Row = NewObject(Ours); if (UHorizontalBoxSlot* Slot = Row->AddChildToHorizontalBox(IconFrame)) { Slot->SetPadding(FMargin(0.f, 0.f, 10.f, 0.f)); Slot->SetVerticalAlignment(VAlign_Center); } if (UHorizontalBoxSlot* Slot = Row->AddChildToHorizontalBox(Label)) { Slot->SetVerticalAlignment(VAlign_Center); } Ours->SetContent(Row); if (UButtonSlot* ContentSlot = Cast(Row->Slot)) { ContentSlot->SetHorizontalAlignment(HAlign_Left); ContentSlot->SetVerticalAlignment(VAlign_Center); } if (Original) { Ours->SetToolTipText(Original->GetToolTipText()); } FScriptDelegate Delegate; Delegate.BindUFunction(this, GET_FUNCTION_NAME_CHECKED(ULPSettingsSubsystem, OpenSettings)); Ours->OnClicked.Add(Delegate); UE_LOG(LogLifePlay, Display, TEXT("Reglages : entree Settings ajoutee sur l'ecran titre")); } // InsertChildAt ne réordonne que le tableau UMG. La boîte Slate, elle, garde le bouton en dernier, // donc sous Exit. On reconstruit les emplacements pour que l'ordre peint soit celui du tableau. if (OrderedMenu.Get() != Menu) { struct FKept { UWidget* Widget = nullptr; FMargin Padding; EHorizontalAlignment HAlign = HAlign_Fill; EVerticalAlignment VAlign = VAlign_Fill; FSlateChildSize Size; }; TArray Kept; for (int32 Index = 0; Index < Menu->GetChildrenCount(); ++Index) { UWidget* Child = Menu->GetChildAt(Index); if (!Child || Child == Ours) { continue; } FKept Item; Item.Widget = Child; if (const UVerticalBoxSlot* Slot = Cast(Child->Slot)) { Item.Padding = Slot->GetPadding(); Item.HAlign = Slot->GetHorizontalAlignment(); Item.VAlign = Slot->GetVerticalAlignment(); Item.Size = Slot->GetSize(); } Kept.Add(Item); } FKept SettingsItem; SettingsItem.Widget = Ours; SettingsItem.Padding = FMargin(0.f, 0.f, 0.f, 10.f); SettingsItem.HAlign = HAlign_Fill; if (const UVerticalBoxSlot* TitleSlot = Cast(TitleMods->Slot)) { SettingsItem.Padding = TitleSlot->GetPadding(); SettingsItem.HAlign = TitleSlot->GetHorizontalAlignment(); SettingsItem.VAlign = TitleSlot->GetVerticalAlignment(); SettingsItem.Size = TitleSlot->GetSize(); } int32 InsertAt = Kept.Num(); for (int32 Index = 0; Index < Kept.Num(); ++Index) { if (Kept[Index].Widget == TitleMods) { InsertAt = Index + 1; break; } } Kept.Insert(SettingsItem, InsertAt); Menu->ClearChildren(); for (const FKept& Item : Kept) { if (UVerticalBoxSlot* Slot = Cast(Menu->AddChild(Item.Widget))) { Slot->SetPadding(Item.Padding); Slot->SetHorizontalAlignment(Item.HAlign); Slot->SetVerticalAlignment(Item.VAlign); Slot->SetSize(Item.Size); } } OrderedMenu = Menu; UE_LOG(LogLifePlay, Display, TEXT("Reglages : Settings place apres Mod Manager")); } const ESlateVisibility TitleVisibility = TitleMods->GetVisibility(); if (Ours->GetVisibility() != TitleVisibility) { Ours->SetVisibility(TitleVisibility); } } void ULPSettingsSubsystem::EnsureTabs(UScrollBox* Scroll) { if (TabBar.IsValid()) { return; } UCanvasPanel* Canvas = Cast(Scroll->GetParent()); UCanvasPanelSlot* ScrollSlot = Cast(Scroll->Slot); if (!Canvas || !ScrollSlot) { return; } UHorizontalBox* Bar = NewObject(Canvas, TEXT("LPSettingsTabs")); UCanvasPanelSlot* BarSlot = Canvas->AddChildToCanvas(Bar); if (!BarSlot) { return; } BarSlot->SetAnchors(ScrollSlot->GetAnchors()); BarSlot->SetAlignment(ScrollSlot->GetAlignment()); BarSlot->SetPosition(ScrollSlot->GetPosition() - FVector2D(0.f, 46.f)); BarSlot->SetSize(FVector2D(ScrollSlot->GetSize().X, 40.f)); BarSlot->SetZOrder(ScrollSlot->GetZOrder() + 1); TabBar = Bar; TabLabels.Reset(); const FName Clicks[] = { GET_FUNCTION_NAME_CHECKED(ULPSettingsSubsystem, ShowGeneral), GET_FUNCTION_NAME_CHECKED(ULPSettingsSubsystem, ShowAudio), GET_FUNCTION_NAME_CHECKED(ULPSettingsSubsystem, ShowVideo), GET_FUNCTION_NAME_CHECKED(ULPSettingsSubsystem, ShowKeyboard), GET_FUNCTION_NAME_CHECKED(ULPSettingsSubsystem, ShowLewd), GET_FUNCTION_NAME_CHECKED(ULPSettingsSubsystem, ShowMisc) }; for (int32 Index = 0; Index < UE_ARRAY_COUNT(LPSettingsUi::TabNames); ++Index) { UButton* Button = NewObject(Bar); UTextBlock* Label = NewObject(Button); Label->SetText(FText::FromString(LPSettingsUi::TabNames[Index])); Label->SetFont(FCoreStyle::GetDefaultFontStyle("Bold", 14)); Button->SetContent(Label); FButtonStyle Style = Button->GetStyle(); Style.NormalPadding = FMargin(10.f, 4.f); Style.PressedPadding = FMargin(10.f, 5.f, 10.f, 3.f); Button->SetStyle(Style); if (UHorizontalBoxSlot* Slot = Cast(Bar->AddChild(Button))) { Slot->SetPadding(FMargin(3.f, 0.f)); Slot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic)); Slot->SetVerticalAlignment(VAlign_Center); } FScriptDelegate Delegate; Delegate.BindUFunction(this, Clicks[Index]); Button->OnClicked.Add(Delegate); TabLabels.Add(Label); } UNativeWidgetHost* Host = NewObject(Canvas, TEXT("LPSettingsKeyboard")); if (UCanvasPanelSlot* HostSlot = Canvas->AddChildToCanvas(Host)) { HostSlot->SetAnchors(ScrollSlot->GetAnchors()); HostSlot->SetAlignment(ScrollSlot->GetAlignment()); HostSlot->SetPosition(ScrollSlot->GetPosition() + FVector2D(0.f, 48.f)); const FVector2D Size = ScrollSlot->GetSize(); HostSlot->SetSize(FVector2D(Size.X, FMath::Max(120.f, Size.Y - 48.f))); HostSlot->SetZOrder(ScrollSlot->GetZOrder() + 1); } Host->SetVisibility(ESlateVisibility::Collapsed); KeyboardHost = Host; ActiveTab = 0; ApplyRows(); UE_LOG(LogLifePlay, Display, TEXT("Reglages : onglets General, Audio, Video, Keyboard, Lewd, Misc")); } void ULPSettingsSubsystem::SelectTab(int32 Index) { ActiveTab = Index; ApplyRows(); } void ULPSettingsSubsystem::ApplyRows() { UScrollBox* Scroll = SettingsList.Get(); if (!Scroll) { return; } for (int32 Index = 0; Index < TabLabels.Num(); ++Index) { if (UTextBlock* Label = TabLabels[Index].Get()) { const bool bOn = Index == ActiveTab; Label->SetColorAndOpacity(FSlateColor(bOn ? FLinearColor(1.f, 0.92f, 0.45f) : FLinearColor(0.75f, 0.75f, 0.75f))); } } for (int32 Index = 0; Index < Scroll->GetChildrenCount(); ++Index) { UWidget* Child = Scroll->GetChildAt(Index); if (!Child || Child->GetFName() == TEXT("LPKeyboardRow")) { if (Child) { Child->SetVisibility(ESlateVisibility::Collapsed); } continue; } LPSettingsUi::RenameVolumes(Child); const int32 Category = LPSettingsUi::CategoryOf(Child); const bool bShow = Category < 0 || (ActiveTab != LPSettingsUi::TabKeyboard && Category == ActiveTab); Child->SetVisibility(bShow ? ESlateVisibility::Visible : ESlateVisibility::Collapsed); } const bool bKeyboard = ActiveTab == LPSettingsUi::TabKeyboard; if (UNativeWidgetHost* Host = KeyboardHost.Get()) { Host->SetVisibility(bKeyboard ? ESlateVisibility::Visible : ESlateVisibility::Collapsed); if (bKeyboard && !bKeyboardPage) { if (ULPKeyboardSubsystem* Keys = GetGameInstance()->GetSubsystem()) { Host->SetContent(Keys->TakeEmbeddedPage()); bKeyboardPage = true; } } } } void ULPSettingsSubsystem::ShowGeneral() { SelectTab(LPSettingsUi::TabGeneral); } void ULPSettingsSubsystem::ShowAudio() { SelectTab(LPSettingsUi::TabAudio); } void ULPSettingsSubsystem::ShowVideo() { SelectTab(LPSettingsUi::TabVideo); } void ULPSettingsSubsystem::ShowKeyboard() { SelectTab(LPSettingsUi::TabKeyboard); } void ULPSettingsSubsystem::ShowLewd() { SelectTab(LPSettingsUi::TabLewd); } void ULPSettingsSubsystem::ShowMisc() { SelectTab(LPSettingsUi::TabMisc); }