deargui-vpl/applications/nodehub/utilities/pin_renderer.cpp
2026-02-03 18:25:25 +01:00

444 lines
14 KiB
C++

#define IMGUI_DEFINE_MATH_OPERATORS
#include "pin_renderer.h"
#include "../app.h"
#include "../utilities/node_renderer_base.h"
#include <imgui_internal.h>
//==============================================================================
// ParameterPinRenderer Implementation
//==============================================================================
void ParameterPinRenderer::Render(
ed::PinId pinId,
ed::PinKind kind,
const Pin& pin,
bool isLinked,
App* app,
const Config* overrideConfig)
{
const Config& config = overrideConfig ? *overrideConfig : m_Config;
// Start vertical layout for this pin
ImGui::BeginVertical(pinId.AsPointer());
// Calculate layout BEFORE rendering
ImVec2 startCursor = ImGui::GetCursorScreenPos();
Layout layout = CalculateLayout(
kind,
config.showLabel ? pin.Name.c_str() : nullptr,
config.iconSize,
config.showLabel,
startCursor);
// For INPUT pins: Icon at TOP, label below
// For OUTPUT pins: Label at TOP (optional), icon at bottom
if (kind == ed::PinKind::Input)
{
// Draw icon first (at top)
ImGui::SetCursorScreenPos(layout.iconRect.Min);
DrawIcon(pin, isLinked, (int)(m_Alpha * 255), layout.iconRect, app);
// Draw label below icon (if showing)
if (config.showLabel)
{
ImGui::SetCursorScreenPos(layout.labelRect.Min);
ImGui::BeginHorizontal("label");
ImGui::Spring(1, 0);
ImGui::TextUnformatted(pin.Name.c_str());
ImGui::Spring(1, 0);
ImGui::EndHorizontal();
}
}
else // Output
{
// Draw label first (if showing)
if (config.showLabel)
{
ImGui::SetCursorScreenPos(layout.labelRect.Min);
ImGui::BeginHorizontal("label");
ImGui::Spring(1, 0);
ImGui::TextUnformatted(pin.Name.c_str());
ImGui::Spring(1, 0);
ImGui::EndHorizontal();
}
// Draw icon at bottom
ImGui::SetCursorScreenPos(layout.iconRect.Min);
// layout.iconRect.Min.y -= 30; // slight nudge for better vertical alignment
DrawIcon(pin, isLinked, (int)(m_Alpha * 255), layout.iconRect, app);
}
// Register pin with editor
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);
BeginPin(pinId, kind);
// Set pin rect and pivot
ed::PinRect(layout.combinedRect.Min, layout.combinedRect.Max);
ed::PinPivotRect(layout.pivotPoint, layout.pivotPoint);
EndPin();
ImGui::PopStyleVar();
// Store layout for queries
m_LastPivotPosition = layout.pivotPoint;
m_LastRenderBounds = layout.combinedRect;
m_RelativeOffset = layout.pivotPoint - startCursor;
// Tooltip: Show parameter name and value
if (ImGui::IsItemHovered())
{
std::string tooltipText = pin.Name.empty() ? "<unnamed>" : pin.Name;
// Get value based on connection status
std::string valueStr;
auto* link = app->FindLinkConnectedToPin(pin.ID);
bool isLinked = (link != nullptr && link->EndPinID == pin.ID);
if (isLinked)
{
// Connected: get value from source parameter node
auto* sourcePin = app->FindPin(link->StartPinID);
if (sourcePin && sourcePin->Node && sourcePin->Node->Type == NodeType::Parameter)
{
Node* paramNode = sourcePin->Node;
switch (paramNode->ParameterType)
{
case PinType::Bool:
valueStr = paramNode->BoolValue ? "true" : "false";
break;
case PinType::Int:
{
char buf[32];
snprintf(buf, sizeof(buf), "%d", paramNode->IntValue);
valueStr = buf;
}
break;
case PinType::Float:
{
char buf[32];
snprintf(buf, sizeof(buf), "%.3f", paramNode->FloatValue);
valueStr = buf;
}
break;
case PinType::String:
valueStr = paramNode->StringValue;
break;
default:
valueStr = "?";
break;
}
}
else
{
valueStr = "[Connected]";
}
}
else
{
// Unconnected: get default value from node
if (pin.Node)
{
const int pinId = ToRuntimeId(pin.ID);
auto& paramValues = pin.Node->UnconnectedParamValues;
if (paramValues.find(pinId) != paramValues.end())
{
valueStr = paramValues[pinId];
}
else
{
// Default value based on type
switch (pin.Type)
{
case PinType::Bool: valueStr = "false"; break;
case PinType::Int: valueStr = "0"; break;
case PinType::Float: valueStr = "0.0"; break;
case PinType::String: valueStr = ""; break;
default: valueStr = "?"; break;
}
}
}
else
{
valueStr = "[Not connected]";
}
}
// Format tooltip: "Name: Value"
char tooltip[512];
if (!valueStr.empty())
{
snprintf(tooltip, sizeof(tooltip), "%s\nValue: %s", tooltipText.c_str(), valueStr.c_str());
}
else
{
snprintf(tooltip, sizeof(tooltip), "%s", tooltipText.c_str());
}
// Use deferred tooltip system (set hovered pin and tooltip text)
app->m_HoveredPin = const_cast<Pin*>(&pin);
app->m_HoveredPinTooltip = tooltip;
}
ImGui::EndVertical();
}
ParameterPinRenderer::Layout ParameterPinRenderer::CalculateLayout(
ed::PinKind kind,
const char* labelText,
float iconSize,
bool showLabel,
const ImVec2& cursorPos)
{
Layout layout;
ImVec2 iconSizeVec(iconSize, iconSize);
float labelHeight = showLabel ? ImGui::GetTextLineHeight() : 0.0f;
float spacing = showLabel ? m_Config.labelSpacing : 0.0f;
if (kind == ed::PinKind::Input)
{
// INPUT: Icon at top, label below
// Pivot at TOP edge of icon (links come from above)
layout.iconRect = ImRect(cursorPos, cursorPos + iconSizeVec);
if (showLabel)
{
ImVec2 labelStart = ImVec2(cursorPos.x, cursorPos.y + iconSize + spacing);
float labelWidth = ImGui::CalcTextSize(labelText).x;
layout.labelRect = ImRect(
labelStart,
labelStart + ImVec2(labelWidth, labelHeight));
layout.combinedRect = layout.iconRect;
layout.combinedRect.Add(layout.labelRect);
}
else
{
layout.combinedRect = layout.iconRect;
layout.labelRect = ImRect(); // Empty
}
// Pivot at TOP CENTER of icon (where link connects)
layout.pivotPoint = ImVec2( layout.iconRect.GetCenter().x, layout.iconRect.Min.y);
}
else // Output
{
// OUTPUT: Label at top (optional), icon at bottom
// Pivot at BOTTOM edge of icon (links go below)
ImVec2 iconStart = cursorPos;
if (showLabel)
{
// Place icon below label
layout.labelRect = ImRect(
cursorPos,
cursorPos + ImVec2(ImGui::CalcTextSize(labelText).x, labelHeight));
iconStart = ImVec2(cursorPos.x, cursorPos.y + labelHeight + spacing);
}
else
{
layout.labelRect = ImRect(); // Empty
}
layout.iconRect = ImRect(iconStart, iconStart + iconSizeVec);
layout.combinedRect = layout.iconRect;
if (showLabel)
layout.combinedRect.Add(layout.labelRect);
// Pivot at BOTTOM CENTER of icon (where link connects)
layout.pivotPoint = ImVec2(
layout.iconRect.GetCenter().x,
layout.iconRect.Max.y);
}
// Apply edge offset
layout.iconRect.Translate(m_Config.edgeOffset);
layout.labelRect.Translate(m_Config.edgeOffset);
layout.combinedRect.Translate(m_Config.edgeOffset);
layout.pivotPoint += m_Config.edgeOffset;
return layout;
}
void ParameterPinRenderer::DrawIcon(
const Pin& pin,
bool isLinked,
int alpha,
const ImRect& iconRect,
App* app)
{
// Set cursor to icon position, then use config offset for fine-tuning
ImGui::SetCursorScreenPos(iconRect.Min);
// ImVec2 cursorPos = ImGui::GetCursorScreenPos();
// Use NodeRendererBase::DrawPinIcon with offset from config
ax::NodeRendering::NodeRendererBase::DrawPinIcon(pin, isLinked, alpha, m_Config.iconOffset, app);
}
void ParameterPinRenderer::BeginPin(ed::PinId id, ed::PinKind kind)
{
m_IsActive = true;
ed::BeginPin(id, kind);
// Set link direction based on kind
if (kind == ed::PinKind::Input)
{
// Links come from above (vertical, negative Y)
ed::PushStyleVar(ed::StyleVar_TargetDirection, ImVec2(0.0f, -1.0f));
}
else
{
// Links go below (vertical, positive Y)
ed::PushStyleVar(ed::StyleVar_SourceDirection, ImVec2(0.0f, 1.0f));
}
}
void ParameterPinRenderer::EndPin()
{
if (m_IsActive)
{
ed::PopStyleVar(); // Pop direction
ed::EndPin();
m_IsActive = false;
}
}
//==============================================================================
// FlowPinRenderer Implementation
//==============================================================================
void FlowPinRenderer::Render(
ed::PinId pinId,
ed::PinKind kind,
const Pin& pin,
App* app,
const Config* overrideConfig)
{
const Config& config = overrideConfig ? *overrideConfig : m_Config;
// Create dummy to reserve space (editor will use this as base position)
ImVec2 dummyPos = ImGui::GetCursorScreenPos();
ImGui::Dummy(ImVec2(config.pinSize, config.pinSize));
// Calculate layout with offset
Layout layout = CalculateLayout(kind, config.pinSize, config.edgeOffset, dummyPos);
// Register pin with editor
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);
BeginPin(pinId, kind);
// Set pin rect to visual position (where we actually draw)
ed::PinRect(layout.visualRect.Min, layout.visualRect.Max);
ed::PinPivotRect(layout.pivotPoint, layout.pivotPoint);
EndPin();
ImGui::PopStyleVar();
// Draw the visual square
ImU32 fillColor = IM_COL32(
(config.fillColor >> IM_COL32_R_SHIFT) & 0xFF,
(config.fillColor >> IM_COL32_G_SHIFT) & 0xFF,
(config.fillColor >> IM_COL32_B_SHIFT) & 0xFF,
(int)(m_Alpha * 255));
ImU32 borderColor = IM_COL32(
(config.borderColor >> IM_COL32_R_SHIFT) & 0xFF,
(config.borderColor >> IM_COL32_G_SHIFT) & 0xFF,
(config.borderColor >> IM_COL32_B_SHIFT) & 0xFF,
(int)(m_Alpha * 200));
DrawSquare(layout.visualRect, config.rounding, config.borderWidth,
fillColor, borderColor, (int)(m_Alpha * 255));
// Store layout for queries
m_LastPivotPosition = layout.pivotPoint;
m_LastRenderBounds = layout.visualRect;
m_RelativeOffset = layout.pivotPoint - dummyPos;
// Tooltip
if (ImGui::IsItemHovered())
{
char tooltip[256];
snprintf(tooltip, sizeof(tooltip), "Flow %s: %s\n(Execution trigger)",
kind == ed::PinKind::Input ? "Input" : "Output",
pin.Name.c_str());
ImGui::SetTooltip("%s", tooltip);
}
}
FlowPinRenderer::Layout FlowPinRenderer::CalculateLayout(
ed::PinKind kind,
float pinSize,
float edgeOffset,
const ImVec2& dummyPos)
{
Layout layout;
// Start with dummy rect
ImRect baseRect(dummyPos, dummyPos + ImVec2(pinSize, pinSize));
// Translate based on kind
if (kind == ed::PinKind::Input)
{
// Move LEFT (negative X)
layout.visualRect = baseRect;
layout.visualRect.TranslateX(-edgeOffset);
}
else // Output
{
// Move RIGHT (positive X)
layout.visualRect = baseRect;
layout.visualRect.TranslateX(edgeOffset);
}
// Pivot at center of visual rect
layout.pivotPoint = layout.visualRect.GetCenter();
return layout;
}
void FlowPinRenderer::DrawSquare(
const ImRect& rect,
float rounding,
float borderWidth,
ImU32 fillColor,
ImU32 borderColor,
int alpha)
{
auto drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(rect.Min, rect.Max, fillColor, rounding);
drawList->AddRect(rect.Min, rect.Max, borderColor, rounding, 0, borderWidth);
}
void FlowPinRenderer::BeginPin(ed::PinId id, ed::PinKind kind)
{
m_IsActive = true;
ed::BeginPin(id, kind);
// Set link direction based on kind
if (kind == ed::PinKind::Input)
{
// Links come from left (horizontal, negative X)
ed::PushStyleVar(ed::StyleVar_TargetDirection, ImVec2(-1.0f, 0.0f));
}
else
{
// Links go right (horizontal, positive X)
ed::PushStyleVar(ed::StyleVar_SourceDirection, ImVec2(1.0f, 0.0f));
}
}
void FlowPinRenderer::EndPin()
{
if (m_IsActive)
{
ed::PopStyleVar(); // Pop direction
ed::EndPin();
m_IsActive = false;
}
}