#include "UI.hpp" #include "Model/GlobalStates.hpp" #include "Model/Project.hpp" #include "Model/Workflow/Nodes/DocumentNodes.hpp" #include "Model/Workflow/Nodes/NumericNodes.hpp" #include "Model/Workflow/Nodes/TextNodes.hpp" #include "Model/Workflow/Nodes/UserInputNodes.hpp" #include "Model/Workflow/Workflow.hpp" #include "Utils/I18n.hpp" #include "Utils/Macros.hpp" #include #include #include #include #include #include #include namespace ImNodes = ax::NodeEditor; namespace { class WorkflowUI { private: std::unique_ptr mWorkflow; ImNodes::EditorContext* mContext; ImNodes::NodeId mContextMenuNodeId = 0; ImNodes::PinId mContextMenuPinId = 0; ImNodes::LinkId mContextMenuLinkId = 0; public: WorkflowUI(std::unique_ptr workflow) : mWorkflow{ std::move(workflow) } { mContext = ImNodes::CreateEditor(); } ~WorkflowUI() { ImNodes::DestroyEditor(mContext); } void Display() { ImNodes::SetCurrentEditor(mContext); ImNodes::Begin(""); // Defer creation of tooltip because within the node editor, cursor positioning is going to be off const char* tooltipMessage = nullptr; for (auto& node : mWorkflow->GetNodes()) { if (!node) continue; ImNodes::BeginNode(node->GetId()); node->Draw(); ImNodes::EndNode(); } for (auto& conn : mWorkflow->GetConnections()) { if (!conn.IsValid()) continue; auto srcId = mWorkflow->GetNodes()[conn.SourceNode]->GetOutputPinUniqueId(conn.SourcePin); auto dstId = mWorkflow->GetNodes()[conn.DestinationNode]->GetInputPinUniqueId(conn.DestinationPin); ImNodes::Link(conn.GetLinkId(), srcId, dstId); } if (ImNodes::BeginCreate()) { ImNodes::PinId src = 0, dst = 0; if (ImNodes::QueryNewLink(&src, &dst)) { if (!src || !dst) { goto createError; } auto [srcNode, srcPinId, srcIsOutput] = mWorkflow->DisassembleGlobalPinId(src); auto [dstNode, dstPinId, dstIsOutput] = mWorkflow->DisassembleGlobalPinId(dst); if (srcNode == dstNode) { ImNodes::RejectNewItem(); goto createError; } if (srcIsOutput == dstIsOutput) { ImNodes::RejectNewItem(); goto createError; } auto srcPin = srcNode->GetOutputPin(srcPinId); auto dstPin = dstNode->GetOutputPin(dstPinId); if (srcPin.MatchingType != dstPin.MatchingType) { ImNodes::RejectNewItem(); goto createError; } if (ImNodes::AcceptNewItem()) { mWorkflow->Connect(*srcNode, srcPinId, *dstNode, dstPinId); } } ImNodes::PinId newNodePin = 0; if (ImNodes::QueryNewNode(&newNodePin)) { auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId(newNodePin); if ((isOutput && node->GetOutputPin(pinId).IsConnected()) || (!isOutput && node->GetInputPin(pinId).IsConnected())) { ImNodes::RejectNewItem(); goto createError; } if (ImNodes::AcceptNewItem()) { ImNodes::Suspend(); ImGui::BeginPopup("CreateNodeCtxMenu"); ImNodes::Resume(); } } } createError: ImNodes::EndCreate(); if (ImNodes::BeginDelete()) { ImNodes::LinkId deletedLinkId; if (ImNodes::QueryDeletedLink(&deletedLinkId)) { auto& conn = *mWorkflow->GetConnectionByLinkId(deletedLinkId); mWorkflow->RemoveConnection(conn.Id); } ImNodes::NodeId deletedNodeId; if (ImNodes::QueryDeletedNode(&deletedNodeId)) { auto node = mWorkflow->GetNodeByNodeId(deletedNodeId); if (!node) { ImNodes::RejectDeletedItem(); goto deleteError; } if (node->IsLocked()) { ImNodes::RejectDeletedItem(); goto deleteError; } } } deleteError: ImNodes::EndDelete(); // Popups ImNodes::Suspend(); if (ImNodes::ShowNodeContextMenu(&mContextMenuNodeId)) { ImGui::OpenPopup("NodeCtxMenu"); } else if (ImNodes::ShowPinContextMenu(&mContextMenuPinId)) { ImGui::OpenPopup("PinCtxMenu"); } else if (ImNodes::ShowLinkContextMenu(&mContextMenuLinkId)) { ImGui::OpenPopup("LinkCtxMenu"); } if (ImGui::BeginPopup("NodeCtxMenu")) { auto& node = *mWorkflow->GetNodeByNodeId(mContextMenuNodeId); node.DrawDebugInfo(); if (ImGui::MenuItem(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE))) { ImNodes::DeleteNode(mContextMenuNodeId); } ImGui::EndPopup(); } if (ImGui::BeginPopup("PinCtxMenu")) { auto [node, pinId, isOutput] = mWorkflow->DisassembleGlobalPinId(mContextMenuPinId); if (isOutput) { node->DrawOutputPinDebugInfo(pinId); } else { node->DrawInputPinDebugInfo(pinId); } if (ImGui::MenuItem(ICON_FA_UNLINK " " I18N_TEXT("Disconnect", L10N_DISCONNECT))) { if (isOutput) { auto& pin = node->GetOutputPin(pinId); if (pin.IsConnected()) { auto linkId = mWorkflow->GetConnectionById(pin.Connection)->GetLinkId(); ImNodes::DeleteLink(linkId); } } else { auto& pin = node->GetInputPin(pinId); if (pin.IsConstantConnection()) { // TODO } else if (pin.IsConnected()) { auto linkId = mWorkflow->GetConnectionById(pin.Connection)->GetLinkId(); ImNodes::DeleteLink(linkId); } } } ImGui::EndPopup(); } if (ImGui::BeginPopup("LinkCtxMenu")) { auto& conn = *mWorkflow->GetConnectionByLinkId(mContextMenuLinkId); conn.DrawDebugInfo(); if (ImGui::MenuItem(ICON_FA_TRASH " " I18N_TEXT("Delete", L10N_DELETE))) { ImNodes::DeleteLink(mContextMenuLinkId); } ImGui::EndPopup(); } if (ImGui::BeginPopup("CreateNodeCtxMenu")) { for (int i = WorkflowNode::CG_Numeric; i < WorkflowNode::InvalidCategory; ++i) { auto category = (WorkflowNode::Category)i; auto members = WorkflowNode::QueryCategoryMembers(category); if (ImGui::BeginMenu(WorkflowNode::FormatCategory(category))) { for (auto member : members) { if (ImGui::MenuItem(WorkflowNode::FormatKind(member))) { // Create node auto uptr = WorkflowNode::CreateByKind(member); mWorkflow->AddNode(std::move(uptr)); } } ImGui::EndMenu(); } } ImGui::EndPopup(); } if (tooltipMessage) { ImGui::BeginTooltip(); ImGui::TextUnformatted(tooltipMessage); ImGui::EndTooltip(); } ImNodes::Resume(); ImNodes::End(); } void Close() { // TODO } }; } // namespace void UI::WorkflowsTab() { auto& project = *GlobalStates::GetInstance().GetCurrentProject(); static std::unique_ptr openWorkflow; static AssetList::ListState state; bool openedDummy = true; // Toolbar item: close if (ImGui::Button(ICON_FA_TIMES " " I18N_TEXT("Close", L10N_CLOSE), openWorkflow == nullptr)) { openWorkflow->Close(); openWorkflow = nullptr; } // Toolbar item: open... ImGui::SameLine(); if (ImGui::Button((I18N_TEXT("Open asset...", L10N_ASSET_OPEN)))) { ImGui::OpenPopup(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE)); } if (ImGui::BeginPopupModal(I18N_TEXT("Open asset", L10N_ASSET_OPEN_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::Button(ICON_FA_FOLDER_OPEN " " I18N_TEXT("Open", L10N_OPEN), state.SelectedAsset == nullptr)) { ImGui::CloseCurrentPopup(); auto workflow = project.Workflows.Load(*state.SelectedAsset); openWorkflow = std::make_unique(std::move(workflow)); } ImGui::SameLine(); project.Workflows.DisplayControls(state); project.Workflows.DisplayDetailsList(state); ImGui::EndPopup(); } // Toolbar item: manage... ImGui::SameLine(); if (ImGui::Button(I18N_TEXT("Manage assets...", L10N_ASSET_MANAGE))) { ImGui::OpenPopup(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE)); } if (ImGui::BeginPopupModal(I18N_TEXT("Manage assets", L10N_ASSET_MANAGE_DIALOG_TITLE), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { project.Workflows.DisplayControls(state); project.Workflows.DisplayDetailsList(state); ImGui::EndPopup(); } if (openWorkflow) { openWorkflow->Display(); } }