Skip to content
73 changes: 73 additions & 0 deletions SERE/RuiNodeEditor/RuiNodeEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,76 @@ void NodeEditor::SetStyles(ImFlow::StyleManager& styles) {
styles.SetNodeErrorStyle(errorNodeStyle);
}

void NodeEditor::CopyNodes() {
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<int> dis;

// Reset copied nodes list
rapidjson::Document doc;
doc.SetObject();
m_lCopiedNodes.SetArray();

// Serialize currently selected nodes
for (auto& [nodeId, nodePtr] : mINF.getNodes()) {
if (!nodePtr->isSelected()) {
continue;
}

// Convert node to JSON to preserve its internal state
rapidjson::GenericValue<rapidjson::UTF8<>> val;
val.SetObject();
std::dynamic_pointer_cast<RuiBaseNode>(nodePtr)->Serialize(val, doc.GetAllocator());

// Eventually rename arguments
if (val.HasMember("ArgName") && val["ArgName"].IsString()) {
val["ArgName"].SetString(std::format("{}(copy)", val["ArgName"].GetString()), doc.GetAllocator());
}

// Assign new id to node copy (stolen from ImNodeFlow::addNode)
auto uid = dis(gen);
while (mINF.getNodes().contains(uid))uid = dis(gen);
val["Id"].SetInt(uid);
m_lCopiedNodes.PushBack(val, doc.GetAllocator());
}
}

void NodeEditor::PasteNodes() {
if (m_lCopiedNodes.GetArray().Empty()) {
return;
}

std::set<int> newNodeIds = {};

for (auto itr = m_lCopiedNodes.Begin(); itr != m_lCopiedNodes.End(); itr++) {
rapidjson::GenericObject node = itr->GetObject();
std::string name = node["Name"].GetString();
std::string category = node["Category"].GetString();
auto newnode = nodeTypes[category][name].RecreateNode(mINF, render, mINF.getStyleManager(), node);

ImVec2 pos;
pos.x = node["PosX"].GetFloat() + 10;
pos.y = node["PosY"].GetFloat() + 10;
newnode->setPos(pos);

newNodeIds.insert(node["Id"].GetInt());
}

// Select only pasted nodes
for (auto& [nodeId, nodePtr] : mINF.getNodes()) {
// Unselect nodes
if (nodePtr->isSelected()) {
nodePtr->selected(false);
continue;
}

// Select new nodes
if (newNodeIds.contains(nodePtr->getUID()))
{
nodePtr->selected(true);
}
}

// Only allow one paste action
m_lCopiedNodes.SetArray();
}
6 changes: 4 additions & 2 deletions SERE/RuiNodeEditor/RuiNodeEditor.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ class NodeEditor{
private:
ImFlow::ImNodeFlow mINF;
RenderInstance& render;


rapidjson::GenericValue<rapidjson::UTF8<>> m_lCopiedNodes;
std::map<std::string,NodeCategory> nodeTypes;
public:
NodeEditor(RenderInstance& rend);
Expand All @@ -36,6 +35,9 @@ class NodeEditor{
void Export();
void Clear();

void CopyNodes();
void PasteNodes();

template<class T> void AddNodeType() {


Expand Down
24 changes: 24 additions & 0 deletions SERE/SERE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,17 @@ int main(int, char**)
}
ImGui::EndMenu();
}

if (ImGui::BeginMenu("Edit")) {
if (ImGui::MenuItem("Copy")) {
nodeEdit.CopyNodes();
}
if (ImGui::MenuItem("Paste")) {
nodeEdit.PasteNodes();
}
ImGui::EndMenu();
}

ImGui::EndMainMenuBar();
}

Expand All @@ -340,6 +351,19 @@ int main(int, char**)
HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync
//HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync
g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);

// Copy/paste shortcuts
// https://github.com/ocornut/imgui/issues/456#issuecomment-2290384494
ImGuiKeyChord chord = ImGuiMod_Ctrl | ImGuiKey_C;
bool isRouted = ImGui::GetShortcutRoutingData(chord)->RoutingCurr != ImGuiKeyOwner_NoOwner;
if (!isRouted && ImGui::IsKeyChordPressed(chord)) {
nodeEdit.CopyNodes();
}
chord = ImGuiMod_Ctrl | ImGuiKey_V;
isRouted = ImGui::GetShortcutRoutingData(chord)->RoutingCurr != ImGuiKeyOwner_NoOwner;
if (!isRouted && ImGui::IsKeyChordPressed(chord)) {
nodeEdit.PasteNodes();
}
}

// Cleanup
Expand Down