379 lines
12 KiB
C++
Executable File
379 lines
12 KiB
C++
Executable File
#include "ImNodeFlow.h"
|
|
|
|
namespace ImFlow {
|
|
// -----------------------------------------------------------------------------------------------------------------
|
|
// LINK
|
|
|
|
void Link::update() {
|
|
ImVec2 start = m_left->pinPoint();
|
|
ImVec2 end = m_right->pinPoint();
|
|
float thickness = m_left->getStyle()->extra.link_thickness;
|
|
bool mouseClickState = m_inf->getSingleUseClick();
|
|
|
|
if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) &&
|
|
ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
|
m_selected = false;
|
|
|
|
if (smart_bezier_collider(ImGui::GetMousePos(), start, end, 2.5)) {
|
|
m_hovered = true;
|
|
thickness = m_left->getStyle()->extra.link_hovered_thickness;
|
|
if (mouseClickState) {
|
|
m_inf->consumeSingleUseClick();
|
|
m_selected = true;
|
|
}
|
|
} else {
|
|
m_hovered = false;
|
|
}
|
|
|
|
if (m_selected)
|
|
smart_bezier(start, end, m_left->getStyle()->extra.outline_color,
|
|
thickness +
|
|
m_left->getStyle()->extra.link_selected_outline_thickness);
|
|
smart_bezier(start, end, m_left->getStyle()->color, thickness);
|
|
|
|
if (m_selected && ImGui::IsKeyPressed(ImGuiKey_Delete, false))
|
|
m_right->deleteLink();
|
|
}
|
|
|
|
Link::~Link() { m_left->deleteLink(); }
|
|
|
|
// -----------------------------------------------------------------------------------------------------------------
|
|
// BASE NODE
|
|
|
|
bool BaseNode::isHovered() {
|
|
ImVec2 paddingTL = {m_style->padding.x, m_style->padding.y};
|
|
ImVec2 paddingBR = {m_style->padding.z, m_style->padding.w};
|
|
return ImGui::IsMouseHoveringRect(
|
|
m_inf->grid2screen(m_pos - paddingTL),
|
|
m_inf->grid2screen(m_pos + m_size + paddingBR));
|
|
}
|
|
|
|
void BaseNode::update() {
|
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
|
ImGui::PushID(this);
|
|
bool mouseClickState = m_inf->getSingleUseClick();
|
|
ImVec2 offset = m_inf->grid2screen({0.f, 0.f});
|
|
ImVec2 paddingTL = {m_style->padding.x, m_style->padding.y};
|
|
ImVec2 paddingBR = {m_style->padding.z, m_style->padding.w};
|
|
|
|
draw_list->ChannelsSetCurrent(1); // Foreground
|
|
ImGui::SetCursorScreenPos(offset + m_pos);
|
|
|
|
ImGui::BeginGroup();
|
|
|
|
// Header
|
|
ImGui::BeginGroup();
|
|
ImGui::TextColored(m_style->header_title_color, m_title.c_str());
|
|
ImGui::Spacing();
|
|
ImGui::EndGroup();
|
|
float headerH = ImGui::GetItemRectSize().y;
|
|
float titleW = ImGui::GetItemRectSize().x;
|
|
|
|
// Inputs
|
|
ImGui::BeginGroup();
|
|
for (auto &p : m_ins) {
|
|
p->setPos(ImGui::GetCursorPos());
|
|
p->update();
|
|
}
|
|
for (auto &p : m_dynamicIns) {
|
|
if (p.first == 1) {
|
|
p.second->setPos(ImGui::GetCursorPos());
|
|
p.second->update();
|
|
p.first = 0;
|
|
}
|
|
}
|
|
ImGui::EndGroup();
|
|
ImGui::SameLine();
|
|
|
|
// Content
|
|
ImGui::BeginGroup();
|
|
draw();
|
|
ImGui::Dummy(ImVec2(0.f, 0.f));
|
|
ImGui::EndGroup();
|
|
ImGui::SameLine();
|
|
|
|
// Outputs
|
|
float maxW = 0.0f;
|
|
for (auto &p : m_outs) {
|
|
float w = p->calcWidth();
|
|
if (w > maxW)
|
|
maxW = w;
|
|
}
|
|
for (auto &p : m_dynamicOuts) {
|
|
float w = p.second->calcWidth();
|
|
if (w > maxW)
|
|
maxW = w;
|
|
}
|
|
ImGui::BeginGroup();
|
|
for (auto &p : m_outs) {
|
|
// FIXME: This looks horrible
|
|
if ((m_pos + ImVec2(titleW, 0) + m_inf->getGrid().scroll()).x <
|
|
ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW)
|
|
p->setPos(ImGui::GetCursorPos() + ImGui::GetWindowPos() +
|
|
ImVec2(maxW - p->calcWidth(), 0.f));
|
|
else
|
|
p->setPos(ImVec2((m_pos + ImVec2(titleW - p->calcWidth(), 0) +
|
|
m_inf->getGrid().scroll())
|
|
.x,
|
|
ImGui::GetCursorPos().y + ImGui::GetWindowPos().y));
|
|
p->update();
|
|
}
|
|
for (auto &p : m_dynamicOuts) {
|
|
// FIXME: This looks horrible
|
|
if ((m_pos + ImVec2(titleW, 0) + m_inf->getGrid().scroll()).x <
|
|
ImGui::GetCursorPos().x + ImGui::GetWindowPos().x + maxW)
|
|
p.second->setPos(ImGui::GetCursorPos() + ImGui::GetWindowPos() +
|
|
ImVec2(maxW - p.second->calcWidth(), 0.f));
|
|
else
|
|
p.second->setPos(
|
|
ImVec2((m_pos + ImVec2(titleW - p.second->calcWidth(), 0) +
|
|
m_inf->getGrid().scroll())
|
|
.x,
|
|
ImGui::GetCursorPos().y + ImGui::GetWindowPos().y));
|
|
p.second->update();
|
|
p.first -= 1;
|
|
}
|
|
|
|
ImGui::EndGroup();
|
|
|
|
ImGui::EndGroup();
|
|
m_size = ImGui::GetItemRectSize();
|
|
ImVec2 headerSize = ImVec2(m_size.x + paddingBR.x, headerH);
|
|
|
|
// Background
|
|
draw_list->ChannelsSetCurrent(0);
|
|
draw_list->AddRectFilled(offset + m_pos - paddingTL,
|
|
offset + m_pos + m_size + paddingBR, m_style->bg,
|
|
m_style->radius);
|
|
draw_list->AddRectFilled(offset + m_pos - paddingTL,
|
|
offset + m_pos + headerSize, m_style->header_bg,
|
|
m_style->radius, ImDrawFlags_RoundCornersTop);
|
|
|
|
ImU32 col = m_style->border_color;
|
|
float thickness = m_style->border_thickness;
|
|
ImVec2 ptl = paddingTL;
|
|
ImVec2 pbr = paddingBR;
|
|
if (m_selected) {
|
|
col = m_style->border_selected_color;
|
|
thickness = m_style->border_selected_thickness;
|
|
}
|
|
if (thickness < 0.f) {
|
|
ptl.x -= thickness / 2;
|
|
ptl.y -= thickness / 2;
|
|
pbr.x -= thickness / 2;
|
|
pbr.y -= thickness / 2;
|
|
thickness *= -1.f;
|
|
}
|
|
draw_list->AddRect(offset + m_pos - ptl, offset + m_pos + m_size + pbr, col,
|
|
m_style->radius, 0, thickness);
|
|
|
|
if (!ImGui::IsKeyDown(ImGuiKey_LeftCtrl) &&
|
|
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
|
!m_inf->on_selected_node())
|
|
selected(false);
|
|
|
|
if (isHovered()) {
|
|
m_inf->hoveredNode(this);
|
|
if (mouseClickState) {
|
|
selected(true);
|
|
m_inf->consumeSingleUseClick();
|
|
}
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(ImGuiKey_Delete) && !ImGui::IsAnyItemActive() &&
|
|
isSelected())
|
|
destroy();
|
|
|
|
bool onHeader = ImGui::IsMouseHoveringRect(offset + m_pos - paddingTL,
|
|
offset + m_pos + headerSize);
|
|
if (onHeader && mouseClickState) {
|
|
m_inf->consumeSingleUseClick();
|
|
m_dragged = true;
|
|
m_inf->draggingNode(true);
|
|
}
|
|
if (m_dragged || (m_selected && m_inf->isNodeDragged())) {
|
|
float step =
|
|
m_inf->getStyle().grid_size / m_inf->getStyle().grid_subdivisions;
|
|
m_posTarget += ImGui::GetIO().MouseDelta;
|
|
// "Slam" The position
|
|
m_pos.x = round(m_posTarget.x / step) * step;
|
|
m_pos.y = round(m_posTarget.y / step) * step;
|
|
|
|
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
|
m_dragged = false;
|
|
m_inf->draggingNode(false);
|
|
m_posTarget = m_pos;
|
|
}
|
|
}
|
|
ImGui::PopID();
|
|
|
|
// Resolve output pins values
|
|
for (auto &p : m_outs)
|
|
p->resolve();
|
|
for (auto &p : m_dynamicOuts)
|
|
p.second->resolve();
|
|
|
|
// Deleting dead pins
|
|
m_dynamicIns.erase(
|
|
std::remove_if(m_dynamicIns.begin(), m_dynamicIns.end(),
|
|
[](const std::pair<int, std::shared_ptr<Pin>> &p) {
|
|
return p.first == 0;
|
|
}),
|
|
m_dynamicIns.end());
|
|
m_dynamicOuts.erase(
|
|
std::remove_if(m_dynamicOuts.begin(), m_dynamicOuts.end(),
|
|
[](const std::pair<int, std::shared_ptr<Pin>> &p) {
|
|
return p.first == 0;
|
|
}),
|
|
m_dynamicOuts.end());
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------------------------
|
|
// HANDLER
|
|
|
|
int ImNodeFlow::m_instances = 0;
|
|
|
|
bool ImNodeFlow::on_selected_node() {
|
|
return std::any_of(m_nodes.begin(), m_nodes.end(), [](const auto &n) {
|
|
return n.second->isSelected() && n.second->isHovered();
|
|
});
|
|
}
|
|
|
|
bool ImNodeFlow::on_free_space() {
|
|
return std::all_of(m_nodes.begin(), m_nodes.end(),
|
|
[](const auto &n) { return !n.second->isHovered(); }) &&
|
|
std::all_of(m_links.begin(), m_links.end(),
|
|
[](const auto &l) { return !l.lock()->isHovered(); });
|
|
}
|
|
|
|
ImVec2 ImNodeFlow::screen2grid(const ImVec2 &p) {
|
|
if (ImGui::GetCurrentContext() == m_context.getRawContext())
|
|
return p - m_context.scroll();
|
|
else
|
|
return p - m_context.origin() - m_context.scroll() * m_context.scale();
|
|
}
|
|
|
|
ImVec2 ImNodeFlow::grid2screen(const ImVec2 &p) {
|
|
if (ImGui::GetCurrentContext() == m_context.getRawContext())
|
|
return p + m_context.scroll();
|
|
else
|
|
return p + m_context.origin() + m_context.scroll() * m_context.scale();
|
|
}
|
|
|
|
void ImNodeFlow::addLink(std::shared_ptr<Link> &link) {
|
|
m_links.push_back(link);
|
|
}
|
|
|
|
void ImNodeFlow::update() {
|
|
// Updating looping stuff
|
|
m_hovering = nullptr;
|
|
m_hoveredNode = nullptr;
|
|
m_draggingNode = m_draggingNodeNext;
|
|
m_singleUseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
|
|
|
// Create child canvas
|
|
m_context.begin();
|
|
|
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
|
|
|
// Display grid
|
|
ImVec2 win_pos = ImGui::GetCursorScreenPos();
|
|
ImVec2 canvas_sz = ImGui::GetWindowSize();
|
|
for (float x = fmodf(m_context.scroll().x, m_style.grid_size);
|
|
x < canvas_sz.x; x += m_style.grid_size)
|
|
draw_list->AddLine(ImVec2(x, 0.0f) + win_pos,
|
|
ImVec2(x, canvas_sz.y) + win_pos, m_style.colors.grid);
|
|
for (float y = fmodf(m_context.scroll().y, m_style.grid_size);
|
|
y < canvas_sz.y; y += m_style.grid_size)
|
|
draw_list->AddLine(ImVec2(0.0f, y) + win_pos,
|
|
ImVec2(canvas_sz.x, y) + win_pos, m_style.colors.grid);
|
|
for (float x = fmodf(m_context.scroll().x,
|
|
m_style.grid_size / m_style.grid_subdivisions);
|
|
x < canvas_sz.x; x += m_style.grid_size / m_style.grid_subdivisions)
|
|
draw_list->AddLine(ImVec2(x, 0.0f) + win_pos,
|
|
ImVec2(x, canvas_sz.y) + win_pos,
|
|
m_style.colors.subGrid);
|
|
for (float y = fmodf(m_context.scroll().y,
|
|
m_style.grid_size / m_style.grid_subdivisions);
|
|
y < canvas_sz.y; y += m_style.grid_size / m_style.grid_subdivisions)
|
|
draw_list->AddLine(ImVec2(0.0f, y) + win_pos,
|
|
ImVec2(canvas_sz.x, y) + win_pos,
|
|
m_style.colors.subGrid);
|
|
|
|
// Update and draw nodes
|
|
// TODO: I don't like this
|
|
draw_list->ChannelsSplit(2);
|
|
for (auto &node : m_nodes) {
|
|
node.second->update();
|
|
}
|
|
std::erase_if(m_nodes, [](const auto &n) { return n.second->toDestroy(); });
|
|
draw_list->ChannelsMerge();
|
|
for (auto &node : m_nodes) {
|
|
node.second->updatePublicStatus();
|
|
}
|
|
|
|
// Update and draw links
|
|
for (auto &l : m_links) {
|
|
if (!l.expired())
|
|
l.lock()->update();
|
|
}
|
|
|
|
// Links drop-off
|
|
if (m_dragOut && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
|
if (!m_hovering) {
|
|
if (on_free_space() && m_droppedLinkPopUp) {
|
|
if (m_droppedLinkPupUpComboKey == ImGuiKey_None ||
|
|
ImGui::IsKeyDown(m_droppedLinkPupUpComboKey)) {
|
|
m_droppedLinkLeft = m_dragOut;
|
|
ImGui::OpenPopup("DroppedLinkPopUp");
|
|
}
|
|
}
|
|
} else
|
|
m_dragOut->createLink(m_hovering);
|
|
}
|
|
|
|
// Links drag-out
|
|
if (!m_draggingNode && m_hovering && !m_dragOut &&
|
|
ImGui::IsMouseClicked(ImGuiMouseButton_Left))
|
|
m_dragOut = m_hovering;
|
|
if (m_dragOut) {
|
|
if (m_dragOut->getType() == PinType_Output)
|
|
smart_bezier(m_dragOut->pinPoint(), ImGui::GetMousePos(),
|
|
m_dragOut->getStyle()->color,
|
|
m_dragOut->getStyle()->extra.link_dragged_thickness);
|
|
else
|
|
smart_bezier(ImGui::GetMousePos(), m_dragOut->pinPoint(),
|
|
m_dragOut->getStyle()->color,
|
|
m_dragOut->getStyle()->extra.link_dragged_thickness);
|
|
|
|
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))
|
|
m_dragOut = nullptr;
|
|
}
|
|
|
|
// Right-click PopUp
|
|
if (m_rightClickPopUp && ImGui::IsMouseClicked(ImGuiMouseButton_Right) &&
|
|
ImGui::IsWindowHovered()) {
|
|
m_hoveredNodeAux = m_hoveredNode;
|
|
ImGui::OpenPopup("RightClickPopUp");
|
|
}
|
|
if (ImGui::BeginPopup("RightClickPopUp")) {
|
|
m_rightClickPopUp(m_hoveredNodeAux);
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// Dropped Link PopUp
|
|
if (ImGui::BeginPopup("DroppedLinkPopUp")) {
|
|
m_droppedLinkPopUp(m_droppedLinkLeft);
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
// Removing dead Links
|
|
m_links.erase(
|
|
std::remove_if(m_links.begin(), m_links.end(),
|
|
[](const std::weak_ptr<Link> &l) { return l.expired(); }),
|
|
m_links.end());
|
|
|
|
m_context.end();
|
|
}
|
|
} // namespace ImFlow
|