#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> &p) { return p.first == 0; }), m_dynamicIns.end()); m_dynamicOuts.erase( std::remove_if(m_dynamicOuts.begin(), m_dynamicOuts.end(), [](const std::pair> &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) { 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 &l) { return l.expired(); }), m_links.end()); m_context.end(); } } // namespace ImFlow