impr: More view comments and refactoring

This commit is contained in:
WerWolv
2023-09-03 10:27:03 +02:00
parent 5538307838
commit 367c4ec9c8
3 changed files with 379 additions and 217 deletions

View File

@@ -55,6 +55,10 @@ namespace hex::plugin::builtin {
std::vector<Workspace*> &getWorkspaceStack() { return *this->m_workspaceStack; }
private:
void drawContextMenus(ViewDataProcessor::Workspace &workspace);
void drawNode(dp::Node &node);
private:
bool m_updateNodePositions = false;
int m_rightClickedId = -1;

View File

@@ -72,7 +72,7 @@ namespace hex::plugin::builtin {
ImGui::SameLine();
// Draw clickable link to the current commit
// Draw a clickable link to the current commit
if (ImGui::Hyperlink(hex::format("{0}@{1}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()).c_str()))
hex::openWebpage("https://github.com/WerWolv/ImHex/commit/" + ImHexApi::System::getCommitHash(true));
@@ -84,7 +84,7 @@ namespace hex::plugin::builtin {
ImGui::SameLine();
//Draw clickable link to the GitHub repository
// Draw a clickable link to the GitHub repository
if (ImGui::Hyperlink("WerWolv/ImHex"))
hex::openWebpage("https://github.com/WerWolv/ImHex");
@@ -97,7 +97,7 @@ namespace hex::plugin::builtin {
ImGui::TextUnformatted("hex.builtin.view.help.about.donations"_lang);
ImGui::Separator();
constexpr const char *Links[] = { "https://werwolv.net/donate", "https://www.patreon.com/werwolv", "https://github.com/sponsors/WerWolv" };
constexpr std::array Links = { "https://werwolv.net/donate", "https://www.patreon.com/werwolv", "https://github.com/sponsors/WerWolv" };
ImGui::TextFormattedWrapped("{}", static_cast<const char *>("hex.builtin.view.help.about.thanks"_lang));
@@ -209,7 +209,7 @@ namespace hex::plugin::builtin {
fs::openFolderExternal(path);
}
} else {
ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed), wolv::util::toUTF8String(path).c_str());
ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed), wolv::util::toUTF8String(path));
}
}
}
@@ -225,7 +225,7 @@ namespace hex::plugin::builtin {
void ViewAbout::drawAboutPopup() {
if (ImGui::BeginPopupModal(View::toWindowName("hex.builtin.view.help.about.name").c_str(), &this->m_aboutWindowOpen)) {
// Allow window to be closed by pressing ESC
// Allow the window to be closed by pressing ESC
if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape)))
ImGui::CloseCurrentPopup();
@@ -240,7 +240,7 @@ namespace hex::plugin::builtin {
ImGui::EndTabItem();
}
// Draw contributors tab
// Draw contributor tab
if (ImGui::BeginTabItem("hex.builtin.view.help.about.contributor"_lang)) {
ImGui::NewLine();
if (ImGui::BeginChild(1)) {

View File

@@ -18,6 +18,9 @@
namespace hex::plugin::builtin {
/**
* @brief Input node that can be used to add an input to a custom node
*/
class NodeCustomInput : public dp::Node {
public:
NodeCustomInput() : Node("hex.builtin.nodes.custom.input.header", { dp::Attribute(dp::Attribute::IOType::Out, dp::Attribute::Type::Integer, "hex.builtin.nodes.common.input") }) { }
@@ -25,12 +28,14 @@ namespace hex::plugin::builtin {
void drawNode() override {
ImGui::PushItemWidth(100_scaled);
// Draw combo box to select the type of the input
if (ImGui::Combo("##type", &this->m_type, "Integer\0Float\0Buffer\0")) {
this->setAttributes({
{ dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.input") }
});
{ dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.input") }
});
}
// Draw text input to set the name of the input
if (ImGui::InputText("##name", this->m_name)) {
this->setUnlocalizedTitle(this->m_name);
}
@@ -41,6 +46,7 @@ namespace hex::plugin::builtin {
void setValue(auto value) { this->m_value = std::move(value); }
const std::string &getName() const { return this->m_name; }
dp::Attribute::Type getType() const {
switch (this->m_type) {
default:
@@ -71,8 +77,8 @@ namespace hex::plugin::builtin {
this->setUnlocalizedTitle(this->m_name);
this->setAttributes({
{ dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.input") }
});
{ dp::Attribute(dp::Attribute::IOType::Out, this->getType(), "hex.builtin.nodes.common.input") }
});
}
private:
@@ -82,6 +88,9 @@ namespace hex::plugin::builtin {
std::variant<i128, long double, std::vector<u8>> m_value;
};
/**
* @brief Input node that can be used to add an output to a custom node
*/
class NodeCustomOutput : public dp::Node {
public:
NodeCustomOutput() : Node("hex.builtin.nodes.custom.output.header", { dp::Attribute(dp::Attribute::IOType::In, dp::Attribute::Type::Integer, "hex.builtin.nodes.common.output") }) { }
@@ -89,12 +98,15 @@ namespace hex::plugin::builtin {
void drawNode() override {
ImGui::PushItemWidth(100_scaled);
// Draw combo box to select the type of the output
if (ImGui::Combo("##type", &this->m_type, "Integer\0Float\0Buffer\0")) {
this->setAttributes({
{ dp::Attribute(dp::Attribute::IOType::In, this->getType(), "hex.builtin.nodes.common.output") }
});
{ dp::Attribute(dp::Attribute::IOType::In, this->getType(), "hex.builtin.nodes.common.output") }
});
}
// Draw text input to set the name of the output
if (ImGui::InputText("##name", this->m_name)) {
this->setUnlocalizedTitle(this->m_name);
}
@@ -135,8 +147,8 @@ namespace hex::plugin::builtin {
this->setUnlocalizedTitle(this->m_name);
this->setAttributes({
{ dp::Attribute(dp::Attribute::IOType::In, this->getType(), "hex.builtin.nodes.common.output") }
});
{ dp::Attribute(dp::Attribute::IOType::In, this->getType(), "hex.builtin.nodes.common.output") }
});
}
private:
@@ -146,14 +158,21 @@ namespace hex::plugin::builtin {
std::variant<i128, long double, std::vector<u8>> m_value;
};
/**
* @brief Custom node that can be used to create custom data processing nodes
*/
class NodeCustom : public dp::Node {
public:
explicit NodeCustom(ViewDataProcessor *dataProcessor) : Node("hex.builtin.nodes.custom.custom.header", {}), m_dataProcessor(dataProcessor) { }
~NodeCustom() override = default;
void drawNode() override {
// Update attributes if we have to
if (this->m_requiresAttributeUpdate) {
this->m_requiresAttributeUpdate = false;
// Find all input and output nodes that are used by the workspace of this node
// and set the attributes of this node to the attributes of the input and output nodes
this->setAttributes(this->findAttributes());
}
@@ -161,12 +180,17 @@ namespace hex::plugin::builtin {
bool editing = false;
if (this->m_editable) {
// Draw name input field
ImGui::InputTextIcon("##name", ICON_VS_SYMBOL_KEY, this->m_name);
// Prevent editing mode from deactivating when the input field is focused
editing = ImGui::IsItemActive();
// Draw edit button
if (ImGui::Button("hex.builtin.nodes.custom.custom.edit"_lang, ImVec2(200_scaled, ImGui::GetTextLineHeightWithSpacing()))) {
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.custom_node.name");
// Open the custom node's workspace
this->m_dataProcessor->getWorkspaceStack().push_back(&this->m_workspace);
this->m_requiresAttributeUpdate = true;
@@ -179,12 +203,14 @@ namespace hex::plugin::builtin {
}
}
// Enable editing mode when the shift button is pressed
this->m_editable = ImGui::GetIO().KeyShift || editing;
ImGui::PopItemWidth();
}
void process() override {
// Find the index of an attribute by its id
auto indexFromId = [this](u32 id) -> std::optional<u32> {
const auto &attributes = this->getAttributes();
for (u32 i = 0; i < attributes.size(); i++)
@@ -228,6 +254,7 @@ namespace hex::plugin::builtin {
for (auto &endNode : this->m_workspace.endNodes) {
endNode->resetOutputData();
// Reset processed inputs of all nodes
for (auto &node : this->m_workspace.nodes)
node->resetProcessedInputs();
@@ -236,10 +263,12 @@ namespace hex::plugin::builtin {
// Forward output node values to outputs
for (auto &attribute : this->getAttributes()) {
// Find the index of the attribute
auto index = indexFromId(attribute.getId());
if (!index.has_value())
continue;
// Find the node that is connected to the attribute
if (auto output = this->findOutput(attribute.getUnlocalizedName()); output != nullptr) {
switch (attribute.getType()) {
case dp::Attribute::Type::Integer: {
@@ -279,6 +308,7 @@ namespace hex::plugin::builtin {
std::vector<dp::Attribute> findAttributes() {
std::vector<dp::Attribute> result;
// Search through all nodes in the workspace and add all input and output nodes to the result
for (auto &node : this->m_workspace.nodes) {
if (auto *inputNode = dynamic_cast<NodeCustomInput*>(node.get()); inputNode != nullptr)
result.emplace_back(dp::Attribute::IOType::In, inputNode->getType(), inputNode->getName());
@@ -406,44 +436,69 @@ namespace hex::plugin::builtin {
void ViewDataProcessor::eraseLink(Workspace &workspace, int id) {
auto link = std::find_if(workspace.links.begin(), workspace.links.end(), [&id](auto link) { return link.getId() == id; });
// Find the link with the given ID
auto link = std::find_if(workspace.links.begin(), workspace.links.end(),
[&id](auto link) {
return link.getId() == id;
});
// Return if the link was not found
if (link == workspace.links.end())
return;
// Remove the link from all attributes that are connected to it
for (auto &node : workspace.nodes) {
for (auto &attribute : node->getAttributes()) {
attribute.removeConnectedAttribute(id);
}
}
// Remove the link from the workspace
workspace.links.erase(link);
ImHexApi::Provider::markDirty();
}
void ViewDataProcessor::eraseNodes(Workspace &workspace, const std::vector<int> &ids) {
// Loop over the IDs of all nodes that should be removed
// and remove all links that are connected to the attributes of the node
for (int id : ids) {
// Find the node with the given ID
auto node = std::find_if(workspace.nodes.begin(), workspace.nodes.end(),
[&id](const auto &node) {
return node->getId() == id;
});
[&id](const auto &node) {
return node->getId() == id;
});
// Loop over all attributes of that node
for (auto &attr : (*node)->getAttributes()) {
std::vector<int> linksToRemove;
// Find all links that are connected to the attribute and remove them
for (auto &[linkId, connectedAttr] : attr.getConnectedAttributes())
linksToRemove.push_back(linkId);
// Remove the links from the workspace
for (auto linkId : linksToRemove)
eraseLink(workspace, linkId);
}
}
// Loop over the IDs of all nodes that should be removed
// and remove the nodes from the workspace
for (int id : ids) {
auto node = std::find_if(workspace.nodes.begin(), workspace.nodes.end(), [&id](const auto &node) { return node->getId() == id; });
// Find the node with the given ID
auto node = std::find_if(workspace.nodes.begin(), workspace.nodes.end(),
[&id](const auto &node) {
return node->getId() == id;
});
std::erase_if(workspace.endNodes, [&id](const auto &node) { return node->getId() == id; });
// Remove the node from the workspace
std::erase_if(workspace.endNodes,
[&id](const auto &node) {
return node->getId() == id;
});
// Remove the node from the workspace
workspace.nodes.erase(node);
}
@@ -451,97 +506,125 @@ namespace hex::plugin::builtin {
}
void ViewDataProcessor::processNodes(Workspace &workspace) {
// If the number of end nodes does not match the number of data overlays,
// the overlays have to be recreated
if (workspace.dataOverlays.size() != workspace.endNodes.size()) {
// Delete all the overlays from the current provider
for (auto overlay : workspace.dataOverlays)
ImHexApi::Provider::get()->deleteOverlay(overlay);
workspace.dataOverlays.clear();
// Create a new overlay for each end node
for (u32 i = 0; i < workspace.endNodes.size(); i++)
workspace.dataOverlays.push_back(ImHexApi::Provider::get()->newOverlay());
}
u32 overlayIndex = 0;
for (auto endNode : workspace.endNodes) {
endNode->setCurrentOverlay(workspace.dataOverlays[overlayIndex]);
overlayIndex++;
// Set the current overlay of each end node to the corresponding overlay
for (auto [index, endNode] : workspace.endNodes | std::views::enumerate) {
endNode->setCurrentOverlay(workspace.dataOverlays[index]);
}
// Reset any potential node errors
workspace.currNodeError.reset();
// Process all nodes in the workspace
try {
for (auto &endNode : workspace.endNodes) {
// Reset the output data of the end node
endNode->resetOutputData();
// Reset processed inputs of all nodes
for (auto &node : workspace.nodes)
node->resetProcessedInputs();
// Process the end node
endNode->process();
}
} catch (dp::Node::NodeError &e) {
// Handle user errors
// Add the node error to the current workspace, so it can be displayed
workspace.currNodeError = e;
// Delete all overlays
for (auto overlay : workspace.dataOverlays)
ImHexApi::Provider::get()->deleteOverlay(overlay);
workspace.dataOverlays.clear();
} catch (std::runtime_error &e) {
// Handle internal errors
log::fatal("Node implementation bug! {}", e.what());
}
}
void ViewDataProcessor::reloadCustomNodes() {
// Delete all custom nodes
this->m_customNodes.clear();
// Loop over all custom node folders
for (const auto &basePath : fs::getDefaultPaths(fs::ImHexPath::Nodes)) {
// Loop over all files in the folder
for (const auto &entry : std::fs::recursive_directory_iterator(basePath)) {
if (entry.path().extension() == ".hexnode") {
try {
nlohmann::json nodeJson = nlohmann::json::parse(wolv::io::File(entry.path(), wolv::io::File::Mode::Read).readString());
// Skip files that are not .hexnode files
if (entry.path().extension() != ".hexnode")
continue;
this->m_customNodes.push_back(CustomNode { LangEntry(nodeJson.at("name")), nodeJson });
} catch (nlohmann::json::exception &e) {
continue;
}
try {
// Load the content of the file as JSON
wolv::io::File file(entry.path(), wolv::io::File::Mode::Read);
nlohmann::json nodeJson = nlohmann::json::parse(file.readString());
// Add the loaded node to the list of custom nodes
this->m_customNodes.push_back(CustomNode { LangEntry(nodeJson.at("name")), nodeJson });
} catch (nlohmann::json::exception &e) {
log::warn("Failed to load custom node {}", entry.path().string());
continue;
}
}
}
}
void ViewDataProcessor::drawContent() {
auto &workspace = *this->m_workspaceStack->back();
void ViewDataProcessor::drawContextMenus(ViewDataProcessor::Workspace &workspace) {
// Handle the right click context menus
if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
// Clear selections
ImNodes::ClearNodeSelection();
ImNodes::ClearLinkSelection();
bool popWorkspace = false;
if (ImGui::Begin(View::toWindowName("hex.builtin.view.data_processor.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) {
ImNodes::SetCurrentContext(workspace.context.get());
// Save the current mouse position
this->m_rightClickedCoords = ImGui::GetMousePos();
if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
ImNodes::ClearNodeSelection();
ImNodes::ClearLinkSelection();
this->m_rightClickedCoords = ImGui::GetMousePos();
if (ImNodes::IsNodeHovered(&this->m_rightClickedId))
ImGui::OpenPopup("Node Menu");
else if (ImNodes::IsLinkHovered(&this->m_rightClickedId))
ImGui::OpenPopup("Link Menu");
else {
ImGui::OpenPopup("Context Menu");
this->reloadCustomNodes();
}
// Show a different context menu depending on if a node, a link
// or the background was right-clicked
if (ImNodes::IsNodeHovered(&this->m_rightClickedId))
ImGui::OpenPopup("Node Menu");
else if (ImNodes::IsLinkHovered(&this->m_rightClickedId))
ImGui::OpenPopup("Link Menu");
else {
ImGui::OpenPopup("Context Menu");
this->reloadCustomNodes();
}
}
if (ImGui::BeginPopup("Context Menu")) {
std::unique_ptr<dp::Node> node;
// Draw main context menu
if (ImGui::BeginPopup("Context Menu")) {
std::unique_ptr<dp::Node> node;
if (ImNodes::NumSelectedNodes() > 0 || ImNodes::NumSelectedLinks() > 0) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.name"_lang)) {
// Check if any link or node has been selected previously
if (ImNodes::NumSelectedNodes() > 0 || ImNodes::NumSelectedLinks() > 0) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_selection"_lang)) {
// Delete selected nodes
{
std::vector<int> ids;
ids.resize(ImNodes::NumSelectedNodes());
ImNodes::GetSelectedNodes(ids.data());
this->eraseNodes(workspace, ids);
ImNodes::ClearNodeSelection();
}
// Delete selected links
{
std::vector<int> ids;
ids.resize(ImNodes::NumSelectedLinks());
ImNodes::GetSelectedLinks(ids.data());
@@ -550,89 +633,219 @@ namespace hex::plugin::builtin {
ImNodes::ClearLinkSelection();
}
}
}
for (const auto &[unlocalizedCategory, unlocalizedName, function] : ContentRegistry::DataProcessorNode::impl::getEntries()) {
if (unlocalizedCategory.empty() && unlocalizedName.empty()) {
ImGui::Separator();
} else if (unlocalizedCategory.empty()) {
// Draw all nodes that are registered in the content registry
for (const auto &[unlocalizedCategory, unlocalizedName, function] : ContentRegistry::DataProcessorNode::impl::getEntries()) {
if (unlocalizedCategory.empty() && unlocalizedName.empty()) {
// Draw a separator if the node has no category and no name
ImGui::Separator();
} else if (unlocalizedCategory.empty()) {
// Draw the node if it has no category
if (ImGui::MenuItem(LangEntry(unlocalizedName))) {
node = function();
}
} else {
// Draw the node inside its sub menu if it has a category
if (ImGui::BeginMenu(LangEntry(unlocalizedCategory))) {
if (ImGui::MenuItem(LangEntry(unlocalizedName))) {
node = function();
}
} else {
if (ImGui::BeginMenu(LangEntry(unlocalizedCategory))) {
if (ImGui::MenuItem(LangEntry(unlocalizedName))) {
node = function();
}
ImGui::EndMenu();
}
ImGui::EndMenu();
}
}
if (ImGui::BeginMenu("hex.builtin.nodes.custom"_lang)) {
ImGui::Separator();
for (auto &customNode : this->m_customNodes) {
if (ImGui::MenuItem(customNode.name.c_str())) {
node = loadNode(customNode.data);
}
}
ImGui::EndMenu();
}
if (node != nullptr) {
bool hasOutput = false;
bool hasInput = false;
for (auto &attr : node->getAttributes()) {
if (attr.getIOType() == dp::Attribute::IOType::Out)
hasOutput = true;
if (attr.getIOType() == dp::Attribute::IOType::In)
hasInput = true;
}
if (hasInput && !hasOutput)
workspace.endNodes.push_back(node.get());
ImNodes::SetNodeScreenSpacePos(node->getId(), this->m_rightClickedCoords);
workspace.nodes.push_back(std::move(node));
ImHexApi::Provider::markDirty();
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.place_node.name");
}
ImGui::EndPopup();
}
if (ImGui::BeginPopup("Node Menu")) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.save_node"_lang)) {
auto it = std::find_if(workspace.nodes.begin(), workspace.nodes.end(),
[this](const auto &node) {
return node->getId() == this->m_rightClickedId;
});
if (it != workspace.nodes.end()) {
auto &node = *it;
fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, [&](const std::fs::path &path){
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
outputFile.writeString(ViewDataProcessor::saveNode(node.get()).dump(4));
});
}
}
// Draw custom nodes submenu
if (ImGui::BeginMenu("hex.builtin.nodes.custom"_lang)) {
ImGui::Separator();
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_node"_lang))
this->eraseNodes(workspace, { this->m_rightClickedId });
ImGui::EndPopup();
// Draw entries for each custom node
for (auto &customNode : this->m_customNodes) {
if (ImGui::MenuItem(customNode.name.c_str())) {
node = loadNode(customNode.data);
}
}
ImGui::EndMenu();
}
if (ImGui::BeginPopup("Link Menu")) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_link"_lang))
this->eraseLink(workspace, this->m_rightClickedId);
// Check if a node has been selected in the menu
if (node != nullptr) {
// Place the node in the workspace
ImGui::EndPopup();
// Check if the node has inputs and/or outputs
bool hasOutput = false;
bool hasInput = false;
for (auto &attr : node->getAttributes()) {
if (attr.getIOType() == dp::Attribute::IOType::Out)
hasOutput = true;
if (attr.getIOType() == dp::Attribute::IOType::In)
hasInput = true;
}
// If the node has only inputs and no outputs, it's considered an end node
// Add it to the list of end nodes to be processed
if (hasInput && !hasOutput)
workspace.endNodes.push_back(node.get());
// Set the position of the node to the position where the user right-clicked
ImNodes::SetNodeScreenSpacePos(node->getId(), this->m_rightClickedCoords);
workspace.nodes.push_back(std::move(node));
ImHexApi::Provider::markDirty();
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.place_node.name");
}
ImGui::EndPopup();
}
// Draw node right click menu
if (ImGui::BeginPopup("Node Menu")) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.save_node"_lang)) {
// Find the node that was right-clicked
auto it = std::find_if(workspace.nodes.begin(), workspace.nodes.end(),
[this](const auto &node) {
return node->getId() == this->m_rightClickedId;
});
// Check if the node was found
if (it != workspace.nodes.end()) {
auto &node = *it;
// Save the node to a file
fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } }, [&](const std::fs::path &path){
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
outputFile.writeString(ViewDataProcessor::saveNode(node.get()).dump(4));
});
}
}
ImGui::Separator();
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_node"_lang))
this->eraseNodes(workspace, { this->m_rightClickedId });
ImGui::EndPopup();
}
// Draw link right click menu
if (ImGui::BeginPopup("Link Menu")) {
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.remove_link"_lang))
this->eraseLink(workspace, this->m_rightClickedId);
ImGui::EndPopup();
}
}
void ViewDataProcessor::drawNode(dp::Node &node) {
// If a node position update is pending, update the node position
int nodeId = node.getId();
if (this->m_updateNodePositions) {
this->m_updateNodePositions = false;
ImNodes::SetNodeGridSpacePos(nodeId, node.getPosition());
} else {
if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Nodes, nodeId) >= 0)
node.setPosition(ImNodes::GetNodeGridSpacePos(nodeId));
}
// Draw the node
ImNodes::BeginNode(nodeId);
{
// Draw the node's title bar
ImNodes::BeginNodeTitleBar();
{
ImGui::TextUnformatted(LangEntry(node.getUnlocalizedTitle()));
}
ImNodes::EndNodeTitleBar();
// Draw the node's body
ImGui::PopStyleVar();
node.drawNode();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0F, 1.0F));
// Draw all attributes of the node
for (auto &attribute : node.getAttributes()) {
ImNodesPinShape pinShape;
// Set the pin shape depending on the attribute type
auto attributeType = attribute.getType();
switch (attributeType) {
default:
case dp::Attribute::Type::Integer:
pinShape = ImNodesPinShape_Circle;
break;
case dp::Attribute::Type::Float:
pinShape = ImNodesPinShape_Triangle;
break;
case dp::Attribute::Type::Buffer:
pinShape = ImNodesPinShape_Quad;
break;
}
// Draw the attribute
if (attribute.getIOType() == dp::Attribute::IOType::In) {
ImNodes::BeginInputAttribute(attribute.getId(), pinShape);
// Draw input fields for attributes that have no connected links
if (attribute.getConnectedAttributes().empty() && attributeType != dp::Attribute::Type::Buffer) {
auto &defaultValue = attribute.getDefaultData();
ImGui::PushItemWidth(100_scaled);
if (attributeType == dp::Attribute::Type::Integer) {
defaultValue.resize(sizeof(i128));
auto value = i64(*reinterpret_cast<i128*>(defaultValue.data()));
if (ImGui::InputScalar(LangEntry(attribute.getUnlocalizedName()), ImGuiDataType_S64, &value)) {
std::fill(defaultValue.begin(), defaultValue.end(), 0x00);
i128 writeValue = value;
std::memcpy(defaultValue.data(), &writeValue, sizeof(writeValue));
}
} else if (attributeType == dp::Attribute::Type::Float) {
defaultValue.resize(sizeof(long double));
auto value = double(*reinterpret_cast<long double*>(defaultValue.data()));
if (ImGui::InputScalar(LangEntry(attribute.getUnlocalizedName()), ImGuiDataType_Double, &value)) {
std::fill(defaultValue.begin(), defaultValue.end(), 0x00);
long double writeValue = value;
std::memcpy(defaultValue.data(), &writeValue, sizeof(writeValue));
}
}
ImGui::PopItemWidth();
} else {
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
}
ImNodes::EndInputAttribute();
} else if (attribute.getIOType() == dp::Attribute::IOType::Out) {
ImNodes::BeginOutputAttribute(attribute.getId(), ImNodesPinShape(pinShape + 1));
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
ImNodes::EndOutputAttribute();
}
}
}
ImNodes::EndNode();
ImNodes::SetNodeGridSpacePos(nodeId, node.getPosition());
}
void ViewDataProcessor::drawContent() {
auto &workspace = *this->m_workspaceStack->back();
bool popWorkspace = false;
if (ImGui::Begin(View::toWindowName("hex.builtin.view.data_processor.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) {
// Set the ImNodes context to the current workspace context
ImNodes::SetCurrentContext(workspace.context.get());
this->drawContextMenus(workspace);
// Draw error tooltip when hovering over a node that has an error
{
int nodeId;
if (ImNodes::IsNodeHovered(&nodeId) && workspace.currNodeError.has_value() && workspace.currNodeError->node->getId() == nodeId) {
@@ -644,123 +857,53 @@ namespace hex::plugin::builtin {
}
}
// Draw the main node editor workspace window
if (ImGui::BeginChild("##node_editor", ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 1.3F))) {
ImNodes::BeginNodeEditor();
// Loop over all nodes that have been placed in the workspace
for (auto &node : workspace.nodes) {
ImNodes::SnapNodeToGrid(node->getId());
// If the node has an error, draw it with a red outline
const bool hasError = workspace.currNodeError.has_value() && workspace.currNodeError->node == node.get();
if (hasError)
ImNodes::PushColorStyle(ImNodesCol_NodeOutline, 0xFF0000FF);
int nodeId = node->getId();
if (this->m_updateNodePositions) {
this->m_updateNodePositions = false;
ImNodes::SetNodeGridSpacePos(nodeId, node->getPosition());
} else {
if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Nodes, nodeId) >= 0)
node->setPosition(ImNodes::GetNodeGridSpacePos(nodeId));
}
ImNodes::BeginNode(nodeId);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted(LangEntry(node->getUnlocalizedTitle()));
ImNodes::EndNodeTitleBar();
ImGui::PopStyleVar();
node->drawNode();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0F, 1.0F));
for (auto &attribute : node->getAttributes()) {
ImNodesPinShape pinShape;
auto attributeType = attribute.getType();
switch (attributeType) {
default:
case dp::Attribute::Type::Integer:
pinShape = ImNodesPinShape_Circle;
break;
case dp::Attribute::Type::Float:
pinShape = ImNodesPinShape_Triangle;
break;
case dp::Attribute::Type::Buffer:
pinShape = ImNodesPinShape_Quad;
break;
}
if (attribute.getIOType() == dp::Attribute::IOType::In) {
ImNodes::BeginInputAttribute(attribute.getId(), pinShape);
if (attribute.getConnectedAttributes().empty() && attributeType != dp::Attribute::Type::Buffer) {
auto &defaultValue = attribute.getDefaultData();
ImGui::PushItemWidth(100_scaled);
if (attributeType == dp::Attribute::Type::Integer) {
defaultValue.resize(sizeof(i128));
auto value = i64(*reinterpret_cast<i128*>(defaultValue.data()));
if (ImGui::InputScalar(LangEntry(attribute.getUnlocalizedName()), ImGuiDataType_S64, &value)) {
std::fill(defaultValue.begin(), defaultValue.end(), 0x00);
i128 writeValue = value;
std::memcpy(defaultValue.data(), &writeValue, sizeof(writeValue));
}
} else if (attributeType == dp::Attribute::Type::Float) {
defaultValue.resize(sizeof(long double));
auto value = double(*reinterpret_cast<long double*>(defaultValue.data()));
if (ImGui::InputScalar(LangEntry(attribute.getUnlocalizedName()), ImGuiDataType_Double, &value)) {
std::fill(defaultValue.begin(), defaultValue.end(), 0x00);
long double writeValue = value;
std::memcpy(defaultValue.data(), &writeValue, sizeof(writeValue));
}
}
ImGui::PopItemWidth();
} else {
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
}
ImNodes::EndInputAttribute();
} else if (attribute.getIOType() == dp::Attribute::IOType::Out) {
ImNodes::BeginOutputAttribute(attribute.getId(), ImNodesPinShape(pinShape + 1));
ImGui::TextUnformatted(LangEntry(attribute.getUnlocalizedName()));
ImNodes::EndOutputAttribute();
}
}
ImNodes::EndNode();
ImNodes::SetNodeGridSpacePos(nodeId, node->getPosition());
// Draw the node
this->drawNode(*node);
if (hasError)
ImNodes::PopColorStyle();
}
std::vector<int> linksToRemove;
for (const auto &link : workspace.links) {
if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getFromId()) == -1 ||
ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getToId()) == -1) {
// Handle removing links that are connected to attributes that don't exist anymore
{
std::vector<int> linksToRemove;
for (const auto &link : workspace.links) {
if (ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getFromId()) == -1 ||
ImNodes::ObjectPoolFind(ImNodes::EditorContextGet().Pins, link.getToId()) == -1) {
linksToRemove.push_back(link.getId());
linksToRemove.push_back(link.getId());
}
}
for (auto linkId : linksToRemove)
this->eraseLink(workspace, linkId);
}
for (auto linkId : linksToRemove)
this->eraseLink(workspace, linkId);
// Draw links
for (const auto &link : workspace.links) {
ImNodes::Link(link.getId(), link.getFromId(), link.getToId());
}
// Draw the mini map in the bottom right
ImNodes::MiniMap(0.2F, ImNodesMiniMapLocation_BottomRight);
// Draw the help text if no nodes have been placed yet
if (workspace.nodes.empty())
ImGui::TextFormattedCentered("{}", "hex.builtin.view.data_processor.help_text"_lang);
// Draw a close button if there is more than one workspace on the stack
if (this->m_workspaceStack->size() > 1) {
ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x - ImGui::GetTextLineHeightWithSpacing() * 1.5F, ImGui::GetTextLineHeightWithSpacing() * 0.2F));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0F, 4.0F));
@@ -774,14 +917,18 @@ namespace hex::plugin::builtin {
}
ImGui::EndChild();
if (ImGui::IconButton(ICON_VS_DEBUG_START, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen)) || this->m_continuousEvaluation)
this->processNodes(workspace);
// Draw the control bar at the bottom
{
if (ImGui::IconButton(ICON_VS_DEBUG_START, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen)) || this->m_continuousEvaluation)
this->processNodes(workspace);
ImGui::SameLine();
ImGui::SameLine();
ImGui::Checkbox("Continuous evaluation", &this->m_continuousEvaluation);
ImGui::Checkbox("Continuous evaluation", &this->m_continuousEvaluation);
}
// Erase links that have been distroyed
{
int linkId;
if (ImNodes::IsLinkDestroyed(&linkId)) {
@@ -789,12 +936,15 @@ namespace hex::plugin::builtin {
}
}
// Handle creation of new links
{
int from, to;
if (ImNodes::IsLinkCreated(&from, &to)) {
do {
dp::Attribute *fromAttr = nullptr, *toAttr = nullptr;
// Find the attributes that are connected by the link
for (auto &node : workspace.nodes) {
for (auto &attribute : node->getAttributes()) {
if (attribute.getId() == from)
@@ -804,20 +954,26 @@ namespace hex::plugin::builtin {
}
}
// If one of the attributes could not be found, the link is invalid and can't be created
if (fromAttr == nullptr || toAttr == nullptr)
break;
// If the attributes have different types, don't create the link
if (fromAttr->getType() != toAttr->getType())
break;
// If the link tries to connect two input or two output attributes, don't create the link
if (fromAttr->getIOType() == toAttr->getIOType())
break;
// If the link tries to connect to a input attribute that already has a link connected to it, don't create the link
if (!toAttr->getConnectedAttributes().empty())
break;
// Add a new link to the current workspace
auto newLink = workspace.links.emplace_back(from, to);
// Add the link to the attributes that are connected by it
fromAttr->addConnectedAttribute(newLink.getId(), toAttr);
toAttr->addConnectedAttribute(newLink.getId(), fromAttr);
@@ -826,6 +982,7 @@ namespace hex::plugin::builtin {
}
}
// Handle deletion of links using the Delete key
{
const int selectedLinkCount = ImNodes::NumSelectedLinks();
if (selectedLinkCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) {
@@ -839,6 +996,7 @@ namespace hex::plugin::builtin {
}
}
// Handle deletion of noes using the Delete key
{
const int selectedNodeCount = ImNodes::NumSelectedNodes();
if (selectedNodeCount > 0 && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) {
@@ -852,6 +1010,7 @@ namespace hex::plugin::builtin {
}
ImGui::End();
// Remove the top-most workspace from the stack if requested
if (popWorkspace) {
this->m_workspaceStack->pop_back();
this->m_updateNodePositions = true;
@@ -910,7 +1069,6 @@ namespace hex::plugin::builtin {
std::unique_ptr<dp::Node> ViewDataProcessor::loadNode(const nlohmann::json &node) {
try {
auto &nodeEntries = ContentRegistry::DataProcessorNode::impl::getEntries();
std::unique_ptr<dp::Node> newNode;