deargui-vpl/examples/blueprints-example/blocks/parameter_operation.cpp

574 lines
21 KiB
C++

#define IMGUI_DEFINE_MATH_OPERATORS
#include "parameter_operation.h"
#include "../app.h"
#include "../utilities/node_renderer_base.h"
#include "NodeEx.h"
#include "../../crude_json.h"
#include <imgui.h>
#include <imgui_node_editor.h>
namespace ed = ax::NodeEditor;
using namespace ax::NodeRendering;
//------------------------------------------------------------------------------
// Parameter Operation Registry
//------------------------------------------------------------------------------
ParameterOperationRegistry::ParameterOperationRegistry()
{
// Register some default operations (stubbed for now)
// Int operations
RegisterOperation(ParameterOperationDef("int_add", "Add", PinType::Int, PinType::Int, PinType::Int));
RegisterOperation(ParameterOperationDef("int_sub", "Subtract", PinType::Int, PinType::Int, PinType::Int));
RegisterOperation(ParameterOperationDef("int_mul", "Multiply", PinType::Int, PinType::Int, PinType::Int));
RegisterOperation(ParameterOperationDef("int_div", "Divide", PinType::Int, PinType::Int, PinType::Int));
// Float operations
RegisterOperation(ParameterOperationDef("float_add", "Add", PinType::Float, PinType::Float, PinType::Float));
RegisterOperation(ParameterOperationDef("float_sub", "Subtract", PinType::Float, PinType::Float, PinType::Float));
RegisterOperation(ParameterOperationDef("float_mul", "Multiply", PinType::Float, PinType::Float, PinType::Float));
RegisterOperation(ParameterOperationDef("float_div", "Divide", PinType::Float, PinType::Float, PinType::Float));
// String operations
RegisterOperation(ParameterOperationDef("string_concat", "Concatenate", PinType::String, PinType::String, PinType::String));
// Bool operations
RegisterOperation(ParameterOperationDef("bool_and", "AND", PinType::Bool, PinType::Bool, PinType::Bool));
RegisterOperation(ParameterOperationDef("bool_or", "OR", PinType::Bool, PinType::Bool, PinType::Bool));
// Comparison operations (Int -> Bool)
RegisterOperation(ParameterOperationDef("int_eq", "Equal", PinType::Int, PinType::Int, PinType::Bool));
RegisterOperation(ParameterOperationDef("int_lt", "Less Than", PinType::Int, PinType::Int, PinType::Bool));
RegisterOperation(ParameterOperationDef("int_gt", "Greater Than", PinType::Int, PinType::Int, PinType::Bool));
// Float comparison
RegisterOperation(ParameterOperationDef("float_eq", "Equal", PinType::Float, PinType::Float, PinType::Bool));
RegisterOperation(ParameterOperationDef("float_lt", "Less Than", PinType::Float, PinType::Float, PinType::Bool));
RegisterOperation(ParameterOperationDef("float_gt", "Greater Than", PinType::Float, PinType::Float, PinType::Bool));
}
void ParameterOperationRegistry::RegisterOperation(const ParameterOperationDef& opDef)
{
m_Operations.push_back(opDef);
}
std::vector<ParameterOperationDef> ParameterOperationRegistry::GetMatchingOperations(
PinType inputA, PinType inputB, PinType output)
{
std::vector<ParameterOperationDef> matching;
for (const auto& op : m_Operations)
{
if (op.InputAType == inputA && op.InputBType == inputB && op.OutputType == output)
{
matching.push_back(op);
}
}
return matching;
}
const ParameterOperationDef* ParameterOperationRegistry::GetOperation(const std::string& uuid)
{
for (const auto& op : m_Operations)
{
if (op.UUID == uuid)
return &op;
}
return nullptr;
}
//------------------------------------------------------------------------------
// Parameter Operation Block
//------------------------------------------------------------------------------
void ParameterOperationBlock::Build(Node& node, App* app)
{
node.Type = m_Type;
node.Color = m_Color;
// Clear existing pins
m_InputParams.clear();
m_OutputParams.clear();
// Build 2 input parameters at top
AddInputParameter(app, node, "A", m_InputAType);
AddInputParameter(app, node, "B", m_InputBType);
// Build 1 output parameter at bottom
AddOutputParameter(app, node, "Result", m_OutputType);
}
int ParameterOperationBlock::Run(Node& node, App* app)
{
// Stub for now - will execute the selected operation
if (m_OperationUUID.empty())
{
printf("[ParamOp] No operation selected\n");
return E_OK;
}
auto* opDef = ParameterOperationRegistry::Instance().GetOperation(m_OperationUUID);
if (!opDef)
{
printf("[ParamOp] Operation '%s' not found\n", m_OperationUUID.c_str());
return E_OK;
}
printf("[ParamOp] Executing operation: %s (%s)\n", opDef->Label.c_str(), opDef->UUID.c_str());
// Get input values (stub - actual implementation would read from connected parameters)
// For now, just get default values
int paramIndex = 0;
int inputAInt = 0, inputBInt = 0;
float inputAFloat = 0.0f, inputBFloat = 0.0f;
bool inputABool = false, inputBBool = false;
std::string inputAString = "", inputBString = "";
// Read input values based on type
for (const auto& pin : node.Inputs)
{
if (pin.Type == PinType::Flow)
continue;
if (paramIndex == 0)
{
// Input A
switch (m_InputAType)
{
case PinType::Int: inputAInt = GetInputParamValueInt(pin, node, app, 0); break;
case PinType::Float: inputAFloat = GetInputParamValueFloat(pin, node, app, 0.0f); break;
case PinType::Bool: inputABool = GetInputParamValueBool(pin, node, app, false); break;
case PinType::String: inputAString = GetInputParamValueString(pin, node, app, ""); break;
default: break;
}
}
else if (paramIndex == 1)
{
// Input B
switch (m_InputBType)
{
case PinType::Int: inputBInt = GetInputParamValueInt(pin, node, app, 0); break;
case PinType::Float: inputBFloat = GetInputParamValueFloat(pin, node, app, 0.0f); break;
case PinType::Bool: inputBBool = GetInputParamValueBool(pin, node, app, false); break;
case PinType::String: inputBString = GetInputParamValueString(pin, node, app, ""); break;
default: break;
}
}
paramIndex++;
}
// Execute operation (stub implementations)
bool resultBool = false;
int resultInt = 0;
float resultFloat = 0.0f;
std::string resultString = "";
if (opDef->UUID == "int_add")
resultInt = inputAInt + inputBInt;
else if (opDef->UUID == "int_sub")
resultInt = inputAInt - inputBInt;
else if (opDef->UUID == "int_mul")
resultInt = inputAInt * inputBInt;
else if (opDef->UUID == "int_div")
resultInt = inputBInt != 0 ? inputAInt / inputBInt : 0;
else if (opDef->UUID == "float_add")
resultFloat = inputAFloat + inputBFloat;
else if (opDef->UUID == "float_sub")
resultFloat = inputAFloat - inputBFloat;
else if (opDef->UUID == "float_mul")
resultFloat = inputAFloat * inputBFloat;
else if (opDef->UUID == "float_div")
resultFloat = inputBFloat != 0.0f ? inputAFloat / inputBFloat : 0.0f;
else if (opDef->UUID == "string_concat")
resultString = inputAString + inputBString;
else if (opDef->UUID == "bool_and")
resultBool = inputABool && inputBBool;
else if (opDef->UUID == "bool_or")
resultBool = inputABool || inputBBool;
else if (opDef->UUID == "int_eq")
resultBool = (inputAInt == inputBInt);
else if (opDef->UUID == "int_lt")
resultBool = (inputAInt < inputBInt);
else if (opDef->UUID == "int_gt")
resultBool = (inputAInt > inputBInt);
else if (opDef->UUID == "float_eq")
resultBool = (inputAFloat == inputBFloat);
else if (opDef->UUID == "float_lt")
resultBool = (inputAFloat < inputBFloat);
else if (opDef->UUID == "float_gt")
resultBool = (inputAFloat > inputBFloat);
// Set output value
paramIndex = 0;
for (const auto& pin : node.Outputs)
{
if (pin.Type == PinType::Flow)
continue;
if (paramIndex == 0)
{
// Output
switch (m_OutputType)
{
case PinType::Int: SetOutputParamValueInt(pin, node, app, resultInt); break;
case PinType::Float: SetOutputParamValueFloat(pin, node, app, resultFloat); break;
case PinType::Bool: SetOutputParamValueBool(pin, node, app, resultBool); break;
case PinType::String: SetOutputParamValueString(pin, node, app, resultString); break;
default: break;
}
break;
}
paramIndex++;
}
return E_OK;
}
void ParameterOperationBlock::Render(Node& node, App* app, Pin* newLinkPin)
{
// Check if node is currently running (for red border visualization)
float currentTime = ImGui::GetTime();
bool isRunning = false;
auto runningIt = app->m_RunningNodes.find(node.ID);
if (runningIt != app->m_RunningNodes.end())
{
isRunning = (currentTime < runningIt->second);
}
// Get styles from StyleManager - use ParameterStyle for visual distinction
auto& styleManager = app->GetStyleManager();
auto& paramStyle = styleManager.ParameterStyle;
// Parameter Operations get a slightly blue border for distinction
ImColor borderColor = isRunning ? paramStyle.BorderColorRunning : ImColor(180, 200, 255, 255); // Light blue
float activeBorderWidth = isRunning ? paramStyle.BorderWidthRunning : paramStyle.BorderWidth;
// Use NodeStyleScope with ParameterStyle for parameter-like appearance
NodeStyleScope style(
paramStyle.BgColor, // Parameter-style background (grayer)
borderColor, // border (red if running)
paramStyle.Rounding, activeBorderWidth, // More rounded than blocks
paramStyle.Padding, // padding
ImVec2(0.0f, 1.0f), // source direction (down)
ImVec2(0.0f, -1.0f) // target direction (up)
);
ed::BeginNode(node.ID);
ImGui::PushID(node.ID.AsPointer());
ImGui::BeginVertical("node");
// Center - header (simple centered text)
ImGui::BeginHorizontal("content");
ImGui::Spring(1, 0.0f);
ImGui::BeginVertical("center");
ImGui::Dummy(ImVec2(styleManager.MinNodeWidth, 0)); // Minimum width
ImGui::Spring(1);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1));
ImGui::TextUnformatted(node.Name.c_str());
ImGui::PopStyleColor();
ImGui::Spring(1);
ImGui::EndVertical();
ImGui::Spring(1, 0.0f);
ImGui::EndHorizontal();
ImGui::EndVertical();
// Save cursor and get node bounds
ImVec2 contentEndPos = ImGui::GetCursorScreenPos();
ImVec2 nodePos = ed::GetNodePosition(node.ID);
ImVec2 nodeSize = ed::GetNodeSize(node.ID);
if (nodeSize.x <= 0 || nodeSize.y <= 0)
{
ImVec2 contentMin = ImGui::GetItemRectMin();
ImVec2 contentMax = ImGui::GetItemRectMax();
nodeSize = contentMax - contentMin;
}
ImRect nodeRect = ImRect(nodePos, nodePos + nodeSize);
// Collect pins by type
std::vector<Pin*> inputParams;
std::vector<Pin*> outputParams;
for (auto& pin : node.Inputs)
{
if (pin.Type != PinType::Flow)
inputParams.push_back(&pin);
}
for (auto& pin : node.Outputs)
{
if (pin.Type != PinType::Flow)
outputParams.push_back(&pin);
}
// Render input parameters at top edge using NodeEx
if (!inputParams.empty())
{
float spacing = 1.0f / (inputParams.size() + 1);
for (size_t i = 0; i < inputParams.size(); ++i)
{
Pin* pin = inputParams[i];
float offset = spacing * (i + 1);
float alpha = GetPinAlpha(pin, newLinkPin, app);
ed::PinState state = (alpha < 1.0f) ? ed::PinState::Deactivated : ed::PinState::Normal;
ImRect pinRect = ed::PinEx(pin->ID, ed::PinKind::Input, ed::PinEdge::Top,
offset, styleManager.ParameterPinEdgeOffset, nodeRect, state);
pin->LastPivotPosition = ImVec2(pinRect.GetCenter().x, pinRect.Min.y);
pin->LastRenderBounds = pinRect;
pin->HasPositionData = true;
}
}
// Render output parameters at bottom edge using NodeEx
if (!outputParams.empty())
{
float spacing = 1.0f / (outputParams.size() + 1);
for (size_t i = 0; i < outputParams.size(); ++i)
{
Pin* pin = outputParams[i];
float offset = spacing * (i + 1);
float alpha = GetPinAlpha(pin, newLinkPin, app);
ed::PinState state = (alpha < 1.0f) ? ed::PinState::Deactivated : ed::PinState::Normal;
ImRect pinRect = ed::PinEx(pin->ID, ed::PinKind::Output, ed::PinEdge::Bottom,
offset, styleManager.ParameterPinEdgeOffset, nodeRect, state);
pin->LastPivotPosition = ImVec2(pinRect.GetCenter().x, pinRect.Max.y);
pin->LastRenderBounds = pinRect;
pin->HasPositionData = true;
}
}
// Restore cursor
ImGui::SetCursorScreenPos(contentEndPos);
ImGui::PopID();
ed::EndNode();
}
void ParameterOperationBlock::OnMenu(Node& node, App* app)
{
// Call base class menu first
ParameterizedBlock::OnMenu(node, app);
ImGui::Separator();
ImGui::TextUnformatted("Parameter Operation");
// Show current operation
if (!m_OperationUUID.empty())
{
auto* opDef = ParameterOperationRegistry::Instance().GetOperation(m_OperationUUID);
if (opDef)
{
ImGui::Text("Operation: %s", opDef->Label.c_str());
}
else
{
ImGui::Text("Operation: (unknown)");
}
}
else
{
ImGui::Text("Operation: (none selected)");
}
ImGui::Separator();
// Type selection submenus
if (ImGui::BeginMenu("Set Input A Type"))
{
if (ImGui::MenuItem("Bool", nullptr, m_InputAType == PinType::Bool))
{
m_InputAType = PinType::Bool;
m_OperationUUID = ""; // Clear operation when type changes
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("Int", nullptr, m_InputAType == PinType::Int))
{
m_InputAType = PinType::Int;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("Float", nullptr, m_InputAType == PinType::Float))
{
m_InputAType = PinType::Float;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("String", nullptr, m_InputAType == PinType::String))
{
m_InputAType = PinType::String;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Set Input B Type"))
{
if (ImGui::MenuItem("Bool", nullptr, m_InputBType == PinType::Bool))
{
m_InputBType = PinType::Bool;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("Int", nullptr, m_InputBType == PinType::Int))
{
m_InputBType = PinType::Int;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("Float", nullptr, m_InputBType == PinType::Float))
{
m_InputBType = PinType::Float;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("String", nullptr, m_InputBType == PinType::String))
{
m_InputBType = PinType::String;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Set Output Type"))
{
if (ImGui::MenuItem("Bool", nullptr, m_OutputType == PinType::Bool))
{
m_OutputType = PinType::Bool;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("Int", nullptr, m_OutputType == PinType::Int))
{
m_OutputType = PinType::Int;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("Float", nullptr, m_OutputType == PinType::Float))
{
m_OutputType = PinType::Float;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
if (ImGui::MenuItem("String", nullptr, m_OutputType == PinType::String))
{
m_OutputType = PinType::String;
m_OperationUUID = "";
node.Inputs.clear();
node.Outputs.clear();
Build(node, app);
}
ImGui::EndMenu();
}
ImGui::Separator();
// Operation selection based on current types
auto matchingOps = ParameterOperationRegistry::Instance().GetMatchingOperations(
m_InputAType, m_InputBType, m_OutputType);
if (!matchingOps.empty())
{
if (ImGui::BeginMenu("Select Operation"))
{
for (const auto& op : matchingOps)
{
bool isSelected = (m_OperationUUID == op.UUID);
if (ImGui::MenuItem(op.Label.c_str(), nullptr, isSelected))
{
m_OperationUUID = op.UUID;
// Update node name to reflect operation
node.Name = op.Label;
}
}
ImGui::EndMenu();
}
}
else
{
ImGui::TextDisabled("No operations available for current types");
}
}
void ParameterOperationBlock::SaveState(Node& node, crude_json::value& nodeData, const Container* container, App* app)
{
// Call base class
ParameterizedBlock::SaveState(node, nodeData, container, app);
// Save operation state
nodeData["op_input_a_type"] = (double)static_cast<int>(m_InputAType);
nodeData["op_input_b_type"] = (double)static_cast<int>(m_InputBType);
nodeData["op_output_type"] = (double)static_cast<int>(m_OutputType);
nodeData["op_uuid"] = m_OperationUUID;
}
void ParameterOperationBlock::LoadState(Node& node, const crude_json::value& nodeData, Container* container, App* app)
{
// Load operation state first (before base class, so pins are correct)
if (nodeData.contains("op_input_a_type"))
m_InputAType = static_cast<PinType>((int)nodeData["op_input_a_type"].get<double>());
else
m_InputAType = PinType::Int;
if (nodeData.contains("op_input_b_type"))
m_InputBType = static_cast<PinType>((int)nodeData["op_input_b_type"].get<double>());
else
m_InputBType = PinType::Int;
if (nodeData.contains("op_output_type"))
m_OutputType = static_cast<PinType>((int)nodeData["op_output_type"].get<double>());
else
m_OutputType = PinType::Int;
if (nodeData.contains("op_uuid"))
m_OperationUUID = nodeData["op_uuid"].get<crude_json::string>();
else
m_OperationUUID = "";
// Rebuild pins with loaded types
node.Inputs.clear();
node.Outputs.clear();
m_InputParams.clear();
m_OutputParams.clear();
Build(node, app);
// Call base class to load parameter values
ParameterizedBlock::LoadState(node, nodeData, container, app);
}
// Register block
REGISTER_BLOCK(ParameterOperationBlock, "ParamOp");