// Copyright Epic Games, Inc. All Rights Reserved. #include "SMassQueryEditorView.h" #include "MassDebuggerModel.h" #include "Dom/JsonObject.h" #include "HAL/FileManager.h" #include "HAL/PlatformApplicationMisc.h" #include "Misc/FileHelper.h" #include "Serialization/JsonWriter.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "SMassQueryEditor" namespace UE::MassDebugger { void SQueryEditorView::Construct(const FArguments& InArgs, TSharedRef InDebuggerModel) { DebuggerModel = InDebuggerModel; InDebuggerModel->OnRefreshDelegate.AddSP(this, &SQueryEditorView::OnRefresh); InDebuggerModel->OnQueriesChangedDelegate.AddSP(this, &SQueryEditorView::OnRefresh); ChildSlot [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(4) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(2) [ SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().Padding(2) [ SNew(SButton) .OnClicked(this, &SQueryEditorView::OnSaveQuery) .ToolTipText(LOCTEXT("SaveQuery_Tooltip", "Save selected query")) .Content() [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.Save")) ] ] + SHorizontalBox::Slot().AutoWidth().Padding(2) [ SNew(SButton) .OnClicked(this, &SQueryEditorView::OnSaveAllQueries) .ToolTipText(LOCTEXT("SaveAllQueries_Tooltip", "Save all queries")) .Content() [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.SaveModified")) ] ] + SHorizontalBox::Slot().AutoWidth().Padding(2) [ SNew(SButton) .OnClicked(this, &SQueryEditorView::OnCopyQuery) .ToolTipText(LOCTEXT("CopyQuery_Tooltip", "Copy selected query as JSON")) .Content() [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("GenericCommands.Copy")) ] ] + SHorizontalBox::Slot().AutoWidth().Padding(2) [ SNew(SButton) .OnClicked(this, &SQueryEditorView::OnPasteQuery) .ToolTipText(LOCTEXT("PasteQuery_Tooltip", "Paste JSON from clipboard as new query")) .Content() [ SNew(SImage) .Image(FAppStyle::GetBrush("GenericCommands.Paste")) ] ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(2) [ SNew(SButton) .Text(LOCTEXT("AddQuery", "Add Query")) .OnClicked(this, &SQueryEditorView::OnAddQuery) ] + SVerticalBox::Slot() .AutoHeight() .Padding(2) [ SNew(SSeparator) ] + SVerticalBox::Slot() .FillHeight(1.0f) .Padding(2) [ SAssignNew(QueryListView, SListView>) .ListItemsSource(&InDebuggerModel->QueryList) .SelectionMode(ESelectionMode::Single) .OnGenerateRow(this, &SQueryEditorView::OnGenerateQueryRow) .OnSelectionChanged_Lambda([this, WeakModel = InDebuggerModel.ToWeakPtr()](TSharedPtr InQuery, ESelectInfo::Type InSelectType) { if(TSharedPtr Model = WeakModel.Pin()) { if (!InQuery.IsValid()) { if (SelectedQuery.IsValid() && Model->QueryList.Contains(SelectedQuery)) { QueryListView->SetSelection(SelectedQuery); } else { SelectedQuery = Model->QueryList.Num() > 0 ? Model->QueryList[0] : InQuery; QueryEditor->SetQuery(SelectedQuery); } } else { SelectedQuery = InQuery; QueryEditor->SetQuery(SelectedQuery); } } }) ] ] + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(4) [ SAssignNew(QueryEditor, SQueryEditor) .DebuggerModel(InDebuggerModel) .InitialRequirements(FMassFragmentRequirements()) .OnRequirementsChanged(this, &SQueryEditorView::OnRequirementsChanged) ] ]; InDebuggerModel->RegisterQueryEditor(SharedThis(this)); } void SQueryEditorView::OnRefresh() { if (QueryListView.IsValid()) { QueryListView->RequestListRefresh(); } } TSharedRef SQueryEditorView::OnGenerateQueryRow(TSharedPtr InItem, const TSharedRef& OwnerTable) { return SNew(STableRow>, OwnerTable) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(0.3f) .VAlign(VAlign_Center) .Padding(2) [ SNew(STextBlock) .Text_Lambda([InItem]() { return InItem.IsValid() ? FText::FromString(InItem->Name) : FText::GetEmpty(); }) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2) [ SNew(SButton) .Text(LOCTEXT("ViewEntities", "View Entities")) .OnClicked(this, &SQueryEditorView::OnViewEntities, InItem) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2) [ SNew(SButton) .OnClicked(this, &SQueryEditorView::OnDeleteQuery, InItem) [ SNew(SImage) .Image(FCoreStyle::Get().GetBrush("Icons.Delete")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ]; } FReply SQueryEditorView::OnAddQuery() { TSharedPtr NewQuery = MakeShared(); NewQuery->Name = TEXT("NewQuery"); if (TSharedPtr Model = DebuggerModel.Pin()) { Model->QueryList.Add(NewQuery); if (QueryListView.IsValid()) { QueryListView->RequestListRefresh(); QueryListView->SetSelection(NewQuery); QueryListView->RequestScrollIntoView(NewQuery); } Model->RefreshQueries(); } return FReply::Handled(); } FReply SQueryEditorView::OnDeleteQuery(TSharedPtr InItem) { if (TSharedPtr Model = DebuggerModel.Pin()) { Model->QueryList.Remove(InItem); if (QueryListView.IsValid()) { QueryListView->RequestListRefresh(); } if (SelectedQuery == InItem && QueryEditor.IsValid()) { SelectedQuery.Reset(); QueryEditor->SetQuery(SelectedQuery); } Model->RefreshQueries(); } return FReply::Handled(); } FReply SQueryEditorView::OnEditQuery(TSharedPtr InItem) { if (!InItem.IsValid() || !QueryEditor.IsValid()) { return FReply::Unhandled(); } SelectedQuery = InItem; QueryEditor->SetQuery(SelectedQuery); return FReply::Handled(); } FReply SQueryEditorView::OnViewEntities(TSharedPtr InItem) { TSharedPtr Model = DebuggerModel.Pin(); if (InItem.IsValid() && Model.IsValid() && !Model->IsStale()) { FMassEntityQuery Query = InItem->BuildEntityQuery(Model->Environment->GetMutableEntityManager().ToSharedRef()); Model->ShowEntitiesView(0, Query); } return FReply::Handled(); } void SQueryEditorView::OnRequirementsChanged(TSharedPtr& Query) { } void SQueryEditorView::ShowQuery(const FMassEntityQuery& InQuery, const FString& InQueryName) { if (TSharedPtr Model = DebuggerModel.Pin()) { TSharedPtr NewQuery = MakeShared(); NewQuery->Name = InQueryName; NewQuery->InitFromEntityQuery(InQuery, *Model); Model->QueryList.Add(NewQuery); if (QueryListView.IsValid()) { QueryListView->RequestListRefresh(); QueryListView->SetSelection(NewQuery); QueryListView->RequestScrollIntoView(NewQuery); } Model->RefreshQueries(); } } FReply SQueryEditorView::OnSaveQuery() { if (!SelectedQuery.IsValid()) { return FReply::Handled(); } const FString FileName = TEXT("Queries.json"); const FString SavedDir = FPaths::ProjectSavedDir() / TEXT("MassDebugger"); IFileManager::Get().MakeDirectory(*SavedDir, true); const FString FullPath = SavedDir / FileName; TSharedPtr RootObject = MakeShared(); TArray> QueriesArray; if (FPaths::FileExists(FullPath)) { FString Existing; FFileHelper::LoadFileToString(Existing, *FullPath); TSharedPtr Loaded; const TSharedRef> Reader = TJsonReaderFactory<>::Create(Existing); if (FJsonSerializer::Deserialize(Reader, Loaded) && Loaded.IsValid()) { const TArray>* QueriesArrayJson;; Loaded->TryGetArrayField(TEXT("Queries"), QueriesArrayJson); } } const TSharedPtr SelJson = SelectedQuery->SerializeToJson(); bool bReplaced = false; for (int32 i = 0; i < QueriesArray.Num(); ++i) { if (QueriesArray[i]->AsObject()->GetStringField(TEXT("Name")) == SelectedQuery->Name) { QueriesArray[i] = MakeShared(SelJson); bReplaced = true; break; } } if (!bReplaced) { QueriesArray.Add(MakeShared(SelJson)); } RootObject->SetArrayField(TEXT("Queries"), QueriesArray); FString OutString; const TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutString); FJsonSerializer::Serialize(RootObject.ToSharedRef(), Writer); FFileHelper::SaveStringToFile(OutString, *FullPath); return FReply::Handled(); } FReply SQueryEditorView::OnSaveAllQueries() { if (TSharedPtr Model = DebuggerModel.Pin()) { const FString FileName = TEXT("Queries.json"); const FString SavedDir = FPaths::ProjectSavedDir() / TEXT("MassDebugger"); IFileManager::Get().MakeDirectory(*SavedDir, /*Tree=*/true); const FString FullPath = SavedDir / FileName; TSharedPtr RootObject = MakeShared(); TArray> QueriesArray; for (TSharedPtr& Query : Model->QueryList) { QueriesArray.Add(MakeShared(Query->SerializeToJson())); } RootObject->SetArrayField(TEXT("Queries"), MoveTemp(QueriesArray)); FString OutString; const TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutString); FJsonSerializer::Serialize(RootObject.ToSharedRef(), Writer); FFileHelper::SaveStringToFile(OutString, *FullPath); } return FReply::Handled(); } FReply SQueryEditorView::OnCopyQuery() { if (!SelectedQuery.IsValid()) { return FReply::Handled(); } FString JsonString; const TSharedRef> Writer = TJsonWriterFactory<>::Create(&JsonString); FJsonSerializer::Serialize(SelectedQuery->SerializeToJson().ToSharedRef(), Writer); FPlatformApplicationMisc::ClipboardCopy(*JsonString); return FReply::Handled(); } FReply SQueryEditorView::OnPasteQuery() { TSharedPtr Model = DebuggerModel.Pin(); FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); TSharedPtr NewQuery = MakeShared(); if (Model && NewQuery->DeserializeFromJsonString(ClipboardText)) { Model->QueryList.Add(NewQuery); if (QueryListView.IsValid()) { QueryListView->RequestListRefresh(); QueryListView->SetSelection(NewQuery); QueryListView->RequestScrollIntoView(NewQuery); } Model->RefreshQueries(); } return FReply::Handled(); } void SQueryEditorView::OnQueriesChanged() { TSharedPtr Model = DebuggerModel.Pin(); if (!Model) { return; } if (QueryListView.IsValid()) { QueryListView->RequestListRefresh(); } if (Model->QueryList.Num() > 0) { SelectedQuery = Model->QueryList[0]; QueryListView->SetSelection(SelectedQuery); } else { SelectedQuery.Reset(); } if (QueryEditor.IsValid()) { QueryEditor->SetQuery(SelectedQuery); } } } // namespace UE::MassDebugger #undef LOCTEXT_NAMESPACE