Skip to content

File NodeEditor.cpp

File List > item > NodeEditor.cpp

Go to the documentation of this file

#include "NodeEditor.h"
#include "Input.h"

#include <cstdint>
#include <string>

#include "operator.h"
#include "ImGui/imgui_internal.h"

const ImVec2 ImguiNodes::GetInPinPos(const ImVec2& _header_size, float _offset, int _idx)
{
    ImVec2 inp_curs = min + _header_size;
    inp_curs.y += _offset * _idx;
    return inp_curs;
}

ImguiNodes::~ImguiNodes()
{
}

void ImguiNodes::UpdateStates()
{
    int i_off = 0;
    for (int i = 0; auto & i_p : m_node->n_in) {

        if (m_states.find(&i_p) == m_states.end())
            m_states[&i_p] = {};

        const bool is_connected = Nodes::n_in_link.find(&i_p) != Nodes::n_in_link.end();                // is this pin connected
        Parameters* tar_o = is_connected ? Nodes::GetParamPtr(Nodes::n_in_link[&i_p], false) : nullptr; // get the link target ptr
        const Nodes::ParaLink tar_link = is_connected ? Nodes::n_in_link[&i_p] : Nodes::ParaLink{};
        if (!is_connected) i_off++;

        m_states[&i_p] = { tar_o, is_connected, i, tar_link, std::to_string(reinterpret_cast<std::uintptr_t>(&i_p)) + "p", std::to_string(reinterpret_cast<std::uintptr_t>(&i_p)) + "s" };

        i++;
        i_off++;
    }

    int o_off = 0;
    for (int i = 0; auto & o_p : m_node->n_out) {
        if (m_states.find(&o_p) == m_states.end())
            m_states[&o_p] = {};

        const bool is_connected = Nodes::n_out_link.find(&o_p) != Nodes::n_out_link.end();
        Parameters* tar_i = is_connected ? Nodes::GetParamPtr(Nodes::n_out_link[&o_p], true) : nullptr; // get the link target ptr
        const Nodes::ParaLink tar_link = is_connected ? Nodes::n_out_link[&o_p] : Nodes::ParaLink{};

        //m_states[&o_p] = { tar_i, is_connected, i, tar_link, std::to_string((int)&o_p) + "p"};
        m_states[&o_p] = { tar_i, is_connected, i, tar_link, std::to_string(reinterpret_cast<std::uintptr_t>(&o_p)) + "p"};

        i++;
        o_off++;
    }

    max_pin_offset = std::max(i_off, o_off);
}

const ImVec2 ImguiNodes::GetOutPinPos(const ImVec2& _header_size, float _offset, int _idx)
{
    ImVec2 outp_curs = ImVec2(max.x, min.y) + _header_size;
    outp_curs.y += _offset * _idx;
    return outp_curs;
}

NodeEditor::NodeEditor(NodeEditorType type)
    : _type(type)
{
    Zoom(3.0f);

    Nodes* add = new Nodes{ "Add", SCL_MATH_NODE };
    add->PushIn({ INT_PARA, "int1" });
    add->PushIn({ INT_PARA, "int2" });
    add->PushIn({ VEC2_PARA, "vec2_test" });
    add->PushIn({ VEC3_PARA, "vec3_test" });
    add->PushOut({ INT_PARA, "int3" });
    add->PushOut({ VEC4_PARA, "?adwasdw" });
    PushNode(add);

    Nodes* sub = new Nodes{ "Subtract", VEC_MATH_NODE };
    sub->SetPos({ 100,50 });
    sub->PushOut({ FLOAT_PARA, "outp" });
    sub->PushOut({ VEC2_PARA, "outp2" });
    add->LinkIn(0, sub, 0);
    add->LinkIn(2, sub, 1);
    PushNode(sub);

    Nodes* sub2 = new Nodes{ "Subtract2", VEC_MATH_NODE };
    sub2->SetPos({ 100,-50 });
    sub2->PushOut({ VEC3_PARA, "outp" });
    sub2->PushOut({ VEC4_PARA, "outp2" });
    add->LinkIn(3, sub2, 0);
    PushNode(sub2);

    Nodes* mix = new Nodes{ "Mixture", VEC_MATH_NODE };
    mix->SetPos({ -100, 0 });
    mix->PushIn({ VEC4_PARA, "inp" });
    mix->PushIn({ VEC4_PARA, "inp2" });
    mix->PushIn({ VEC2_PARA, "inp3" });
    mix->LinkIn(0, sub2, 1);
    mix->LinkIn(1, add, 1);
    PushNode(mix);

    for (auto& n : _node_pool) n.UpdateStates();
}

void NodeEditor::ResetState()
{
    if (!is_editing_pin_in || is_editing_pin_out)
        editing_in_pin = nullptr;

    if (!is_editing_pin_out || is_editing_pin_in)
        editing_out_pin = nullptr;

    if (!is_editing_pin_in && !is_editing_pin_out)
        pressed_pin = nullptr;

    is_editing_pin_in = is_editing_pin_out = false;
    is_hover_on_in = is_hover_on_out = is_press_on_in = is_press_on_out = false;
    hovered_pin = nullptr;
    editing_cn_type_b = editing_cn_type;
    editing_cn_type = O_I;
    tar_pin_pos = { 0,0 };

    if (Input::IsKeyPressed(Input::SHIFT))
        is_node_movable = true;
}

float NodeEditor::th_curvity = 1.6f;
float NodeEditor::th_offset = 5;
float NodeEditor::th_rounding = 2;

void NodeEditor::Render(const Context& ctx, const char* _lable, const ImVec2& _size /*= {0,0}*/)
{
    ImGui::Separator();
    if (ImGui::BeginChild(_lable, ImGui::GetContentRegionAvail(), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground)) {


        const ImVec2 pos = ImGui::GetCursorScreenPos();
        NE_size = ImGui::GetContentRegionAvail() + ImVec2(0, 20);
        NE_center = pos + NE_size / 2.0f;


        ImGui::GetForegroundDrawList()->AddRectFilled(pos, pos + ImGui::GetContentRegionAvail(), IM_COL32(255, 255, 255, 20), 5.0f);
        ImGui::Text(_lable);
        ImGui::SliderFloat("B_curvity", &th_curvity, 0, 15);
        ImGui::SliderFloat("N_rounding", &th_rounding, 0, 15);
        ImGui::SliderFloat("P_offset", &th_offset, 2, 10);

#ifdef _DEBUG
        ImGui::Text("in: %i | out: %i | type: %i", is_editing_pin_in, is_editing_pin_out, editing_cn_type);
        ImGui::Text("in_p: 0x%x | out_p: 0x%x", editing_in_pin, editing_out_pin);
        ImGui::Text("act_node: x0%x", (editing_node) ? (*editing_node).m_node : nullptr);
        ImGui::Text("press: 0x%x", pressed_pin);
        ImGui::Text("hover: 0x%x", hovered_pin);
        ImGui::Text("H_in: %i | H_out: %i", is_hover_on_in, is_hover_on_out);
        ImGui::Text("P_in: %i | P_out: %i", is_press_on_in, is_press_on_out);
#endif

        ApplyTransform();
        Parameters* pressed_pin_b = pressed_pin;
        Parameters* hovered_pin_b = hovered_pin;
        Parameters* editing_in_pin_b = editing_in_pin;
        Parameters* editing_out_pin_b = editing_out_pin;
        const bool is_hover_on_in_b = is_hover_on_in;
        ResetState();

        ImFont* font = ImGui::GetIO().Fonts->Fonts[1];
        ImGui::PushFont(font);
        ImGui::GetStyle().Colors[ImGuiCol_Text] = ImVec4(0.9f, 0.9f, 0.9f, 1);

        const float rounding = th_rounding * o_scale[0];
        const float pin_offset = th_offset * o_scale[0];
        const ImVec2 pin_size = ImVec2(0.1f, 0.1f) * o_scale;
        const ImVec2 head_size = ImVec2(0, 15) * o_scale;
        const ImVec2 handle_offset = ImVec2(th_curvity * 10, 0) * o_scale;

        const ImVec2 mouse = ImGui::GetMousePos() - ImGui::GetCursorScreenPos();

        const glm::vec2 pos_trans = o_position * o_scale;

        for (auto& node : _node_pool) {
            //RenderNode(node);

            const float pin_list_size = node.max_pin_offset * th_offset;

            node.min = NE_center + o_Transform * (node->o_position * glm::vec2(-1, 1) - (ImVec2(50, 15 + pin_list_size) * node->o_scale / 2.0f));
            node.header = node.min - ImVec2(0, 5) * o_scale;
            node.max = NE_center + o_Transform * (node->o_position * glm::vec2(-1, 1) + (ImVec2(50, 15 + pin_list_size) * node->o_scale / 2.0f));

            const ImDrawFlags flags = node->is_open ? ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight : ImDrawFlags_None;
            // [  HEAD  ]
            ImGui::GetWindowDrawList()->AddRectFilled(
                node.header,
                ImVec2(node.max.x, node.min.y),
                ImguiNodes::n_color_list[node->n_type],
                rounding,
                flags
            );

            const ImVec2 arror_up = node.min - ImVec2(-2.5f, 4.0f) * o_scale - ImVec2(8, 0.2f);
            const ImVec2 arror_dn = node.min - ImVec2(-3.0f, 4.2f) * o_scale - ImVec2(8, 0.2f);
            ImGui::GetWindowDrawList()->AddText(font, 4 * o_scale[0], node.min - ImVec2(-6, 4.5) * o_scale, IM_COL32(255, 255, 255, 255), node->n_name.c_str());
            ImGui::RenderArrow(ImGui::GetWindowDrawList(), node->is_open ? arror_up : arror_dn, IM_COL32(255, 255, 255, 255), node->is_open ? ImGuiDir_Down : ImGuiDir_Right, o_scale[0] * 0.2f);

            if (Input::IsMouseClicked() && Input::IsMousePressed(Input::MouseButtons::LMB))
                if (node.header < ImGui::GetMousePos() && ImGui::GetMousePos() < node.max) {
                    if (ImGui::GetMousePos() < ImVec2(node.header.x + ((float)node.max.x - node.header.x) * 0.125f, node.min.y))
                        node->is_open = !node->is_open;
                    else
                        active_node_id = node->n_id;

                    no_node_clicked &= false;
                }

            //[  Outline  ]
            if (node->n_id == active_node_id)
                ImGui::GetWindowDrawList()->AddRect(
                    node.header,
                    node->is_open ? node.max : ImVec2(node.max.x, node.min.y),
                    IM_COL32(255, 255, 255, 255),
                    rounding,
                    0,
                    2.0f
                );

            // [  BODY  ]
            if (node->is_open) {
                ImGui::GetWindowDrawList()->AddRectFilled(
                    node.min,
                    node.max,
                    IM_COL32(10, 10, 10, 255),
                    rounding,
                    ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight
                );

                ImVec2 inp_curs = node.min + head_size;
                const float margin = (node.header.y - node.min.y) / 2;
                for (auto& i_p : node->n_in) {

                    const bool is_connected = node.m_states[&i_p].p_connected;                  // is this pin connected
                    Parameters* tar_o = node.m_states[&i_p].p_tar;                              // get the link target ptr

                    bool hovered = false;
                    const bool click = ImGui::PinButton(i_p.para_name.c_str(), node.m_states[&i_p].p_ID.c_str(), true, inp_curs, pin_size, false, ImguiNodes::pin_color_list[i_p.para_type], &hovered, is_connected);

#ifdef _DEBUG
                    if (hovered) {
                        // for debug
                        ImGui::Text("this_i: 0x%x", &i_p);
                        ImGui::Text(is_connected ? "C" : "NC");
                        ImGui::Text("tar_o: 0x%x", tar_o);
                    }
#endif


                    if (click) {                                  // if on click, update editing event
                        pressed_pin = &i_p;
                        is_press_on_in = true;
                        if (is_connected) {
                            is_editing_pin_out |= true;
                            editing_out_pin = tar_o;
                            editing_cn_type = O_M;
                            Nodes::ParaLink& tar_link = node.m_states[&i_p].p_link;
                            tar_pin_pos = _node_pool[_node_index_cache[tar_link.first]].GetOutPinPos(head_size, pin_offset, tar_link.second);
                            editing_para_type = tar_o->para_type;
                        }
                        else {
                            is_editing_pin_in |= true;
                            editing_in_pin = &i_p;
                            editing_cn_type = M_I;
                            tar_pin_pos = inp_curs;
                        }
                    }

                    if (hovered) {
                        hovered_node = &node;
                        hovered_pin = &i_p;
                        is_hover_on_in = true;
                    }

                    if (is_connected) {

                        const Nodes::ParaLink tar_link = node.m_states[&i_p].p_link;

                        ImVec2 start_p;

                        if (tar_link.first->is_open)
                            start_p = _node_pool[_node_index_cache[tar_link.first]].GetOutPinPos(head_size, pin_offset, tar_link.second);
                        else
                            start_p = ImVec2(_node_pool[_node_index_cache[tar_link.first]].max.x, _node_pool[_node_index_cache[tar_link.first]].min.y) + ImVec2(margin, margin);

                        if (!click && editing_in_pin != &i_p) {
                            ImGui::GetWindowDrawList()->AddBezierCubic(                     // static rendering: out -> in
                                start_p,
                                start_p + ImVec2(th_curvity * 10, 0) * o_scale,
                                inp_curs - ImVec2(th_curvity * 10, 0) * o_scale,
                                inp_curs,
                                ImguiNodes::pin_color_list[i_p.para_type],
                                0.5f * o_scale[0]
                            );
                        }
                    }

                    //      create connection     |           break connection through output
                    if (Input::IsMousePressed() && (editing_in_pin == &i_p)) {
                        tar_pin_pos = inp_curs;
                        editing_node = &node;
                        editing_para_type = i_p.para_type;
                    }

                    // [ Slider ]
                    if (!is_connected) {
                        inp_curs.y += pin_offset;
                        ImGui::SetCursorScreenPos(inp_curs + ImVec2(o_scale[0] * 2, 0));
                        is_node_movable &= !UI::ParaInput::RenderParam(&i_p, node.m_states[&i_p].p_s_ID.c_str(), o_scale[0] / 3, 1.7f);
                    }

                    inp_curs.y += pin_offset;
                }

                ImVec2 outp_curs = ImVec2(node.max.x, node.min.y) + head_size;
                for (auto& o_p : node->n_out) {
                    const bool is_connected = node.m_states[&o_p].p_connected;
                    Parameters* tar_i = node.m_states[&o_p].p_tar; // get the link target ptr
                    Nodes* tar_n = is_connected ? Nodes::n_out_link[&o_p].first : nullptr;

                    bool hovered = false;
                    const bool click = ImGui::PinButton(o_p.para_name.c_str(), node.m_states[&o_p].p_ID.c_str(), true, outp_curs, pin_size, true, ImguiNodes::pin_color_list[o_p.para_type], &hovered, is_connected);

#ifdef _DEBUG
                    if (hovered) {
                        ImGui::Text("this_o: 0x%x", &o_p);
                        ImGui::Text(is_connected ? "C" : "NC");
                        ImGui::Text("tar_i: 0x%x", tar_i);
                    }
#endif // _DEBUG

                    if (click) {
                        pressed_pin = &o_p;
                        is_press_on_out = true;
                        if (is_connected) {
                            editing_in_pin = tar_i;
                            is_editing_pin_in |= true;
                            editing_cn_type = M_I;
                        }
                        else {
                            editing_out_pin = &o_p;
                            is_editing_pin_out |= true;
                            editing_cn_type = O_M;
                        }
                    }

                    if (hovered) {
                        hovered_node = &node;
                        hovered_pin = &o_p;
                        is_hover_on_out = true;
                    }

                    if (Input::IsMousePressed() && editing_out_pin == &o_p) {
                        tar_pin_pos = outp_curs;
                        editing_node = &node;
                        editing_para_type = o_p.para_type;

                    }

                    outp_curs.y += pin_offset;
                }
            }
            else { // for closed nods
                const float margin = (node.header.y - node.min.y) / 2;
                const ImVec2 pin_pos = node.header - ImVec2(margin, margin);
                for (auto& i : node->n_in) {
                    if (Nodes::n_in_link.find(&i) != Nodes::n_in_link.end()) {
                        Nodes::ParaLink& tar_link = Nodes::n_in_link[&i];

                        ImVec2 end_p;

                        if (tar_link.first->is_open)
                            end_p = _node_pool[_node_index_cache[tar_link.first]].GetOutPinPos(head_size, pin_offset, tar_link.second);
                        else
                            end_p = ImVec2(_node_pool[_node_index_cache[tar_link.first]].max.x, _node_pool[_node_index_cache[tar_link.first]].min.y) + ImVec2(margin, margin);

                        //DEBUG(_node_pool[_node_index_cache[tar_link.first]]->n_name)
                        ImGui::GetWindowDrawList()->AddBezierCubic(
                            end_p,
                            end_p + ImVec2(th_curvity * 10, 0) * o_scale,
                            pin_pos - ImVec2(th_curvity * 10, 0) * o_scale,
                            pin_pos,
                            IM_COL32(255, 255, 255, 255),
                            0.5f * o_scale[0]
                        );
                    }
                }
            }


            //[  Move  ]
            if (node->n_id == active_node_id && is_node_movable && Input::IsMousePressed(Input::MouseButtons::LMB))
                if (!is_editing_pin_in && !is_editing_pin_out) {
                    const glm::vec2 mouse_delta = glm::vec2(Input::GetDeltaMouseX(), Input::GetDeltaMouseY());
                    node->Move((glm::vec2(-1, 1) / o_scale) * mouse_delta);
                }

        }

        //[ Active connection ] & [ Break-Connect Opr ]
        switch (editing_cn_type_b)
        {
        case NodeEditor::O_I:

            break;
        case NodeEditor::O_M:
            if (Input::IsMouseLeft()) {
                if (hovered_pin_b == nullptr) {                         // connect to nothing
                    Nodes::BreakLink(pressed_pin_b, Nodes::IN);
                    goto skip_O_M;
                }

                if (is_hover_on_out) {                                  // meaningless
                    Nodes::BreakLink(pressed_pin_b, Nodes::OUT);
                    goto skip_O_M;
                }

                if (hovered_node != editing_node) {                                                  // connect to other pins
                    Nodes::BreakLink(editing_out_pin_b, Nodes::OUT);
                    const int self_loc = Nodes::GetParamLoc((*editing_node).m_node, editing_out_pin_b, Nodes::OUT);
                    const int tar_loc = Nodes::GetParamLoc((*hovered_node).m_node, hovered_pin_b, Nodes::IN);

                    (*hovered_node).m_node->LinkIn(tar_loc, (*editing_node).m_node, self_loc);
                    goto skip_O_M;
                }

            skip_O_M:
                if (editing_node)editing_node->UpdateStates();
                if (hovered_node)hovered_node->UpdateStates();
                editing_node = nullptr;
                editing_out_pin = editing_in_pin = nullptr;
                is_editing_pin_in = is_editing_pin_out = false;
            }
            else {
                RenderMark(ADD_MARK, false);
                ImGui::GetWindowDrawList()->AddBezierCubic(
                    tar_pin_pos,
                    tar_pin_pos + handle_offset,
                    ImGui::GetMousePos() - handle_offset,      // handle merge to cursor: out -> cursor
                    ImGui::GetMousePos(),
                    ImguiNodes::pin_color_list[editing_para_type],
                    0.5f * o_scale[0]
                );
            }

            break;
        case NodeEditor::M_I:
            if (Input::IsMouseLeft()) {
                if (hovered_pin_b == nullptr) {                         //connect to nothing
                    Nodes::BreakLink(editing_in_pin_b, Nodes::IN);
                    goto skip_M_I;
                }

                if (is_hover_on_in) {
                    Nodes::BreakLink(editing_in_pin_b, Nodes::IN);      // meaningless
                    goto skip_M_I;
                }


                if (hovered_pin_b != pressed_pin_b) {                   // connect to other pins
                    Nodes::BreakLink(editing_in_pin_b, Nodes::IN);
                    const int self_loc = Nodes::GetParamLoc((*hovered_node).m_node, hovered_pin_b, Nodes::OUT);         //self target location
                    const int tar_loc = Nodes::GetParamLoc((*editing_node).m_node, editing_in_pin_b, Nodes::IN);        //get target location

                    (*hovered_node).m_node->LinkOut(self_loc, (*editing_node).m_node, tar_loc);
                    goto skip_M_I;
                }

            skip_M_I:
                if (editing_node)editing_node->UpdateStates();
                if (hovered_node)hovered_node->UpdateStates();
                editing_node = nullptr;
                editing_out_pin = editing_in_pin = nullptr;
                is_editing_pin_in = is_editing_pin_out = false;
            }
            else {
                RenderMark(ADD_MARK);
                ImGui::GetWindowDrawList()->AddBezierCubic(
                    ImGui::GetMousePos(),
                    ImGui::GetMousePos() + handle_offset,
                    tar_pin_pos - handle_offset,
                    tar_pin_pos,
                    ImguiNodes::pin_color_list[editing_para_type],
                    0.5f * o_scale[0]
                );
            }
            break;
        default:
            break;
        }

    }

    ImGui::PopFont();
    ImGui::GetStyle().Colors[ImGuiCol_Text] = ImVec4(1, 1, 1, 1);
    ImGui::EndChild();

    Reset();
}


void NodeEditor::RenderNode(Nodes& _node)
{

}

void NodeEditor::RenderMark(MarkType _type, bool _is_left /*= true*/)
{
    const float offset = 7;
    if (_is_left)
        RenderMark(_type, ImGui::GetMousePos() + ImVec2(-offset, -offset));
    else
        RenderMark(_type, ImGui::GetMousePos() + ImVec2( offset, -offset));
}

void NodeEditor::RenderMark(MarkType _type, ImVec2 _pos)
{
    const float offset_x = 5;
    const float offset_y = 1;
    switch (_type)
    {
    case NodeEditor::NONE_MARK:
        break;
    case NodeEditor::ADD_MARK:
        ImGui::GetWindowDrawList()->AddRectFilled(_pos - ImVec2(offset_x, offset_y), _pos + ImVec2(offset_x, offset_y), IM_COL32(255, 255, 255, 255));
        ImGui::GetWindowDrawList()->AddRectFilled(_pos - ImVec2(offset_y, offset_x), _pos + ImVec2(offset_y, offset_x), IM_COL32(255, 255, 255, 255));
        break;
    case NodeEditor::MINUS_MARK:
        ImGui::GetWindowDrawList()->AddRectFilled(_pos - ImVec2(offset_x, offset_y), _pos + ImVec2(offset_x, offset_y), IM_COL32(255, 255, 255, 255));
        break;
    case NodeEditor::SELECT_MARK:
        break;
    case NodeEditor::MOVE_MARK:
        break;
    case NodeEditor::CUT_MARK:
        break;
    default:
        break;
    }
}

void NodeEditor::Resize()
{
    NE_size = ImGui::GetContentRegionAvail() + ImVec2(0, 20);

}

void NodeEditor::Reset()
{
    if (Input::IsMouseClicked()) {
        if (no_node_clicked)
            active_node_id = 0;

        no_node_clicked = true;
    }
}

void NodeEditor::MoveView()
{
    const ImVec2 mouse_pos = ImVec2(Input::GetMousePosX(), Input::GetMousePosY());
    Move({ Input::GetDeltaMouseX() / o_scale[0], Input::GetDeltaMouseY() / o_scale[0] });
}

#include "xdz_math.h"
void NodeEditor::PushView()
{
    const ImVec2 mouse_pos = ImVec2(Input::GetMousePosX(), Input::GetMousePosY());
    Zoom((float)glm::pow(0.8f, -0.05 * xdzm::dir_float_dist(Input::GetDeltaMouseX(), Input::GetDeltaMouseY())));
}

void NodeEditor::ZoomView()
{
    const ImVec2 mouse_pos = ImVec2(Input::GetMousePosX(), Input::GetMousePosY());
    Zoom(glm::pow(0.8f, -Input::GetScrollY()));
}