// Copyright Epic Games, Inc. All Rights Reserved. #include "DeviceProfileDetails.h" #include "Containers/BitArray.h" #include "Containers/Set.h" #include "Containers/SparseArray.h" #include "CoreTypes.h" #include "Delegates/Delegate.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "DeviceProfiles/DeviceProfile.h" #include "DeviceProfiles/DeviceProfileManager.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Application/SlateApplication.h" #include "HAL/IConsoleManager.h" #include "HAL/PlatformCrt.h" #include "IDetailGroup.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Layout/Children.h" #include "Layout/Margin.h" #include "Layout/Visibility.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "Misc/EnumRange.h" #include "Misc/Optional.h" #include "PropertyEditorModule.h" #include "PropertyHandle.h" #include "Serialization/Archive.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Styling/SlateColor.h" #include "Templates/Casts.h" #include "Templates/Less.h" #include "Templates/Tuple.h" #include "Templates/UnrealTemplate.h" #include "TextureLODSettingsDetails.h" #include "Types/SlateStructs.h" #include "UObject/UnrealType.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SCompoundWidget.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/SListView.h" #include "Widgets/Views/STableRow.h" class ITableRow; class STableViewBase; class SWidget; class UObject; #define LOCTEXT_NAMESPACE "DeviceProfileDetails" //////////////////////////////////////////////// // FDeviceProfileDetails void FDeviceProfileDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { // Hide all the properties apart from the Console Variables. IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("DeviceSettings"); TSharedPtr DeviceTypeHandle = DetailBuilder.GetProperty("DeviceType"); DetailBuilder.HideProperty(DeviceTypeHandle); TSharedPtr MeshLODSettingsHandle = DetailBuilder.GetProperty("MeshLODSettings"); DetailBuilder.HideProperty(MeshLODSettingsHandle); // Setup the parent profile panel ParentProfileDetails = MakeShareable(new FDeviceProfileParentPropertyDetails(&DetailBuilder)); ParentProfileDetails->CreateParentPropertyView(); // Setup the console variable editor ConsoleVariablesDetails = MakeShareable(new FDeviceProfileConsoleVariablesPropertyDetails(&DetailBuilder)); ConsoleVariablesDetails->CreateConsoleVariablesPropertyView(); TextureLODSettingsDetails = MakeShareable(new FDeviceProfileTextureLODSettingsDetails(&DetailBuilder)); TextureLODSettingsDetails->CreateTextureLODSettingsPropertyView(); } //////////////////////////////////////////////// // DeviceProfilePropertyConstants /** Property layout constants, we will use this for consistent spacing across the details view */ namespace DeviceProfilePropertyConstants { const FMargin PropertyPadding(2.0f, 0.0f, 2.0f, 0.0f); const FMargin CVarSelectionMenuPadding(10.0f, 2.0f); } //////////////////////////////////////////////// // DeviceProfileCVarFormatHelper /** Some helper fucntions to assist us with displaying Console Variables from the CVars property */ namespace DeviceProfileCVarFormatHelper { /** The available Console Variable Categories a CVar will be listed under. */ enum ECVarGroup { CVG_Uncategorized = 0, CVG_Rendering, CVG_Physics, CVG_Network, CVG_Console, CVG_Compatibility, CVG_UserInterface, CVG_ScalabilityGroups, Max_CVarCategories, }; /** * Convert the CVar group enum enum its display name. * * @param CatEnum - The ECVarGroup index * * @return The display name of the group. */ FText CategoryTextFromEnum(ECVarGroup CatEnum) { switch(CatEnum) { case CVG_Rendering: return LOCTEXT("RenderingCVarGroupTitle", "Rendering"); case CVG_Physics: return LOCTEXT("PhysicsCVarGroupTitle", "Physics"); case CVG_Network: return LOCTEXT("NetworkCVarGroupTitle", "Network"); case CVG_Console: return LOCTEXT("ConsoleCVarGroupTitle", "Console"); case CVG_Compatibility: return LOCTEXT("CompatibilityCVarGroupTitle", "Compatibility"); case CVG_UserInterface: return LOCTEXT("UICVarGroupTitle", "User Interface"); case CVG_ScalabilityGroups: return LOCTEXT("ScalabilityGroupCVarGroupTitle", "Scalability Group"); default: break; } return LOCTEXT("UncategorizedCVarGroupTitle", "Uncategorized"); } /** * Convert the CVar group enum enum its CVar prefix. * * @param CatEnum - The ECVarGroup index * * @return The prefix of the group. */ FString CategoryPrefixFromEnum(ECVarGroup CatEnum) { switch (CatEnum) { case CVG_Rendering: return TEXT("r"); case CVG_Physics: return TEXT("p"); case CVG_Network: return TEXT("net"); case CVG_Console: return TEXT("con"); case CVG_Compatibility: return TEXT("compat"); case CVG_UserInterface: return TEXT("ui"); case CVG_ScalabilityGroups: return TEXT("sg"); default: break; } return FString(); } /** * Convert the CVar prefix to the CVar group enum entry. * * @param InPrefix - The Prefix of the Console Variable * * @return The enum entry of the group. */ ECVarGroup CategoryEnumFromPrefix(const FString& InPrefix) { if (InPrefix == TEXT("r") || InPrefix == TEXT("r.")) { return CVG_Rendering; } if (InPrefix == TEXT("p") || InPrefix == TEXT("p.")) { return CVG_Physics; } if (InPrefix == TEXT("net") || InPrefix == TEXT("net.")) { return CVG_Network; } if (InPrefix == TEXT("con") || InPrefix == TEXT("con.")) { return CVG_Console; } if (InPrefix == TEXT("compat") || InPrefix == TEXT("compat.")) { return CVG_Compatibility; } if (InPrefix == TEXT("ui") || InPrefix == TEXT("ui.")) { return CVG_UserInterface; } if (InPrefix == TEXT("sg") || InPrefix == TEXT("sg.")) { return CVG_ScalabilityGroups; } return CVG_Uncategorized; } }; ENUM_RANGE_BY_COUNT(DeviceProfileCVarFormatHelper::ECVarGroup, DeviceProfileCVarFormatHelper::Max_CVarCategories); //////////////////////////////////////////////// // FConsoleVariablesAvailableVisitor /** * Console variable visitor which collects our desired information from the console manager iterator */ class FConsoleVariablesAvailableVisitor { public: // @param Name must not be 0 // @param CVar must not be 0 static void OnConsoleVariable(const TCHAR *Name, IConsoleObject* CVar, TArray>* Sink) { if(CVar->AsVariable()) { Sink->Add(MakeShareable(new FString(Name))); } } }; //////////////////////////////////////////////// // SCVarSelectionPanel /** * Slate Widget to display all available CVars for a given Console Variable group. */ class SCVarSelectionPanel : public SCompoundWidget { private: /** Delegate type to notify listeners that a CVar was selected for add */ DECLARE_DELEGATE_OneParam(FOnCVarAddedDelegate, const FString&); public: SLATE_BEGIN_ARGS(SCVarSelectionPanel) {} SLATE_DEFAULT_SLOT(FArguments, Content) SLATE_EVENT(FOnCVarAddedDelegate,OnCVarSelected) SLATE_END_ARGS() /** Constructs this widget with InArgs */ void Construct(const FArguments& InArgs, const FString& CVarPrefix); /** * Handle the cvar selection from this panel * * @param CVar - The selected CVar */ FReply HandleCVarSelected(const TSharedPtr CVar); /** * Row generation widget for the list of available CVars for add * * @param InItem - The CVar, as a string, we are creating a row for * @param OwnerTable - The table widget that this row will be added to */ TSharedRef GenerateCVarItemRow(TSharedPtr InItem, const TSharedRef& OwnerTable); /** * Called by Slate when the filter box changes text. * * @param InFilterText - The substring which we should use to filter CVars */ void OnFilterTextChanged(const FText& InFilterText); private: /** Handle to the list view of selectable console variables */ TSharedPtr>> CVarListView; /** Text entry to filter console variable strings */ TSharedPtr CVarFilterBox; /** The collection of CVars for a groups selection panel*/ TArray> CVarsToDisplay; /** The collection of CVars for a groups selection panel*/ TArray> AllAvailableCVars; /** Delegate used to notify listeners that a CVar was selected for add */ FOnCVarAddedDelegate OnCVarSelected; /** True if the search box will take keyboard focus next frame */ bool bPendingFocusNextFrame; }; void SCVarSelectionPanel::Construct(const FArguments& InArgs, const FString& CVarPrefix) { OnCVarSelected = InArgs._OnCVarSelected; TArray> UnprocessedCVars; IConsoleManager::Get().ForEachConsoleObjectThatStartsWith( FConsoleObjectVisitor::CreateStatic( &FConsoleVariablesAvailableVisitor::OnConsoleVariable, &UnprocessedCVars), *CVarPrefix); if (DeviceProfileCVarFormatHelper::CategoryEnumFromPrefix(CVarPrefix) == DeviceProfileCVarFormatHelper::CVG_Uncategorized) { // Make a list of existing prefixes TSet CategoryPrefixes; for (DeviceProfileCVarFormatHelper::ECVarGroup PrefixEnum : TEnumRange()) { FString Prefix = DeviceProfileCVarFormatHelper::CategoryPrefixFromEnum(PrefixEnum); if (Prefix.Len() > 0) { CategoryPrefixes.Add(Prefix + TEXT(".")); } } // Add all cvars that *don't* match one of the other groups for (const TSharedPtr& TestCVarPtr : UnprocessedCVars) { const FString& TestCVar = *TestCVarPtr.Get(); bool bBelongsInUncategorized = false; // Figure out if the prefix on this variable belongs to any other category or if it's missing a prefix int32 FirstPeriodIndex = INDEX_NONE; TestCVar.FindChar(TEXT('.'), /*out*/ FirstPeriodIndex); if (FirstPeriodIndex != INDEX_NONE) { const FString TestPrefix(TestCVar.Left(FirstPeriodIndex+1)); bBelongsInUncategorized = !CategoryPrefixes.Contains(TestPrefix); } else { // No period means no prefix, so it couldn't be in any other category bBelongsInUncategorized = true; } if (bBelongsInUncategorized) { AllAvailableCVars.Add(TestCVarPtr); } } } else { AllAvailableCVars = UnprocessedCVars; } // Sort the list AllAvailableCVars.Sort([](const TSharedPtr& A, const TSharedPtr& B) { return *A < *B; }); // Duplicate the list CVarsToDisplay = AllAvailableCVars; ChildSlot [ SNew(SBox) .WidthOverride(300.f) .HeightOverride(512.f) .Content() [ SNew(SVerticalBox) +SVerticalBox::Slot() .Padding(FMargin(4.0f)) .AutoHeight() [ SAssignNew(CVarFilterBox,SSearchBox) .OnTextChanged(this, &SCVarSelectionPanel::OnFilterTextChanged) ] + SVerticalBox::Slot() [ SAssignNew(CVarListView, SListView>) .ListItemsSource(&CVarsToDisplay) .OnGenerateRow(this, &SCVarSelectionPanel::GenerateCVarItemRow) ] ] ]; } FReply SCVarSelectionPanel::HandleCVarSelected(const TSharedPtr CVar) { OnCVarSelected.ExecuteIfBound(*CVar.Get()); return FReply::Handled(); } TSharedRef SCVarSelectionPanel::GenerateCVarItemRow(TSharedPtr InItem, const TSharedRef& OwnerTable) { FText ComposedTooltip = LOCTEXT("CVarSelectionMenuTooltip", "Select a Console Variable to add to the device profile"); if (IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(**InItem)) { ComposedTooltip = FText::Format(LOCTEXT("CVarSelectionMenuTooltipWithHelp", "{0}\n\n{1}"), ComposedTooltip, FText::FromString(Var->GetHelp())); } return SNew(STableRow>, OwnerTable) [ SNew(SButton) .ForegroundColor(FSlateColor::UseForeground()) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .OnClicked(this, &SCVarSelectionPanel::HandleCVarSelected, InItem) .ContentPadding(DeviceProfilePropertyConstants::CVarSelectionMenuPadding) .ToolTipText(ComposedTooltip) [ SNew(STextBlock) .Text(FText::FromString(*InItem)) ] ]; } void SCVarSelectionPanel::OnFilterTextChanged(const FText& InFilterText) { const FString CurrentFilterText = InFilterText.ToString(); // Recreate the list of available CVars using the filter CVarsToDisplay.Empty(); for(auto& NextCVar : AllAvailableCVars) { if(CurrentFilterText.Len() == 0 || NextCVar->Contains(CurrentFilterText)) { CVarsToDisplay.Add(NextCVar); } } CVarListView->RequestListRefresh(); } //////////////////////////////////////////////// // FDeviceProfileParentPropertyDetails FDeviceProfileParentPropertyDetails::FDeviceProfileParentPropertyDetails(IDetailLayoutBuilder* InDetailBuilder) : DetailBuilder(InDetailBuilder) , ActiveDeviceProfile(nullptr) { ParentPropertyNameHandle = DetailBuilder->GetProperty("BaseProfileName"); TArray OuterObjects; ParentPropertyNameHandle->GetOuterObjects(OuterObjects); if (OuterObjects.Num() == 1) { ActiveDeviceProfile = CastChecked(OuterObjects[0]); } } void FDeviceProfileParentPropertyDetails::CreateParentPropertyView() { UDeviceProfile* ParentProfile = ActiveDeviceProfile ? Cast(ActiveDeviceProfile->Parent) : nullptr; while(ParentProfile != nullptr) { ParentProfile->OnCVarsUpdated().BindSP(this, &FDeviceProfileParentPropertyDetails::OnParentPropertyChanged); ParentProfile = Cast(ParentProfile->Parent); } DetailBuilder->HideProperty(ParentPropertyNameHandle); FString CurrentParentName; ParentPropertyNameHandle->GetValue(CurrentParentName); IDetailCategoryBuilder& ParentDetailCategory = DetailBuilder->EditCategory("ParentDeviceProfile"); IDetailGroup& ParentNameGroup = ParentDetailCategory.AddGroup(TEXT("ParentProfileName"), LOCTEXT("ParentProfileOptionsGroupTitle", "Parent Profile Name")); ParentNameGroup.HeaderRow() [ SNew(SBox) .Padding(DeviceProfilePropertyConstants::PropertyPadding) [ SNew(STextBlock) .Text(LOCTEXT("DeviceProfileSelectParentPropertyTitle", "Selected Parent:")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; AvailableParentProfiles.Add(MakeShareable(new FString(LOCTEXT("NoParentSelection", "None").ToString()))); if(ActiveDeviceProfile != nullptr) { TArray AllPossibleParents; UDeviceProfileManager::Get().GetAllPossibleParentProfiles(ActiveDeviceProfile, AllPossibleParents); for(auto& NextProfile : AllPossibleParents) { AvailableParentProfiles.Add(MakeShareable(new FString(NextProfile->GetName()))); } } FText ParentNameText = CurrentParentName.Len() > 0 ? FText::FromString(CurrentParentName) : LOCTEXT("NoParentSelection", "None"); ParentNameGroup.AddWidgetRow() [ SNew(SComboBox>) .OptionsSource(&AvailableParentProfiles) .OnGenerateWidget(this, &FDeviceProfileParentPropertyDetails::HandleDeviceProfileParentComboBoxGenarateWidget) .OnSelectionChanged(this, &FDeviceProfileParentPropertyDetails::HandleDeviceProfileParentSelectionChanged) .Content() [ SNew(STextBlock) .Text(ParentNameText) ] ]; // If we have a parent, display Console Variable information if(ActiveDeviceProfile != nullptr && ActiveDeviceProfile->BaseProfileName.Len() > 0) { // Get a list of the current profiles CVar names to use as a filter when showing parent CVars TArray DeviceProfileCVarNames; for (auto& NextActiveProfileCVar : ActiveDeviceProfile->CVars) { FString CVarName; FString CVarValue; NextActiveProfileCVar.Split(TEXT("="), &CVarName, &CVarValue); DeviceProfileCVarNames.Add(CVarName); } TMap ParentCVarInformation; ActiveDeviceProfile->GatherParentCVarInformationRecursively(ParentCVarInformation); IDetailGroup* ParentCVarsGroup = nullptr; ParentCVarInformation.KeySort(TLess<>()); for(auto& ParentCVar : ParentCVarInformation) { FString ParentCVarName; FString ParentCVarValue; ParentCVar.Value.Split(TEXT("="), &ParentCVarName, &ParentCVarValue); // Do not display Parent CVars if the child has them overridden bool bDisplayCVar = DeviceProfileCVarNames.Find(ParentCVarName) == INDEX_NONE; if(bDisplayCVar) { if(ParentCVarsGroup == nullptr) { ParentCVarsGroup = &ParentDetailCategory.AddGroup(TEXT("ParentProfileOptions"), LOCTEXT("ParentConsoleOptionsGroupTitle", "Parent Console Variables")); ParentCVarsGroup->HeaderRow() [ SNew(SBox) .Padding(DeviceProfilePropertyConstants::PropertyPadding) [ SNew(STextBlock) .Text(LOCTEXT("DeviceProfileParentCVarsTitle", "Inherited Console Variables")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; } ParentCVarsGroup->AddWidgetRow() .IsEnabled(true) .Visibility(EVisibility::Visible) .NameContent() [ SNew(STextBlock) .Text(FText::FromString(ParentCVarName)) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(STextBlock) .Text(FText::FromString(ParentCVarValue)) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; } } } } void FDeviceProfileParentPropertyDetails::HandleDeviceProfileParentSelectionChanged(TSharedPtr NewSelection, ESelectInfo::Type SelectInfo) { ActiveDeviceProfile->BaseProfileName = *NewSelection.Get() == LOCTEXT("NoParentSelection", "None").ToString() ? TEXT("") : *NewSelection.Get(); // -> Refresh the UI of the Details view to display the parent selection DetailBuilder->ForceRefreshDetails(); } TSharedRef FDeviceProfileParentPropertyDetails::HandleDeviceProfileParentComboBoxGenarateWidget(TSharedPtr InItem) { return SNew(SBox) .Padding(DeviceProfilePropertyConstants::CVarSelectionMenuPadding) [ SNew(STextBlock) .Text(FText::FromString(*InItem)) ]; } void FDeviceProfileParentPropertyDetails::OnParentPropertyChanged() { DetailBuilder->ForceRefreshDetails(); } //////////////////////////////////////////////// // FDeviceProfileConsoleVariablesPropertyDetails FDeviceProfileConsoleVariablesPropertyDetails::FDeviceProfileConsoleVariablesPropertyDetails(IDetailLayoutBuilder* InDetailBuilder) : DetailBuilder(InDetailBuilder) { CVarsHandle = DetailBuilder->GetProperty("CVars"); } void FDeviceProfileConsoleVariablesPropertyDetails::CreateConsoleVariablesPropertyView() { FSimpleDelegate OnCVarPropertyChangedDelegate = FSimpleDelegate::CreateSP(this, &FDeviceProfileConsoleVariablesPropertyDetails::OnCVarPropertyChanged); CVarsHandle->SetOnPropertyValueChanged(OnCVarPropertyChangedDelegate); DetailBuilder->HideProperty(CVarsHandle); TSharedPtr CVarsArrayHandle = CVarsHandle->AsArray(); IDetailCategoryBuilder& CVarDetailCategory = DetailBuilder->EditCategory("ConsoleVariables"); uint32 CVarCount = 0; ensure(CVarsArrayHandle->GetNumElements(CVarCount) == FPropertyAccess::Success); // Sort the properties handles into Categories TMap>> CategoryPropertyMap; // Add all the CVar groups, even if these are empty for (int32 CategoryIdx = 0; CategoryIdx < (int32)DeviceProfileCVarFormatHelper::Max_CVarCategories; CategoryIdx++) { CategoryPropertyMap.FindOrAdd((DeviceProfileCVarFormatHelper::ECVarGroup)CategoryIdx); } for (uint32 CVarPropertyIdx = 0; CVarPropertyIdx < CVarCount; CVarPropertyIdx++) { // Get the current CVar as a string FString CVarValue; TSharedRef CVarElementHandle = CVarsArrayHandle->GetElement(CVarPropertyIdx); ensure(CVarElementHandle->GetValue(CVarValue) == FPropertyAccess::Success); // Parse the CVar entry and obtain the name and Category Name const FString CVarName = CVarValue.Left(CVarValue.Find(TEXT("="))); int32 CategoryEndIndex = CVarName.Find(TEXT(".")); FString CVarAbrv = CVarName.Left(CategoryEndIndex); DeviceProfileCVarFormatHelper::ECVarGroup CVarCategory = DeviceProfileCVarFormatHelper::CategoryEnumFromPrefix(CVarAbrv); TArray>* CurrentPropertyCategoryGroup = CategoryPropertyMap.Find(CVarCategory); CurrentPropertyCategoryGroup->Add(CVarElementHandle); } // Put the property handles into the UI group for the details view. for (auto& Current : CategoryPropertyMap) { TArray>& CurrentGroupsProperties = Current.Value; CurrentGroupsProperties.Sort([](const TSharedRef& A, const TSharedRef& B) { FString AVal; A->GetValue(AVal); FString BVal; B->GetValue(BVal); return AVal < BVal; }); const FText GroupName = DeviceProfileCVarFormatHelper::CategoryTextFromEnum(Current.Key); FString CVarPrefix = DeviceProfileCVarFormatHelper::CategoryPrefixFromEnum(Current.Key); if (CVarPrefix.Len() > 0) { CVarPrefix += TEXT("."); } // Find the Property table UI Group for the current CVar IDetailGroup& CVarGroup = CVarDetailCategory.AddGroup(*GroupName.ToString(), GroupName); CVarDetailGroups.Add(GroupName.ToString(), &CVarGroup); CVarGroup.HeaderRow() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(DeviceProfilePropertyConstants::PropertyPadding) .AutoWidth() [ SNew(STextBlock) .Text(GroupName) .Font(IDetailLayoutBuilder::GetDetailFont()) ] + SHorizontalBox::Slot() .Padding(DeviceProfilePropertyConstants::PropertyPadding) .AutoWidth() [ SNew(SComboButton) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .ContentPadding(4.0f) .ForegroundColor(FSlateColor::UseForeground()) .IsFocusable(false) .ButtonContent() [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.PlusCircle")) ] .MenuContent() [ SNew(SCVarSelectionPanel, CVarPrefix) .OnCVarSelected(this, &FDeviceProfileConsoleVariablesPropertyDetails::HandleCVarAdded) ] ] + SHorizontalBox::Slot() .Padding(DeviceProfilePropertyConstants::PropertyPadding) .AutoWidth() [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .OnClicked(this, &FDeviceProfileConsoleVariablesPropertyDetails::OnRemoveAllFromGroup, (int32)Current.Key) .ContentPadding(4.0f) .ForegroundColor(FSlateColor::UseForeground()) .IsFocusable(false) [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.Delete")) ] ] ]; for (auto& Property : CurrentGroupsProperties) { CreateRowWidgetForCVarProperty(Property, CVarGroup); } CVarDetailCategory.InitiallyCollapsed(true); } } void FDeviceProfileConsoleVariablesPropertyDetails::CreateRowWidgetForCVarProperty(TSharedPtr InProperty, IDetailGroup& InGroup) const { FString UnformattedCVar; ensure(InProperty->GetValue(UnformattedCVar) == FPropertyAccess::Success); const int32 CVarNameValueSplitIdx = UnformattedCVar.Find(TEXT("=")); ensure(CVarNameValueSplitIdx != INDEX_NONE); const FString CVarName = UnformattedCVar.Left(CVarNameValueSplitIdx); const FString CVarValueAsString = UnformattedCVar.Right(UnformattedCVar.Len() - (CVarNameValueSplitIdx + 1)); FText CVarHelp; if (IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(*CVarName)) { CVarHelp = FText::FromString(Var->GetHelp()); } InGroup.AddWidgetRow() .IsEnabled(true) .Visibility(EVisibility::Visible) .NameContent() [ SNew(STextBlock) .Text(FText::FromString(CVarName)) .ToolTipText(CVarHelp) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(SEditableTextBox) .Text(FText::FromString(CVarValueAsString)) .SelectAllTextWhenFocused(true) .OnTextCommitted(const_cast(this), &FDeviceProfileConsoleVariablesPropertyDetails::OnCVarValueCommited, InProperty) ] + SHorizontalBox::Slot() .Padding(DeviceProfilePropertyConstants::PropertyPadding) .AutoWidth() [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .OnClicked(const_cast(this), &FDeviceProfileConsoleVariablesPropertyDetails::OnRemoveCVarProperty, InProperty) .ContentPadding(4.0f) .ForegroundColor(FSlateColor::UseForeground()) .IsFocusable(false) [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.X")) ] ] ]; } void FDeviceProfileConsoleVariablesPropertyDetails::HandleCVarAdded(const FString& SelectedCVar) { if (IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(*SelectedCVar)) { const FString CompleteCVarString = FString::Printf(TEXT("%s=%s"), *SelectedCVar, *CVar->GetString()); TArray RawPtrs; CVarsHandle->AccessRawData(RawPtrs); // Update the CVars with the selection { CVarsHandle->NotifyPreChange(); for (void* RawPtr : RawPtrs) { TArray& Array = *(TArray*)RawPtr; Array.Add(CompleteCVarString); } CVarsHandle->NotifyPostChange(EPropertyChangeType::ArrayAdd); } // Update the UI with the selection // -> Close the selection menu FSlateApplication::Get().DismissAllMenus(); } } void FDeviceProfileConsoleVariablesPropertyDetails::OnCVarValueCommited(const FText& CommentText, ETextCommit::Type CommitInfo, TSharedPtr CVarPropertyHandle) { if (CVarPropertyHandle->IsValidHandle()) { const TSharedPtr ParentHandle = CVarPropertyHandle->GetParentHandle(); const TSharedPtr ParentArrayHandle = ParentHandle->AsArray(); // Get the current CVar as a string FString OldCompleteCVarValue; ensure(CVarPropertyHandle->GetValue(OldCompleteCVarValue) == FPropertyAccess::Success); // Update the CVar. I.e. MyCVar=1 const FString CVarLHS = OldCompleteCVarValue.Left(OldCompleteCVarValue.Find(TEXT("="))); const FString CVarRHS = CommentText.ToString(); FString NewCompleteCVar = FString::Printf(TEXT("%s=%s"), *CVarLHS, *CVarRHS); if(OldCompleteCVarValue!=NewCompleteCVar) { CVarPropertyHandle->SetValue(NewCompleteCVar); } } } FReply FDeviceProfileConsoleVariablesPropertyDetails::OnRemoveCVarProperty(TSharedPtr CVarPropertyHandle) { if (CVarPropertyHandle->IsValidHandle()) { const TSharedPtr ParentHandle = CVarPropertyHandle->GetParentHandle(); const TSharedPtr ParentArrayHandle = ParentHandle->AsArray(); ParentArrayHandle->DeleteItem(CVarPropertyHandle->GetIndexInArray()); } OnCVarPropertyChanged(); return FReply::Handled(); } FReply FDeviceProfileConsoleVariablesPropertyDetails::OnRemoveAllFromGroup(int32 CVarCategory) { const TSharedPtr CVarsArrayHandle = CVarsHandle->AsArray(); uint32 CVarCount = 0; ensure(CVarsArrayHandle->GetNumElements(CVarCount) == FPropertyAccess::Success); FString CVarPrefix = DeviceProfileCVarFormatHelper::CategoryPrefixFromEnum((DeviceProfileCVarFormatHelper::ECVarGroup)CVarCategory); for (int32 CVarPropertyIdx = CVarCount-1; CVarPropertyIdx >= 0; CVarPropertyIdx--) { // Get the current CVar as a string FString CVarValue; TSharedRef CVarElementHandle = CVarsArrayHandle->GetElement(CVarPropertyIdx); ensure(CVarElementHandle->GetValue(CVarValue) == FPropertyAccess::Success); // Parse the CVar entry and obtain the name and Category Name const FString CVarName = CVarValue.Left(CVarValue.Find(TEXT("="))); int32 CategoryEndIndex = CVarName.Find(TEXT(".")); FString CurrentCVarPrefix = CVarName.Left(CategoryEndIndex); if(CVarPrefix == CurrentCVarPrefix) { CVarsArrayHandle->DeleteItem(CVarElementHandle->GetIndexInArray()); } } OnCVarPropertyChanged(); return FReply::Handled(); } void FDeviceProfileConsoleVariablesPropertyDetails::OnCVarPropertyChanged() { DetailBuilder->ForceRefreshDetails(); } #undef LOCTEXT_NAMESPACE