#define IMGUI_DEFINE_MATH_OPERATORS #include "node_renderer_base.h" #include "../app.h" #include #include namespace ed = ax::NodeEditor; namespace ax { namespace NodeRendering { // ===== Icon Drawing (moved from drawing.cpp) ===== void NodeRendererBase::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) { auto rect = ImRect(a, b); auto rect_x = rect.Min.x; auto rect_y = rect.Min.y; auto rect_w = rect.Max.x - rect.Min.x; auto rect_h = rect.Max.y - rect.Min.y; auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; auto rect_center = ImVec2(rect_center_x, rect_center_y); const auto outline_scale = rect_w / 24.0f; const auto extra_segments = static_cast(2 * outline_scale); // for full circle if (type == IconType::Flow) { const auto origin_scale = rect_w / 24.0f; const auto offset_x = 1.0f * origin_scale; auto offset_y = 0.0f * origin_scale; const auto margin = (filled ? 2.0f : 2.0f) * origin_scale; const auto rounding = 0.1f * origin_scale; const auto tip_round = 0.7f; // percentage of triangle edge (for tip) const auto canvas = ImRect( rect.Min.x + margin + offset_x, rect.Min.y + margin + offset_y, rect.Max.x - margin + offset_x, rect.Max.y - margin + offset_y); const auto canvas_x = canvas.Min.x; const auto canvas_y = canvas.Min.y; const auto canvas_w = canvas.Max.x - canvas.Min.x; const auto canvas_h = canvas.Max.y - canvas.Min.y; const auto left = canvas_x + canvas_w * 0.5f * 0.3f; const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; const auto top = canvas_y + canvas_h * 0.5f * 0.2f; const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; const auto center_y = (top + bottom) * 0.5f; const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); const auto tip_right = ImVec2(right, center_y); const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); drawList->PathBezierCubicCurveTo( ImVec2(left, top), ImVec2(left, top), ImVec2(left, top) + ImVec2(rounding, 0)); drawList->PathLineTo(tip_top); drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); drawList->PathBezierCubicCurveTo( tip_right, tip_right, tip_bottom + (tip_right - tip_bottom) * tip_round); drawList->PathLineTo(tip_bottom); drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); drawList->PathBezierCubicCurveTo( ImVec2(left, bottom), ImVec2(left, bottom), ImVec2(left, bottom) - ImVec2(0, rounding)); if (!filled) { if (innerColor & 0xFF000000) drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); drawList->PathStroke(color, true, 2.0f * outline_scale); } else drawList->PathFillConvex(color); } else { auto triangleStart = rect_center_x + 0.32f * rect_w; auto rect_offset = -static_cast(rect_w * 0.25f * 0.25f); rect.Min.x += rect_offset; rect.Max.x += rect_offset; rect_x += rect_offset; rect_center_x += rect_offset * 0.5f; rect_center.x += rect_offset * 0.5f; if (type == IconType::Circle) { const auto c = rect_center; if (!filled) { const auto r = 0.5f * rect_w / 2.0f - 0.5f; if (innerColor & 0xFF000000) drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments); drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale); } else { drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments); } } if (type == IconType::Square) { if (filled) { const auto r = 0.5f * rect_w / 2.0f; const auto p0 = rect_center - ImVec2(r, r); const auto p1 = rect_center + ImVec2(r, r); #if IMGUI_VERSION_NUM > 18101 drawList->AddRectFilled(p0, p1, color, 0, ImDrawFlags_RoundCornersAll); #else drawList->AddRectFilled(p0, p1, color, 0, 15); #endif } else { const auto r = 0.5f * rect_w / 2.0f - 0.5f; const auto p0 = rect_center - ImVec2(r, r); const auto p1 = rect_center + ImVec2(r, r); if (innerColor & 0xFF000000) { #if IMGUI_VERSION_NUM > 18101 drawList->AddRectFilled(p0, p1, innerColor, 0, ImDrawFlags_RoundCornersAll); #else drawList->AddRectFilled(p0, p1, innerColor, 0, 15); #endif } #if IMGUI_VERSION_NUM > 18101 drawList->AddRect(p0, p1, color, 0, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale); #else drawList->AddRect(p0, p1, color, 0, 15, 2.0f * outline_scale); #endif } } if (type == IconType::Grid) { const auto r = 0.5f * rect_w / 2.0f; const auto w = ceilf(r / 3.0f); const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); auto tl = baseTl; auto br = baseBr; for (int i = 0; i < 3; ++i) { tl.x = baseTl.x; br.x = baseBr.x; drawList->AddRectFilled(tl, br, color); tl.x += w * 2; br.x += w * 2; if (i != 1 || filled) drawList->AddRectFilled(tl, br, color); tl.x += w * 2; br.x += w * 2; drawList->AddRectFilled(tl, br, color); tl.y += w * 2; br.y += w * 2; } triangleStart = br.x + w + 1.0f / 24.0f * rect_w; } if (type == IconType::RoundSquare) { if (filled) { const auto r = 0.5f * rect_w / 2.0f; const auto cr = r * 0.5f; const auto p0 = rect_center - ImVec2(r, r); const auto p1 = rect_center + ImVec2(r, r); #if IMGUI_VERSION_NUM > 18101 drawList->AddRectFilled(p0, p1, color, cr, ImDrawFlags_RoundCornersAll); #else drawList->AddRectFilled(p0, p1, color, cr, 15); #endif } else { const auto r = 0.5f * rect_w / 2.0f - 0.5f; const auto cr = r * 0.5f; const auto p0 = rect_center - ImVec2(r, r); const auto p1 = rect_center + ImVec2(r, r); if (innerColor & 0xFF000000) { #if IMGUI_VERSION_NUM > 18101 drawList->AddRectFilled(p0, p1, innerColor, cr, ImDrawFlags_RoundCornersAll); #else drawList->AddRectFilled(p0, p1, innerColor, cr, 15); #endif } #if IMGUI_VERSION_NUM > 18101 drawList->AddRect(p0, p1, color, cr, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale); #else drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale); #endif } } else if (type == IconType::Diamond) { if (filled) { const auto r = 0.607f * rect_w / 2.0f; const auto c = rect_center; drawList->PathLineTo(c + ImVec2( 0, -r)); drawList->PathLineTo(c + ImVec2( r, 0)); drawList->PathLineTo(c + ImVec2( 0, r)); drawList->PathLineTo(c + ImVec2(-r, 0)); drawList->PathFillConvex(color); } else { const auto r = 0.607f * rect_w / 2.0f - 0.5f; const auto c = rect_center; drawList->PathLineTo(c + ImVec2( 0, -r)); drawList->PathLineTo(c + ImVec2( r, 0)); drawList->PathLineTo(c + ImVec2( 0, r)); drawList->PathLineTo(c + ImVec2(-r, 0)); if (innerColor & 0xFF000000) drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); drawList->PathStroke(color, true, 2.0f * outline_scale); } } } } // ===== ImGui-friendly Icon Widget ===== void NodeRendererBase::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) { if (ImGui::IsRectVisible(size)) { auto cursorPos = ImGui::GetCursorScreenPos(); auto drawList = ImGui::GetWindowDrawList(); DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor)); } ImGui::Dummy(size); } // ===== Pin Icon Drawing ===== void NodeRendererBase::DrawPinIcon(const Pin& pin, bool connected, int alpha, const ImVec2& offset, App* app) { auto drawList = ImGui::GetWindowDrawList(); auto cursorPos = ImGui::GetCursorScreenPos(); auto iconSize = ImVec2(static_cast(app->m_PinIconSize), static_cast(app->m_PinIconSize)); // Calculate icon position with offset auto iconPos = cursorPos + offset; auto iconRect = ImRect(iconPos, iconPos + iconSize); // Get icon type and color IconType iconType; ImColor color = app->GetIconColor(pin.Type); color.Value.w = alpha / 255.0f; switch (pin.Type) { case PinType::Flow: iconType = IconType::Flow; break; case PinType::Bool: iconType = IconType::Circle; break; case PinType::Int: iconType = IconType::Circle; break; case PinType::Float: iconType = IconType::Circle; break; case PinType::String: iconType = IconType::Circle; break; case PinType::Object: iconType = IconType::Circle; break; case PinType::Function: iconType = IconType::Circle; break; case PinType::Delegate: iconType = IconType::Square; break; default: return; } // Draw icon at offset position DrawIcon(drawList, iconRect.Min, iconRect.Max, iconType, false, color, ImColor(32, 32, 32, alpha)); } // ===== Style Management ===== NodeRendererBase::NodeStyleScope::NodeStyleScope(const ImColor& bgColor, const ImColor& borderColor, float rounding, float borderWidth, const ImVec4& padding, const ImVec2& sourceDir, const ImVec2& targetDir) { ed::PushStyleColor(ed::StyleColor_NodeBg, bgColor); ed::PushStyleColor(ed::StyleColor_NodeBorder, borderColor); ed::PushStyleVar(ed::StyleVar_NodeRounding, rounding); ed::PushStyleVar(ed::StyleVar_NodeBorderWidth, borderWidth); ed::PushStyleVar(ed::StyleVar_NodePadding, padding); ed::PushStyleVar(ed::StyleVar_SourceDirection, sourceDir); ed::PushStyleVar(ed::StyleVar_TargetDirection, targetDir); ed::PushStyleVar(ed::StyleVar_PinArrowSize, 0.0f); ed::PushStyleVar(ed::StyleVar_PinArrowWidth, 0.0f); } NodeRendererBase::NodeStyleScope::~NodeStyleScope() { ed::PopStyleVar(m_StyleVarCount); ed::PopStyleColor(m_StyleColorCount); } // ===== Tooltip Management ===== void NodeRendererBase::SetPinTooltip(Pin* pin, const char* label, const char* typeName, App* app) { app->m_HoveredPin = pin; char tooltip[256]; snprintf(tooltip, sizeof(tooltip), "%s\nType: %s", label, typeName); app->m_HoveredPinTooltip = tooltip; } // ===== Input Field Helpers ===== void NodeRendererBase::HandleTextInput(bool isActive, ed::NodeId nodeId, ed::NodeId& staticEditingNode) { if (isActive && staticEditingNode != nodeId) { ed::EnableShortcuts(false); staticEditingNode = nodeId; } else if (!isActive && staticEditingNode == nodeId) { ed::EnableShortcuts(true); staticEditingNode = 0; } } // ===== Pin Alpha Calculation ===== float NodeRendererBase::GetPinAlpha(Pin* pin, Pin* newLinkPin, App* app) { float alpha = ImGui::GetStyle().Alpha; if (newLinkPin && !app->CanCreateLink(newLinkPin, pin) && pin != newLinkPin) alpha = alpha * (48.0f / 255.0f); return alpha; } } // namespace NodeRendering } // namespace ax