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:
@@ -61,9 +61,10 @@ static int find_by_id(const std::vector<DagStep>& p, const std::string& id) {
|
||||
|
||||
static ImVec4 kind_color(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::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);
|
||||
}
|
||||
@@ -120,70 +121,6 @@ static bool topo_sort(std::vector<DagStep>& pipeline) {
|
||||
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 changed = false;
|
||||
|
||||
@@ -201,12 +138,47 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
s_ctx = ed::CreateEditor(&cfg);
|
||||
}
|
||||
|
||||
changed |= draw_add_toolbar(pipeline);
|
||||
ImGui::Separator();
|
||||
// Palette drop zone: accept dropped node types (from the Functions panel)
|
||||
// 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::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 ───────────────────────────────────────────────────────────
|
||||
for (int i = 0; i < static_cast<int>(pipeline.size()); ++i) {
|
||||
DagStep& step = pipeline[static_cast<size_t>(i)];
|
||||
@@ -279,11 +251,13 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
// Output pin
|
||||
ImGui::Dummy(ImVec2(0, 2));
|
||||
ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output);
|
||||
ImGui::Text("out ->");
|
||||
ed::EndPin();
|
||||
// Output pin (skip for the terminal Output node — it has no output)
|
||||
if (def->kind != DagKind::Output) {
|
||||
ImGui::Dummy(ImVec2(0, 2));
|
||||
ed::BeginPin(ed::PinId(output_pin_id(step.editor_uid)), ed::PinKind::Output);
|
||||
ImGui::Text("out ->");
|
||||
ed::EndPin();
|
||||
}
|
||||
|
||||
ed::EndNode();
|
||||
}
|
||||
@@ -369,12 +343,17 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
|
||||
|
||||
ed::NodeId 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()) {
|
||||
uint32_t uid = static_cast<uint32_t>(del_nid.Get());
|
||||
int idx = find_by_uid(pipeline, uid);
|
||||
if (idx >= 0) {
|
||||
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& sid : step.source_ids) {
|
||||
if (sid == del_step_id) sid.clear();
|
||||
|
||||
Reference in New Issue
Block a user