custom socket styling

This commit is contained in:
anthony@rabine.fr 2025-10-01 11:00:31 +02:00
parent d06f05d207
commit 883257fd78
4 changed files with 267 additions and 32 deletions

@ -1 +1 @@
Subproject commit 1d06616de63ab497f18e9403b128b6eccef3115d Subproject commit e4984a42d3939d361f977e8d0a56bbfd49d0a702

View file

@ -10,6 +10,9 @@
#include "base_node.h" #include "base_node.h"
#include "gui.h" #include "gui.h"
namespace Nw
{
enum class PinType enum class PinType
{ {
Flow, Flow,
@ -40,19 +43,43 @@ enum class NodeType
struct PinStyle struct PinStyle
{ {
/// @brief Socket and link color /// @brief Socket and link color
ImU32 color; ImU32 color{IM_COL32(255,255,255,255)};
/// @brief Socket shape ID /// @brief Socket shape ID
int socket_shape; int socket_shape{5};
/// @brief Socket radius /// @brief Socket radius
float socket_radius; float socket_radius{4.f};
/// @brief Socket radius when hovered /// @brief Socket radius when hovered
float socket_hovered_radius; float socket_hovered_radius{4.4f};
/// @brief Socket radius when connected /// @brief Socket radius when connected
float socket_connected_radius; float socket_connected_radius{4.2f};
/// @brief Socket outline thickness when empty /// @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 * @brief Basically a wrapper class around ImGuiNodeEditor Node structure
* *
@ -69,7 +96,7 @@ public:
virtual void DrawProperties(std::shared_ptr<IStoryProject> story) = 0; virtual void DrawProperties(std::shared_ptr<IStoryProject> 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 { virtual bool HasSync() const {

View file

@ -12,6 +12,108 @@
#include "gui.h" #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 class FunctionEntryWidget : public BaseNodeWidget
{ {
public: public:
@ -27,27 +129,123 @@ public:
ImGui::SetNextItemWidth(100.f); 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(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
float socket_size = 4; 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 // Définir les points du polygone pour le symbole de synchronisation
// C'est un polygone fermé à 5 points // C'est un polygone fermé à 5 points
ImVec2 p1(pin_pos.x - socket_size * 0.5f, pin_pos.y - socket_size); 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 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 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 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 p5(pin.pos.x + socket_size * 1.5f, pin.pos.y);
ImVec2 vertices[] = {p1, p2, p5, p3, p4}; // Ordre des sommets ImVec2 vertices[] = {p1, p2, p5, p3, p4}; // Ordre des sommets
// Pour la détection de survol (hover) on peut toujours utiliser le rectangle englobant // 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 tl = pin.pos - ImVec2(socket_size * 1.5f, socket_size);
ImVec2 br = 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)); 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)) 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 else
{ {
draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255,255,255,255), ImDrawFlags_Closed, 1.3f); draw_list->AddPolyline(vertices, IM_ARRAYSIZE(vertices), IM_COL32(255,255,255,255), ImDrawFlags_Closed, 1.3f);
} }
} }
*/
} }
virtual bool HasSync() const override { virtual bool HasSync() const override {

View file

@ -59,10 +59,15 @@ public:
if (port.customSocketIcon) if (port.customSocketIcon)
{ {
ImFlow::BaseNode::addIN<int>("In" + std::to_string(i), 0, ImFlow::ConnectionFilter::SameType())->renderer([this, i](ImFlow::Pin* p) { ImFlow::BaseNode::addIN<int>("In" + std::to_string(i), 0, ImFlow::ConnectionFilter::SameType())->renderer([this, i](ImFlow::Pin* p) {
ImGui::Text("C"); Nw::Pin pin;
p->drawDecoration(); pin.index = i;
//p->drawSocket(); pin.isConnected = p->isConnected();
m_widget->DrawSocket(i, true, p->getPos(), p->isConnected()); pin.pinKind = Nw::PinKind::Input;
pin.pinPoint = p->pinPoint();
pin.pos = p->getPos();
pin.size = p->getSize();
m_widget->DrawSocket(pin);
}); });
} }
else else
@ -78,10 +83,16 @@ public:
if (port.customSocketIcon) if (port.customSocketIcon)
{ {
ImFlow::BaseNode::addOUT<int>("Out" + std::to_string(i), nullptr)->renderer([this, i](ImFlow::Pin* p) { ImFlow::BaseNode::addOUT<int>("Out" + std::to_string(i), nullptr)->renderer([this, i](ImFlow::Pin* p) {
ImGui::Text("C");
p->drawDecoration(); Nw::Pin pin;
// p->drawSocket(); pin.index = i;
m_widget->DrawSocket(i, false, p->getPos(), p->isConnected()); 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 else
@ -122,9 +133,6 @@ struct NodeEditorPage : public ImFlow::BaseNode
{ {
mINF.setSize({500, 500}); mINF.setSize({500, 500});
// mINF.addNode<SimpleSum>({0, 0});
// mINF.addNode<SimpleSum>({10, 10});
} }
~NodeEditorPage() { ~NodeEditorPage() {
@ -182,7 +190,7 @@ struct NodeEditorPage : public ImFlow::BaseNode
mINF.rightClickPopUpContent([this, openPopupPosition, &nodesFactory, &widgetFactory, &storyManager](ImFlow::BaseNode* node){ mINF.rightClickPopUpContent([this, openPopupPosition, &nodesFactory, &widgetFactory, &storyManager](ImFlow::BaseNode* node){
// std::cout << "Right-clicked on node: " << node->getName() << std::endl; // std::cout << "Right-clicked on node: " << node->getName() << std::endl;
auto newNodePostion = openPopupPosition; auto newNodePosition = mINF.screen2grid(openPopupPosition);
auto nodeTypes = nodesFactory.ListOfNodes(); auto nodeTypes = nodesFactory.ListOfNodes();
for (auto &type : nodeTypes) for (auto &type : nodeTypes)
@ -196,11 +204,11 @@ struct NodeEditorPage : public ImFlow::BaseNode
if (n) if (n)
{ {
// Create delegate // Create delegate
auto delegate = mINF.addNode<NodeDelegate>({newNodePostion.x, newNodePostion.y}); auto delegate = mINF.placeNode<NodeDelegate>();
// Link with the widget // Link with the widget
delegate->SetWidget(n); delegate->SetWidget(n);
n->Base()->SetPosition(newNodePostion.x, newNodePostion.y); n->Base()->SetPosition(newNodePosition.x, newNodePosition.y);
n->Initialize(); n->Initialize();
// AddNode(n); // AddNode(n);
} }