1112 lines
44 KiB
C++
1112 lines
44 KiB
C++
|
// https://github.com/CedricGuillemet/ImGuizmo
|
||
|
// v 1.89 WIP
|
||
|
//
|
||
|
// The MIT License(MIT)
|
||
|
//
|
||
|
// Copyright(c) 2021 Cedric Guillemet
|
||
|
//
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
// of this software and associated documentation files(the "Software"), to deal
|
||
|
// in the Software without restriction, including without limitation the rights
|
||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||
|
// copies of the Software, and to permit persons to whom the Software is
|
||
|
// furnished to do so, subject to the following conditions :
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be included in all
|
||
|
// copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
// SOFTWARE.
|
||
|
//
|
||
|
|
||
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||
|
#include "imgui.h"
|
||
|
#include "imgui_internal.h"
|
||
|
#include <math.h>
|
||
|
#include <vector>
|
||
|
#include <float.h>
|
||
|
#include <array>
|
||
|
#include "GraphEditor.h"
|
||
|
|
||
|
namespace GraphEditor {
|
||
|
|
||
|
static inline float Distance(const ImVec2& a, const ImVec2& b)
|
||
|
{
|
||
|
return sqrtf((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
|
||
|
}
|
||
|
|
||
|
static inline float sign(float v)
|
||
|
{
|
||
|
return (v >= 0.f) ? 1.f : -1.f;
|
||
|
}
|
||
|
|
||
|
static ImVec2 GetInputSlotPos(Delegate& delegate, const Node& node, SlotIndex slotIndex, float factor)
|
||
|
{
|
||
|
ImVec2 Size = node.mRect.GetSize() * factor;
|
||
|
size_t InputsCount = delegate.GetTemplate(node.mTemplateIndex).mInputCount;
|
||
|
return ImVec2(node.mRect.Min.x * factor,
|
||
|
node.mRect.Min.y * factor + Size.y * ((float)slotIndex + 1) / ((float)InputsCount + 1) + 8.f);
|
||
|
}
|
||
|
|
||
|
static ImVec2 GetOutputSlotPos(Delegate& delegate, const Node& node, SlotIndex slotIndex, float factor)
|
||
|
{
|
||
|
ImVec2 Size = node.mRect.GetSize() * factor;
|
||
|
size_t OutputsCount = delegate.GetTemplate(node.mTemplateIndex).mOutputCount;
|
||
|
return ImVec2(node.mRect.Min.x * factor + Size.x,
|
||
|
node.mRect.Min.y * factor + Size.y * ((float)slotIndex + 1) / ((float)OutputsCount + 1) + 8.f);
|
||
|
}
|
||
|
|
||
|
static ImRect GetNodeRect(const Node& node, float factor)
|
||
|
{
|
||
|
ImVec2 Size = node.mRect.GetSize() * factor;
|
||
|
return ImRect(node.mRect.Min * factor, node.mRect.Min * factor + Size);
|
||
|
}
|
||
|
|
||
|
static ImVec2 editingNodeSource;
|
||
|
static bool editingInput = false;
|
||
|
static ImVec2 captureOffset;
|
||
|
|
||
|
enum NodeOperation
|
||
|
{
|
||
|
NO_None,
|
||
|
NO_EditingLink,
|
||
|
NO_QuadSelecting,
|
||
|
NO_MovingNodes,
|
||
|
NO_EditInput,
|
||
|
NO_PanView,
|
||
|
};
|
||
|
static NodeOperation nodeOperation = NO_None;
|
||
|
|
||
|
static void HandleZoomScroll(ImRect regionRect, ViewState& viewState, const Options& options)
|
||
|
{
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
|
||
|
if (regionRect.Contains(io.MousePos))
|
||
|
{
|
||
|
if (io.MouseWheel < -FLT_EPSILON)
|
||
|
{
|
||
|
viewState.mFactorTarget *= 1.f - options.mZoomRatio;
|
||
|
}
|
||
|
|
||
|
if (io.MouseWheel > FLT_EPSILON)
|
||
|
{
|
||
|
viewState.mFactorTarget *= 1.0f + options.mZoomRatio;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ImVec2 mouseWPosPre = (io.MousePos - ImGui::GetCursorScreenPos()) / viewState.mFactor;
|
||
|
viewState.mFactorTarget = ImClamp(viewState.mFactorTarget, options.mMinZoom, options.mMaxZoom);
|
||
|
viewState.mFactor = ImLerp(viewState.mFactor, viewState.mFactorTarget, options.mZoomLerpFactor);
|
||
|
ImVec2 mouseWPosPost = (io.MousePos - ImGui::GetCursorScreenPos()) / viewState.mFactor;
|
||
|
if (ImGui::IsMousePosValid())
|
||
|
{
|
||
|
viewState.mPosition += mouseWPosPost - mouseWPosPre;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void GraphEditorClear()
|
||
|
{
|
||
|
nodeOperation = NO_None;
|
||
|
}
|
||
|
|
||
|
static void FitNodes(Delegate& delegate, ViewState& viewState, const ImVec2 viewSize, bool selectedNodesOnly)
|
||
|
{
|
||
|
const size_t nodeCount = delegate.GetNodeCount();
|
||
|
|
||
|
if (!nodeCount)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bool validNode = false;
|
||
|
ImVec2 min(FLT_MAX, FLT_MAX);
|
||
|
ImVec2 max(-FLT_MAX, -FLT_MAX);
|
||
|
for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
|
||
|
{
|
||
|
const Node& node = delegate.GetNode(nodeIndex);
|
||
|
|
||
|
if (selectedNodesOnly && !node.mSelected)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
min = ImMin(min, node.mRect.Min);
|
||
|
min = ImMin(min, node.mRect.Max);
|
||
|
max = ImMax(max, node.mRect.Min);
|
||
|
max = ImMax(max, node.mRect.Max);
|
||
|
validNode = true;
|
||
|
}
|
||
|
|
||
|
if (!validNode)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
min -= viewSize * 0.05f;
|
||
|
max += viewSize * 0.05f;
|
||
|
ImVec2 nodesSize = max - min;
|
||
|
ImVec2 nodeCenter = (max + min) * 0.5f;
|
||
|
|
||
|
float ratioY = viewSize.y / nodesSize.y;
|
||
|
float ratioX = viewSize.x / nodesSize.x;
|
||
|
|
||
|
viewState.mFactor = viewState.mFactorTarget = ImMin(ImMin(ratioY, ratioX), 1.f);
|
||
|
viewState.mPosition = ImVec2(-nodeCenter.x, -nodeCenter.y) + (viewSize * 0.5f) / viewState.mFactorTarget;
|
||
|
}
|
||
|
|
||
|
static void DisplayLinks(Delegate& delegate,
|
||
|
ImDrawList* drawList,
|
||
|
const ImVec2 offset,
|
||
|
const float factor,
|
||
|
const ImRect regionRect,
|
||
|
NodeIndex hoveredNode,
|
||
|
const Options& options)
|
||
|
{
|
||
|
const size_t linkCount = delegate.GetLinkCount();
|
||
|
for (LinkIndex linkIndex = 0; linkIndex < linkCount; linkIndex++)
|
||
|
{
|
||
|
const auto link = delegate.GetLink(linkIndex);
|
||
|
const auto nodeInput = delegate.GetNode(link.mInputNodeIndex);
|
||
|
const auto nodeOutput = delegate.GetNode(link.mOutputNodeIndex);
|
||
|
ImVec2 p1 = offset + GetOutputSlotPos(delegate, nodeInput, link.mInputSlotIndex, factor);
|
||
|
ImVec2 p2 = offset + GetInputSlotPos(delegate, nodeOutput, link.mOutputSlotIndex, factor);
|
||
|
|
||
|
// con. view clipping
|
||
|
if ((p1.y < 0.f && p2.y < 0.f) || (p1.y > regionRect.Max.y && p2.y > regionRect.Max.y) ||
|
||
|
(p1.x < 0.f && p2.x < 0.f) || (p1.x > regionRect.Max.x && p2.x > regionRect.Max.x))
|
||
|
continue;
|
||
|
|
||
|
bool highlightCons = hoveredNode == link.mInputNodeIndex || hoveredNode == link.mOutputNodeIndex;
|
||
|
uint32_t col = delegate.GetTemplate(nodeInput.mTemplateIndex).mHeaderColor | (highlightCons ? 0xF0F0F0 : 0);
|
||
|
if (options.mDisplayLinksAsCurves)
|
||
|
{
|
||
|
// curves
|
||
|
drawList->AddBezierCubic(p1, p1 + ImVec2(50, 0) * factor, p2 + ImVec2(-50, 0) * factor, p2, 0xFF000000, options.mLineThickness * 1.5f * factor);
|
||
|
drawList->AddBezierCubic(p1, p1 + ImVec2(50, 0) * factor, p2 + ImVec2(-50, 0) * factor, p2, col, options.mLineThickness * 1.5f * factor);
|
||
|
/*
|
||
|
ImVec2 p10 = p1 + ImVec2(20.f * factor, 0.f);
|
||
|
ImVec2 p20 = p2 - ImVec2(20.f * factor, 0.f);
|
||
|
|
||
|
ImVec2 dif = p20 - p10;
|
||
|
ImVec2 p1a, p1b;
|
||
|
if (fabsf(dif.x) > fabsf(dif.y))
|
||
|
{
|
||
|
p1a = p10 + ImVec2(fabsf(fabsf(dif.x) - fabsf(dif.y)) * 0.5 * sign(dif.x), 0.f);
|
||
|
p1b = p1a + ImVec2(fabsf(dif.y) * sign(dif.x) , dif.y);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p1a = p10 + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(dif.x)) * 0.5 * sign(dif.y));
|
||
|
p1b = p1a + ImVec2(dif.x, fabsf(dif.x) * sign(dif.y));
|
||
|
}
|
||
|
drawList->AddLine(p1, p10, col, 3.f * factor);
|
||
|
drawList->AddLine(p10, p1a, col, 3.f * factor);
|
||
|
drawList->AddLine(p1a, p1b, col, 3.f * factor);
|
||
|
drawList->AddLine(p1b, p20, col, 3.f * factor);
|
||
|
drawList->AddLine(p20, p2, col, 3.f * factor);
|
||
|
*/
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// straight lines
|
||
|
std::array<ImVec2, 6> pts;
|
||
|
int ptCount = 0;
|
||
|
ImVec2 dif = p2 - p1;
|
||
|
|
||
|
ImVec2 p1a, p1b;
|
||
|
const float limitx = 12.f * factor;
|
||
|
if (dif.x < limitx)
|
||
|
{
|
||
|
ImVec2 p10 = p1 + ImVec2(limitx, 0.f);
|
||
|
ImVec2 p20 = p2 - ImVec2(limitx, 0.f);
|
||
|
|
||
|
dif = p20 - p10;
|
||
|
p1a = p10 + ImVec2(0.f, dif.y * 0.5f);
|
||
|
p1b = p1a + ImVec2(dif.x, 0.f);
|
||
|
|
||
|
pts = { p1, p10, p1a, p1b, p20, p2 };
|
||
|
ptCount = 6;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (fabsf(dif.y) < 1.f)
|
||
|
{
|
||
|
pts = { p1, (p1 + p2) * 0.5f, p2 };
|
||
|
ptCount = 3;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (fabsf(dif.y) < 10.f)
|
||
|
{
|
||
|
if (fabsf(dif.x) > fabsf(dif.y))
|
||
|
{
|
||
|
p1a = p1 + ImVec2(fabsf(fabsf(dif.x) - fabsf(dif.y)) * 0.5f * sign(dif.x), 0.f);
|
||
|
p1b = p1a + ImVec2(fabsf(dif.y) * sign(dif.x), dif.y);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p1a = p1 + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(dif.x)) * 0.5f * sign(dif.y));
|
||
|
p1b = p1a + ImVec2(dif.x, fabsf(dif.x) * sign(dif.y));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (fabsf(dif.x) > fabsf(dif.y))
|
||
|
{
|
||
|
float d = fabsf(dif.y) * sign(dif.x) * 0.5f;
|
||
|
p1a = p1 + ImVec2(d, dif.y * 0.5f);
|
||
|
p1b = p1a + ImVec2(fabsf(fabsf(dif.x) - fabsf(d) * 2.f) * sign(dif.x), 0.f);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
float d = fabsf(dif.x) * sign(dif.y) * 0.5f;
|
||
|
p1a = p1 + ImVec2(dif.x * 0.5f, d);
|
||
|
p1b = p1a + ImVec2(0.f, fabsf(fabsf(dif.y) - fabsf(d) * 2.f) * sign(dif.y));
|
||
|
}
|
||
|
}
|
||
|
pts = { p1, p1a, p1b, p2 };
|
||
|
ptCount = 4;
|
||
|
}
|
||
|
}
|
||
|
float highLightFactor = factor * (highlightCons ? 2.0f : 1.f);
|
||
|
for (int pass = 0; pass < 2; pass++)
|
||
|
{
|
||
|
drawList->AddPolyline(pts.data(), ptCount, pass ? col : 0xFF000000, false, (pass ? options.mLineThickness : (options.mLineThickness * 1.5f)) * highLightFactor);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void HandleQuadSelection(Delegate& delegate, ImDrawList* drawList, const ImVec2 offset, const float factor, ImRect contentRect, const Options& options)
|
||
|
{
|
||
|
if (!options.mAllowQuadSelection)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
static ImVec2 quadSelectPos;
|
||
|
//auto& nodes = delegate->GetNodes();
|
||
|
auto nodeCount = delegate.GetNodeCount();
|
||
|
|
||
|
if (nodeOperation == NO_QuadSelecting && ImGui::IsWindowFocused())
|
||
|
{
|
||
|
const ImVec2 bmin = ImMin(quadSelectPos, io.MousePos);
|
||
|
const ImVec2 bmax = ImMax(quadSelectPos, io.MousePos);
|
||
|
drawList->AddRectFilled(bmin, bmax, options.mQuadSelection, 1.f);
|
||
|
drawList->AddRect(bmin, bmax, options.mQuadSelectionBorder, 1.f);
|
||
|
if (!io.MouseDown[0])
|
||
|
{
|
||
|
if (!io.KeyCtrl && !io.KeyShift)
|
||
|
{
|
||
|
for (size_t nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
|
||
|
{
|
||
|
delegate.SelectNode(nodeIndex, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
nodeOperation = NO_None;
|
||
|
ImRect selectionRect(bmin, bmax);
|
||
|
for (int nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
|
||
|
{
|
||
|
const auto node = delegate.GetNode(nodeIndex);
|
||
|
ImVec2 nodeRectangleMin = offset + node.mRect.Min * factor;
|
||
|
ImVec2 nodeRectangleMax = nodeRectangleMin + node.mRect.GetSize() * factor;
|
||
|
if (selectionRect.Overlaps(ImRect(nodeRectangleMin, nodeRectangleMax)))
|
||
|
{
|
||
|
if (io.KeyCtrl)
|
||
|
{
|
||
|
delegate.SelectNode(nodeIndex, false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delegate.SelectNode(nodeIndex, true);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!io.KeyShift)
|
||
|
{
|
||
|
delegate.SelectNode(nodeIndex, false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (nodeOperation == NO_None && io.MouseDown[0] && ImGui::IsWindowFocused() &&
|
||
|
contentRect.Contains(io.MousePos))
|
||
|
{
|
||
|
nodeOperation = NO_QuadSelecting;
|
||
|
quadSelectPos = io.MousePos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool HandleConnections(ImDrawList* drawList,
|
||
|
NodeIndex nodeIndex,
|
||
|
const ImVec2 offset,
|
||
|
const float factor,
|
||
|
Delegate& delegate,
|
||
|
const Options& options,
|
||
|
bool bDrawOnly,
|
||
|
SlotIndex& inputSlotOver,
|
||
|
SlotIndex& outputSlotOver,
|
||
|
const bool inMinimap)
|
||
|
{
|
||
|
static NodeIndex editingNodeIndex;
|
||
|
static SlotIndex editingSlotIndex;
|
||
|
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
const auto node = delegate.GetNode(nodeIndex);
|
||
|
const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex);
|
||
|
const auto linkCount = delegate.GetLinkCount();
|
||
|
|
||
|
size_t InputsCount = nodeTemplate.mInputCount;
|
||
|
size_t OutputsCount = nodeTemplate.mOutputCount;
|
||
|
inputSlotOver = -1;
|
||
|
outputSlotOver = -1;
|
||
|
|
||
|
// draw/use inputs/outputs
|
||
|
bool hoverSlot = false;
|
||
|
for (int i = 0; i < 2; i++)
|
||
|
{
|
||
|
float closestDistance = FLT_MAX;
|
||
|
SlotIndex closestConn = -1;
|
||
|
ImVec2 closestTextPos;
|
||
|
ImVec2 closestPos;
|
||
|
const size_t slotCount[2] = {InputsCount, OutputsCount};
|
||
|
|
||
|
for (SlotIndex slotIndex = 0; slotIndex < slotCount[i]; slotIndex++)
|
||
|
{
|
||
|
const char** con = i ? nodeTemplate.mOutputNames : nodeTemplate.mInputNames;
|
||
|
const char* conText = (con && con[slotIndex]) ? con[slotIndex] : "";
|
||
|
|
||
|
ImVec2 p =
|
||
|
offset + (i ? GetOutputSlotPos(delegate, node, slotIndex, factor) : GetInputSlotPos(delegate, node, slotIndex, factor));
|
||
|
float distance = Distance(p, io.MousePos);
|
||
|
bool overCon = (nodeOperation == NO_None || nodeOperation == NO_EditingLink) &&
|
||
|
(distance < options.mNodeSlotRadius * 2.f) && (distance < closestDistance);
|
||
|
|
||
|
|
||
|
ImVec2 textSize;
|
||
|
textSize = ImGui::CalcTextSize(conText);
|
||
|
ImVec2 textPos =
|
||
|
p + ImVec2(-options.mNodeSlotRadius * (i ? -1.f : 1.f) * (overCon ? 3.f : 2.f) - (i ? 0 : textSize.x),
|
||
|
-textSize.y / 2);
|
||
|
|
||
|
ImRect nodeRect = GetNodeRect(node, factor);
|
||
|
if (!inMinimap && (overCon || (nodeRect.Contains(io.MousePos - offset) && closestConn == -1 &&
|
||
|
(editingInput == (i != 0)) && nodeOperation == NO_EditingLink)))
|
||
|
{
|
||
|
closestDistance = distance;
|
||
|
closestConn = slotIndex;
|
||
|
closestTextPos = textPos;
|
||
|
closestPos = p;
|
||
|
|
||
|
if (i)
|
||
|
{
|
||
|
outputSlotOver = slotIndex;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
inputSlotOver = slotIndex;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const ImU32* slotColorSource = i ? nodeTemplate.mOutputColors : nodeTemplate.mInputColors;
|
||
|
const ImU32 slotColor = slotColorSource ? slotColorSource[slotIndex] : options.mDefaultSlotColor;
|
||
|
drawList->AddCircleFilled(p, options.mNodeSlotRadius, IM_COL32(0, 0, 0, 200));
|
||
|
drawList->AddCircleFilled(p, options.mNodeSlotRadius * 0.75f, slotColor);
|
||
|
if (!options.mDrawIONameOnHover)
|
||
|
{
|
||
|
drawList->AddText(io.FontDefault, 14, textPos + ImVec2(2, 2), IM_COL32(0, 0, 0, 255), conText);
|
||
|
drawList->AddText(io.FontDefault, 14, textPos, IM_COL32(150, 150, 150, 255), conText);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (closestConn != -1)
|
||
|
{
|
||
|
const char** con = i ? nodeTemplate.mOutputNames : nodeTemplate.mInputNames;
|
||
|
const char* conText = (con && con[closestConn]) ? con[closestConn] : "";
|
||
|
const ImU32* slotColorSource = i ? nodeTemplate.mOutputColors : nodeTemplate.mInputColors;
|
||
|
const ImU32 slotColor = slotColorSource ? slotColorSource[closestConn] : options.mDefaultSlotColor;
|
||
|
hoverSlot = true;
|
||
|
drawList->AddCircleFilled(closestPos, options.mNodeSlotRadius * options.mNodeSlotHoverFactor * 0.75f, IM_COL32(0, 0, 0, 200));
|
||
|
drawList->AddCircleFilled(closestPos, options.mNodeSlotRadius * options.mNodeSlotHoverFactor, slotColor);
|
||
|
drawList->AddText(io.FontDefault, 16, closestTextPos + ImVec2(1, 1), IM_COL32(0, 0, 0, 255), conText);
|
||
|
drawList->AddText(io.FontDefault, 16, closestTextPos, IM_COL32(250, 250, 250, 255), conText);
|
||
|
bool inputToOutput = (!editingInput && !i) || (editingInput && i);
|
||
|
if (nodeOperation == NO_EditingLink && !io.MouseDown[0] && !bDrawOnly)
|
||
|
{
|
||
|
if (inputToOutput)
|
||
|
{
|
||
|
// check loopback
|
||
|
Link nl;
|
||
|
if (editingInput)
|
||
|
nl = Link{nodeIndex, closestConn, editingNodeIndex, editingSlotIndex};
|
||
|
else
|
||
|
nl = Link{editingNodeIndex, editingSlotIndex, nodeIndex, closestConn};
|
||
|
|
||
|
if (!delegate.AllowedLink(nl.mOutputNodeIndex, nl.mInputNodeIndex))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
bool alreadyExisting = false;
|
||
|
for (size_t linkIndex = 0; linkIndex < linkCount; linkIndex++)
|
||
|
{
|
||
|
const auto link = delegate.GetLink(linkIndex);
|
||
|
if (!memcmp(&link, &nl, sizeof(Link)))
|
||
|
{
|
||
|
alreadyExisting = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!alreadyExisting)
|
||
|
{
|
||
|
for (int linkIndex = 0; linkIndex < linkCount; linkIndex++)
|
||
|
{
|
||
|
const auto link = delegate.GetLink(linkIndex);
|
||
|
if (link.mOutputNodeIndex == nl.mOutputNodeIndex && link.mOutputSlotIndex == nl.mOutputSlotIndex)
|
||
|
{
|
||
|
delegate.DelLink(linkIndex);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
delegate.AddLink(nl.mInputNodeIndex, nl.mInputSlotIndex, nl.mOutputNodeIndex, nl.mOutputSlotIndex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// when ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() is uncommented, one can't click the node
|
||
|
// input/output when mouse is over the node itself.
|
||
|
if (nodeOperation == NO_None &&
|
||
|
/*ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() &&*/ io.MouseClicked[0] && !bDrawOnly)
|
||
|
{
|
||
|
nodeOperation = NO_EditingLink;
|
||
|
editingInput = i == 0;
|
||
|
editingNodeSource = closestPos;
|
||
|
editingNodeIndex = nodeIndex;
|
||
|
editingSlotIndex = closestConn;
|
||
|
if (editingInput)
|
||
|
{
|
||
|
// remove existing link
|
||
|
for (int linkIndex = 0; linkIndex < linkCount; linkIndex++)
|
||
|
{
|
||
|
const auto link = delegate.GetLink(linkIndex);
|
||
|
if (link.mOutputNodeIndex == nodeIndex && link.mOutputSlotIndex == closestConn)
|
||
|
{
|
||
|
delegate.DelLink(linkIndex);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return hoverSlot;
|
||
|
}
|
||
|
|
||
|
static void DrawGrid(ImDrawList* drawList, ImVec2 windowPos, const ViewState& viewState, const ImVec2 canvasSize, ImU32 gridColor, ImU32 gridColor2, float gridSize)
|
||
|
{
|
||
|
float gridSpace = gridSize * viewState.mFactor;
|
||
|
int divx = static_cast<int>(-viewState.mPosition.x / gridSize);
|
||
|
int divy = static_cast<int>(-viewState.mPosition.y / gridSize);
|
||
|
for (float x = fmodf(viewState.mPosition.x * viewState.mFactor, gridSpace); x < canvasSize.x; x += gridSpace, divx ++)
|
||
|
{
|
||
|
bool tenth = !(divx % 10);
|
||
|
drawList->AddLine(ImVec2(x, 0.0f) + windowPos, ImVec2(x, canvasSize.y) + windowPos, tenth ? gridColor2 : gridColor);
|
||
|
}
|
||
|
for (float y = fmodf(viewState.mPosition.y * viewState.mFactor, gridSpace); y < canvasSize.y; y += gridSpace, divy ++)
|
||
|
{
|
||
|
bool tenth = !(divy % 10);
|
||
|
drawList->AddLine(ImVec2(0.0f, y) + windowPos, ImVec2(canvasSize.x, y) + windowPos, tenth ? gridColor2 : gridColor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// return true if node is hovered
|
||
|
static bool DrawNode(ImDrawList* drawList,
|
||
|
NodeIndex nodeIndex,
|
||
|
const ImVec2 offset,
|
||
|
const float factor,
|
||
|
Delegate& delegate,
|
||
|
bool overInput,
|
||
|
const Options& options,
|
||
|
const bool inMinimap,
|
||
|
const ImRect& viewPort)
|
||
|
{
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
const auto node = delegate.GetNode(nodeIndex);
|
||
|
IM_ASSERT((node.mRect.GetWidth() != 0.f) && (node.mRect.GetHeight() != 0.f) && "Nodes must have a non-zero rect.");
|
||
|
const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex);
|
||
|
const ImVec2 nodeRectangleMin = offset + node.mRect.Min * factor;
|
||
|
|
||
|
const bool old_any_active = ImGui::IsAnyItemActive();
|
||
|
ImGui::SetCursorScreenPos(nodeRectangleMin);
|
||
|
const ImVec2 nodeSize = node.mRect.GetSize() * factor;
|
||
|
|
||
|
// test nested IO
|
||
|
drawList->ChannelsSetCurrent(1); // Background
|
||
|
const size_t InputsCount = nodeTemplate.mInputCount;
|
||
|
const size_t OutputsCount = nodeTemplate.mOutputCount;
|
||
|
|
||
|
/*
|
||
|
for (int i = 0; i < 2; i++)
|
||
|
{
|
||
|
const size_t slotCount[2] = {InputsCount, OutputsCount};
|
||
|
|
||
|
for (size_t slotIndex = 0; slotIndex < slotCount[i]; slotIndex++)
|
||
|
{
|
||
|
const char* con = i ? nodeTemplate.mOutputNames[slotIndex] : nodeTemplate.mInputNames[slotIndex];//node.mOutputs[slot_idx] : node->mInputs[slot_idx];
|
||
|
if (!delegate->IsIOPinned(nodeIndex, slot_idx, i == 1))
|
||
|
{
|
||
|
|
||
|
}
|
||
|
continue;
|
||
|
|
||
|
ImVec2 p = offset + (i ? GetOutputSlotPos(delegate, node, slotIndex, factor) : GetInputSlotPos(delegate, node, slotIndex, factor));
|
||
|
const float arc = 28.f * (float(i) * 0.3f + 1.0f) * (i ? 1.f : -1.f);
|
||
|
const float ofs = 0.f;
|
||
|
|
||
|
ImVec2 pts[3] = {p + ImVec2(arc + ofs, 0.f), p + ImVec2(0.f + ofs, -arc), p + ImVec2(0.f + ofs, arc)};
|
||
|
drawList->AddTriangleFilled(pts[0], pts[1], pts[2], i ? 0xFFAA5030 : 0xFF30AA50);
|
||
|
drawList->AddTriangle(pts[0], pts[1], pts[2], 0xFF000000, 2.f);
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
ImGui::SetCursorScreenPos(nodeRectangleMin);
|
||
|
float maxHeight = ImMin(viewPort.Max.y, nodeRectangleMin.y + nodeSize.y) - nodeRectangleMin.y;
|
||
|
float maxWidth = ImMin(viewPort.Max.x, nodeRectangleMin.x + nodeSize.x) - nodeRectangleMin.x;
|
||
|
ImGui::InvisibleButton("node", ImVec2(maxWidth, maxHeight));
|
||
|
// must be called right after creating the control we want to be able to move
|
||
|
bool nodeMovingActive = ImGui::IsItemActive();
|
||
|
|
||
|
// Save the size of what we have emitted and whether any of the widgets are being used
|
||
|
bool nodeWidgetsActive = (!old_any_active && ImGui::IsAnyItemActive());
|
||
|
ImVec2 nodeRectangleMax = nodeRectangleMin + nodeSize;
|
||
|
|
||
|
bool nodeHovered = false;
|
||
|
if (ImGui::IsItemHovered() && nodeOperation == NO_None && !overInput)
|
||
|
{
|
||
|
nodeHovered = true;
|
||
|
}
|
||
|
|
||
|
if (ImGui::IsWindowFocused())
|
||
|
{
|
||
|
if ((nodeWidgetsActive || nodeMovingActive) && !inMinimap)
|
||
|
{
|
||
|
if (!node.mSelected)
|
||
|
{
|
||
|
if (!io.KeyShift)
|
||
|
{
|
||
|
const auto nodeCount = delegate.GetNodeCount();
|
||
|
for (size_t i = 0; i < nodeCount; i++)
|
||
|
{
|
||
|
delegate.SelectNode(i, false);
|
||
|
}
|
||
|
}
|
||
|
delegate.SelectNode(nodeIndex, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (nodeMovingActive && io.MouseDown[0] && nodeHovered && !inMinimap)
|
||
|
{
|
||
|
if (nodeOperation != NO_MovingNodes)
|
||
|
{
|
||
|
nodeOperation = NO_MovingNodes;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const bool currentSelectedNode = node.mSelected;
|
||
|
const ImU32 node_bg_color = nodeHovered ? nodeTemplate.mBackgroundColorOver : nodeTemplate.mBackgroundColor;
|
||
|
|
||
|
drawList->AddRect(nodeRectangleMin,
|
||
|
nodeRectangleMax,
|
||
|
currentSelectedNode ? options.mSelectedNodeBorderColor : options.mNodeBorderColor,
|
||
|
options.mRounding,
|
||
|
ImDrawFlags_RoundCornersAll,
|
||
|
currentSelectedNode ? options.mBorderSelectionThickness : options.mBorderThickness);
|
||
|
|
||
|
ImVec2 imgPos = nodeRectangleMin + ImVec2(14, 25);
|
||
|
ImVec2 imgSize = nodeRectangleMax + ImVec2(-5, -5) - imgPos;
|
||
|
float imgSizeComp = std::min(imgSize.x, imgSize.y);
|
||
|
|
||
|
drawList->AddRectFilled(nodeRectangleMin, nodeRectangleMax, node_bg_color, options.mRounding);
|
||
|
/*float progress = delegate->NodeProgress(nodeIndex);
|
||
|
if (progress > FLT_EPSILON && progress < 1.f - FLT_EPSILON)
|
||
|
{
|
||
|
ImVec2 progressLineA = nodeRectangleMax - ImVec2(nodeSize.x - 2.f, 3.f);
|
||
|
ImVec2 progressLineB = progressLineA + ImVec2(nodeSize.x * factor - 4.f, 0.f);
|
||
|
drawList->AddLine(progressLineA, progressLineB, 0xFF400000, 3.f);
|
||
|
drawList->AddLine(progressLineA, ImLerp(progressLineA, progressLineB, progress), 0xFFFF0000, 3.f);
|
||
|
}*/
|
||
|
ImVec2 imgPosMax = imgPos + ImVec2(imgSizeComp, imgSizeComp);
|
||
|
|
||
|
//ImVec2 imageSize = delegate->GetEvaluationSize(nodeIndex);
|
||
|
/*float imageRatio = 1.f;
|
||
|
if (imageSize.x > 0.f && imageSize.y > 0.f)
|
||
|
{
|
||
|
imageRatio = imageSize.y / imageSize.x;
|
||
|
}
|
||
|
ImVec2 quadSize = imgPosMax - imgPos;
|
||
|
ImVec2 marge(0.f, 0.f);
|
||
|
if (imageRatio > 1.f)
|
||
|
{
|
||
|
marge.x = (quadSize.x - quadSize.y / imageRatio) * 0.5f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
marge.y = (quadSize.y - quadSize.y * imageRatio) * 0.5f;
|
||
|
}*/
|
||
|
|
||
|
//delegate->DrawNodeImage(drawList, ImRect(imgPos, imgPosMax), marge, nodeIndex);
|
||
|
|
||
|
drawList->AddRectFilled(nodeRectangleMin,
|
||
|
ImVec2(nodeRectangleMax.x, nodeRectangleMin.y + 20),
|
||
|
nodeTemplate.mHeaderColor, options.mRounding);
|
||
|
|
||
|
drawList->PushClipRect(nodeRectangleMin, ImVec2(nodeRectangleMax.x, nodeRectangleMin.y + 20), true);
|
||
|
drawList->AddText(nodeRectangleMin + ImVec2(2, 2), IM_COL32(0, 0, 0, 255), node.mName);
|
||
|
drawList->PopClipRect();
|
||
|
|
||
|
ImRect customDrawRect(nodeRectangleMin + ImVec2(options.mRounding, 20 + options.mRounding), nodeRectangleMax - ImVec2(options.mRounding, options.mRounding));
|
||
|
if (customDrawRect.Max.y > customDrawRect.Min.y && customDrawRect.Max.x > customDrawRect.Min.x)
|
||
|
{
|
||
|
delegate.CustomDraw(drawList, customDrawRect, nodeIndex);
|
||
|
}
|
||
|
/*
|
||
|
const ImTextureID bmpInfo = (ImTextureID)(uint64_t)delegate->GetBitmapInfo(nodeIndex).idx;
|
||
|
if (bmpInfo)
|
||
|
{
|
||
|
ImVec2 bmpInfoPos(nodeRectangleMax - ImVec2(26, 12));
|
||
|
ImVec2 bmpInfoSize(20, 20);
|
||
|
if (delegate->NodeIsCompute(nodeIndex))
|
||
|
{
|
||
|
drawList->AddImageQuad(bmpInfo,
|
||
|
bmpInfoPos,
|
||
|
bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f),
|
||
|
bmpInfoPos + bmpInfoSize,
|
||
|
bmpInfoPos + ImVec2(0., bmpInfoSize.y));
|
||
|
}
|
||
|
else if (delegate->NodeIs2D(nodeIndex))
|
||
|
{
|
||
|
drawList->AddImageQuad(bmpInfo,
|
||
|
bmpInfoPos,
|
||
|
bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f),
|
||
|
bmpInfoPos + bmpInfoSize,
|
||
|
bmpInfoPos + ImVec2(0., bmpInfoSize.y));
|
||
|
}
|
||
|
else if (delegate->NodeIsCubemap(nodeIndex))
|
||
|
{
|
||
|
drawList->AddImageQuad(bmpInfo,
|
||
|
bmpInfoPos + ImVec2(0., bmpInfoSize.y),
|
||
|
bmpInfoPos + bmpInfoSize,
|
||
|
bmpInfoPos + ImVec2(bmpInfoSize.x, 0.f),
|
||
|
bmpInfoPos);
|
||
|
}
|
||
|
}*/
|
||
|
return nodeHovered;
|
||
|
}
|
||
|
|
||
|
bool DrawMiniMap(ImDrawList* drawList, Delegate& delegate, ViewState& viewState, const Options& options, const ImVec2 windowPos, const ImVec2 canvasSize)
|
||
|
{
|
||
|
if (Distance(options.mMinimap.Min, options.mMinimap.Max) <= FLT_EPSILON)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const size_t nodeCount = delegate.GetNodeCount();
|
||
|
|
||
|
if (!nodeCount)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ImVec2 min(FLT_MAX, FLT_MAX);
|
||
|
ImVec2 max(-FLT_MAX, -FLT_MAX);
|
||
|
const ImVec2 margin(50, 50);
|
||
|
for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
|
||
|
{
|
||
|
const Node& node = delegate.GetNode(nodeIndex);
|
||
|
min = ImMin(min, node.mRect.Min - margin);
|
||
|
min = ImMin(min, node.mRect.Max + margin);
|
||
|
max = ImMax(max, node.mRect.Min - margin);
|
||
|
max = ImMax(max, node.mRect.Max + margin);
|
||
|
}
|
||
|
|
||
|
// add view in world space
|
||
|
const ImVec2 worldSizeView = canvasSize / viewState.mFactor;
|
||
|
const ImVec2 viewMin(-viewState.mPosition.x, -viewState.mPosition.y);
|
||
|
const ImVec2 viewMax = viewMin + worldSizeView;
|
||
|
min = ImMin(min, viewMin);
|
||
|
max = ImMax(max, viewMax);
|
||
|
const ImVec2 nodesSize = max - min;
|
||
|
const ImVec2 middleWorld = (min + max) * 0.5f;
|
||
|
const ImVec2 minScreen = windowPos + options.mMinimap.Min * canvasSize;
|
||
|
const ImVec2 maxScreen = windowPos + options.mMinimap.Max * canvasSize;
|
||
|
const ImVec2 viewSize = maxScreen - minScreen;
|
||
|
const ImVec2 middleScreen = (minScreen + maxScreen) * 0.5f;
|
||
|
const float ratioY = viewSize.y / nodesSize.y;
|
||
|
const float ratioX = viewSize.x / nodesSize.x;
|
||
|
const float factor = ImMin(ImMin(ratioY, ratioX), 1.f);
|
||
|
|
||
|
drawList->AddRectFilled(minScreen, maxScreen, IM_COL32(30, 30, 30, 200), 3, ImDrawFlags_RoundCornersAll);
|
||
|
|
||
|
for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
|
||
|
{
|
||
|
const Node& node = delegate.GetNode(nodeIndex);
|
||
|
const auto nodeTemplate = delegate.GetTemplate(node.mTemplateIndex);
|
||
|
|
||
|
ImRect rect = node.mRect;
|
||
|
rect.Min -= middleWorld;
|
||
|
rect.Min *= factor;
|
||
|
rect.Min += middleScreen;
|
||
|
|
||
|
rect.Max -= middleWorld;
|
||
|
rect.Max *= factor;
|
||
|
rect.Max += middleScreen;
|
||
|
|
||
|
drawList->AddRectFilled(rect.Min, rect.Max, nodeTemplate.mBackgroundColor, 1, ImDrawFlags_RoundCornersAll);
|
||
|
if (node.mSelected)
|
||
|
{
|
||
|
drawList->AddRect(rect.Min, rect.Max, options.mSelectedNodeBorderColor, 1, ImDrawFlags_RoundCornersAll);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add view
|
||
|
ImVec2 viewMinScreen = (viewMin - middleWorld) * factor + middleScreen;
|
||
|
ImVec2 viewMaxScreen = (viewMax - middleWorld) * factor + middleScreen;
|
||
|
drawList->AddRectFilled(viewMinScreen, viewMaxScreen, IM_COL32(255, 255, 255, 32), 1, ImDrawFlags_RoundCornersAll);
|
||
|
drawList->AddRect(viewMinScreen, viewMaxScreen, IM_COL32(255, 255, 255, 128), 1, ImDrawFlags_RoundCornersAll);
|
||
|
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
const bool mouseInMinimap = ImRect(minScreen, maxScreen).Contains(io.MousePos);
|
||
|
if (mouseInMinimap && io.MouseClicked[0])
|
||
|
{
|
||
|
const ImVec2 clickedRatio = (io.MousePos - minScreen) / viewSize;
|
||
|
const ImVec2 worldPosCenter = ImVec2(ImLerp(min.x, max.x, clickedRatio.x), ImLerp(min.y, max.y, clickedRatio.y));
|
||
|
|
||
|
ImVec2 worldPosViewMin = worldPosCenter - worldSizeView * 0.5;
|
||
|
ImVec2 worldPosViewMax = worldPosCenter + worldSizeView * 0.5;
|
||
|
if (worldPosViewMin.x < min.x)
|
||
|
{
|
||
|
worldPosViewMin.x = min.x;
|
||
|
worldPosViewMax.x = worldPosViewMin.x + worldSizeView.x;
|
||
|
}
|
||
|
if (worldPosViewMin.y < min.y)
|
||
|
{
|
||
|
worldPosViewMin.y = min.y;
|
||
|
worldPosViewMax.y = worldPosViewMin.y + worldSizeView.y;
|
||
|
}
|
||
|
if (worldPosViewMax.x > max.x)
|
||
|
{
|
||
|
worldPosViewMax.x = max.x;
|
||
|
worldPosViewMin.x = worldPosViewMax.x - worldSizeView.x;
|
||
|
}
|
||
|
if (worldPosViewMax.y > max.y)
|
||
|
{
|
||
|
worldPosViewMax.y = max.y;
|
||
|
worldPosViewMin.y = worldPosViewMax.y - worldSizeView.y;
|
||
|
}
|
||
|
viewState.mPosition = ImVec2(-worldPosViewMin.x, -worldPosViewMin.y);
|
||
|
}
|
||
|
return mouseInMinimap;
|
||
|
}
|
||
|
|
||
|
void Show(Delegate& delegate, const Options& options, ViewState& viewState, bool enabled, FitOnScreen* fit)
|
||
|
{
|
||
|
ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.f);
|
||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
|
||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f);
|
||
|
|
||
|
const ImVec2 windowPos = ImGui::GetCursorScreenPos();
|
||
|
const ImVec2 canvasSize = ImGui::GetContentRegionAvail();
|
||
|
const ImVec2 scrollRegionLocalPos(0, 0);
|
||
|
|
||
|
ImRect regionRect(windowPos, windowPos + canvasSize);
|
||
|
|
||
|
HandleZoomScroll(regionRect, viewState, options);
|
||
|
ImVec2 offset = ImGui::GetCursorScreenPos() + viewState.mPosition * viewState.mFactor;
|
||
|
captureOffset = viewState.mPosition * viewState.mFactor;
|
||
|
|
||
|
//ImGui::InvisibleButton("GraphEditorButton", canvasSize);
|
||
|
ImGui::BeginChildFrame(71711, canvasSize);
|
||
|
|
||
|
ImGui::SetCursorPos(windowPos);
|
||
|
ImGui::BeginGroup();
|
||
|
|
||
|
ImGuiIO& io = ImGui::GetIO();
|
||
|
|
||
|
// Create our child canvas
|
||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1));
|
||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(30, 30, 30, 200));
|
||
|
|
||
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||
|
ImGui::PushClipRect(regionRect.Min, regionRect.Max, true);
|
||
|
drawList->AddRectFilled(windowPos, windowPos + canvasSize, options.mBackgroundColor);
|
||
|
|
||
|
// Background or Display grid
|
||
|
if (options.mRenderGrid)
|
||
|
{
|
||
|
DrawGrid(drawList, windowPos, viewState, canvasSize, options.mGridColor, options.mGridColor2, options.mGridSize);
|
||
|
}
|
||
|
|
||
|
// Fit view
|
||
|
if (fit && ((*fit == Fit_AllNodes) || (*fit == Fit_SelectedNodes)))
|
||
|
{
|
||
|
FitNodes(delegate, viewState, canvasSize, (*fit == Fit_SelectedNodes));
|
||
|
}
|
||
|
|
||
|
if (enabled)
|
||
|
{
|
||
|
static NodeIndex hoveredNode = -1;
|
||
|
// Display links
|
||
|
drawList->ChannelsSplit(3);
|
||
|
|
||
|
// minimap
|
||
|
drawList->ChannelsSetCurrent(2); // minimap
|
||
|
const bool inMinimap = DrawMiniMap(drawList, delegate, viewState, options, windowPos, canvasSize);
|
||
|
|
||
|
// Focus rectangle
|
||
|
if (ImGui::IsWindowFocused())
|
||
|
{
|
||
|
drawList->AddRect(regionRect.Min, regionRect.Max, options.mFrameFocus, 1.f, 0, 2.f);
|
||
|
}
|
||
|
|
||
|
drawList->ChannelsSetCurrent(1); // Background
|
||
|
|
||
|
// Links
|
||
|
DisplayLinks(delegate, drawList, offset, viewState.mFactor, regionRect, hoveredNode, options);
|
||
|
|
||
|
// edit node link
|
||
|
if (nodeOperation == NO_EditingLink)
|
||
|
{
|
||
|
ImVec2 p1 = editingNodeSource;
|
||
|
ImVec2 p2 = io.MousePos;
|
||
|
drawList->AddLine(p1, p2, IM_COL32(200, 200, 200, 255), 3.0f);
|
||
|
}
|
||
|
|
||
|
// Display nodes
|
||
|
drawList->PushClipRect(regionRect.Min, regionRect.Max, true);
|
||
|
hoveredNode = -1;
|
||
|
|
||
|
SlotIndex inputSlotOver = -1;
|
||
|
SlotIndex outputSlotOver = -1;
|
||
|
NodeIndex nodeOver = -1;
|
||
|
|
||
|
const auto nodeCount = delegate.GetNodeCount();
|
||
|
for (int i = 0; i < 2; i++)
|
||
|
{
|
||
|
for (NodeIndex nodeIndex = 0; nodeIndex < nodeCount; nodeIndex++)
|
||
|
{
|
||
|
//const auto* node = &nodes[nodeIndex];
|
||
|
const auto node = delegate.GetNode(nodeIndex);
|
||
|
if (node.mSelected != (i != 0))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// node view clipping
|
||
|
ImRect nodeRect = GetNodeRect(node, viewState.mFactor);
|
||
|
nodeRect.Min += offset;
|
||
|
nodeRect.Max += offset;
|
||
|
if (!regionRect.Overlaps(nodeRect))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ImGui::PushID((int)nodeIndex);
|
||
|
SlotIndex inputSlot = -1;
|
||
|
SlotIndex outputSlot = -1;
|
||
|
|
||
|
bool overInput = (!inMinimap) && HandleConnections(drawList, nodeIndex, offset, viewState.mFactor, delegate, options, false, inputSlot, outputSlot, inMinimap);
|
||
|
|
||
|
// shadow
|
||
|
/*
|
||
|
ImVec2 shadowOffset = ImVec2(30, 30);
|
||
|
ImVec2 shadowPivot = (nodeRect.Min + nodeRect.Max) /2.f;
|
||
|
ImVec2 shadowPointMiddle = shadowPivot + shadowOffset;
|
||
|
ImVec2 shadowPointTop = ImVec2(shadowPivot.x, nodeRect.Min.y) + shadowOffset;
|
||
|
ImVec2 shadowPointBottom = ImVec2(shadowPivot.x, nodeRect.Max.y) + shadowOffset;
|
||
|
ImVec2 shadowPointLeft = ImVec2(nodeRect.Min.x, shadowPivot.y) + shadowOffset;
|
||
|
ImVec2 shadowPointRight = ImVec2(nodeRect.Max.x, shadowPivot.y) + shadowOffset;
|
||
|
|
||
|
// top left
|
||
|
drawList->AddRectFilledMultiColor(nodeRect.Min + shadowOffset, shadowPointMiddle, IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 255), IM_COL32(0, 0, 0, 0));
|
||
|
|
||
|
// top right
|
||
|
drawList->AddRectFilledMultiColor(shadowPointTop, shadowPointRight, IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 0), IM_COL32(0, 0, 0, 255));
|
||
|
|
||
|
// bottom left
|
||
|
drawList->AddRectFilledMultiColor(shadowPointLeft, shadowPointBottom, IM_COL32(0 ,0, 0, 0), IM_COL32(0, 0, 0, 255), IM_COL32(0, 0, 0, 0), IM_COL32(0,0,0,0));
|
||
|
|
||
|
// bottom right
|
||
|
drawList->AddRectFilledMultiColor(shadowPointMiddle, nodeRect.Max + shadowOffset, IM_COL32(0, 0, 0, 255), IM_COL32(0 ,0, 0, 0), IM_COL32(0,0,0,0), IM_COL32(0, 0, 0, 0));
|
||
|
*/
|
||
|
if (DrawNode(drawList, nodeIndex, offset, viewState.mFactor, delegate, overInput, options, inMinimap, regionRect))
|
||
|
{
|
||
|
hoveredNode = nodeIndex;
|
||
|
}
|
||
|
|
||
|
HandleConnections(drawList, nodeIndex, offset, viewState.mFactor, delegate, options, true, inputSlot, outputSlot, inMinimap);
|
||
|
if (inputSlot != -1 || outputSlot != -1)
|
||
|
{
|
||
|
inputSlotOver = inputSlot;
|
||
|
outputSlotOver = outputSlot;
|
||
|
nodeOver = nodeIndex;
|
||
|
}
|
||
|
|
||
|
ImGui::PopID();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
drawList->PopClipRect();
|
||
|
|
||
|
if (nodeOperation == NO_MovingNodes)
|
||
|
{
|
||
|
if (ImGui::IsMouseDragging(0, 1))
|
||
|
{
|
||
|
ImVec2 delta = io.MouseDelta / viewState.mFactor;
|
||
|
if (fabsf(delta.x) >= 1.f || fabsf(delta.y) >= 1.f)
|
||
|
{
|
||
|
delegate.MoveSelectedNodes(delta);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
drawList->ChannelsSetCurrent(0);
|
||
|
|
||
|
// quad selection
|
||
|
if (!inMinimap)
|
||
|
{
|
||
|
HandleQuadSelection(delegate, drawList, offset, viewState.mFactor, regionRect, options);
|
||
|
}
|
||
|
|
||
|
drawList->ChannelsMerge();
|
||
|
|
||
|
// releasing mouse button means it's done in any operation
|
||
|
if (nodeOperation == NO_PanView)
|
||
|
{
|
||
|
if (!io.MouseDown[2])
|
||
|
{
|
||
|
nodeOperation = NO_None;
|
||
|
}
|
||
|
}
|
||
|
else if (nodeOperation != NO_None && !io.MouseDown[0])
|
||
|
{
|
||
|
nodeOperation = NO_None;
|
||
|
}
|
||
|
|
||
|
// right click
|
||
|
if (!inMinimap && nodeOperation == NO_None && regionRect.Contains(io.MousePos) &&
|
||
|
(ImGui::IsMouseClicked(1) /*|| (ImGui::IsWindowFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Tab))*/))
|
||
|
{
|
||
|
delegate.RightClick(nodeOver, inputSlotOver, outputSlotOver);
|
||
|
}
|
||
|
|
||
|
// Scrolling
|
||
|
if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && io.MouseClicked[2] && nodeOperation == NO_None)
|
||
|
{
|
||
|
nodeOperation = NO_PanView;
|
||
|
}
|
||
|
if (nodeOperation == NO_PanView)
|
||
|
{
|
||
|
viewState.mPosition += io.MouseDelta / viewState.mFactor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ImGui::PopClipRect();
|
||
|
|
||
|
ImGui::PopStyleColor(1);
|
||
|
ImGui::PopStyleVar(2);
|
||
|
ImGui::EndGroup();
|
||
|
ImGui::EndChildFrame();
|
||
|
|
||
|
ImGui::PopStyleVar(3);
|
||
|
|
||
|
// change fit to none
|
||
|
if (fit)
|
||
|
{
|
||
|
*fit = Fit_None;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool EditOptions(Options& options)
|
||
|
{
|
||
|
bool updated = false;
|
||
|
if (ImGui::CollapsingHeader("Colors", nullptr))
|
||
|
{
|
||
|
ImColor backgroundColor(options.mBackgroundColor);
|
||
|
ImColor gridColor(options.mGridColor);
|
||
|
ImColor selectedNodeBorderColor(options.mSelectedNodeBorderColor);
|
||
|
ImColor nodeBorderColor(options.mNodeBorderColor);
|
||
|
ImColor quadSelection(options.mQuadSelection);
|
||
|
ImColor quadSelectionBorder(options.mQuadSelectionBorder);
|
||
|
ImColor defaultSlotColor(options.mDefaultSlotColor);
|
||
|
ImColor frameFocus(options.mFrameFocus);
|
||
|
|
||
|
updated |= ImGui::ColorEdit4("Background", (float*)&backgroundColor);
|
||
|
updated |= ImGui::ColorEdit4("Grid", (float*)&gridColor);
|
||
|
updated |= ImGui::ColorEdit4("Selected Node Border", (float*)&selectedNodeBorderColor);
|
||
|
updated |= ImGui::ColorEdit4("Node Border", (float*)&nodeBorderColor);
|
||
|
updated |= ImGui::ColorEdit4("Quad Selection", (float*)&quadSelection);
|
||
|
updated |= ImGui::ColorEdit4("Quad Selection Border", (float*)&quadSelectionBorder);
|
||
|
updated |= ImGui::ColorEdit4("Default Slot", (float*)&defaultSlotColor);
|
||
|
updated |= ImGui::ColorEdit4("Frame when has focus", (float*)&frameFocus);
|
||
|
|
||
|
options.mBackgroundColor = backgroundColor;
|
||
|
options.mGridColor = gridColor;
|
||
|
options.mSelectedNodeBorderColor = selectedNodeBorderColor;
|
||
|
options.mNodeBorderColor = nodeBorderColor;
|
||
|
options.mQuadSelection = quadSelection;
|
||
|
options.mQuadSelectionBorder = quadSelectionBorder;
|
||
|
options.mDefaultSlotColor = defaultSlotColor;
|
||
|
options.mFrameFocus = frameFocus;
|
||
|
}
|
||
|
|
||
|
if (ImGui::CollapsingHeader("Options", nullptr))
|
||
|
{
|
||
|
updated |= ImGui::InputFloat4("Minimap", &options.mMinimap.Min.x);
|
||
|
updated |= ImGui::InputFloat("Line Thickness", &options.mLineThickness);
|
||
|
updated |= ImGui::InputFloat("Grid Size", &options.mGridSize);
|
||
|
updated |= ImGui::InputFloat("Rounding", &options.mRounding);
|
||
|
updated |= ImGui::InputFloat("Zoom Ratio", &options.mZoomRatio);
|
||
|
updated |= ImGui::InputFloat("Zoom Lerp Factor", &options.mZoomLerpFactor);
|
||
|
updated |= ImGui::InputFloat("Border Selection Thickness", &options.mBorderSelectionThickness);
|
||
|
updated |= ImGui::InputFloat("Border Thickness", &options.mBorderThickness);
|
||
|
updated |= ImGui::InputFloat("Slot Radius", &options.mNodeSlotRadius);
|
||
|
updated |= ImGui::InputFloat("Slot Hover Factor", &options.mNodeSlotHoverFactor);
|
||
|
updated |= ImGui::InputFloat2("Zoom min/max", &options.mMinZoom);
|
||
|
updated |= ImGui::InputFloat("Slot Hover Factor", &options.mSnap);
|
||
|
|
||
|
if (ImGui::RadioButton("Curved Links", options.mDisplayLinksAsCurves))
|
||
|
{
|
||
|
options.mDisplayLinksAsCurves = !options.mDisplayLinksAsCurves;
|
||
|
updated = true;
|
||
|
}
|
||
|
if (ImGui::RadioButton("Straight Links", !options.mDisplayLinksAsCurves))
|
||
|
{
|
||
|
options.mDisplayLinksAsCurves = !options.mDisplayLinksAsCurves;
|
||
|
updated = true;
|
||
|
}
|
||
|
|
||
|
updated |= ImGui::Checkbox("Allow Quad Selection", &options.mAllowQuadSelection);
|
||
|
updated |= ImGui::Checkbox("Render Grid", &options.mRenderGrid);
|
||
|
updated |= ImGui::Checkbox("Draw IO names on hover", &options.mDrawIONameOnHover);
|
||
|
}
|
||
|
|
||
|
return updated;
|
||
|
}
|
||
|
|
||
|
} // namespace
|