feat(shaders_lab): Output node + Functions palette with drag-drop
- DagKind::Output (new enum): terminal sink; compiler wires fragColor to its source_ids[0] - dag_catalog: "output" node (1 input, red) - dag_compile: skips Output in node_<i> emission; final fragColor resolves from Output's connection - dag_node_editor: no more Add button; drops "DAG_NODE_TYPE" payloads at mouse canvas position; Output cannot be deleted; Output has no output pin - dag_palette (new fn): Functions window with grouped, draggable node cards - main.cpp: "Functions" window added; ensure_dag_default seeds plasma + connected Output
This commit is contained in:
@@ -12,6 +12,7 @@ add_imgui_app(shaders_lab
|
|||||||
${CMAKE_SOURCE_DIR}/functions/gfx/dag_uniforms.cpp
|
${CMAKE_SOURCE_DIR}/functions/gfx/dag_uniforms.cpp
|
||||||
${CMAKE_SOURCE_DIR}/functions/gfx/dag_panel.cpp
|
${CMAKE_SOURCE_DIR}/functions/gfx/dag_panel.cpp
|
||||||
${CMAKE_SOURCE_DIR}/functions/gfx/dag_node_editor.cpp
|
${CMAKE_SOURCE_DIR}/functions/gfx/dag_node_editor.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/functions/gfx/dag_palette.cpp
|
||||||
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
|
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(shaders_lab PRIVATE
|
target_include_directories(shaders_lab PRIVATE
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "gfx/dag_uniforms.h"
|
#include "gfx/dag_uniforms.h"
|
||||||
#include "gfx/dag_panel.h"
|
#include "gfx/dag_panel.h"
|
||||||
#include "gfx/dag_node_editor.h"
|
#include "gfx/dag_node_editor.h"
|
||||||
|
#include "gfx/dag_palette.h"
|
||||||
#include "core/fps_overlay.h"
|
#include "core/fps_overlay.h"
|
||||||
#include "seed_shaders.h"
|
#include "seed_shaders.h"
|
||||||
|
|
||||||
@@ -73,14 +74,32 @@ static void load_preset(const char* src) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void ensure_dag_default() {
|
static void ensure_dag_default() {
|
||||||
|
// Seed with a Plasma connected to an Output node.
|
||||||
if (g_pipeline.empty()) {
|
if (g_pipeline.empty()) {
|
||||||
const fn::gfx::DagNodeDef* def = fn::gfx::dag_find("plasma");
|
const fn::gfx::DagNodeDef* plasma = fn::gfx::dag_find("plasma");
|
||||||
if (def) {
|
if (plasma) {
|
||||||
fn::gfx::DagStep step;
|
fn::gfx::DagStep s;
|
||||||
step.id = "n_default";
|
s.id = "n_plasma";
|
||||||
step.name = def->name;
|
s.name = plasma->name;
|
||||||
step.params = def->param_defaults;
|
s.params = plasma->param_defaults;
|
||||||
g_pipeline.push_back(step);
|
g_pipeline.push_back(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ensure there is always an Output node at the end.
|
||||||
|
bool has_output = false;
|
||||||
|
for (const auto& s : g_pipeline) {
|
||||||
|
const fn::gfx::DagNodeDef* d = fn::gfx::dag_find(s.name);
|
||||||
|
if (d && d->kind == fn::gfx::DagKind::Output) { has_output = true; break; }
|
||||||
|
}
|
||||||
|
if (!has_output) {
|
||||||
|
const fn::gfx::DagNodeDef* out = fn::gfx::dag_find("output");
|
||||||
|
if (out) {
|
||||||
|
fn::gfx::DagStep s;
|
||||||
|
s.id = "n_output";
|
||||||
|
s.name = out->name;
|
||||||
|
s.editor_pos_x = 500.0f;
|
||||||
|
if (!g_pipeline.empty()) s.source_ids[0] = g_pipeline.front().id;
|
||||||
|
g_pipeline.push_back(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +200,12 @@ static void render() {
|
|||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
// --- Functions palette (drag into DAG Pipeline) ---
|
||||||
|
if (ImGui::Begin("Functions")) {
|
||||||
|
fn::gfx::dag_palette();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
// --- Generated GLSL window (DAG compiled output, read-only) ---
|
// --- Generated GLSL window (DAG compiled output, read-only) ---
|
||||||
if (ImGui::Begin("Generated GLSL")) {
|
if (ImGui::Begin("Generated GLSL")) {
|
||||||
if (g_dag_glsl.empty()) {
|
if (g_dag_glsl.empty()) {
|
||||||
|
|||||||
@@ -225,6 +225,21 @@ static const std::vector<DagNodeDef>& build_catalog() {
|
|||||||
v.push_back(std::move(n));
|
v.push_back(std::move(n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Output (sink — drives fragColor) ─────────────────────────
|
||||||
|
{
|
||||||
|
DagNodeDef n;
|
||||||
|
n.name = "output";
|
||||||
|
n.label = "Output";
|
||||||
|
n.desc = "canvas DAG output";
|
||||||
|
n.kind = DagKind::Output;
|
||||||
|
n.num_inputs = 1;
|
||||||
|
n.param_names = {"", "", "", ""};
|
||||||
|
n.param_defaults = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
n.controls = {};
|
||||||
|
n.body_glsl = [](int) -> std::string { return ""; };
|
||||||
|
v.push_back(std::move(n));
|
||||||
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}();
|
}();
|
||||||
return catalog;
|
return catalog;
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
|
|||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit per-node functions with signatures based on num_inputs
|
// Emit per-node functions (skip Output: it's a sink, no body)
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
const DagStep& step = pipeline[static_cast<size_t>(i)];
|
const DagStep& step = pipeline[static_cast<size_t>(i)];
|
||||||
const DagNodeDef* def = dag_find(step.name);
|
const DagNodeDef* def = dag_find(step.name);
|
||||||
if (!def) continue;
|
if (!def) continue;
|
||||||
|
if (def->kind == DagKind::Output) continue;
|
||||||
|
|
||||||
int ni = def->num_inputs;
|
int ni = def->num_inputs;
|
||||||
out << "vec4 node_" << i << "(";
|
out << "vec4 node_" << i << "(";
|
||||||
@@ -43,34 +44,30 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
|
|||||||
out << "void main() {\n";
|
out << "void main() {\n";
|
||||||
out << " vec2 uv = gl_FragCoord.xy / u_resolution;\n";
|
out << " vec2 uv = gl_FragCoord.xy / u_resolution;\n";
|
||||||
|
|
||||||
|
int last_valid_out = -1;
|
||||||
|
int output_idx = -1;
|
||||||
|
|
||||||
for (int i = 0; i < n; ++i) {
|
for (int i = 0; i < n; ++i) {
|
||||||
const DagStep& step = pipeline[static_cast<size_t>(i)];
|
const DagStep& step = pipeline[static_cast<size_t>(i)];
|
||||||
const DagNodeDef* def = dag_find(step.name);
|
const DagNodeDef* def = dag_find(step.name);
|
||||||
if (!def) {
|
if (!def) continue;
|
||||||
if (i == 0) {
|
if (def->kind == DagKind::Output) { output_idx = i; continue; }
|
||||||
out << " vec4 out_" << i << " = vec4(0.0, 0.0, 0.0, 1.0);\n";
|
|
||||||
} else {
|
|
||||||
out << " vec4 out_" << i << " = out_" << (i - 1) << ";\n";
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ni = def->num_inputs;
|
int ni = def->num_inputs;
|
||||||
|
|
||||||
// Resolve each input slot
|
|
||||||
// For slot k: look for source_ids[k] in pipeline[0..i-1]; fallback = prev output
|
|
||||||
auto resolve = [&](int k) -> std::string {
|
auto resolve = [&](int k) -> std::string {
|
||||||
const std::string& sid = step.source_ids[static_cast<size_t>(k)];
|
const std::string& sid = step.source_ids[static_cast<size_t>(k)];
|
||||||
if (!sid.empty()) {
|
if (!sid.empty()) {
|
||||||
for (int j = 0; j < i; ++j) {
|
for (int j = 0; j < i; ++j) {
|
||||||
if (pipeline[static_cast<size_t>(j)].id == sid) {
|
if (pipeline[static_cast<size_t>(j)].id == sid) {
|
||||||
|
const DagNodeDef* jdef = dag_find(pipeline[static_cast<size_t>(j)].name);
|
||||||
|
if (jdef && jdef->kind == DagKind::Output) continue; // Output has no out_j
|
||||||
return "out_" + std::to_string(j);
|
return "out_" + std::to_string(j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fallback
|
if (last_valid_out < 0) return "vec4(0.0, 0.0, 0.0, 1.0)";
|
||||||
if (i == 0) return "vec4(0.0, 0.0, 0.0, 1.0)";
|
return "out_" + std::to_string(last_valid_out);
|
||||||
return "out_" + std::to_string(i - 1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
out << " vec4 out_" << i << " = node_" << i << "(";
|
out << " vec4 out_" << i << " = node_" << i << "(";
|
||||||
@@ -80,9 +77,29 @@ std::string compile_dag_to_glsl(const std::vector<DagStep>& pipeline) {
|
|||||||
}
|
}
|
||||||
if (ni > 0) out << ", ";
|
if (ni > 0) out << ", ";
|
||||||
out << "uv);\n";
|
out << "uv);\n";
|
||||||
|
|
||||||
|
last_valid_out = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve fragColor: if there's an Output node with a connection, use that; else fallback.
|
||||||
|
auto seed = [&]() { out << " fragColor = vec4(0.04, 0.04, 0.06, 1.0);\n"; };
|
||||||
|
|
||||||
|
if (output_idx >= 0) {
|
||||||
|
const std::string& sid = pipeline[static_cast<size_t>(output_idx)].source_ids[0];
|
||||||
|
int src = -1;
|
||||||
|
if (!sid.empty()) {
|
||||||
|
for (int j = 0; j < output_idx; ++j) {
|
||||||
|
if (pipeline[static_cast<size_t>(j)].id == sid) { src = j; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (src >= 0) out << " fragColor = out_" << src << ";\n";
|
||||||
|
else seed();
|
||||||
|
} else if (last_valid_out >= 0) {
|
||||||
|
out << " fragColor = out_" << last_valid_out << ";\n";
|
||||||
|
} else {
|
||||||
|
seed();
|
||||||
}
|
}
|
||||||
|
|
||||||
out << " fragColor = out_" << (n - 1) << ";\n";
|
|
||||||
out << "}\n";
|
out << "}\n";
|
||||||
|
|
||||||
return out.str();
|
return out.str();
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ static int find_by_id(const std::vector<DagStep>& p, const std::string& id) {
|
|||||||
|
|
||||||
static ImVec4 kind_color(DagKind kind) {
|
static ImVec4 kind_color(DagKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
|
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
|
||||||
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
|
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
|
||||||
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
|
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
|
||||||
|
case DagKind::Output: return ImVec4(0.85f, 0.25f, 0.25f, 1.0f);
|
||||||
}
|
}
|
||||||
return ImVec4(1, 1, 1, 1);
|
return ImVec4(1, 1, 1, 1);
|
||||||
}
|
}
|
||||||
@@ -120,70 +121,6 @@ static bool topo_sort(std::vector<DagStep>& pipeline) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add node popup toolbar — rendered OUTSIDE ed::Begin
|
|
||||||
static bool draw_add_toolbar(std::vector<DagStep>& pipeline) {
|
|
||||||
bool changed = false;
|
|
||||||
int sz = static_cast<int>(pipeline.size());
|
|
||||||
if (ImGui::Button("+ Add Node")) {
|
|
||||||
ImGui::OpenPopup("ne_add_popup");
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Text("%d/%d", sz, MAX_NODES);
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("Clear") && !pipeline.empty()) {
|
|
||||||
ImGui::OpenPopup("ne_clear_confirm");
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("Fit")) {
|
|
||||||
if (s_ctx) {
|
|
||||||
ed::SetCurrentEditor(s_ctx);
|
|
||||||
ed::NavigateToContent(0.0f);
|
|
||||||
ed::SetCurrentEditor(nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginPopupModal("ne_clear_confirm", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
||||||
ImGui::Text("Vaciar el pipeline?");
|
|
||||||
if (ImGui::Button("Si")) {
|
|
||||||
pipeline.clear();
|
|
||||||
changed = true;
|
|
||||||
ImGui::CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
ImGui::SameLine();
|
|
||||||
if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); }
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginPopup("ne_add_popup")) {
|
|
||||||
const char* kind_names[] = { "Gen", "Op", "Blend" };
|
|
||||||
DagKind kinds[] = { DagKind::Gen, DagKind::Op, DagKind::Blend };
|
|
||||||
for (int k = 0; k < 3; ++k) {
|
|
||||||
if (ImGui::BeginMenu(kind_names[k])) {
|
|
||||||
for (const auto& def : dag_catalog()) {
|
|
||||||
if (def.kind != kinds[k]) continue;
|
|
||||||
if (ImGui::MenuItem(def.label.c_str())) {
|
|
||||||
if (sz < MAX_NODES) {
|
|
||||||
DagStep step;
|
|
||||||
step.id = "n" + std::to_string(s_next_uid);
|
|
||||||
step.name = def.name;
|
|
||||||
step.params = def.param_defaults;
|
|
||||||
step.editor_uid = s_next_uid++;
|
|
||||||
// stagger position so nodes don't stack
|
|
||||||
step.editor_pos_x = 50.0f + static_cast<float>(sz) * 220.0f;
|
|
||||||
step.editor_pos_y = 100.0f;
|
|
||||||
pipeline.push_back(step);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
@@ -201,12 +138,47 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
|||||||
s_ctx = ed::CreateEditor(&cfg);
|
s_ctx = ed::CreateEditor(&cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
changed |= draw_add_toolbar(pipeline);
|
// Palette drop zone: accept dropped node types (from the Functions panel)
|
||||||
ImGui::Separator();
|
// and queue an add at the mouse canvas position (resolved after ed::Begin).
|
||||||
|
static std::string s_pending_add_name;
|
||||||
|
static ImVec2 s_pending_add_pos(0, 0);
|
||||||
|
static bool s_pending_add = false;
|
||||||
|
|
||||||
|
ImVec2 origin = ImGui::GetCursorScreenPos();
|
||||||
|
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||||
|
ImGui::InvisibleButton("##dag_drop_zone", avail,
|
||||||
|
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
|
||||||
|
if (ImGui::BeginDragDropTarget()) {
|
||||||
|
if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("DAG_NODE_TYPE")) {
|
||||||
|
s_pending_add_name.assign(static_cast<const char*>(p->Data), static_cast<size_t>(p->DataSize));
|
||||||
|
s_pending_add_pos = ImGui::GetMousePos();
|
||||||
|
s_pending_add = true;
|
||||||
|
}
|
||||||
|
ImGui::EndDragDropTarget();
|
||||||
|
}
|
||||||
|
ImGui::SetCursorScreenPos(origin);
|
||||||
|
|
||||||
ed::SetCurrentEditor(s_ctx);
|
ed::SetCurrentEditor(s_ctx);
|
||||||
ed::Begin("dag_editor", ImVec2(0.0f, 0.0f));
|
ed::Begin("dag_editor", ImVec2(0.0f, 0.0f));
|
||||||
|
|
||||||
|
if (s_pending_add) {
|
||||||
|
const DagNodeDef* def = dag_find(s_pending_add_name);
|
||||||
|
if (def && static_cast<int>(pipeline.size()) < MAX_NODES) {
|
||||||
|
uint32_t uid = s_next_uid++;
|
||||||
|
DagStep step;
|
||||||
|
step.id = "n" + std::to_string(uid);
|
||||||
|
step.name = def->name;
|
||||||
|
step.params = def->param_defaults;
|
||||||
|
step.editor_uid = uid;
|
||||||
|
ImVec2 canvas_pos = ed::ScreenToCanvas(s_pending_add_pos);
|
||||||
|
step.editor_pos_x = canvas_pos.x;
|
||||||
|
step.editor_pos_y = canvas_pos.y;
|
||||||
|
pipeline.push_back(step);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
s_pending_add = false;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Draw nodes ───────────────────────────────────────────────────────────
|
// ── Draw nodes ───────────────────────────────────────────────────────────
|
||||||
for (int i = 0; i < static_cast<int>(pipeline.size()); ++i) {
|
for (int i = 0; i < static_cast<int>(pipeline.size()); ++i) {
|
||||||
DagStep& step = pipeline[static_cast<size_t>(i)];
|
DagStep& step = pipeline[static_cast<size_t>(i)];
|
||||||
@@ -279,11 +251,13 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
|||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
|
|
||||||
// Output pin
|
// Output pin (skip for the terminal Output node — it has no output)
|
||||||
ImGui::Dummy(ImVec2(0, 2));
|
if (def->kind != DagKind::Output) {
|
||||||
ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output);
|
ImGui::Dummy(ImVec2(0, 2));
|
||||||
ImGui::Text("out ->");
|
ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output);
|
||||||
ed::EndPin();
|
ImGui::Text("out ->");
|
||||||
|
ed::EndPin();
|
||||||
|
}
|
||||||
|
|
||||||
ed::EndNode();
|
ed::EndNode();
|
||||||
}
|
}
|
||||||
@@ -369,12 +343,17 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
|||||||
|
|
||||||
ed::NodeId del_nid;
|
ed::NodeId del_nid;
|
||||||
while (ed::QueryDeletedNode(&del_nid)) {
|
while (ed::QueryDeletedNode(&del_nid)) {
|
||||||
|
uint32_t uid = static_cast<uint32_t>(del_nid.Get());
|
||||||
|
int idx = find_by_uid(pipeline, uid);
|
||||||
|
// Refuse to delete the Output node (it's the sink)
|
||||||
|
const DagNodeDef* ddef = (idx >= 0) ? dag_find(pipeline[static_cast<size_t>(idx)].name) : nullptr;
|
||||||
|
if (ddef && ddef->kind == DagKind::Output) {
|
||||||
|
ed::RejectDeletedItem();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (ed::AcceptDeletedItem()) {
|
if (ed::AcceptDeletedItem()) {
|
||||||
uint32_t uid = static_cast<uint32_t>(del_nid.Get());
|
|
||||||
int idx = find_by_uid(pipeline, uid);
|
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
const std::string& del_step_id = pipeline[static_cast<size_t>(idx)].id;
|
const std::string& del_step_id = pipeline[static_cast<size_t>(idx)].id;
|
||||||
// Clear any source_ids pointing to the deleted node
|
|
||||||
for (auto& step : pipeline) {
|
for (auto& step : pipeline) {
|
||||||
for (auto& sid : step.source_ids) {
|
for (auto& sid : step.source_ids) {
|
||||||
if (sid == del_step_id) sid.clear();
|
if (sid == del_step_id) sid.clear();
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#include "gfx/dag_palette.h"
|
||||||
|
#include "gfx/dag_catalog.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
namespace fn::gfx {
|
||||||
|
|
||||||
|
static ImVec4 kind_tint(DagKind kind) {
|
||||||
|
switch (kind) {
|
||||||
|
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
|
||||||
|
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
|
||||||
|
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
|
||||||
|
case DagKind::Output: return ImVec4(0.85f, 0.25f, 0.25f, 1.0f);
|
||||||
|
}
|
||||||
|
return ImVec4(1, 1, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_group(const char* header, DagKind kind) {
|
||||||
|
if (!ImGui::CollapsingHeader(header, ImGuiTreeNodeFlags_DefaultOpen)) return;
|
||||||
|
|
||||||
|
ImVec4 col = kind_tint(kind);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(col.x * 0.35f, col.y * 0.35f, col.z * 0.35f, 0.9f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(col.x * 0.55f, col.y * 0.55f, col.z * 0.55f, 1.0f));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(col.x * 0.75f, col.y * 0.75f, col.z * 0.75f, 1.0f));
|
||||||
|
|
||||||
|
for (const auto& def : dag_catalog()) {
|
||||||
|
if (def.kind != kind) continue;
|
||||||
|
ImGui::PushID(def.name.c_str());
|
||||||
|
ImGui::Button(def.label.c_str(), ImVec2(-1, 0));
|
||||||
|
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
|
||||||
|
ImGui::SetDragDropPayload("DAG_NODE_TYPE", def.name.c_str(), def.name.size());
|
||||||
|
ImGui::Text("+ %s", def.label.c_str());
|
||||||
|
if (!def.desc.empty()) {
|
||||||
|
ImGui::TextDisabled("%s", def.desc.c_str());
|
||||||
|
}
|
||||||
|
ImGui::EndDragDropSource();
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered() && !def.desc.empty()) {
|
||||||
|
ImGui::SetTooltip("%s", def.desc.c_str());
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dag_palette() {
|
||||||
|
ImGui::TextDisabled("Drag into the DAG Pipeline canvas.");
|
||||||
|
ImGui::Spacing();
|
||||||
|
draw_group("Generators", DagKind::Gen);
|
||||||
|
draw_group("Operators", DagKind::Op);
|
||||||
|
draw_group("Blends", DagKind::Blend);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace fn::gfx
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace fn::gfx {
|
||||||
|
|
||||||
|
// Renderiza una paleta del catalogo de nodos agrupada por kind (Gen / Op / Blend).
|
||||||
|
// Cada item es un drag source con payload "DAG_NODE_TYPE" (nombre del nodo).
|
||||||
|
// El Output no aparece en la paleta (es un sink fijo en el grafo).
|
||||||
|
// Llamar dentro de un ImGui::Begin/End.
|
||||||
|
void dag_palette();
|
||||||
|
|
||||||
|
} // namespace fn::gfx
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
name: dag_palette
|
||||||
|
kind: component
|
||||||
|
lang: cpp
|
||||||
|
domain: gfx
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "void dag_palette()"
|
||||||
|
description: "Paleta de nodos del catalogo, agrupada por kind (Gen/Op/Blend). Cada entrada es un drag source con payload DAG_NODE_TYPE que contiene el name del nodo. El node editor recibe el drop y anade un nuevo DagStep en la posicion del mouse."
|
||||||
|
tags: [imgui, dag, palette, drag-and-drop, gfx, component]
|
||||||
|
uses_functions: [dag_catalog_cpp_gfx]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: [imgui, dag_catalog]
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "cpp/functions/gfx/dag_palette.cpp"
|
||||||
|
framework: imgui
|
||||||
|
params: []
|
||||||
|
output: "Dibuja los widgets de paleta dentro del ImGui::Begin/End del caller. No tiene estado propio."
|
||||||
|
---
|
||||||
|
|
||||||
|
# dag_palette
|
||||||
|
|
||||||
|
Paleta draggable de nodos del catalogo. Sustituye el boton "+ Add Node" del panel del DAG: en lugar de pulsar y navegar un submenu, el usuario arrastra el nodo desde la paleta al canvas del editor.
|
||||||
|
|
||||||
|
## Uso
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (ImGui::Begin("Functions")) {
|
||||||
|
fn::gfx::dag_palette();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
```
|
||||||
|
|
||||||
|
El node editor debe aceptar payloads con identificador `"DAG_NODE_TYPE"` y crear el `DagStep` correspondiente en la posicion del cursor.
|
||||||
|
|
||||||
|
## Drop zone del editor
|
||||||
|
|
||||||
|
El `dag_node_editor` usa un `ImGui::InvisibleButton` del tamano del canvas como drop target antes de `ed::Begin()`, y resetea el cursor para que el editor se dibuje encima. Al detectar drop, convierte la posicion screen a canvas con `ed::ScreenToCanvas()` y encola un add que se aplica tras abrir el contexto del editor.
|
||||||
@@ -16,18 +16,20 @@ static std::string make_id() {
|
|||||||
|
|
||||||
static ImVec4 kind_color(DagKind kind) {
|
static ImVec4 kind_color(DagKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
|
case DagKind::Gen: return ImVec4(0.25f, 0.55f, 0.90f, 1.0f);
|
||||||
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
|
case DagKind::Op: return ImVec4(0.65f, 0.40f, 0.90f, 1.0f);
|
||||||
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
|
case DagKind::Blend: return ImVec4(0.90f, 0.65f, 0.15f, 1.0f);
|
||||||
|
case DagKind::Output: return ImVec4(0.85f, 0.25f, 0.25f, 1.0f);
|
||||||
}
|
}
|
||||||
return ImVec4(1, 1, 1, 1);
|
return ImVec4(1, 1, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* kind_label(DagKind kind) {
|
static const char* kind_label(DagKind kind) {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case DagKind::Gen: return "Gen";
|
case DagKind::Gen: return "Gen";
|
||||||
case DagKind::Op: return "Op";
|
case DagKind::Op: return "Op";
|
||||||
case DagKind::Blend: return "Blend";
|
case DagKind::Blend: return "Blend";
|
||||||
|
case DagKind::Output: return "Output";
|
||||||
}
|
}
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace fn::gfx {
|
namespace fn::gfx {
|
||||||
|
|
||||||
enum class DagKind { Gen, Op, Blend };
|
enum class DagKind { Gen, Op, Blend, Output };
|
||||||
|
|
||||||
struct DagControl {
|
struct DagControl {
|
||||||
enum class Kind { Slider, XY, Color };
|
enum class Kind { Slider, XY, Color };
|
||||||
|
|||||||
Reference in New Issue
Block a user