Clavier : LPKeyboardSubsystem (bouton et panneau Clavier, touches dans Saved/Config/Windows/LPKeyboard.ini)
Phase Clavier (CHG-156, 164, 165). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5e901bef31
commit
91259316a6
2 changed files with 1026 additions and 0 deletions
958
Source/LifePlay/LPKeyboardSubsystem.cpp
Normal file
958
Source/LifePlay/LPKeyboardSubsystem.cpp
Normal file
|
|
@ -0,0 +1,958 @@
|
|||
// Menu Clavier. Voir LPKeyboardSubsystem.h.
|
||||
|
||||
#include "LPKeyboardSubsystem.h"
|
||||
|
||||
#include "Components/Button.h"
|
||||
#include "Components/ScrollBox.h"
|
||||
#include "Components/ScrollBoxSlot.h"
|
||||
#include "Components/TextBlock.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/GameViewportClient.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Framework/Application/IInputProcessor.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "GameFramework/InputSettings.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "GameFramework/PlayerInput.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "LifePlay.h"
|
||||
#include "Misc/ConfigCacheIni.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Styling/CoreStyle.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Layout/SBorder.h"
|
||||
#include "Widgets/Layout/SBox.h"
|
||||
#include "Widgets/Layout/SScrollBox.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const FName ButtonName(TEXT("LPKeyboardRow"));
|
||||
const TCHAR* InputSection = TEXT("/Script/Engine.InputSettings");
|
||||
|
||||
FString KeyboardIniPath()
|
||||
{
|
||||
return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Config/Windows/LPKeyboard.ini"));
|
||||
}
|
||||
|
||||
FString ExportAction(const FInputActionKeyMapping& Map)
|
||||
{
|
||||
FString Text;
|
||||
FInputActionKeyMapping::StaticStruct()->ExportText(Text, &Map, nullptr, nullptr, PPF_None, nullptr);
|
||||
return Text;
|
||||
}
|
||||
|
||||
FString ExportAxis(const FInputAxisKeyMapping& Map)
|
||||
{
|
||||
FString Text;
|
||||
FInputAxisKeyMapping::StaticStruct()->ExportText(Text, &Map, nullptr, nullptr, PPF_None, nullptr);
|
||||
return Text;
|
||||
}
|
||||
|
||||
struct FBindRow
|
||||
{
|
||||
FName Name;
|
||||
FString Label;
|
||||
bool bAxis = false;
|
||||
float Scale = 0.f;
|
||||
FKey Key;
|
||||
bool bShift = false;
|
||||
bool bCtrl = false;
|
||||
bool bAlt = false;
|
||||
bool bCmd = false;
|
||||
};
|
||||
|
||||
struct FLiveKeyboard
|
||||
{
|
||||
TWeakObjectPtr<ULPKeyboardSubsystem> Owner;
|
||||
TSharedPtr<class SLPKeyboardMenu> Menu;
|
||||
TSharedPtr<IInputProcessor> Processor;
|
||||
bool bEmbedded = false;
|
||||
};
|
||||
|
||||
bool IsRemappable(const FKey& Key)
|
||||
{
|
||||
return Key.IsValid() && !Key.IsAnalog() && !Key.IsAxis1D() && !Key.IsGamepadKey();
|
||||
}
|
||||
|
||||
FString ActionLabel(FName Name)
|
||||
{
|
||||
static const TCHAR* Known[][2] = {
|
||||
{TEXT("Pause"), TEXT("Pause")},
|
||||
{TEXT("Journal"), TEXT("Journal")},
|
||||
{TEXT("Inventory"), TEXT("Inventory")},
|
||||
{TEXT("MainMenu"), TEXT("Main Menu")},
|
||||
{TEXT("CharacterMenu"), TEXT("Character Menu")},
|
||||
{TEXT("ModuleMenu"), TEXT("Modules")},
|
||||
{TEXT("CityMenu"), TEXT("City")},
|
||||
{TEXT("StatMenu"), TEXT("Statistics")},
|
||||
{TEXT("ContactsMenu"), TEXT("Contacts")},
|
||||
{TEXT("RotateCamera"), TEXT("Rotate Camera")},
|
||||
{TEXT("ManualAnimating"), TEXT("Manual Animation")},
|
||||
{TEXT("SpeedUp"), TEXT("Speed Up")},
|
||||
{TEXT("SpeedDown"), TEXT("Slow Down")},
|
||||
{TEXT("SwapRoles"), TEXT("Swap Roles")},
|
||||
{TEXT("SwitchCamera"), TEXT("Switch Camera")},
|
||||
{TEXT("NextBuilding"), TEXT("Next Building")},
|
||||
{TEXT("PreviousBuilding"), TEXT("Previous Building")},
|
||||
{TEXT("TestScenes"), TEXT("Test Scenes")},
|
||||
{TEXT("Recommended"), TEXT("Recommended")},
|
||||
{TEXT("QuickSave"), TEXT("Quick Save")},
|
||||
{TEXT("Quickload"), TEXT("Quick Load")},
|
||||
{TEXT("SearchBarToggle"), TEXT("Search")},
|
||||
{TEXT("Interact"), TEXT("Interact")},
|
||||
};
|
||||
const FString Raw = Name.ToString();
|
||||
for (int32 Index = 0; Index < UE_ARRAY_COUNT(Known); ++Index)
|
||||
{
|
||||
if (Raw == Known[Index][0])
|
||||
{
|
||||
return Known[Index][1];
|
||||
}
|
||||
}
|
||||
if (Raw.StartsWith(TEXT("Favorite")))
|
||||
{
|
||||
return TEXT("Favorite ") + Raw.RightChop(8);
|
||||
}
|
||||
return Raw;
|
||||
}
|
||||
|
||||
FString AxisLabel(FName Name, float Scale)
|
||||
{
|
||||
const bool bPositive = Scale >= 0.f;
|
||||
if (Name == TEXT("LeftRight"))
|
||||
{
|
||||
return bPositive ? TEXT("Right") : TEXT("Left");
|
||||
}
|
||||
if (Name == TEXT("DownUp"))
|
||||
{
|
||||
return bPositive ? TEXT("Forward") : TEXT("Back");
|
||||
}
|
||||
if (Name == TEXT("MoveForward"))
|
||||
{
|
||||
return bPositive ? TEXT("Walk Forward") : TEXT("Walk Back");
|
||||
}
|
||||
if (Name == TEXT("MoveRight"))
|
||||
{
|
||||
return bPositive ? TEXT("Walk Right") : TEXT("Walk Left");
|
||||
}
|
||||
if (Name == TEXT("PenisRotation"))
|
||||
{
|
||||
return bPositive ? TEXT("Rotation +") : TEXT("Rotation −");
|
||||
}
|
||||
return Name.ToString() + (bPositive ? TEXT(" +") : TEXT(" −"));
|
||||
}
|
||||
|
||||
struct FBindGroup
|
||||
{
|
||||
int32 Order = 9;
|
||||
const TCHAR* Title = TEXT("Other");
|
||||
};
|
||||
|
||||
FBindGroup GroupFor(FName Name)
|
||||
{
|
||||
const FString Raw = Name.ToString();
|
||||
if (Raw.StartsWith(TEXT("Favorite")))
|
||||
{
|
||||
return {4, TEXT("Favorites")};
|
||||
}
|
||||
if (Name == TEXT("MoveForward") || Name == TEXT("MoveRight"))
|
||||
{
|
||||
return {0, TEXT("Movement")};
|
||||
}
|
||||
if (Name == TEXT("LeftRight") || Name == TEXT("DownUp") || Name == TEXT("ScrollDownUp")
|
||||
|| Name == TEXT("RotateCamera") || Name == TEXT("SwitchCamera"))
|
||||
{
|
||||
return {1, TEXT("Camera")};
|
||||
}
|
||||
if (Name == TEXT("Interact") || Name == TEXT("SwapRoles"))
|
||||
{
|
||||
return {2, TEXT("Interaction")};
|
||||
}
|
||||
if (Name == TEXT("Pause") || Name == TEXT("Journal") || Name == TEXT("Inventory")
|
||||
|| Name == TEXT("MainMenu") || Name == TEXT("CharacterMenu") || Name == TEXT("ModuleMenu")
|
||||
|| Name == TEXT("CityMenu") || Name == TEXT("StatMenu") || Name == TEXT("ContactsMenu")
|
||||
|| Name == TEXT("SearchBarToggle"))
|
||||
{
|
||||
return {3, TEXT("Menus")};
|
||||
}
|
||||
if (Name == TEXT("SpeedUp") || Name == TEXT("SpeedDown"))
|
||||
{
|
||||
return {5, TEXT("Time")};
|
||||
}
|
||||
if (Name == TEXT("NextBuilding") || Name == TEXT("PreviousBuilding"))
|
||||
{
|
||||
return {6, TEXT("World")};
|
||||
}
|
||||
if (Name == TEXT("QuickSave") || Name == TEXT("Quickload"))
|
||||
{
|
||||
return {7, TEXT("Save")};
|
||||
}
|
||||
if (Name == TEXT("ManualAnimating") || Name == TEXT("PenisRotation") || Name == TEXT("TestScenes") || Name == TEXT("Recommended"))
|
||||
{
|
||||
return {8, TEXT("Scene")};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
FString ChordText(const FKey& Key, bool bShift, bool bCtrl, bool bAlt)
|
||||
{
|
||||
FString Text;
|
||||
if (bCtrl)
|
||||
{
|
||||
Text += TEXT("Ctrl+");
|
||||
}
|
||||
if (bShift)
|
||||
{
|
||||
Text += TEXT("Shift+");
|
||||
}
|
||||
if (bAlt)
|
||||
{
|
||||
Text += TEXT("Alt+");
|
||||
}
|
||||
Text += Key.GetDisplayName().ToString();
|
||||
return Text;
|
||||
}
|
||||
|
||||
TSharedRef<STextBlock> MakeText(const FString& Value, int32 Size, const FLinearColor& Color)
|
||||
{
|
||||
FSlateFontInfo Font = FCoreStyle::GetDefaultFontStyle("Regular", Size);
|
||||
Font.OutlineSettings.OutlineSize = 2;
|
||||
Font.OutlineSettings.bApplyOutlineToDropShadows = true;
|
||||
Font.OutlineSettings.OutlineColor = FLinearColor(0.f, 0.f, 0.f, 0.8f);
|
||||
return SNew(STextBlock)
|
||||
.Text(FText::FromString(Value))
|
||||
.Font(Font)
|
||||
.ColorAndOpacity(FSlateColor(Color));
|
||||
}
|
||||
|
||||
class SLPKeyboardMenu : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SLPKeyboardMenu) {}
|
||||
SLATE_ARGUMENT(bool, bEmbedded)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs, ULPKeyboardSubsystem* InOwner)
|
||||
{
|
||||
Owner = InOwner;
|
||||
bEmbedded = InArgs._bEmbedded;
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBox)
|
||||
.HAlign(bEmbedded ? HAlign_Fill : HAlign_Center)
|
||||
.VAlign(bEmbedded ? VAlign_Fill : VAlign_Center)
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderImage(bEmbedded ? FCoreStyle::Get().GetBrush("NoBrush") : FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
|
||||
.Padding(bEmbedded ? 0.f : 16.f)
|
||||
[
|
||||
SNew(SBox)
|
||||
.WidthOverride(bEmbedded ? FOptionalSize() : FOptionalSize(560.f))
|
||||
.HeightOverride(bEmbedded ? FOptionalSize() : FOptionalSize(640.f))
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 0.f, 0.f, 8.f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Change a key, add one, or remove one. Escape cancels the capture. Saved for this version of the game.")))
|
||||
.AutoWrapText(true)
|
||||
.Font(MakeText(TEXT(""), 16, FLinearColor::White)->GetFont())
|
||||
.ColorAndOpacity(FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.95f)))
|
||||
]
|
||||
+ SVerticalBox::Slot().FillHeight(1.f)
|
||||
[
|
||||
SNew(SScrollBox)
|
||||
+ SScrollBox::Slot()
|
||||
[
|
||||
SAssignNew(Rows, SVerticalBox)
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 8.f, 0.f, 8.f)
|
||||
[
|
||||
SAssignNew(StatusBlock, STextBlock)
|
||||
.Font(MakeText(TEXT(""), 16, FLinearColor::White)->GetFont())
|
||||
.ColorAndOpacity(FSlateColor(FLinearColor(0.95f, 0.85f, 0.4f)))
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().FillWidth(1.f).Padding(0.f, 0.f, 8.f, 0.f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked(this, &SLPKeyboardMenu::OnReset)
|
||||
[
|
||||
MakeText(TEXT("Reset"), 20, FLinearColor::White)
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot().FillWidth(1.f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Visibility(bEmbedded ? EVisibility::Collapsed : EVisibility::Visible)
|
||||
.OnClicked(this, &SLPKeyboardMenu::OnClose)
|
||||
[
|
||||
MakeText(TEXT("Close"), 20, FLinearColor::White)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
Rebuild();
|
||||
}
|
||||
|
||||
void Rebuild()
|
||||
{
|
||||
if (!Rows.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Rows->ClearChildren();
|
||||
ULPKeyboardSubsystem* Sub = Owner.Get();
|
||||
const UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||||
if (!Sub || !Settings)
|
||||
{
|
||||
return;
|
||||
}
|
||||
TArray<FBindRow> Binds;
|
||||
for (const FInputActionKeyMapping& Map : Settings->GetActionMappings())
|
||||
{
|
||||
if (!IsRemappable(Map.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FBindRow Row;
|
||||
Row.Name = Map.ActionName;
|
||||
Row.Label = ActionLabel(Map.ActionName);
|
||||
Row.Key = Map.Key;
|
||||
Row.bShift = Map.bShift;
|
||||
Row.bCtrl = Map.bCtrl;
|
||||
Row.bAlt = Map.bAlt;
|
||||
Row.bCmd = Map.bCmd;
|
||||
Binds.Add(Row);
|
||||
}
|
||||
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
|
||||
{
|
||||
if (!IsRemappable(Map.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
FBindRow Row;
|
||||
Row.Name = Map.AxisName;
|
||||
Row.Label = AxisLabel(Map.AxisName, Map.Scale);
|
||||
Row.bAxis = true;
|
||||
Row.Scale = Map.Scale;
|
||||
Row.Key = Map.Key;
|
||||
Binds.Add(Row);
|
||||
}
|
||||
Binds.Sort([](const FBindRow& A, const FBindRow& B)
|
||||
{
|
||||
const int32 GroupA = GroupFor(A.Name).Order;
|
||||
const int32 GroupB = GroupFor(B.Name).Order;
|
||||
if (GroupA != GroupB)
|
||||
{
|
||||
return GroupA < GroupB;
|
||||
}
|
||||
if (A.Label != B.Label)
|
||||
{
|
||||
return A.Label < B.Label;
|
||||
}
|
||||
return A.Key.GetDisplayName().ToString() < B.Key.GetDisplayName().ToString();
|
||||
});
|
||||
|
||||
int32 PreviousGroup = INDEX_NONE;
|
||||
FString Previous;
|
||||
for (const FBindRow& Row : Binds)
|
||||
{
|
||||
const FBindGroup Section = GroupFor(Row.Name);
|
||||
if (Section.Order != PreviousGroup)
|
||||
{
|
||||
PreviousGroup = Section.Order;
|
||||
Previous.Reset();
|
||||
Rows->AddSlot().AutoHeight().Padding(0.f, 12.f, 0.f, 4.f)
|
||||
[
|
||||
MakeText(Section.Title, 20, FLinearColor(1.f, 0.92f, 0.45f))
|
||||
];
|
||||
}
|
||||
if (Row.Label != Previous)
|
||||
{
|
||||
Previous = Row.Label;
|
||||
const FBindRow Group = Row;
|
||||
Rows->AddSlot().AutoHeight().Padding(0.f, 10.f, 0.f, 2.f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().FillWidth(1.f).VAlign(VAlign_Center)
|
||||
[
|
||||
MakeText(Row.Label, 20, FLinearColor(1.f, 1.f, 1.f, 0.95f))
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth()
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this, Group]()
|
||||
{
|
||||
if (ULPKeyboardSubsystem* Sub = Owner.Get())
|
||||
{
|
||||
Sub->BeginWait(Group.Name, Group.bAxis, Group.Scale, EKeys::Invalid, false, false, false, false, true);
|
||||
Sub->RefreshMenu();
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
MakeText(TEXT("Add"), 16, FLinearColor::White)
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
const FBindRow Current = Row;
|
||||
Rows->AddSlot().AutoHeight().Padding(12.f, 2.f, 0.f, 2.f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().FillWidth(1.f).VAlign(VAlign_Center)
|
||||
[
|
||||
MakeText(ChordText(Current.Key, Current.bShift, Current.bCtrl, Current.bAlt), 16, FLinearColor(0.85f, 0.9f, 1.f))
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth().Padding(4.f, 0.f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this, Current]()
|
||||
{
|
||||
if (ULPKeyboardSubsystem* Sub = Owner.Get())
|
||||
{
|
||||
Sub->BeginWait(Current.Name, Current.bAxis, Current.Scale, Current.Key, Current.bShift, Current.bCtrl, Current.bAlt, Current.bCmd, false);
|
||||
Sub->RefreshMenu();
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
MakeText(TEXT("Change"), 16, FLinearColor::White)
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth()
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this, Current]()
|
||||
{
|
||||
if (ULPKeyboardSubsystem* Sub = Owner.Get())
|
||||
{
|
||||
Sub->RemoveBinding(Current.Name, Current.bAxis, Current.Scale, Current.Key, Current.bShift, Current.bCtrl, Current.bAlt, Current.bCmd);
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
MakeText(TEXT("Remove"), 16, FLinearColor::White)
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
if (StatusBlock.IsValid() && Sub)
|
||||
{
|
||||
StatusBlock->SetText(FText::FromString(Sub->GetStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FReply OnReset()
|
||||
{
|
||||
if (ULPKeyboardSubsystem* Sub = Owner.Get())
|
||||
{
|
||||
Sub->ResetToDefault();
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply OnClose()
|
||||
{
|
||||
if (ULPKeyboardSubsystem* Sub = Owner.Get())
|
||||
{
|
||||
Sub->CloseKeyboardMenu();
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
TWeakObjectPtr<ULPKeyboardSubsystem> Owner;
|
||||
TSharedPtr<SVerticalBox> Rows;
|
||||
TSharedPtr<STextBlock> StatusBlock;
|
||||
bool bEmbedded = false;
|
||||
};
|
||||
|
||||
FLiveKeyboard& Live()
|
||||
{
|
||||
static FLiveKeyboard State;
|
||||
return State;
|
||||
}
|
||||
|
||||
class FKeyboardProcessor : public IInputProcessor
|
||||
{
|
||||
public:
|
||||
virtual void Tick(const float, FSlateApplication&, TSharedRef<ICursor>) override {}
|
||||
|
||||
virtual bool HandleKeyDownEvent(FSlateApplication&, const FKeyEvent& Event) override
|
||||
{
|
||||
ULPKeyboardSubsystem* Sub = Live().Owner.Get();
|
||||
if (!Sub || !Live().Menu.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const FKey Key = Event.GetKey();
|
||||
if (Sub->IsWaiting())
|
||||
{
|
||||
if (Key == EKeys::Escape)
|
||||
{
|
||||
Sub->CancelWait();
|
||||
}
|
||||
else if (!Key.IsModifierKey())
|
||||
{
|
||||
Sub->CommitKey(Key, Event.IsShiftDown(), Event.IsControlDown(), Event.IsAltDown(), Event.IsCommandDown());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (Live().bEmbedded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Key == EKeys::Escape)
|
||||
{
|
||||
Sub->CloseKeyboardMenu();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool ULPKeyboardSubsystem::IsMenuOpen() const
|
||||
{
|
||||
return Live().Menu.IsValid() && Live().Owner.Get() == this;
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
if (!LoadSaved())
|
||||
{
|
||||
SaveSaved();
|
||||
}
|
||||
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPKeyboardSubsystem::Tick), 0.5f);
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::Deinitialize()
|
||||
{
|
||||
if (Live().Owner.Get() == this)
|
||||
{
|
||||
CloseKeyboardMenu();
|
||||
}
|
||||
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
bool ULPKeyboardSubsystem::Tick(float DeltaTime)
|
||||
{
|
||||
(void)DeltaTime;
|
||||
UGameInstance* GI = GetGameInstance();
|
||||
bool bFound = false;
|
||||
for (TObjectIterator<UScrollBox> It; It; ++It)
|
||||
{
|
||||
UScrollBox* Scroll = *It;
|
||||
if (!Scroll || Scroll->GetFName() != TEXT("SettingsScrollBox") || Scroll->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const UUserWidget* Owner = Scroll->GetTypedOuter<UUserWidget>();
|
||||
if (!Owner || Owner->HasAnyFlags(RF_ClassDefaultObject) || Owner->GetGameInstance() != GI)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bFound = true;
|
||||
}
|
||||
if (!bFound && !bLoggedMissing)
|
||||
{
|
||||
bLoggedMissing = true;
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Clavier : liste des reglages pas encore ouverte"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::AttachButton(UScrollBox* Scroll)
|
||||
{
|
||||
for (int32 Index = 0; Index < Scroll->GetChildrenCount(); ++Index)
|
||||
{
|
||||
if (Scroll->GetChildAt(Index) && Scroll->GetChildAt(Index)->GetFName() == ButtonName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
UButton* Button = NewObject<UButton>(Scroll, ButtonName);
|
||||
UTextBlock* Label = NewObject<UTextBlock>(Button, TEXT("LPKeyboardLabel"));
|
||||
Label->SetText(FText::FromString(TEXT("Keyboard")));
|
||||
Label->SetColorAndOpacity(FSlateColor(FLinearColor::White));
|
||||
FSlateFontInfo Font = FCoreStyle::GetDefaultFontStyle("Regular", 16);
|
||||
Label->SetFont(Font);
|
||||
Button->SetContent(Label);
|
||||
if (UScrollBoxSlot* Slot = Cast<UScrollBoxSlot>(Scroll->AddChild(Button)))
|
||||
{
|
||||
Slot->SetPadding(FMargin(0.f, 12.f, 0.f, 4.f));
|
||||
Slot->SetHorizontalAlignment(HAlign_Fill);
|
||||
}
|
||||
Button->OnClicked.AddDynamic(this, &ULPKeyboardSubsystem::OpenKeyboardMenu);
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Clavier : bouton ajoute dans les reglages"));
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> ULPKeyboardSubsystem::TakeEmbeddedPage()
|
||||
{
|
||||
if (Live().Menu.IsValid() && Live().Owner.Get() == this)
|
||||
{
|
||||
return Live().Menu.ToSharedRef();
|
||||
}
|
||||
Status = TEXT("Choose Change or Add, then press a key.");
|
||||
bWaiting = false;
|
||||
Live().bEmbedded = true;
|
||||
TSharedRef<SLPKeyboardMenu> Panel = SNew(SLPKeyboardMenu, this).bEmbedded(true);
|
||||
Live().Owner = this;
|
||||
Live().Menu = Panel;
|
||||
if (!Live().Processor.IsValid())
|
||||
{
|
||||
Live().Processor = MakeShared<FKeyboardProcessor>();
|
||||
FSlateApplication::Get().RegisterInputPreProcessor(Live().Processor.ToSharedRef(), 0);
|
||||
}
|
||||
return Panel;
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::OpenKeyboardMenu()
|
||||
{
|
||||
if (Live().Menu.IsValid() || !FSlateApplication::IsInitialized() || !GEngine || !GEngine->GameViewport)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Status = TEXT("Choose Change or Add, then press a key.");
|
||||
bWaiting = false;
|
||||
Live().bEmbedded = false;
|
||||
TSharedRef<SLPKeyboardMenu> Panel = SNew(SLPKeyboardMenu, this).bEmbedded(false);
|
||||
Live().Owner = this;
|
||||
Live().Menu = Panel;
|
||||
Live().Processor = MakeShared<FKeyboardProcessor>();
|
||||
GEngine->GameViewport->AddViewportWidgetContent(Panel, 200);
|
||||
FSlateApplication::Get().RegisterInputPreProcessor(Live().Processor.ToSharedRef(), 0);
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::CloseKeyboardMenu()
|
||||
{
|
||||
if (Live().Owner.Get() != this && Live().Owner.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
bWaiting = false;
|
||||
if (Live().Processor.IsValid() && FSlateApplication::IsInitialized())
|
||||
{
|
||||
FSlateApplication::Get().UnregisterInputPreProcessor(Live().Processor.ToSharedRef());
|
||||
}
|
||||
if (Live().Menu.IsValid() && GEngine && GEngine->GameViewport)
|
||||
{
|
||||
GEngine->GameViewport->RemoveViewportWidgetContent(Live().Menu.ToSharedRef());
|
||||
}
|
||||
Live().Menu.Reset();
|
||||
Live().Processor.Reset();
|
||||
Live().Owner.Reset();
|
||||
Live().bEmbedded = false;
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::BeginWait(FName Name, bool bAxis, float Scale, FKey OldKey, bool bShift, bool bCtrl, bool bAlt, bool bCmd, bool bAdd)
|
||||
{
|
||||
bWaiting = true;
|
||||
bWaitAdd = bAdd;
|
||||
bWaitAxis = bAxis;
|
||||
WaitScale = Scale;
|
||||
WaitName = Name;
|
||||
WaitOldKey = OldKey;
|
||||
bWaitShift = bShift;
|
||||
bWaitCtrl = bCtrl;
|
||||
bWaitAlt = bAlt;
|
||||
bWaitCmd = bCmd;
|
||||
Status = bAdd
|
||||
? FString::Printf(TEXT("New key for %s. Escape cancels."), bAxis ? *AxisLabel(Name, Scale) : *ActionLabel(Name))
|
||||
: FString::Printf(TEXT("Replaces %s. Escape cancels."), *OldKey.GetDisplayName().ToString());
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::CancelWait()
|
||||
{
|
||||
bWaiting = false;
|
||||
Status = TEXT("Capture cancelled.");
|
||||
RefreshMenu();
|
||||
}
|
||||
|
||||
bool ULPKeyboardSubsystem::CommitKey(const FKey& Key, bool bShift, bool bCtrl, bool bAlt, bool bCmd)
|
||||
{
|
||||
bWaiting = false;
|
||||
if (!IsRemappable(Key))
|
||||
{
|
||||
Status = TEXT("This key cannot be assigned.");
|
||||
RefreshMenu();
|
||||
return false;
|
||||
}
|
||||
UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||||
if (!Settings)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
TArray<FString> Cleared;
|
||||
if (!bWaitAdd && WaitOldKey.IsValid())
|
||||
{
|
||||
if (bWaitAxis)
|
||||
{
|
||||
Settings->RemoveAxisMapping(FInputAxisKeyMapping(WaitName, WaitOldKey, WaitScale), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings->RemoveActionMapping(FInputActionKeyMapping(WaitName, WaitOldKey, bWaitShift, bWaitCtrl, bWaitAlt, bWaitCmd), false);
|
||||
}
|
||||
}
|
||||
const TArray<FInputActionKeyMapping> Actions = Settings->GetActionMappings();
|
||||
for (const FInputActionKeyMapping& Map : Actions)
|
||||
{
|
||||
if (Map.Key == Key && Map.bShift == bShift && Map.bCtrl == bCtrl && Map.bAlt == bAlt && Map.bCmd == bCmd)
|
||||
{
|
||||
Settings->RemoveActionMapping(Map, false);
|
||||
Cleared.Add(ActionLabel(Map.ActionName));
|
||||
}
|
||||
}
|
||||
TArray<FInputAxisKeyMapping> Axes = Settings->GetAxisMappings();
|
||||
for (const FInputAxisKeyMapping& Map : Axes)
|
||||
{
|
||||
if (Map.Key == Key && !bShift && !bCtrl && !bAlt && !bCmd)
|
||||
{
|
||||
Settings->RemoveAxisMapping(Map, false);
|
||||
Cleared.Add(AxisLabel(Map.AxisName, Map.Scale));
|
||||
}
|
||||
}
|
||||
if (bWaitAxis)
|
||||
{
|
||||
Settings->AddAxisMapping(FInputAxisKeyMapping(WaitName, Key, WaitScale), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings->AddActionMapping(FInputActionKeyMapping(WaitName, Key, bShift, bCtrl, bAlt, bCmd), false);
|
||||
}
|
||||
ApplyToPlayer();
|
||||
Status = Cleared.Num() > 0
|
||||
? FString::Printf(TEXT("%s saved. Removed from: %s."), *Key.GetDisplayName().ToString(), *FString::Join(Cleared, TEXT(", ")))
|
||||
: FString::Printf(TEXT("%s saved."), *Key.GetDisplayName().ToString());
|
||||
RefreshMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::RemoveBinding(FName Name, bool bAxis, float Scale, FKey Key, bool bShift, bool bCtrl, bool bAlt, bool bCmd)
|
||||
{
|
||||
bWaiting = false;
|
||||
UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||||
if (!Settings)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (bAxis)
|
||||
{
|
||||
Settings->RemoveAxisMapping(FInputAxisKeyMapping(Name, Key, Scale), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Settings->RemoveActionMapping(FInputActionKeyMapping(Name, Key, bShift, bCtrl, bAlt, bCmd), false);
|
||||
}
|
||||
ApplyToPlayer();
|
||||
Status = FString::Printf(TEXT("%s removed."), *Key.GetDisplayName().ToString());
|
||||
RefreshMenu();
|
||||
}
|
||||
|
||||
bool ULPKeyboardSubsystem::LoadDefaults(TArray<FInputActionKeyMapping>& Actions, TArray<FInputAxisKeyMapping>& Axes) const
|
||||
{
|
||||
const FString Path = FPaths::Combine(FPaths::ProjectConfigDir(), TEXT("DefaultInput.ini"));
|
||||
TArray<FString> Lines;
|
||||
if (!FFileHelper::LoadFileToStringArray(Lines, *Path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (FString Line : Lines)
|
||||
{
|
||||
Line.TrimStartAndEndInline();
|
||||
if (Line.StartsWith(TEXT("+ActionMappings=")))
|
||||
{
|
||||
FInputActionKeyMapping Map;
|
||||
const FString Inner = Line.Mid(16);
|
||||
if (FInputActionKeyMapping::StaticStruct()->ImportText(*Inner, &Map, nullptr, PPF_None, nullptr, TEXT("ActionMappings")) && Map.Key.IsValid())
|
||||
{
|
||||
Actions.Add(Map);
|
||||
}
|
||||
}
|
||||
else if (Line.StartsWith(TEXT("+AxisMappings=")))
|
||||
{
|
||||
FInputAxisKeyMapping Map;
|
||||
const FString Inner = Line.Mid(14);
|
||||
if (FInputAxisKeyMapping::StaticStruct()->ImportText(*Inner, &Map, nullptr, PPF_None, nullptr, TEXT("AxisMappings")) && Map.Key.IsValid())
|
||||
{
|
||||
Axes.Add(Map);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Actions.Num() > 0 || Axes.Num() > 0;
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::ResetToDefault()
|
||||
{
|
||||
bWaiting = false;
|
||||
TArray<FInputActionKeyMapping> Actions;
|
||||
TArray<FInputAxisKeyMapping> Axes;
|
||||
UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||||
if (!Settings || !LoadDefaults(Actions, Axes))
|
||||
{
|
||||
Status = TEXT("The original keys could not be found.");
|
||||
RefreshMenu();
|
||||
return;
|
||||
}
|
||||
const TArray<FInputActionKeyMapping> CurrentActions = Settings->GetActionMappings();
|
||||
for (const FInputActionKeyMapping& Map : CurrentActions)
|
||||
{
|
||||
Settings->RemoveActionMapping(Map, false);
|
||||
}
|
||||
const TArray<FInputAxisKeyMapping> CurrentAxes = Settings->GetAxisMappings();
|
||||
for (const FInputAxisKeyMapping& Map : CurrentAxes)
|
||||
{
|
||||
Settings->RemoveAxisMapping(Map, false);
|
||||
}
|
||||
for (const FInputActionKeyMapping& Map : Actions)
|
||||
{
|
||||
Settings->AddActionMapping(Map, false);
|
||||
}
|
||||
for (const FInputAxisKeyMapping& Map : Axes)
|
||||
{
|
||||
Settings->AddAxisMapping(Map, false);
|
||||
}
|
||||
ApplyToPlayer();
|
||||
Status = TEXT("Original keys restored.");
|
||||
RefreshMenu();
|
||||
}
|
||||
|
||||
bool ULPKeyboardSubsystem::LoadSaved() const
|
||||
{
|
||||
const FString Path = KeyboardIniPath();
|
||||
if (!FPaths::FileExists(Path))
|
||||
{
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Clavier : %s absent, creation avec les touches actuelles"), *Path);
|
||||
return false;
|
||||
}
|
||||
FConfigFile File;
|
||||
File.Read(Path);
|
||||
TArray<FString> ActionLines;
|
||||
TArray<FString> AxisLines;
|
||||
File.GetArray(InputSection, TEXT("ActionMappings"), ActionLines);
|
||||
File.GetArray(InputSection, TEXT("AxisMappings"), AxisLines);
|
||||
TArray<FInputActionKeyMapping> Actions;
|
||||
TArray<FInputAxisKeyMapping> Axes;
|
||||
for (const FString& Line : ActionLines)
|
||||
{
|
||||
FInputActionKeyMapping Map;
|
||||
if (FInputActionKeyMapping::StaticStruct()->ImportText(*Line, &Map, nullptr, PPF_None, nullptr, TEXT("ActionMappings")) && Map.Key.IsValid())
|
||||
{
|
||||
Actions.Add(Map);
|
||||
}
|
||||
}
|
||||
for (const FString& Line : AxisLines)
|
||||
{
|
||||
FInputAxisKeyMapping Map;
|
||||
if (FInputAxisKeyMapping::StaticStruct()->ImportText(*Line, &Map, nullptr, PPF_None, nullptr, TEXT("AxisMappings")) && Map.Key.IsValid())
|
||||
{
|
||||
Axes.Add(Map);
|
||||
}
|
||||
}
|
||||
UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||||
if (!Settings || (Actions.Num() == 0 && Axes.Num() == 0))
|
||||
{
|
||||
UE_LOG(LogLifePlay, Warning, TEXT("Clavier : %s illisible, les touches du jeu sont gardees"), *Path);
|
||||
return false;
|
||||
}
|
||||
if (Actions.Num() > 0)
|
||||
{
|
||||
const TArray<FInputActionKeyMapping> Current = Settings->GetActionMappings();
|
||||
for (const FInputActionKeyMapping& Map : Current)
|
||||
{
|
||||
Settings->RemoveActionMapping(Map, false);
|
||||
}
|
||||
for (const FInputActionKeyMapping& Map : Actions)
|
||||
{
|
||||
Settings->AddActionMapping(Map, false);
|
||||
}
|
||||
}
|
||||
if (Axes.Num() > 0)
|
||||
{
|
||||
const TArray<FInputAxisKeyMapping> Current = Settings->GetAxisMappings();
|
||||
for (const FInputAxisKeyMapping& Map : Current)
|
||||
{
|
||||
Settings->RemoveAxisMapping(Map, false);
|
||||
}
|
||||
for (const FInputAxisKeyMapping& Map : Axes)
|
||||
{
|
||||
Settings->AddAxisMapping(Map, false);
|
||||
}
|
||||
}
|
||||
Settings->ForceRebuildKeymaps();
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Clavier : %d actions et %d axes relus depuis %s"), Actions.Num(), Axes.Num(), *Path);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::SaveSaved() const
|
||||
{
|
||||
UInputSettings* Settings = UInputSettings::GetInputSettings();
|
||||
if (!Settings)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const FString Path = KeyboardIniPath();
|
||||
IFileManager::Get().MakeDirectory(*FPaths::GetPath(Path), true);
|
||||
FConfigFile File;
|
||||
int32 ActionCount = 0;
|
||||
int32 AxisCount = 0;
|
||||
for (const FInputActionKeyMapping& Map : Settings->GetActionMappings())
|
||||
{
|
||||
if (Map.Key.IsValid())
|
||||
{
|
||||
File.AddToSection(InputSection, TEXT("+ActionMappings"), ExportAction(Map));
|
||||
++ActionCount;
|
||||
}
|
||||
}
|
||||
for (const FInputAxisKeyMapping& Map : Settings->GetAxisMappings())
|
||||
{
|
||||
if (Map.Key.IsValid())
|
||||
{
|
||||
File.AddToSection(InputSection, TEXT("+AxisMappings"), ExportAxis(Map));
|
||||
++AxisCount;
|
||||
}
|
||||
}
|
||||
const bool bWrote = File.Write(Path);
|
||||
UE_LOG(LogLifePlay, Display, TEXT("Clavier : %d actions et %d axes %s dans %s"), ActionCount, AxisCount, bWrote ? TEXT("enregistres") : TEXT("non ecrits"), *Path);
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::ApplyToPlayer() const
|
||||
{
|
||||
if (UInputSettings* Settings = UInputSettings::GetInputSettings())
|
||||
{
|
||||
Settings->ForceRebuildKeymaps();
|
||||
SaveSaved();
|
||||
}
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
if (APlayerController* PC = World->GetFirstPlayerController())
|
||||
{
|
||||
if (UPlayerInput* Input = PC->PlayerInput)
|
||||
{
|
||||
Input->ForceRebuildingKeyMaps(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ULPKeyboardSubsystem::RefreshMenu()
|
||||
{
|
||||
if (Live().Owner.Get() == this && Live().Menu.IsValid())
|
||||
{
|
||||
Live().Menu->Rebuild();
|
||||
}
|
||||
}
|
||||
68
Source/LifePlay/LPKeyboardSubsystem.h
Normal file
68
Source/LifePlay/LPKeyboardSubsystem.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Menu Clavier dans les reglages (MainMenu, liste SettingsScrollBox).
|
||||
//
|
||||
// Les touches du jeu sont les ActionMappings et AxisMappings de DefaultInput.ini, lus par BPs/PC.
|
||||
// Ce sous-systeme ajoute un bouton « Clavier » dans cette liste et ouvre un panneau pour les
|
||||
// changer. Le resultat est ecrit dans Saved/Config/Windows/LPKeyboard.ini de cette version,
|
||||
// pas dans GameSettings.sav : les sauvegardes et le jeu 4.27 ne sont pas touches.
|
||||
// Input.ini est le cache du moteur : il le supprime en quittant quand le diff est vide.
|
||||
// Aucun Blueprint n'est modifie. Sans la liste des reglages, le bouton n'est pas ajoute.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Containers/Ticker.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "Widgets/SWidget.h"
|
||||
#include "LPKeyboardSubsystem.generated.h"
|
||||
|
||||
class UScrollBox;
|
||||
|
||||
UCLASS()
|
||||
class LIFEPLAY_API ULPKeyboardSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
UFUNCTION()
|
||||
void OpenKeyboardMenu();
|
||||
|
||||
TSharedRef<SWidget> TakeEmbeddedPage();
|
||||
void CloseKeyboardMenu();
|
||||
void BeginWait(FName Name, bool bAxis, float Scale, FKey OldKey, bool bShift, bool bCtrl, bool bAlt, bool bCmd, bool bAdd);
|
||||
void CancelWait();
|
||||
bool CommitKey(const FKey& Key, bool bShift, bool bCtrl, bool bAlt, bool bCmd);
|
||||
void RemoveBinding(FName Name, bool bAxis, float Scale, FKey Key, bool bShift, bool bCtrl, bool bAlt, bool bCmd);
|
||||
void ResetToDefault();
|
||||
|
||||
bool IsWaiting() const { return bWaiting; }
|
||||
bool IsMenuOpen() const;
|
||||
const FString& GetStatus() const { return Status; }
|
||||
void RefreshMenu();
|
||||
|
||||
private:
|
||||
bool Tick(float DeltaTime);
|
||||
void AttachButton(UScrollBox* Scroll);
|
||||
void ApplyToPlayer() const;
|
||||
bool LoadSaved() const;
|
||||
void SaveSaved() const;
|
||||
bool LoadDefaults(TArray<struct FInputActionKeyMapping>& Actions, TArray<struct FInputAxisKeyMapping>& Axes) const;
|
||||
|
||||
FTSTicker::FDelegateHandle TickHandle;
|
||||
bool bLoggedMissing = false;
|
||||
|
||||
bool bWaiting = false;
|
||||
bool bWaitAdd = false;
|
||||
bool bWaitAxis = false;
|
||||
float WaitScale = 0.f;
|
||||
FName WaitName;
|
||||
FKey WaitOldKey;
|
||||
bool bWaitShift = false;
|
||||
bool bWaitCtrl = false;
|
||||
bool bWaitAlt = false;
|
||||
bool bWaitCmd = false;
|
||||
FString Status;
|
||||
};
|
||||
Loading…
Reference in a new issue