feat(shaders_lab): per-node preview thumbnails + double-rclick delete node

- DagStep: preview_open flag (default false).
- dag_compile: emit `uniform int u_preview_target` and a series of
  early-return branches at the start of fragColor selection. -1 (default)
  falls through to the real Output-driven fragColor.
- dag_node_previews (new fn): per-node FBO keyed by editor_uid, lazy
  created. Renders each node with preview_open=true to its FBO by
  setting u_preview_target = step index. Texture exposed via
  dag_preview_texture(uid) for ImGui::Image.
- dag_node_editor: small toggle button "[+] preview"/"[-] preview" in
  each non-Output node; when open, ImGui::Image(96x64, V-flipped).
- dag_node_editor: double right-click on hovered node deletes it
  (Output is protected).
- main.cpp: dag_previews_render after Canvas DAG; dag_previews_destroy
  on shutdown.

Single GL program drives both the canvas and all thumbnails — moving
sliders never recompiles, only the topology change does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 02:06:50 +02:00
parent a209afa46b
commit ac65791663
9 changed files with 245 additions and 1 deletions
+42
View File
@@ -1,5 +1,6 @@
#include "gfx/dag_node_editor.h"
#include "gfx/dag_catalog.h"
#include "gfx/dag_node_previews.h"
#include "imgui.h"
#include "imgui_node_editor.h"
#include <algorithm>
@@ -304,6 +305,25 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
}
}
}
// Per-node preview thumbnail (off by default).
if (def->kind != DagKind::Output) {
char btn_lbl[64];
std::snprintf(btn_lbl, sizeof(btn_lbl), "%s preview##pv%u",
step.preview_open ? "[-]" : "[+]", step.editor_uid);
if (ImGui::SmallButton(btn_lbl)) {
step.preview_open = !step.preview_open;
}
if (step.preview_open) {
unsigned tex = dag_preview_texture(step.editor_uid);
if (tex != 0) {
ImGui::Image(static_cast<ImTextureID>(static_cast<intptr_t>(tex)),
ImVec2(96, 64), ImVec2(0, 1), ImVec2(1, 0));
} else {
ImGui::Dummy(ImVec2(96, 64));
}
}
}
ImGui::PopID();
ImGui::EndGroup();
@@ -360,6 +380,28 @@ bool dag_node_editor(std::vector<DagStep>& pipeline) {
ed::Resume();
}
// ── Double right-click on a node deletes it (Output is protected) ──────
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
ed::NodeId hovered = ed::GetHoveredNode();
uint32_t uid = static_cast<uint32_t>(hovered.Get());
if (uid != 0) {
int idx = find_by_uid(pipeline, uid);
if (idx >= 0) {
const DagNodeDef* d = dag_find(pipeline[static_cast<size_t>(idx)].name);
if (d && d->kind != DagKind::Output) {
const std::string del_id = pipeline[static_cast<size_t>(idx)].id;
for (auto& s : pipeline) {
for (auto& sid : s.source_ids) {
if (sid == del_id) sid.clear();
}
}
pipeline.erase(pipeline.begin() + idx);
changed = true;
}
}
}
}
// ── Right-click on a pin clears all connections of that pin ────────────
{
ed::Suspend();