diff --git a/story-editor/externals/ImNodeFlow b/story-editor/externals/ImNodeFlow index 1d06616..e4984a4 160000 --- a/story-editor/externals/ImNodeFlow +++ b/story-editor/externals/ImNodeFlow @@ -1 +1 @@ -Subproject commit 1d06616de63ab497f18e9403b128b6eccef3115d +Subproject commit e4984a42d3939d361f977e8d0a56bbfd49d0a702 diff --git a/story-editor/src/node_editor/base_node_widget.h b/story-editor/src/node_editor/base_node_widget.h index 481a0fd..b84c989 100644 --- a/story-editor/src/node_editor/base_node_widget.h +++ b/story-editor/src/node_editor/base_node_widget.h @@ -10,6 +10,9 @@ #include "base_node.h" #include "gui.h" +namespace Nw +{ + enum class PinType { Flow, @@ -40,19 +43,43 @@ enum class NodeType struct PinStyle { /// @brief Socket and link color - ImU32 color; + ImU32 color{IM_COL32(255,255,255,255)}; /// @brief Socket shape ID - int socket_shape; + int socket_shape{5}; /// @brief Socket radius - float socket_radius; + float socket_radius{4.f}; /// @brief Socket radius when hovered - float socket_hovered_radius; + float socket_hovered_radius{4.4f}; /// @brief Socket radius when connected - float socket_connected_radius; + float socket_connected_radius{4.2f}; /// @brief Socket outline thickness when empty - float socket_thickness; + float socket_thickness{1.f}; + ImVec2 padding = ImVec2(3.f, 1.f); + /// @brief Border and background corner rounding + float bg_radius = 8.f; + /// @brief Border thickness + float border_thickness = 1.f; + /// @brief Background color + ImU32 bg_color = IM_COL32(23, 16, 16, 0); + /// @brief Background color when hovered + ImU32 bg_hover_color = IM_COL32(100, 100, 255, 70); + /// @brief Border color + ImU32 border_color = IM_COL32(255, 255, 255, 0); }; +struct Pin +{ + ImVec2 pos = ImVec2(0.f, 0.f); + ImVec2 size; + ImVec2 pinPoint = ImVec2(0.f, 0.f); + bool isConnected{false}; + int index{0}; + PinKind pinKind{PinKind::Input}; + PinStyle style; +}; + +} // namespace Nw + /** * @brief Basically a wrapper class around ImGuiNodeEditor Node structure * @@ -69,7 +96,7 @@ public: virtual void DrawProperties(std::shared_ptr story) = 0; - virtual void DrawSocket(uint32_t index, bool isInput, ImVec2 pin_pos, bool isConnected) {} + virtual void DrawSocket(const Nw::Pin &pin) {} virtual bool HasSync() const { diff --git a/story-editor/src/node_editor/function_entry_widget.h b/story-editor/src/node_editor/function_entry_widget.h index 515aa57..240ee4a 100644 --- a/story-editor/src/node_editor/function_entry_widget.h +++ b/story-editor/src/node_editor/function_entry_widget.h @@ -12,6 +12,108 @@ #include "gui.h" +void DrawBlueprintSyncSocket(ImDrawList* draw_list, const ImVec2& center, float size, ImU32 color, bool filled = true) { + const float half_size = size * 0.5f; + const float triangle_size = size * 0.6f; // Triangle légèrement plus petit que le carré + + // Coordonnées du carré (partie gauche) + ImVec2 square_min = ImVec2(center.x - half_size, center.y - half_size); + ImVec2 square_max = ImVec2(center.x, center.y + half_size); + + // Coordonnées du triangle (partie droite, pointant vers la droite) + ImVec2 triangle_p1 = ImVec2(center.x, center.y - triangle_size * 0.5f); // Point haut + ImVec2 triangle_p2 = ImVec2(center.x, center.y + triangle_size * 0.5f); // Point bas + ImVec2 triangle_p3 = ImVec2(center.x + triangle_size * 0.7f, center.y); // Point de la pointe + + if (filled) { + // Dessiner le carré rempli + draw_list->AddRectFilled(square_min, square_max, color); + + // Dessiner le triangle rempli + draw_list->AddTriangleFilled(triangle_p1, triangle_p2, triangle_p3, color); + } else { + // Dessiner les contours + const float thickness = 2.0f; + + // Contour du carré + draw_list->AddRect(square_min, square_max, color, 0.0f, 0, thickness); + + // Contour du triangle + draw_list->AddTriangle(triangle_p1, triangle_p2, triangle_p3, color, thickness); + } +} + +// Version avec dégradé pour un effet plus moderne +void DrawBlueprintSyncSocketGradient(ImDrawList* draw_list, const ImVec2& center, float size, ImU32 color_start, ImU32 color_end) { + const float half_size = size * 0.5f; + const float triangle_size = size * 0.6f; + + // Coordonnées du carré + ImVec2 square_min = ImVec2(center.x - half_size, center.y - half_size); + ImVec2 square_max = ImVec2(center.x, center.y + half_size); + + // Coordonnées du triangle + ImVec2 triangle_p1 = ImVec2(center.x, center.y - triangle_size * 0.5f); + ImVec2 triangle_p2 = ImVec2(center.x, center.y + triangle_size * 0.5f); + ImVec2 triangle_p3 = ImVec2(center.x + triangle_size * 0.7f, center.y); + + // Carré avec dégradé horizontal + draw_list->AddRectFilledMultiColor( + square_min, square_max, + color_start, color_end, + color_end, color_start + ); + + // Triangle uni (couleur de fin du dégradé) + draw_list->AddTriangleFilled(triangle_p1, triangle_p2, triangle_p3, color_end); +} + +// Variante avec animation de pulsation pour indiquer l'activité +void DrawBlueprintSyncSocketAnimated(ImDrawList* draw_list, const ImVec2& center, float size, ImU32 color, float time) { + // Effet de pulsation basé sur le temps + float pulse = 0.8f + 0.2f * sinf(time * 3.0f); // Oscille entre 0.8 et 1.0 + float animated_size = size * pulse; + + // Socket principal + DrawBlueprintSyncSocket(draw_list, center, animated_size, color, true); + + // Halo subtil autour + ImU32 halo_color = ImGui::ColorConvertFloat4ToU32(ImVec4( + ((color >> IM_COL32_R_SHIFT) & 0xFF) / 255.0f, + ((color >> IM_COL32_G_SHIFT) & 0xFF) / 255.0f, + ((color >> IM_COL32_B_SHIFT) & 0xFF) / 255.0f, + 0.3f * (pulse - 0.8f) * 5.0f // Alpha qui varie avec la pulsation + )); + + DrawBlueprintSyncSocket(draw_list, center, size * 1.2f, halo_color, false); +} + +// Utilisation dans ImNodeFlow +void DrawSyncSocketInNode(ImDrawList* draw_list, const ImVec2& socket_pos, bool is_connected, bool is_hovered) { + const float socket_size = 16.0f; + + // Couleurs selon l'état + ImU32 base_color = IM_COL32(100, 150, 255, 255); // Bleu par défaut + ImU32 connected_color = IM_COL32(50, 255, 100, 255); // Vert si connecté + ImU32 hover_color = IM_COL32(255, 200, 50, 255); // Orange au survol + + ImU32 final_color = base_color; + if (is_connected) final_color = connected_color; + if (is_hovered) final_color = hover_color; + + // Dessiner le socket + if (is_connected) { + // Version animée si connecté + float time = ImGui::GetTime(); + DrawBlueprintSyncSocketAnimated(draw_list, socket_pos, socket_size, final_color, time); + } else { + // Version statique + DrawBlueprintSyncSocket(draw_list, socket_pos, socket_size, final_color, true); + } +} + + + class FunctionEntryWidget : public BaseNodeWidget { public: @@ -27,27 +129,123 @@ public: ImGui::SetNextItemWidth(100.f); } - void DrawSocket(uint32_t index, bool isInput, ImVec2 pin_pos, bool isConnected) override + + void DrawSocket(const Nw::Pin &pin) override { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Taille du socket + float socket_size = 4.0f; + + // Définir les 5 points du polygone (flèche pointant vers la droite pour Output) + // Pour Input, la flèche pointerait vers la gauche + ImVec2 p1, p2, p3, p4, p5; + + if (pin.pinKind == Nw::PinKind::Output) { + // Flèche pointant vers la droite (→) + p1 = ImVec2(pin.pinPoint.x - socket_size * 1.5f, pin.pinPoint.y - socket_size); + p2 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y - socket_size); + p3 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y); // Pointe + p4 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y + socket_size); + p5 = ImVec2(pin.pinPoint.x - socket_size * 1.5f, pin.pinPoint.y + socket_size); + } else { + // Flèche pointant vers la gauche (←) + p1 = ImVec2(pin.pinPoint.x + socket_size * 1.5f, pin.pinPoint.y - socket_size); + p2 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y - socket_size); + p3 = ImVec2(pin.pinPoint.x - socket_size * 0.5f, pin.pinPoint.y); // Pointe + p4 = ImVec2(pin.pinPoint.x + socket_size * 0.5f, pin.pinPoint.y + socket_size); + p5 = ImVec2(pin.pinPoint.x + socket_size * 1.5f, pin.pinPoint.y + socket_size); + } + + ImVec2 vertices[] = {p1, p2, p3, p4, p5}; + + // Rectangle pour la détection de hover + ImVec2 tl = pin.pinPoint - ImVec2(socket_size * 1.5f, socket_size); + ImVec2 br = pin.pinPoint + ImVec2(socket_size * 1.5f, socket_size); + + bool hovered = ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br); + + // Dessin du socket + if (pin.isConnected) { + // Rempli quand connecté + draw_list->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), + pin.style.color); + } else { + // Contour seulement quand non connecté + if (hovered) { + draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices), + pin.style.color, + ImDrawFlags_Closed, + pin.style.socket_hovered_radius); // Épaisseur au hover + } else { + draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices), + pin.style.color, + ImDrawFlags_Closed, + pin.style.socket_thickness); // Épaisseur normale + } + } + + // Optionnel : dessiner la décoration (fond hover) si nécessaire + if (hovered) { + draw_list->AddRectFilled(pin.pos - pin.style.padding, + pin.pos + pin.size + pin.style.padding, + pin.style.bg_hover_color, + pin.style.bg_radius); + } + + + /* + + // bonne position du socket + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // 1. Dessiner le socket à pin.pinPoint (pas à pin.pos !) + if (pin.isConnected) { + draw_list->AddCircleFilled(pin.pinPoint, + pin.style.socket_connected_radius, + pin.style.color); + } else { + // Gérer le hover vous-même si nécessaire + ImVec2 tl = pin.pinPoint - ImVec2(pin.style.socket_radius, pin.style.socket_radius); + ImVec2 br = pin.pinPoint + ImVec2(pin.style.socket_radius, pin.style.socket_radius); + bool hovered = ImGui::IsMouseHoveringRect(tl, br); + + draw_list->AddCircle(pin.pinPoint, + hovered ? pin.style.socket_hovered_radius + : pin.style.socket_radius, + pin.style.color, + pin.style.socket_shape, + pin.style.socket_thickness); + } + + */ + +/* + ImDrawList* draw_list = ImGui::GetWindowDrawList(); float socket_size = 4; + // pin.pos.x = pin.pos.x + 10; + + ImVec2 w_pos = ImGui::GetCursorPos(); + std::cout << "x = " << w_pos.x << ", y = " << w_pos.y << std::endl;; + // Définir les points du polygone pour le symbole de synchronisation // C'est un polygone fermé à 5 points - ImVec2 p1(pin_pos.x - socket_size * 0.5f, pin_pos.y - socket_size); - ImVec2 p2(pin_pos.x + socket_size * 0.5f, pin_pos.y - socket_size); - ImVec2 p3(pin_pos.x + socket_size * 0.5f, pin_pos.y + socket_size); - ImVec2 p4(pin_pos.x - socket_size * 0.5f, pin_pos.y + socket_size); - ImVec2 p5(pin_pos.x + socket_size * 1.5f, pin_pos.y); + ImVec2 p1(pin.pos.x - socket_size * 0.5f, pin.pos.y - socket_size); + ImVec2 p2(pin.pos.x + socket_size * 0.5f, pin.pos.y - socket_size); + ImVec2 p3(pin.pos.x + socket_size * 0.5f, pin.pos.y + socket_size); + ImVec2 p4(pin.pos.x - socket_size * 0.5f, pin.pos.y + socket_size); + ImVec2 p5(pin.pos.x + socket_size * 1.5f, pin.pos.y); ImVec2 vertices[] = {p1, p2, p5, p3, p4}; // Ordre des sommets // Pour la détection de survol (hover) on peut toujours utiliser le rectangle englobant - ImVec2 tl = pin_pos - ImVec2(socket_size * 1.5f, socket_size); - ImVec2 br = pin_pos + ImVec2(socket_size * 1.5f, socket_size); + ImVec2 tl = pin.pos - ImVec2(socket_size * 1.5f, socket_size); + ImVec2 br = pin.pos + ImVec2(socket_size * 1.5f, socket_size); - if (isConnected) + if (pin.isConnected) { draw_list->AddConvexPolyFilled(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255,255,255,255)); } @@ -55,13 +253,15 @@ public: { if (ImGui::IsItemHovered() || ImGui::IsMouseHoveringRect(tl, br)) { - draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),IM_COL32(255,255,255,255), ImDrawFlags_Closed, 4.67f); + draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices),IM_COL32(255,255,255,255), ImDrawFlags_Closed, 2.0f); } else { draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255,255,255,255), ImDrawFlags_Closed, 1.3f); } } + + */ } virtual bool HasSync() const override { diff --git a/story-editor/src/node_editor/node_editor_page.h b/story-editor/src/node_editor/node_editor_page.h index 954abf2..8dd4e49 100644 --- a/story-editor/src/node_editor/node_editor_page.h +++ b/story-editor/src/node_editor/node_editor_page.h @@ -59,10 +59,15 @@ public: if (port.customSocketIcon) { ImFlow::BaseNode::addIN("In" + std::to_string(i), 0, ImFlow::ConnectionFilter::SameType())->renderer([this, i](ImFlow::Pin* p) { - ImGui::Text("C"); - p->drawDecoration(); - //p->drawSocket(); - m_widget->DrawSocket(i, true, p->getPos(), p->isConnected()); + Nw::Pin pin; + pin.index = i; + pin.isConnected = p->isConnected(); + pin.pinKind = Nw::PinKind::Input; + pin.pinPoint = p->pinPoint(); + pin.pos = p->getPos(); + pin.size = p->getSize(); + + m_widget->DrawSocket(pin); }); } else @@ -78,10 +83,16 @@ public: if (port.customSocketIcon) { ImFlow::BaseNode::addOUT("Out" + std::to_string(i), nullptr)->renderer([this, i](ImFlow::Pin* p) { - ImGui::Text("C"); - p->drawDecoration(); - // p->drawSocket(); - m_widget->DrawSocket(i, false, p->getPos(), p->isConnected()); + + Nw::Pin pin; + pin.index = i; + pin.isConnected = p->isConnected(); + pin.pinKind = Nw::PinKind::Output; + pin.pinPoint = p->pinPoint(); + pin.pos = p->getPos(); + pin.size = p->getSize(); + + m_widget->DrawSocket(pin); }); } else @@ -122,9 +133,6 @@ struct NodeEditorPage : public ImFlow::BaseNode { mINF.setSize({500, 500}); - // mINF.addNode({0, 0}); - // mINF.addNode({10, 10}); - } ~NodeEditorPage() { @@ -182,7 +190,7 @@ struct NodeEditorPage : public ImFlow::BaseNode mINF.rightClickPopUpContent([this, openPopupPosition, &nodesFactory, &widgetFactory, &storyManager](ImFlow::BaseNode* node){ // std::cout << "Right-clicked on node: " << node->getName() << std::endl; - auto newNodePostion = openPopupPosition; + auto newNodePosition = mINF.screen2grid(openPopupPosition); auto nodeTypes = nodesFactory.ListOfNodes(); for (auto &type : nodeTypes) @@ -196,11 +204,11 @@ struct NodeEditorPage : public ImFlow::BaseNode if (n) { // Create delegate - auto delegate = mINF.addNode({newNodePostion.x, newNodePostion.y}); + auto delegate = mINF.placeNode(); // Link with the widget delegate->SetWidget(n); - n->Base()->SetPosition(newNodePostion.x, newNodePostion.y); + n->Base()->SetPosition(newNodePosition.x, newNodePosition.y); n->Initialize(); // AddNode(n); }