// Copyright 2020 Phyronnaz #include "VoxelGraphEditorToolkit.h" #include "VoxelGraphEditorUtilities.h" #include "VoxelGraphGenerator.h" #include "VoxelGraphShortcuts.h" #include "VoxelGraphPreviewSettings.h" #include "VoxelGraphSchema.h" #include "VoxelNodes/VoxelGraphMacro.h" #include "VoxelNodes/VoxelLocalVariables.h" #include "VoxelGraphEditorCommands.h" #include "VoxelGraphNodes/VoxelGraphNode_Root.h" #include "VoxelGraphNodes/VoxelGraphNode.h" #include "VoxelGraphNodes/SVoxelGraphNode.h" #include "VoxelGraphNodes/VoxelGraphNode_Knot.h" #include "VoxelMessages.h" #include "VoxelDebugGraphUtils.h" #include "VoxelEditorModule.h" #include "SVoxelPalette.h" #include "Preview/SVoxelGraphPreview.h" #include "Preview/SVoxelGraphPreviewViewport.h" #include "Preview/VoxelGraphPreview.h" #include "IDetailsView.h" #include "PropertyEditorModule.h" #include "Modules/ModuleManager.h" #include "ScopedTransaction.h" #include "Misc/MessageDialog.h" #include "HAL/PlatformApplicationMisc.h" #include "Editor.h" #include "EditorStyleSet.h" #include "GraphEditorActions.h" #include "GraphEditor.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Commands/GenericCommands.h" #include "Widgets/Docking/SDockTab.h" #include "Widgets/Layout/SScaleBox.h" #include "EdGraph/EdGraph.h" #include "EdGraphUtilities.h" #include "AdvancedPreviewScene.h" #include "MessageLogModule.h" #include "IMessageLogListing.h" #include "Logging/TokenizedMessage.h" const FName FVoxelGraphEditorToolkit::GraphCanvasTabId(TEXT("VoxelGraphEditor_GraphCanvas")); const FName FVoxelGraphEditorToolkit::DebugGraphCanvasTabId(TEXT("VoxelGraphEditor_DebugGraphCanvas")); const FName FVoxelGraphEditorToolkit::PropertiesTabId(TEXT("VoxelGraphEditor_Properties")); const FName FVoxelGraphEditorToolkit::ShortcutsTabId(TEXT("VoxelGraphEditor_Shortcuts")); const FName FVoxelGraphEditorToolkit::PreviewSettingsTabId(TEXT("VoxelGraphEditor_PreviewSettings")); const FName FVoxelGraphEditorToolkit::PaletteTabId(TEXT("VoxelGraphEditor_Palette")); const FName FVoxelGraphEditorToolkit::PreviewTabId(TEXT("VoxelGraphEditor_Preview")); const FName FVoxelGraphEditorToolkit::PreviewViewportTabId(TEXT("VoxelGraphEditor_PreviewViewport")); const FName FVoxelGraphEditorToolkit::MessagesTabId(TEXT("VoxelGraphEditor_Messages")); FVoxelGraphEditorToolkit::FVoxelGraphEditorToolkit() { } FVoxelGraphEditorToolkit::~FVoxelGraphEditorToolkit() { GEditor->UnregisterForUndo(this); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::RegisterTabSpawners(const TSharedRef& InTabManager) { WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(VOXEL_LOCTEXT("Voxel Editor")); auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); FAssetEditorToolkit::RegisterTabSpawners(InTabManager); InTabManager->RegisterTabSpawner(GraphCanvasTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_GraphCanvas)) .SetDisplayName(VOXEL_LOCTEXT("Main Graph")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x")); InTabManager->RegisterTabSpawner(DebugGraphCanvasTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_DebugGraphCanvas)) .SetDisplayName(VOXEL_LOCTEXT("Debug Graph")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x")); InTabManager->RegisterTabSpawner(PropertiesTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_Properties)) .SetDisplayName(VOXEL_LOCTEXT("Details")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(ShortcutsTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_Shortcuts)) .SetDisplayName(VOXEL_LOCTEXT("Shortcuts")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(PreviewSettingsTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_PreviewSettings)) .SetDisplayName(VOXEL_LOCTEXT("Preview Settings")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(PaletteTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_Palette)) .SetDisplayName(VOXEL_LOCTEXT("Palette")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.Palette")); InTabManager->RegisterTabSpawner(PreviewTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_Preview)) .SetDisplayName(VOXEL_LOCTEXT("Preview")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports")); InTabManager->RegisterTabSpawner(PreviewViewportTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_PreviewViewport)) .SetDisplayName(VOXEL_LOCTEXT("3D Preview")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports")); InTabManager->RegisterTabSpawner(MessagesTabId, FOnSpawnTab::CreateSP(this, &FVoxelGraphEditorToolkit::SpawnTab_Messages)) .SetDisplayName(VOXEL_LOCTEXT("Messages")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "MessageLog.TabIcon")); } void FVoxelGraphEditorToolkit::UnregisterTabSpawners(const TSharedRef& InTabManager) { FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); InTabManager->UnregisterTabSpawner(GraphCanvasTabId); InTabManager->UnregisterTabSpawner(DebugGraphCanvasTabId); InTabManager->UnregisterTabSpawner(PropertiesTabId); InTabManager->UnregisterTabSpawner(ShortcutsTabId); InTabManager->UnregisterTabSpawner(PreviewSettingsTabId); InTabManager->UnregisterTabSpawner(PaletteTabId); InTabManager->UnregisterTabSpawner(PreviewTabId); InTabManager->UnregisterTabSpawner(PreviewViewportTabId); InTabManager->UnregisterTabSpawner(MessagesTabId); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::InitVoxelEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit) { FVoxelMessages::Info("You can view and edit Voxel Graphs, but running them requires Voxel Plugin Pro"); Generator = CastChecked(ObjectToEdit); if (!ensureAlways(Generator->VoxelGraph && Generator->VoxelDebugGraph)) { FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, TEXT("VoxelGraphEditorApp"), FTabManager::NewLayout("Standalone_VoxelGraphEditor_Crash")->AddArea(FTabManager::NewPrimaryArea()), false, false, ObjectToEdit, false); return; } // Support undo/redo Generator->SetFlags(RF_Transactional); GEditor->RegisterForUndo(this); FGraphEditorCommands::Register(); FVoxelGraphEditorCommands::Register(); BindGraphCommands(); CreateInternalWidgets(); const TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_VoxelGraphEditor_Layout_v8") ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.1f) ->SetHideTabWell( true ) ->AddTab(GetToolbarTabId(), ETabState::OpenedTab) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.9f) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Vertical) ->SetSizeCoefficient(0.2f) ->Split ( FTabManager::NewStack() ->AddTab( PaletteTabId, ETabState::ClosedTab ) ->AddTab( PreviewSettingsTabId, ETabState::OpenedTab) ) ->Split ( FTabManager::NewStack() ->AddTab( ShortcutsTabId, ETabState::OpenedTab ) ->AddTab( PropertiesTabId, ETabState::OpenedTab ) ) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation( Orient_Vertical ) ->SetSizeCoefficient(0.7f) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.8f) ->SetHideTabWell( true ) ->AddTab( GraphCanvasTabId, ETabState::OpenedTab ) ->AddTab( DebugGraphCanvasTabId, ETabState::ClosedTab ) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.2f) ->AddTab( MessagesTabId, ETabState::OpenedTab ) ) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Vertical) ->SetSizeCoefficient(0.3f) ->Split ( FTabManager::NewStack() ->SetHideTabWell( true ) ->AddTab( PreviewTabId, ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->SetHideTabWell( true ) ->AddTab( PreviewViewportTabId, ETabState::OpenedTab ) ) ) ) ); const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, TEXT("VoxelGraphEditorApp"), StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectToEdit, false); ExtendToolbar(); ExtendMenu(); RegenerateMenusAndToolbars(); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::CreateInternalWidgets() { VoxelGraphEditor = CreateGraphEditorWidget(false); VoxelDebugGraphEditor = CreateGraphEditorWidget(true); FDetailsViewArgs Args; Args.bHideSelectionTip = true; Args.NotifyHook = this; FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); VoxelProperties = PropertyModule.CreateDetailView(Args); VoxelProperties->SetObject(Generator); ShortcutsProperties = PropertyModule.CreateDetailView(Args); ShortcutsProperties->SetObject(GetMutableDefault()); if (!Generator->PreviewSettings) { Generator->PreviewSettings = NewObject(Generator); Generator->PreviewSettings->Graph = Generator; } // Needed for undo/redo Generator->PreviewSettings->SetFlags(RF_Transactional); PreviewSettings = PropertyModule.CreateDetailView(Args); PreviewSettings->SetObject(Generator->PreviewSettings); // Must be created before PreviewViewport PreviewScene = MakeShareable(new FAdvancedPreviewScene(FPreviewScene::ConstructionValues())); Palette = SNew(SVoxelPalette); Preview = SNew(SVoxelGraphPreview).PreviewSettings(Generator->PreviewSettings); PreviewViewport = SNew(SVoxelGraphPreviewViewport).VoxelGraphEditorToolkit(SharedThis(this)); PreviewHandler = MakeShared(Generator, Preview, PreviewViewport, PreviewScene); Preview->SetTexture(Generator->GetPreviewTexture()); // Messages panel FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); FMessageLogInitializationOptions LogOptions; LogOptions.bShowPages = false; LogOptions.bShowFilters = true; LogOptions.bAllowClear = false; LogOptions.MaxPageCount = 1; MessagesListing = MessageLogModule.CreateLogListing("VoxelGraphEditorErrors", LogOptions); MessagesWidget = MessageLogModule.CreateLogListingWidget(MessagesListing.ToSharedRef()); } void FVoxelGraphEditorToolkit::FillToolbar(FToolBarBuilder& ToolbarBuilder) { ToolbarBuilder.BeginSection("Toolbar"); auto& Commands = FVoxelGraphEditorCommands::Get(); ToolbarBuilder.AddToolBarButton(Commands.CompileToCpp); ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.ToggleAutomaticPreview); ToolbarBuilder.AddToolBarButton(Commands.UpdatePreview); ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.ClearNodesMessages); ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.ShowAxisDependencies); ToolbarBuilder.AddSeparator(); ToolbarBuilder.AddToolBarButton(Commands.ShowStats); ToolbarBuilder.AddToolBarButton(Commands.ShowValues); ToolbarBuilder.EndSection(); } void FVoxelGraphEditorToolkit::ExtendToolbar() { TSharedPtr ToolbarExtender = MakeShareable(new FExtender); ToolbarExtender->AddToolBarExtension( "Asset", EExtensionHook::After, GetToolkitCommands(), FToolBarExtensionDelegate::CreateRaw(this, &FVoxelGraphEditorToolkit::FillToolbar) ); AddToolbarExtender(ToolbarExtender); } void FVoxelGraphEditorToolkit::FillVoxelMenu(FMenuBuilder& MenuBuilder) { auto& Commands = FVoxelGraphEditorCommands::Get(); MenuBuilder.AddMenuEntry(Commands.RecreateNodes); } void FVoxelGraphEditorToolkit::AddEditorMenus(FMenuBarBuilder& MenuBarBuilder) { MenuBarBuilder.AddPullDownMenu( VOXEL_LOCTEXT("Voxel"), VOXEL_LOCTEXT("Open the Voxel menu"), FNewMenuDelegate::CreateRaw(this, &FVoxelGraphEditorToolkit::FillVoxelMenu), "Voxel"); } void FVoxelGraphEditorToolkit::ExtendMenu() { TSharedPtr MenuExtender = MakeShareable(new FExtender); MenuExtender->AddMenuBarExtension( "Edit", EExtensionHook::After, GetToolkitCommands(), FMenuBarExtensionDelegate::CreateRaw(this, &FVoxelGraphEditorToolkit::AddEditorMenus)); AddMenuExtender(MenuExtender); } void FVoxelGraphEditorToolkit::BindGraphCommands() { auto& Commands = FVoxelGraphEditorCommands::Get(); ToolkitCommands->MapAction( Commands.CompileToCpp, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CompileToCpp)); ToolkitCommands->MapAction( Commands.RecreateNodes, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::RecreateNodes)); ToolkitCommands->MapAction( Commands.ToggleAutomaticPreview, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::ToggleAutomaticPreview), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FVoxelGraphEditorToolkit::IsToggleAutomaticPreviewChecked)); ToolkitCommands->MapAction( Commands.UpdatePreview, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::UpdatePreview, EVoxelGraphPreviewFlags::UpdateAll | EVoxelGraphPreviewFlags::ManualPreview)); ToolkitCommands->MapAction( Commands.UpdateVoxelWorlds, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::UpdateVoxelWorlds)); ToolkitCommands->MapAction( Commands.ClearNodesMessages, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::ClearNodesMessages)); ToolkitCommands->MapAction( Commands.ShowAxisDependencies, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::ShowAxisDependencies)); ToolkitCommands->MapAction( Commands.ShowStats, FExecuteAction::CreateWeakLambda(Generator, [=]() { Generator->PreviewSettings->bShowStats = !Generator->PreviewSettings->bShowStats; UpdatePreview(EVoxelGraphPreviewFlags::UpdateTextures); }), FCanExecuteAction(), FIsActionChecked::CreateWeakLambda(Generator, [=]() { return Generator->PreviewSettings->bShowStats; })); ToolkitCommands->MapAction( Commands.ShowValues, FExecuteAction::CreateWeakLambda(Generator, [=]() { Generator->PreviewSettings->bShowValues = !Generator->PreviewSettings->bShowValues; UpdatePreview(EVoxelGraphPreviewFlags::UpdateTextures); }), FCanExecuteAction(), FIsActionChecked::CreateWeakLambda(Generator, [=]() { return Generator->PreviewSettings->bShowValues; })); ToolkitCommands->MapAction( FGenericCommands::Get().Undo, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::UndoGraphAction)); ToolkitCommands->MapAction( FGenericCommands::Get().Redo, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::RedoGraphAction)); } TSharedRef FVoxelGraphEditorToolkit::CreateGraphEditorWidget(bool bDebug) { if (!GraphEditorCommands.IsValid()) { GraphEditorCommands = MakeShareable(new FUICommandList); GraphEditorCommands->MapAction(FVoxelGraphEditorCommands::Get().AddInput, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::AddInput), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanAddInput)); GraphEditorCommands->MapAction(FVoxelGraphEditorCommands::Get().DeleteInput, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::DeleteInput), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanDeleteInput)); GraphEditorCommands->MapAction(FVoxelGraphEditorCommands::Get().TogglePinPreview, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnTogglePinPreview)); GraphEditorCommands->MapAction(FVoxelGraphEditorCommands::Get().SplitPin, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnSplitPin)); GraphEditorCommands->MapAction(FVoxelGraphEditorCommands::Get().CombinePin, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnCombinePin)); // Graph Editor Commands GraphEditorCommands->MapAction(FGraphEditorCommands::Get().CreateComment, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnCreateComment) ); // Editing commands GraphEditorCommands->MapAction(FGenericCommands::Get().SelectAll, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::SelectAllNodes), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanSelectAllNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Delete, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::DeleteSelectedNodes), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanDeleteNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Copy, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CopySelectedNodes), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanCopyNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Cut, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CutSelectedNodes), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanCutNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Paste, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::PasteNodes), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanPasteNodes) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::DuplicateNodes), FCanExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::CanDuplicateNodes) ); GraphEditorCommands->MapAction( FVoxelGraphEditorCommands::Get().SelectLocalVariableDeclaration, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnSelectLocalVariableDeclaration) ); GraphEditorCommands->MapAction( FVoxelGraphEditorCommands::Get().SelectLocalVariableUsages, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnSelectLocalVariableUsages) ); GraphEditorCommands->MapAction( FVoxelGraphEditorCommands::Get().ConvertRerouteToVariables, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnConvertRerouteToVariables) ); GraphEditorCommands->MapAction( FVoxelGraphEditorCommands::Get().ConvertVariablesToReroute, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::OnConvertVariablesToReroute) ); GraphEditorCommands->MapAction( FVoxelGraphEditorCommands::Get().ReconstructNode, FExecuteAction::CreateSP(this, &FVoxelGraphEditorToolkit::ReconstructNode) ); // Alignment Commands GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesTop, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnAlignTop ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesMiddle, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnAlignMiddle ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesBottom, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnAlignBottom ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesLeft, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnAlignLeft ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesCenter, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnAlignCenter ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesRight, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnAlignRight ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().StraightenConnections, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnStraightenConnections ) ); // Distribution Commands GraphEditorCommands->MapAction( FGraphEditorCommands::Get().DistributeNodesHorizontally, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnDistributeNodesH ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().DistributeNodesVertically, FExecuteAction::CreateSP( this, &FVoxelGraphEditorToolkit::OnDistributeNodesV ) ); } if (bDebug) { FGraphAppearanceInfo AppearanceInfo; AppearanceInfo.CornerText = VOXEL_LOCTEXT("VOXEL DEBUG"); return SNew(SGraphEditor) .IsEditable(true) .Appearance(AppearanceInfo) .GraphToEdit(Generator->VoxelDebugGraph) .AutoExpandActionMenu(false) .ShowGraphStateOverlay(false); } else { FGraphAppearanceInfo AppearanceInfo; AppearanceInfo.CornerText = VOXEL_LOCTEXT("VOXEL"); SGraphEditor::FGraphEditorEvents InEvents; InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FVoxelGraphEditorToolkit::OnSelectedNodesChanged); InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FVoxelGraphEditorToolkit::OnNodeTitleCommitted); InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FVoxelGraphEditorToolkit::OnNodeDoubleClicked); InEvents.OnSpawnNodeByShortcut = SGraphEditor::FOnSpawnNodeByShortcut::CreateSP(this, &FVoxelGraphEditorToolkit::OnSpawnGraphNodeByShortcut); return SNew(SGraphEditor) .AdditionalCommands(GraphEditorCommands) .IsEditable(true) .Appearance(AppearanceInfo) .GraphToEdit(Generator->VoxelGraph) .GraphEvents(InEvents) .AutoExpandActionMenu(false) .ShowGraphStateOverlay(false); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// bool FVoxelGraphEditorToolkit::GetBoundsForSelectedNodes(class FSlateRect& Rect, float Padding) { return VoxelGraphEditor->GetBoundsForSelectedNodes(Rect, Padding); } int32 FVoxelGraphEditorToolkit::GetNumberOfSelectedNodes() const { return VoxelGraphEditor->GetSelectedNodes().Num(); } FGraphPanelSelectionSet FVoxelGraphEditorToolkit::GetSelectedNodes() const { return VoxelGraphEditor->GetSelectedNodes(); } void FVoxelGraphEditorToolkit::SelectNodesAndZoomToFit(const TArray& Nodes) { if (Nodes.Num() > 0) { VoxelGraphEditor->ClearSelectionSet(); for (auto& Node : Nodes) { VoxelGraphEditor->SetNodeSelection(Node, true); } VoxelGraphEditor->ZoomToFit(true); } } void FVoxelGraphEditorToolkit::RefreshNodesMessages() { for (auto Node : Generator->VoxelGraph->Nodes) { if (Node->IsA() && !Node->IsA()) { TSharedPtr Widget = Node->DEPRECATED_NodeWidget.Pin(); if (Widget.IsValid()) { static_cast(Widget.Get())->RefreshErrorInfo(); } } } } void FVoxelGraphEditorToolkit::TriggerUpdatePreview(EVoxelGraphPreviewFlags Flags) { if (Generator->bAutomaticPreview || EnumHasAnyFlags(Flags, EVoxelGraphPreviewFlags::ManualPreview)) { bUpdatePreviewOnNextTick = true; NextPreviewFlags |= Flags; } } FAdvancedPreviewScene* FVoxelGraphEditorToolkit::GetPreviewScene() const { return PreviewScene.Get(); } void FVoxelGraphEditorToolkit::DebugNodes(const TSet& Nodes) { } inline EMessageSeverity::Type VoxelMessageTypeToMessageSeverity(EVoxelGraphNodeMessageType Type) { switch (Type) { default: ensure(false); case EVoxelGraphNodeMessageType::Info: return EMessageSeverity::Info; case EVoxelGraphNodeMessageType::Warning: return EMessageSeverity::Warning; case EVoxelGraphNodeMessageType::Error: return EMessageSeverity::Error; } } void FVoxelGraphEditorToolkit::AddMessages(const TArray& Messages) { CurrentMessages.Append(Messages); TArray> ListingMessages; for (auto& Message : Messages) { TSharedRef ListingMessage = FTokenizedMessage::Create(VoxelMessageTypeToMessageSeverity(Message.Type)); if (Message.Node.IsValid()) { ListingMessage->AddToken(FActionToken::Create( Message.Node->GetTitle(), Message.Node->GetTitle(), FOnActionTokenExecuted::CreateSP( this, &FVoxelGraphEditorToolkit::SelectNodeAndZoomToFit, Message.Node) )); } ListingMessage->AddToken(FTextToken::Create(FText::FromString(Message.Message))); ListingMessages.Add(ListingMessage); } MessagesListing->AddMessages(ListingMessages, false); } void FVoxelGraphEditorToolkit::ClearMessages(bool bClearAll, EVoxelGraphNodeMessageType MessagesToClear) { MessagesListing->ClearMessages(); if (bClearAll) { CurrentMessages.Reset(); } else { TArray Copy = CurrentMessages; Copy.RemoveAll([&](auto& Message) { return Message.Type == MessagesToClear; }); CurrentMessages.Reset(); AddMessages(Copy); } } void FVoxelGraphEditorToolkit::SaveAsset_Execute() { if (Generator->bCompileToCppOnSave) { } // Make sure to save AFTER compile to cpp to avoid dirtying it again FAssetEditorToolkit::SaveAsset_Execute(); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObject(Generator); if (PreviewHandler.IsValid()) { PreviewHandler->AddReferencedObjects(Collector); } } void FVoxelGraphEditorToolkit::PostUndo(bool bSuccess) { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->ClearSelectionSet(); VoxelGraphEditor->NotifyGraphChanged(); } TriggerUpdatePreview(EVoxelGraphPreviewFlags::UpdateAll); } void FVoxelGraphEditorToolkit::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged) { if (VoxelGraphEditor.IsValid() && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { VoxelGraphEditor->NotifyGraphChanged(); } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::Tick(float DeltaTime) { if (bUpdatePreviewOnNextTick) { UpdatePreview(NextPreviewFlags); bUpdatePreviewOnNextTick = false; NextPreviewFlags = EVoxelGraphPreviewFlags::None; } } TStatId FVoxelGraphEditorToolkit::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FVoxelGraphEditorToolkit, STATGROUP_Tickables); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// TSharedRef FVoxelGraphEditorToolkit::SpawnTab_GraphCanvas(const FSpawnTabArgs& Args) { check(Args.GetTabId() == GraphCanvasTabId); auto Tab = SNew(SDockTab) .Label(VOXEL_LOCTEXT("Main Graph")); GraphTab = Tab; GraphTab->SetContent(VoxelGraphEditor.ToSharedRef()); return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_DebugGraphCanvas(const FSpawnTabArgs& Args) { check(Args.GetTabId() == DebugGraphCanvasTabId); auto Tab = SNew(SDockTab) .Label(VOXEL_LOCTEXT("Debug Graph")); DebugGraphTab = Tab; DebugGraphTab->SetContent(VoxelDebugGraphEditor.ToSharedRef()); return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_Properties(const FSpawnTabArgs& Args) { check(Args.GetTabId() == PropertiesTabId); auto Tab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Details")) .Label(VOXEL_LOCTEXT("Details")) [ VoxelProperties.ToSharedRef() ]; return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_Shortcuts(const FSpawnTabArgs& Args) { check(Args.GetTabId() == ShortcutsTabId); auto Tab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Details")) .Label(VOXEL_LOCTEXT("Shortcuts")) [ ShortcutsProperties.ToSharedRef() ]; return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_PreviewSettings(const FSpawnTabArgs& Args) { check(Args.GetTabId() == PreviewSettingsTabId); auto Tab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Details")) .Label(VOXEL_LOCTEXT("Preview Settings")) [ PreviewSettings.ToSharedRef() ]; return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_Palette(const FSpawnTabArgs& Args) { check(Args.GetTabId() == PaletteTabId); auto Tab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("Kismet.Tabs.Palette")) .Label(VOXEL_LOCTEXT("Palette")) [ Palette.ToSharedRef() ]; return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_Preview(const FSpawnTabArgs& Args) { check(Args.GetTabId() == PreviewTabId); auto Tab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Viewports")) .Label(VOXEL_LOCTEXT("Preview")) [ // Do the scaling here to make math easier SNew(SScaleBox) .Stretch(EStretch::ScaleToFit) [ Preview.ToSharedRef() ] ]; return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_PreviewViewport(const FSpawnTabArgs& Args) { check(Args.GetTabId() == PreviewViewportTabId); auto Tab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Viewports")) .Label(VOXEL_LOCTEXT("3D Preview")) [ PreviewViewport.ToSharedRef() ]; return Tab; } TSharedRef FVoxelGraphEditorToolkit::SpawnTab_Messages(const FSpawnTabArgs& Args) { check(Args.GetTabId() == MessagesTabId); auto Tab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("MessageLog.TabIcon")) .Label(VOXEL_LOCTEXT("Messages")) [ MessagesWidget.ToSharedRef() ]; return Tab; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::OnSelectedNodesChanged(const TSet& NewSelection) { TArray Selection; if (NewSelection.Num()) { for (auto* Object : NewSelection) { if (Cast(Object) || Cast(Object)) { Selection.Add(Generator); } else if (UVoxelGraphNode* GraphNode = Cast(Object)) { Selection.Add(GraphNode->VoxelNode); } else { Selection.Add(Object); } } } else { Selection.Add(Generator); } VoxelProperties->SetObjects(Selection); } void FVoxelGraphEditorToolkit::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged) { if (NodeBeingChanged) { const FScopedTransaction Transaction(VOXEL_LOCTEXT("Rename Node")); NodeBeingChanged->Modify(); NodeBeingChanged->OnRenameNode(NewText.ToString()); } } void FVoxelGraphEditorToolkit::OnNodeDoubleClicked(UEdGraphNode* Node) { if (Node->CanJumpToDefinition()) { Node->JumpToDefinition(); } } FReply FVoxelGraphEditorToolkit::OnSpawnGraphNodeByShortcut(FInputChord InChord, const FVector2D& InPosition) { auto* Ptr = GetDefault()->Shortcuts.FindByPredicate([&](auto& Key) { return Key.IsSameAs(InChord); }); UClass* ClassToSpawn = Ptr ? Ptr->Class : nullptr; if (ClassToSpawn) { FVoxelGraphSchemaAction_NewNode Action(FText(), FText(), FText(), 0); Action.VoxelNodeClass = ClassToSpawn; Action.PerformAction(Generator->VoxelGraph, nullptr, InPosition); } return FReply::Handled(); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::AddInput() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); // Iterator used but should only contain one node for (auto* SelectedNode : SelectedNodes) { if (auto* Node = Cast(SelectedNode)) { Node->AddInputPin(); break; } } } bool FVoxelGraphEditorToolkit::CanAddInput() const { return GetSelectedNodes().Num() == 1; } void FVoxelGraphEditorToolkit::DeleteInput() { UEdGraphPin* SelectedPin = VoxelGraphEditor->GetGraphPinForMenu(); UVoxelGraphNode* SelectedNode = Cast(SelectedPin->GetOwningNode()); if (SelectedNode && SelectedNode == SelectedPin->GetOwningNode()) { SelectedNode->RemoveInputPin(SelectedPin); } } bool FVoxelGraphEditorToolkit::CanDeleteInput() const { return true; } void FVoxelGraphEditorToolkit::OnCreateComment() { FVoxelGraphSchemaAction_NewComment CommentAction; CommentAction.PerformAction(Generator->VoxelGraph, NULL, VoxelGraphEditor->GetPasteLocation()); } void FVoxelGraphEditorToolkit::OnTogglePinPreview() { UEdGraphPin* SelectedPin = VoxelGraphEditor->GetGraphPinForMenu(); UVoxelGraphNode* SelectedNode = Cast(SelectedPin->GetOwningNode()); UVoxelGraphNode* GraphNodeToPreview = Cast(SelectedNode); if (GraphNodeToPreview && GraphNodeToPreview->VoxelNode) { const bool bIsPreviewing = SelectedPin->bIsDiffing; if (Generator->PreviewedPin.Get()) { ensure(!bIsPreviewing || SelectedPin == Generator->PreviewedPin.Get()); ensure(Generator->PreviewedPin.Get()->bIsDiffing); Generator->PreviewedPin.Get()->bIsDiffing = false; Generator->PreviewedPin.SetPin(nullptr); } ensure(!SelectedPin->bIsDiffing); if (!bIsPreviewing) { SelectedPin->bIsDiffing = true; Generator->PreviewedPin.SetPin(SelectedPin); } VoxelGraphEditor->NotifyGraphChanged(); } UpdatePreview(EVoxelGraphPreviewFlags::UpdateAll | EVoxelGraphPreviewFlags::ManualPreview); } void FVoxelGraphEditorToolkit::OnSplitPin() { UEdGraphPin* SelectedPin = VoxelGraphEditor->GetGraphPinForMenu(); UVoxelGraphNode* SelectedNode = Cast(SelectedPin->GetOwningNode()); SelectedNode->TrySplitPin(*SelectedPin, false); } void FVoxelGraphEditorToolkit::OnCombinePin() { UEdGraphPin* SelectedPin = VoxelGraphEditor->GetGraphPinForMenu(); UVoxelGraphNode* SelectedNode = Cast(SelectedPin->GetOwningNode()); SelectedNode->TryCombinePin(*SelectedPin, false); } void FVoxelGraphEditorToolkit::SelectAllNodes() { VoxelGraphEditor->SelectAllNodes(); } void FVoxelGraphEditorToolkit::DeleteSelectedNodes() { const FScopedTransaction Transaction(VOXEL_LOCTEXT("Delete Selected Voxel Node")); VoxelGraphEditor->GetCurrentGraph()->Modify(); const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); VoxelGraphEditor->ClearSelectionSet(); for (auto* Object : SelectedNodes) { UEdGraphNode* Node = CastChecked(Object); if (Node->CanUserDeleteNode()) { if (UVoxelGraphNode* VoxelGraphNode = Cast(Node)) { UVoxelNode* VoxelNode = VoxelGraphNode->VoxelNode; if (VoxelNode) { VoxelNode->Modify(); VoxelNode->MarkPendingKill(); } auto* PreviewedPin = Generator->PreviewedPin.Get(); if (PreviewedPin && PreviewedPin->GetOwningNode() == VoxelGraphNode) { // Clear previewed pin if we delete the owning node Generator->PreviewedPin = {}; // Clear since we're not previewing it anymore PreviewedPin->bIsDiffing = false; } FBlueprintEditorUtils::RemoveNode(NULL, VoxelGraphNode, true); // Make sure Voxel is updated to match graph Generator->CompileVoxelNodesFromGraphNodes(); // Remove this node from the list of all VoxelNodes Generator->AllNodes.Remove(VoxelNode); Generator->MarkPackageDirty(); } else { FBlueprintEditorUtils::RemoveNode(NULL, Node, true); } } } } bool FVoxelGraphEditorToolkit::CanDeleteNodes() const { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (auto* Node : SelectedNodes) { UVoxelGraphNode* GraphNode = Cast(Node); if (GraphNode && !GraphNode->CanUserDeleteNode()) { return false; } } } return SelectedNodes.Num() > 0; } void FVoxelGraphEditorToolkit::DeleteSelectedDuplicatableNodes() { // Cache off the old selection const FGraphPanelSelectionSet OldSelectedNodes = GetSelectedNodes(); // Clear the selection and only select the nodes that can be duplicated FGraphPanelSelectionSet RemainingNodes; VoxelGraphEditor->ClearSelectionSet(); for (auto* SelectedNode : OldSelectedNodes) { UEdGraphNode* Node = Cast(SelectedNode); if (Node && Node->CanDuplicateNode()) { VoxelGraphEditor->SetNodeSelection(Node, true); } else { RemainingNodes.Add(Node); } } // Delete the duplicable nodes DeleteSelectedNodes(); // Reselect whatever's left from the original selection after the deletion VoxelGraphEditor->ClearSelectionSet(); for (auto* RemainingNode : RemainingNodes) { if (UEdGraphNode* Node = Cast(RemainingNode)) { VoxelGraphEditor->SetNodeSelection(Node, true); } } } void FVoxelGraphEditorToolkit::CutSelectedNodes() { CopySelectedNodes(); // Cut should only delete nodes that can be duplicated DeleteSelectedDuplicatableNodes(); } bool FVoxelGraphEditorToolkit::CanCutNodes() const { return CanCopyNodes() && CanDeleteNodes(); } void FVoxelGraphEditorToolkit::CopySelectedNodes() { // Export the selected nodes and place the text on the clipboard FGraphPanelSelectionSet SelectedNodes; { FGraphPanelSelectionSet AllSelectedNodes = GetSelectedNodes(); for (auto* SelectedNode : AllSelectedNodes) { auto* Node = Cast(SelectedNode); if (Node && Node->CanDuplicateNode()) { SelectedNodes.Add(Node); } } } FString ExportedText; for (auto It = SelectedNodes.CreateIterator(); It; ++It) { CastChecked(*It)->PrepareForCopying(); } FEdGraphUtilities::ExportNodesToText(SelectedNodes, /*out*/ ExportedText); FPlatformApplicationMisc::ClipboardCopy(*ExportedText); // Make sure the voxel graph remains the owner of the copied nodes for (auto It = SelectedNodes.CreateIterator(); It; ++It) { if (auto* Node = Cast(*It)) { Node->PostCopyNode(); } } } bool FVoxelGraphEditorToolkit::CanCopyNodes() const { // If any of the nodes can be duplicated then we should allow copying const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (auto* SelectedNode : SelectedNodes) { UEdGraphNode* Node = Cast(SelectedNode); if (Node && Node->CanDuplicateNode()) { return true; } } return false; } void FVoxelGraphEditorToolkit::PasteNodes() { PasteNodesHere(VoxelGraphEditor->GetPasteLocation()); } void FVoxelGraphEditorToolkit::PasteNodesHere(const FVector2D& Location) { // Undo/Redo support const FScopedTransaction Transaction(VOXEL_LOCTEXT("Paste Voxel Node")); Generator->VoxelGraph->Modify(); Generator->Modify(); // Clear the selection set (newly pasted stuff will be selected) VoxelGraphEditor->ClearSelectionSet(); // Grab the text to paste from the clipboard. FString TextToImport; FPlatformApplicationMisc::ClipboardPaste(TextToImport); // Import the nodes TSet PastedNodes; FEdGraphUtilities::ImportNodesFromText(Generator->VoxelGraph, TextToImport, /*out*/ PastedNodes); //Average position of nodes so we can move them while still maintaining relative distances to each other FVector2D AvgNodePosition(0.0f, 0.0f); for (auto* Node : PastedNodes) { AvgNodePosition.X += Node->NodePosX; AvgNodePosition.Y += Node->NodePosY; } if (PastedNodes.Num() > 0) { float InvNumNodes = 1.0f / float(PastedNodes.Num()); AvgNodePosition.X *= InvNumNodes; AvgNodePosition.Y *= InvNumNodes; } TArray PastedVoxelNodes; for (auto* Node : PastedNodes) { if (UVoxelGraphNode* VoxelGraphNode = Cast(Node)) { if (UVoxelNode* VoxelNode = VoxelGraphNode->VoxelNode) { PastedVoxelNodes.Add(VoxelNode); Generator->AllNodes.Add(VoxelNode); VoxelNode->Graph = Generator; } } // Select the newly pasted stuff VoxelGraphEditor->SetNodeSelection(Node, true); Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X; Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y; Node->SnapToGrid(SNodePanel::GetSnapGridSize()); // Give new node a different Guid from the old one Node->CreateNewGuid(); } // Force new pasted VoxelNodes to have same connections as graph nodes Generator->CompileVoxelNodesFromGraphNodes(); // Post copy for local variables for (auto* Node : PastedVoxelNodes) { Node->PostCopyNode(PastedVoxelNodes); } // Update UI VoxelGraphEditor->NotifyGraphChanged(); Generator->PostEditChange(); Generator->MarkPackageDirty(); } bool FVoxelGraphEditorToolkit::CanPasteNodes() const { FString ClipboardContent; FPlatformApplicationMisc::ClipboardPaste(ClipboardContent); return FEdGraphUtilities::CanImportNodesFromText(Generator->VoxelGraph, ClipboardContent); } void FVoxelGraphEditorToolkit::DuplicateNodes() { // Copy and paste current selection CopySelectedNodes(); PasteNodes(); } bool FVoxelGraphEditorToolkit::CanDuplicateNodes() const { return CanCopyNodes(); } void FVoxelGraphEditorToolkit::OnAlignTop() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnAlignTop(); } } void FVoxelGraphEditorToolkit::OnAlignMiddle() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnAlignMiddle(); } } void FVoxelGraphEditorToolkit::OnAlignBottom() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnAlignBottom(); } } void FVoxelGraphEditorToolkit::OnAlignLeft() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnAlignLeft(); } } void FVoxelGraphEditorToolkit::OnAlignCenter() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnAlignCenter(); } } void FVoxelGraphEditorToolkit::OnAlignRight() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnAlignRight(); } } void FVoxelGraphEditorToolkit::OnStraightenConnections() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnStraightenConnections(); } } void FVoxelGraphEditorToolkit::OnDistributeNodesH() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnDistributeNodesH(); } } void FVoxelGraphEditorToolkit::OnDistributeNodesV() { if (VoxelGraphEditor.IsValid()) { VoxelGraphEditor->OnDistributeNodesV(); } } void FVoxelGraphEditorToolkit::OnSelectLocalVariableDeclaration() { const FGraphPanelSelectionSet SelectedNodes = VoxelGraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { VoxelGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UVoxelGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UVoxelNode* CurrentSelectedNoe = GraphNode->VoxelNode; UVoxelLocalVariableUsage* Usage = Cast(CurrentSelectedNoe); if (Usage && Usage->Declaration) { UEdGraphNode* DeclarationGraphNode = Usage->Declaration->GraphNode; if (DeclarationGraphNode) { VoxelGraphEditor->SetNodeSelection(DeclarationGraphNode, true); } } } } VoxelGraphEditor->ZoomToFit(true); } } void FVoxelGraphEditorToolkit::OnSelectLocalVariableUsages() { const FGraphPanelSelectionSet SelectedNodes = VoxelGraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { bool bZoom = false; VoxelGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UVoxelGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UVoxelNode* CurrentSelectedNode = GraphNode->VoxelNode; UVoxelLocalVariableDeclaration* Declaration = Cast(CurrentSelectedNode); for (UVoxelNode* Node : Generator->AllNodes) { auto* Usage = Cast(Node); if (Usage && Usage->Declaration == Declaration) { UEdGraphNode* UsageGraphNode = Usage->GraphNode; if (UsageGraphNode) { bZoom = true; VoxelGraphEditor->SetNodeSelection(UsageGraphNode, true); } } } } } if (bZoom) { VoxelGraphEditor->ZoomToFit(true); } } } void FVoxelGraphEditorToolkit::OnConvertRerouteToVariables() { const FGraphPanelSelectionSet SelectedNodes = VoxelGraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { VoxelGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UVoxelGraphNode_Knot* GraphNode = Cast(*NodeIt); if (GraphNode) { UEdGraph* Graph = GraphNode->GetGraph(); const FScopedTransaction Transaction(VOXEL_LOCTEXT("Convert reroute to local variables")); Graph->Modify(); const TArray& InputPins = GraphNode->GetInputPin()->LinkedTo; TArray OutputPins = GraphNode->GetOutputPin()->LinkedTo; OutputPins.Sort([](UEdGraphPin& A, UEdGraphPin& B) { return A.GetOwningNode()->NodePosY < B.GetOwningNode()->NodePosY; }); TArray Usages; int UsageIndex = -OutputPins.Num() / 2; for (auto* OutputPin : OutputPins) { auto* Usage = Generator->ConstructNewNode(FVector2D(GraphNode->NodePosX + 50, GraphNode->NodePosY + 50 * UsageIndex)); Usages.Add(Usage); UsageIndex++; } // Spawn declaration AFTER usages so that it gets renamed auto* Declaration = Generator->ConstructNewNode(FVector2D(GraphNode->NodePosX - 50, GraphNode->NodePosY)); Declaration->SetCategory(FVoxelPinCategory::FromString(GraphNode->GetInputPin()->PinType.PinCategory)); Declaration->GraphNode->ReconstructNode(); check(Declaration->GraphNode->Pins.Num() == 1); UEdGraphPin* DeclarationInputPin = Declaration->GraphNode->Pins[0]; check(DeclarationInputPin->Direction == EEdGraphPinDirection::EGPD_Input) for (auto* InputPin : InputPins) { InputPin->MakeLinkTo(DeclarationInputPin); } for (int32 Index = 0; Index < OutputPins.Num() ; Index++) { auto* Usage = Usages[Index]; Usage->Declaration = Declaration; Usage->DeclarationGuid = Declaration->VariableGuid; Usage->GraphNode->ReconstructNode(); Usage->GraphNode->GetAllPins()[0]->MakeLinkTo(OutputPins[Index]); // usage node has a single pin } GraphNode->DestroyNode(); } } } } void FVoxelGraphEditorToolkit::OnConvertVariablesToReroute() { const FGraphPanelSelectionSet SelectedNodes = VoxelGraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UVoxelGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UEdGraph* Graph = GraphNode->GetGraph(); const FScopedTransaction Transaction(VOXEL_LOCTEXT("Convert local variables to reroute")); Graph->Modify(); UVoxelNode* CurrentSelectedNode = GraphNode->VoxelNode; UVoxelLocalVariableDeclaration* Declaration = Cast(CurrentSelectedNode); if (!Declaration) { UVoxelLocalVariableUsage* Usage = Cast(CurrentSelectedNode); if (Usage) { Declaration = Usage->Declaration; } } if (!Declaration) { return; } UEdGraphNode* DeclarationGraphNode = Declaration->GraphNode; FGraphNodeCreator KnotNodeCreator(*Graph); UVoxelGraphNode_Knot* KnotNode = KnotNodeCreator.CreateNode(); KnotNodeCreator.Finalize(); KnotNode->NodePosX = DeclarationGraphNode->NodePosX + 50; KnotNode->NodePosY = DeclarationGraphNode->NodePosY; for (UEdGraphPin* Pin : DeclarationGraphNode->GetAllPins()) { if (Pin->Direction == EEdGraphPinDirection::EGPD_Input) { for (UEdGraphPin* InputPin : Pin->LinkedTo) { KnotNode->GetInputPin()->MakeLinkTo(InputPin); } } if (Pin->Direction == EEdGraphPinDirection::EGPD_Output) { for (UEdGraphPin* OutputPin : Pin->LinkedTo) { KnotNode->GetOutputPin()->MakeLinkTo(OutputPin); } } } DeclarationGraphNode->DestroyNode(); for(UVoxelNode* Node : Generator->AllNodes) { auto* Usage = Cast(Node); if (Usage && Usage->Declaration == Declaration) { UEdGraphNode* UsageGraphNode = Usage->GraphNode; if (UsageGraphNode) { UEdGraphPin* Pin = Usage->GraphNode->GetAllPins()[0]; // usage node has a single pin for (UEdGraphPin* OutputPin : Pin->LinkedTo) { KnotNode->GetOutputPin()->MakeLinkTo(OutputPin); } UsageGraphNode->DestroyNode(); } } } KnotNode->PropagatePinType(); } } } } void FVoxelGraphEditorToolkit::ReconstructNode() { const FGraphPanelSelectionSet SelectedNodes = VoxelGraphEditor->GetSelectedNodes(); for(auto& Object : SelectedNodes) { if (auto* Node = Cast(Object)) { Node->ReconstructNode(); } } Generator->CompileVoxelNodesFromGraphNodes(); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::RecreateNodes() { for (int32 I = 0; I < 4; I++) // Hack to make sure they are really recreated { TArray AllNodes; VoxelGraphEditor->GetCurrentGraph()->GetNodesOfClass(AllNodes); for (auto* Node : AllNodes) { Node->ReconstructNode(); } Generator->CompileVoxelNodesFromGraphNodes(); GraphTab->ClearContent(); VoxelGraphEditor = CreateGraphEditorWidget(false); GraphTab->SetContent(VoxelGraphEditor.ToSharedRef()); } ClearNodesMessages(); } /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::CompileToCpp() { FVoxelMessages::Info("Compiling graphs to C++ requires Voxel Plugin Pro"); } /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::ToggleAutomaticPreview() { Generator->Modify(); Generator->bAutomaticPreview = !Generator->bAutomaticPreview; } bool FVoxelGraphEditorToolkit::IsToggleAutomaticPreviewChecked() const { return Generator->bAutomaticPreview; } void FVoxelGraphEditorToolkit::UpdatePreview(EVoxelGraphPreviewFlags Flags) { PreviewHandler->Update(Flags); if (EnumHasAnyFlags(Flags, EVoxelGraphPreviewFlags::ManualPreview)) { FVoxelMessages::Info("You can view and edit Voxel Graphs, but running and previewing them requires Voxel Plugin Pro"); } } void FVoxelGraphEditorToolkit::UpdateVoxelWorlds() { IVoxelEditorModule* VoxelEditorModule = &FModuleManager::LoadModuleChecked("VoxelEditor"); VoxelEditorModule->RefreshVoxelWorlds(Generator); } /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::ClearNodesMessages() { FVoxelGraphErrorReporter::ClearNodesMessages(Generator); ClearMessages(true, EVoxelGraphNodeMessageType::Info); } /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::ShowAxisDependencies() { FVoxelGraphErrorReporter::ClearNodesMessages(Generator); } /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::UndoGraphAction() { GEditor->UndoTransaction(); } void FVoxelGraphEditorToolkit::RedoGraphAction() { // Clear selection, to avoid holding refs to nodes that go away VoxelGraphEditor->ClearSelectionSet(); GEditor->RedoTransaction(); } /////////////////////////////////////////////////////////////////////////////// void FVoxelGraphEditorToolkit::SelectNodeAndZoomToFit(TWeakObjectPtr Node) { if (Node.IsValid() && Node->Graph) { if (Node->Graph == Generator) { SelectNodesAndZoomToFit({ Node->GraphNode }); } else { if (ensure(GEditor->GetEditorSubsystem()->OpenEditorForAsset(Node->Graph))) { auto NewEditor = FVoxelGraphEditorUtilities::GetIVoxelEditorForGraph(Node->Graph->VoxelGraph); if (ensure(NewEditor.IsValid())) { NewEditor->SelectNodesAndZoomToFit({ Node->GraphNode }); } } } } }