// Copyright Epic Games, Inc. All Rights Reserved. #include "SMassQueryEditor.h" #include "SSearchableComboBox.h" #include "SMassQueryEditorView.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Views/SListView.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Text/STextBlock.h" #include "Internationalization/Internationalization.h" #define LOCTEXT_NAMESPACE "SMassRequirementsEditor" namespace UE::MassDebugger { void SQueryEditor::Construct(const FArguments& InArgs) { DebuggerModel = InArgs._DebuggerModel; OnRequirementsChanged = InArgs._OnRequirementsChanged; TWeakPtr WeakThisPtr = StaticCastWeakPtr(AsWeak()); if (TSharedPtr Model = DebuggerModel.Pin()) { Model->CacheFragmentData(); Model->CacheTagData(); for (const TSharedPtr& FragData : Model->CachedFragmentData) { if (FragData->Fragment.IsValid()) { int32 Index = AvailableFragmentNames.Add(MakeShared(FragData->Fragment->GetName())); FragmentNamesToTypes.Add(*AvailableFragmentNames[Index], FragData->Fragment); } } for (const TSharedPtr& TagData : Model->CachedTagData) { if (TagData->Fragment.IsValid()) { int32 Index = AvailableTagNames.Add(MakeShared(TagData->Fragment->GetName())); TagNamesToTypes.Add(*AvailableTagNames[Index], TagData->Fragment); } } } const UEnum* FragmentAccessEnum = StaticEnum(); for (int32 i = 0; i < FragmentAccessEnum->NumEnums() - 1; ++i) { int64 AccessValue = FragmentAccessEnum->GetValueByIndex(i); if (AccessValue != INDEX_NONE) { FragmentAccessNames.Add(MakeShared(FragmentAccessEnum->GetNameByIndex(i))); FragmentAccessMap.Add(FragmentAccessEnum->GetNameByIndex(i), static_cast(AccessValue)); } } const UEnum* FragmentPresenceEnum = StaticEnum(); for (int32 i = 0; i < FragmentPresenceEnum->NumEnums() - 1; ++i) { int64 PresenceValue = FragmentPresenceEnum->GetValueByIndex(i); if (PresenceValue != INDEX_NONE) { FragmentPresenceNames.Add(MakeShared(FragmentPresenceEnum->GetNameByIndex(i))); FragmentPresenceMap.Add(FragmentPresenceEnum->GetNameByIndex(i), static_cast(PresenceValue)); } } Load(); ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(2.0f) [ SNew(SEditableTextBox) .Text_Lambda([WeakThisPtr]() { if (TSharedPtr SharedThis = WeakThisPtr.Pin()) { return SharedThis->Query.IsValid() ? FText::FromString(SharedThis->Query->Name) : FText::GetEmpty(); } return FText::GetEmpty(); }) .OnTextCommitted(this, &SQueryEditor::HandleNameTextCommitted) ] + SVerticalBox::Slot() .AutoHeight() .Padding(2.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("FragmentsLabel", "Fragment Requirements")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SButton) .Text(LOCTEXT("AddFragBtn", "Add Fragment")) .OnClicked(this, &SQueryEditor::OnAddFragment) ] ] + SVerticalBox::Slot() .FillHeight(0.5f) .Padding(2.0f) [ SAssignNew(FragmentListView, SListView>) .ListItemsSource(&FragmentEntries) .OnGenerateRow(this, &SQueryEditor::OnGenerateFragmentRow) ] + SVerticalBox::Slot() .AutoHeight() .Padding(.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("TagsLabel", "Tag Requirements")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SButton) .Text(LOCTEXT("AddTagBtn", "Add Tag")) .OnClicked(this, &SQueryEditor::OnAddTag) ] ] + SVerticalBox::Slot() .FillHeight(0.5f) .Padding(2.0f) [ SAssignNew(TagListView, SListView>) .ListItemsSource(&TagEntries) .OnGenerateRow(this, &SQueryEditor::OnGenerateTagRow) ] ]; } void SQueryEditor::SetQuery(TSharedPtr& InQuery) { Query = InQuery; Load(); } void SQueryEditor::ApplyChanges() { if (Query.IsValid()) { Query->FragmentRequirements = FragmentEntries; Query->TagRequirements = TagEntries; } else { Load(); } } void SQueryEditor::Load() { if (Query.IsValid()) { FragmentEntries = Query->FragmentRequirements; TagEntries = Query->TagRequirements; } else { FragmentEntries.Reset(); TagEntries.Empty(); } if (FragmentListView.IsValid()) { FragmentListView->RequestListRefresh(); } if (TagListView.IsValid()) { TagListView->RequestListRefresh(); } } void SQueryEditor::HandleNameTextCommitted(const FText& InText, ETextCommit::Type InCommitType) { if (Query.IsValid()) { Query->Name = InText.ToString(); NotifyRequirementsChanged(); if (TSharedPtr Model = DebuggerModel.Pin()) { Model->RefreshQueries(); } } } TSharedRef SQueryEditor::OnGenerateFragmentRow(TSharedPtr InEntry, const TSharedRef& OwnerTable) { TSharedPtr SharedThis = StaticCastSharedPtr(AsShared().ToSharedPtr()); return SNew(SFragmentSelectorRow, OwnerTable, InEntry, SharedThis.ToSharedRef(), false); } void SQueryEditor::SFragmentSelectorRow::Construct(const FArguments& InArgs, const TSharedRef& InOwnerTable, const TSharedPtr& InEntry, TSharedRef InOwnerRequirementsEditor, bool bInShowTags) { OwnerRequirementsEditor = InOwnerRequirementsEditor; ConstructInternal(InArgs, InOwnerTable); TArray>* FragmentNamesPtr; TSharedPtr AccessModeSelector; TWeakPtr WeakThisPtr = StaticCastWeakPtr(AsWeak()); bShowTags = bInShowTags; if (bShowTags) { FragmentNamesPtr = &InOwnerRequirementsEditor->AvailableTagNames; AccessModeSelector = SNullWidget::NullWidget.ToSharedPtr(); } else { FragmentNamesPtr = &InOwnerRequirementsEditor->AvailableFragmentNames; AccessModeSelector = SNew(SComboBox>) .OptionsSource(&InOwnerRequirementsEditor->FragmentAccessNames) .OnGenerateWidget_Lambda([](TSharedPtr InName) { return SNew(STextBlock).Text(FText::FromName(*InName)); }) .OnSelectionChanged_Lambda([WeakThisPtr, InEntry](TSharedPtr NewName, ESelectInfo::Type) { TSharedPtr SharedThis = WeakThisPtr.Pin(); if (SharedThis.IsValid() && InEntry.IsValid() && NewName.IsValid() && SharedThis->OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *SharedThis->OwnerRequirementsEditor.Pin(); InEntry->AccessMode = Editor.FragmentAccessMap[*NewName]; Editor.NotifyRequirementsChanged(); } }) [ SNew(STextBlock).Text_Lambda([InEntry]() { switch (InEntry->AccessMode) { case EMassFragmentAccess::ReadOnly: return LOCTEXT("ReadOnly", "ReadOnly"); case EMassFragmentAccess::ReadWrite: return LOCTEXT("ReadWrite", "ReadWrite"); default: return LOCTEXT("NoneAccess", ""); } }) ]; } ChildSlot [ SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().Padding(2) [ SAssignNew(FragmentsComboBox, SSearchableComboBox) .OptionsSource(FragmentNamesPtr) .OnGenerateWidget_Lambda([](const TSharedPtr& InItem) { return SNew(STextBlock) .Text(InItem ? FText::FromString(*InItem) : FText::GetEmpty()); }) .OnSelectionChanged_Lambda([WeakThisPtr, InEntry](const TSharedPtr Value, ESelectInfo::Type) { TSharedPtr SharedThis = WeakThisPtr.Pin(); if (SharedThis.IsValid() && InEntry.IsValid() && Value.IsValid() && SharedThis->OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *SharedThis->OwnerRequirementsEditor.Pin(); if (SharedThis->bShowTags) { InEntry->StructType = Editor.TagNamesToTypes[*Value]; } else { InEntry->StructType = Editor.FragmentNamesToTypes[*Value]; } Editor.NotifyRequirementsChanged(); } }) .Content() [ SNew(STextBlock) .Text_Lambda([InEntry]() -> FText { if (InEntry.IsValid() && InEntry->StructType.IsValid()) { return InEntry->StructType->GetDisplayNameText(); } return LOCTEXT("NoneFragment", ""); }) ] ] + SHorizontalBox::Slot().AutoWidth().Padding(2) [ AccessModeSelector.ToSharedRef() ] + SHorizontalBox::Slot().AutoWidth().Padding(2) [ SNew(SComboBox>) .OptionsSource(&InOwnerRequirementsEditor->FragmentPresenceNames) .OnSelectionChanged_Lambda([WeakThisPtr, InEntry](TSharedPtr NewName, ESelectInfo::Type) { TSharedPtr SharedThis = WeakThisPtr.Pin(); if (SharedThis.IsValid() && InEntry.IsValid() && NewName.IsValid() && SharedThis->OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *SharedThis->OwnerRequirementsEditor.Pin(); InEntry->Presence = Editor.FragmentPresenceMap[*NewName]; Editor.NotifyRequirementsChanged(); } }) .OnGenerateWidget_Lambda([](TSharedPtr InName) { return SNew(STextBlock).Text(FText::FromName(*InName)); }) [ SNew(STextBlock).Text_Lambda([InEntry]() { if (!InEntry.IsValid()) return LOCTEXT("NonePres", ""); switch (InEntry->Presence) { case EMassFragmentPresence::All: return LOCTEXT("All", "All"); case EMassFragmentPresence::Any: return LOCTEXT("Any", "Any"); case EMassFragmentPresence::Optional: return LOCTEXT("Optional", "Optional"); default: return LOCTEXT("None", "None"); } }) ] ] + SHorizontalBox::Slot().AutoWidth().Padding(2) [ SNew(SButton) .Text(LOCTEXT("Remove", "Remove")) .OnClicked( &InOwnerRequirementsEditor.Get(), bShowTags ? &SQueryEditor::OnRemoveTag : &SQueryEditor::OnRemoveFragment, InEntry) ] ]; } TSharedRef SQueryEditor::OnGenerateTagRow(TSharedPtr InEntry, const TSharedRef& OwnerTable) { TSharedPtr SharedThis = StaticCastSharedPtr(AsShared().ToSharedPtr()); return SNew(SFragmentSelectorRow, OwnerTable, InEntry, SharedThis.ToSharedRef(), true); } FReply SQueryEditor::OnAddFragment() { TSharedPtr NewFragmentEntry = MakeShared(); if (AvailableFragmentNames.Num() > 0) { NewFragmentEntry->StructType = (FragmentNamesToTypes[*AvailableFragmentNames[0]]); } FragmentEntries.Add(NewFragmentEntry); FragmentListView->RequestListRefresh(); NotifyRequirementsChanged(); return FReply::Handled(); } FReply SQueryEditor::OnRemoveFragment(TSharedPtr InEntry) { FragmentEntries.Remove(InEntry); FragmentListView->RequestListRefresh(); NotifyRequirementsChanged(); return FReply::Handled(); } FReply SQueryEditor::OnAddTag() { TSharedPtr NewTagEntry = MakeShared(); if (AvailableTagNames.Num() > 0) { NewTagEntry->StructType = (TagNamesToTypes[*AvailableTagNames[0]]); } TagEntries.Add(NewTagEntry); TagListView->RequestListRefresh(); NotifyRequirementsChanged(); return FReply::Handled(); } FReply SQueryEditor::OnRemoveTag(TSharedPtr InEntry) { TagEntries.Remove(InEntry); TagListView->RequestListRefresh(); NotifyRequirementsChanged(); return FReply::Handled(); } void SQueryEditor::SFragmentSelectorRow::OnFragmentTypeChanged(TSharedPtr> NewType, ESelectInfo::Type, TSharedPtr Entry) { if (Entry.IsValid() && NewType.IsValid() && NewType->Get() && OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *OwnerRequirementsEditor.Pin(); Entry->StructType = NewType->Get(); Editor.NotifyRequirementsChanged(); } } void SQueryEditor::SFragmentSelectorRow::OnFragmentAccessChanged(EMassFragmentAccess NewAccess, ESelectInfo::Type, TSharedPtr Entry) { if (Entry.IsValid() && OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *OwnerRequirementsEditor.Pin(); Entry->AccessMode = NewAccess; Editor.NotifyRequirementsChanged(); } } void SQueryEditor::SFragmentSelectorRow::OnFragmentPresenceChanged(EMassFragmentPresence NewPresence, ESelectInfo::Type, TSharedPtr Entry) { if (Entry.IsValid() && OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *OwnerRequirementsEditor.Pin(); Entry->Presence = NewPresence; Editor.NotifyRequirementsChanged(); } } void SQueryEditor::SFragmentSelectorRow::OnTagTypeChanged(TSharedPtr> NewType, ESelectInfo::Type, TSharedPtr Entry) { if (Entry.IsValid() && NewType.IsValid() && NewType->Get() && OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *OwnerRequirementsEditor.Pin(); Entry->StructType = NewType->Get(); Editor.NotifyRequirementsChanged(); } } void SQueryEditor::SFragmentSelectorRow::OnTagPresenceChanged(EMassFragmentPresence NewPresence, ESelectInfo::Type, TSharedPtr Entry) { if (Entry.IsValid() && OwnerRequirementsEditor.IsValid()) { SQueryEditor& Editor = *OwnerRequirementsEditor.Pin(); Entry->Presence = NewPresence; Editor.NotifyRequirementsChanged(); } } FText SQueryEditor::SFragmentSelectorRow::GetFragmentTypeText(TSharedPtr Entry) const { return Entry.IsValid() && Entry->StructType.IsValid() ? FText::FromName(Entry->StructType->GetFName()) : LOCTEXT("NoneFragment", ""); } FText SQueryEditor::SFragmentSelectorRow::GetTagTypeText(TSharedPtr Entry) const { return Entry.IsValid() && Entry->StructType.IsValid() ? FText::FromName(Entry->StructType->GetFName()) : LOCTEXT("NoneTag", ""); } FText SQueryEditor::SFragmentSelectorRow::GetTagPresenceText(TSharedPtr Entry) const { if (!Entry.IsValid()) return LOCTEXT("NonePres", ""); switch (Entry->Presence) { case EMassFragmentPresence::All: return LOCTEXT("All", "All"); case EMassFragmentPresence::Any: return LOCTEXT("Any", "Any"); case EMassFragmentPresence::Optional: return LOCTEXT("Optional", "Optional"); default: return LOCTEXT("None", "None"); } } void SQueryEditor::NotifyRequirementsChanged() { ApplyChanges(); if (OnRequirementsChanged.IsBound()) { OnRequirementsChanged.Execute(Query); } } } // namespace UE::MassDebugger #undef LOCTEXT_NAMESPACE