feat(shaders_lab): independent windows for Code, DAG, Controls, Canvas, GLSL

Remove Code/DAG toggle. Each panel lives in its own dockable window.
Active source (Code or DAG) is whichever window has focus; Canvas title
shows the current active source. Switching active source triggers an
immediate recompile so the canvas reflects the selected pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 21:27:50 +02:00
parent 2bb49e31c0
commit c974eaa604
+85 -93
View File
@@ -13,12 +13,14 @@
#include "seed_shaders.h" #include "seed_shaders.h"
#include <chrono> #include <chrono>
#include <cstdio>
#include <cstring>
#include <string> #include <string>
#include <vector> #include <vector>
enum class AppMode { Code, Dag }; enum class Source { Code, Dag };
static AppMode g_mode = AppMode::Code; static Source g_active = Source::Code;
static fn::gfx::ShaderCanvas g_canvas; static fn::gfx::ShaderCanvas g_canvas;
static std::string g_source = PLASMA; static std::string g_source = PLASMA;
static std::string g_last_err; static std::string g_last_err;
@@ -31,7 +33,7 @@ static std::vector<fn::gfx::DagStep> g_pipeline;
static std::string g_dag_glsl; static std::string g_dag_glsl;
static void try_compile() { static void try_compile() {
if (g_mode == AppMode::Code) { if (g_active == Source::Code) {
auto r = fn::gfx::compile_fragment(g_source); auto r = fn::gfx::compile_fragment(g_source);
if (r.ok) { if (r.ok) {
g_descs = fn::gfx::parse_uniforms(g_source); g_descs = fn::gfx::parse_uniforms(g_source);
@@ -47,8 +49,6 @@ static void try_compile() {
g_dag_glsl = fn::gfx::compile_dag_to_glsl(g_pipeline); g_dag_glsl = fn::gfx::compile_dag_to_glsl(g_pipeline);
auto r = fn::gfx::compile_fragment(g_dag_glsl); auto r = fn::gfx::compile_fragment(g_dag_glsl);
if (r.ok) { if (r.ok) {
g_descs.clear();
g_store.values.clear();
fn::gfx::canvas_set_program(g_canvas, r.program); fn::gfx::canvas_set_program(g_canvas, r.program);
g_last_err.clear(); g_last_err.clear();
g_last_err_line = -1; g_last_err_line = -1;
@@ -64,6 +64,13 @@ static void mark_dirty() {
g_dirty = true; g_dirty = true;
} }
static void set_active(Source s) {
if (g_active != s) {
g_active = s;
try_compile();
}
}
static void load_preset(const char* src) { static void load_preset(const char* src) {
g_source = src; g_source = src;
mark_dirty(); mark_dirty();
@@ -78,11 +85,21 @@ static void ensure_dag_default() {
step.name = def->name; step.name = def->name;
step.params = def->param_defaults; step.params = def->param_defaults;
g_pipeline.push_back(step); g_pipeline.push_back(step);
mark_dirty();
} }
} }
} }
static void draw_err_footer() {
if (g_last_err.empty()) return;
ImGui::Separator();
if (g_last_err_line > 0) {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s",
g_last_err_line, g_last_err.c_str());
} else {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str());
}
}
static void render() { static void render() {
if (!g_canvas.initialized) fn::gfx::canvas_init(g_canvas); if (!g_canvas.initialized) fn::gfx::canvas_init(g_canvas);
@@ -97,90 +114,56 @@ static void render() {
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
// --- Left panel: Code or DAG --- // --- Code window ---
const char* panel_title = (g_mode == AppMode::Code) ? "Code" : "DAG"; if (ImGui::Begin("Code")) {
if (ImGui::Begin(panel_title)) { if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
// Mode toggle topbar set_active(Source::Code);
{
bool code_active = (g_mode == AppMode::Code);
bool dag_active = (g_mode == AppMode::Dag);
if (code_active) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
if (ImGui::Button("Code")) {
if (g_mode != AppMode::Code) {
g_mode = AppMode::Code;
mark_dirty();
}
}
if (code_active) ImGui::PopStyleColor();
ImGui::SameLine();
if (dag_active) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f));
if (ImGui::Button("DAG")) {
if (g_mode != AppMode::Dag) {
g_mode = AppMode::Dag;
ensure_dag_default();
mark_dirty();
}
}
if (dag_active) ImGui::PopStyleColor();
ImGui::Separator();
} }
if (g_mode == AppMode::Code) { if (ImGui::Button("Plasma")) { load_preset(PLASMA); }
if (ImGui::Button("Plasma")) { load_preset(PLASMA); } ImGui::SameLine();
ImGui::SameLine(); if (ImGui::Button("Circle")) { load_preset(CIRCLE); }
if (ImGui::Button("Circle")) { load_preset(CIRCLE); } ImGui::SameLine();
ImGui::SameLine(); if (ImGui::Button("Checker")) { load_preset(CHECKER); }
if (ImGui::Button("Checker")) { load_preset(CHECKER); }
ImVec2 avail = ImGui::GetContentRegionAvail(); bool has_err_here = !g_last_err.empty() && g_active == Source::Code;
float footer_height = g_last_err.empty() ? 0.0f : ImGui::GetTextLineHeightWithSpacing() + 8.0f; ImVec2 avail = ImGui::GetContentRegionAvail();
ImVec2 editor_size(avail.x, avail.y - footer_height); float footer_height = has_err_here ? ImGui::GetTextLineHeightWithSpacing() + 8.0f : 0.0f;
ImVec2 editor_size(avail.x, avail.y - footer_height);
char buf[1 << 16]; char buf[1 << 16];
size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1; size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1;
memcpy(buf, g_source.c_str(), copy_len); memcpy(buf, g_source.c_str(), copy_len);
buf[copy_len] = '\0'; buf[copy_len] = '\0';
ImGui::PushFont(nullptr); if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size,
if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size, ImGuiInputTextFlags_AllowTabInput)) {
ImGuiInputTextFlags_AllowTabInput)) { g_source = buf;
g_source = buf; mark_dirty();
mark_dirty();
}
ImGui::PopFont();
if (!g_last_err.empty()) {
ImGui::Separator();
if (g_last_err_line > 0) {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s",
g_last_err_line, g_last_err.c_str());
} else {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str());
}
}
} else {
// DAG mode
bool topo_changed = fn::gfx::dag_panel(g_pipeline);
if (topo_changed) {
mark_dirty();
}
if (!g_last_err.empty()) {
ImGui::Separator();
if (g_last_err_line > 0) {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s",
g_last_err_line, g_last_err.c_str());
} else {
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", g_last_err.c_str());
}
}
} }
if (has_err_here) draw_err_footer();
} }
ImGui::End(); ImGui::End();
// --- Canvas panel --- // --- DAG Pipeline window ---
if (ImGui::Begin("Canvas")) { if (ImGui::Begin("DAG Pipeline")) {
if (g_mode == AppMode::Code) { if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
set_active(Source::Dag);
}
bool topo_changed = fn::gfx::dag_panel(g_pipeline);
if (topo_changed) mark_dirty();
if (!g_last_err.empty() && g_active == Source::Dag) draw_err_footer();
}
ImGui::End();
// --- Canvas window ---
char canvas_title[64];
snprintf(canvas_title, sizeof(canvas_title), "Canvas [%s]###canvas",
g_active == Source::Code ? "Code" : "DAG");
if (ImGui::Begin(canvas_title)) {
if (g_active == Source::Code) {
fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()), fn::gfx::canvas_render(g_canvas, static_cast<float>(ImGui::GetTime()),
[](unsigned int program) { [](unsigned int program) {
fn::gfx::uniforms_apply(g_store, g_descs, program); fn::gfx::uniforms_apply(g_store, g_descs, program);
@@ -194,28 +177,37 @@ static void render() {
} }
ImGui::End(); ImGui::End();
// --- Controls panel --- // --- Controls window (Code uniforms) ---
if (ImGui::Begin("Controls")) { if (ImGui::Begin("Controls")) {
if (g_mode == AppMode::Code) { if (g_descs.empty()) {
fn::gfx::uniforms_panel(g_store, g_descs); ImGui::TextDisabled("No uniforms declared in Code.");
ImGui::TextDisabled("Use // @slider, @color, @toggle, @xy annotations.");
} else { } else {
// Show generated GLSL read-only fn::gfx::uniforms_panel(g_store, g_descs);
if (ImGui::CollapsingHeader("Generated GLSL")) {
ImVec2 avail = ImGui::GetContentRegionAvail();
ImGui::InputTextMultiline("##dag_glsl",
const_cast<char*>(g_dag_glsl.c_str()),
g_dag_glsl.size() + 1,
ImVec2(avail.x, std::min(avail.y, 400.0f)),
ImGuiInputTextFlags_ReadOnly);
}
} }
ImGui::Spacing(); ImGui::Spacing();
fps_overlay(); fps_overlay();
} }
ImGui::End(); ImGui::End();
// --- Generated GLSL window (DAG compiled output, read-only) ---
if (ImGui::Begin("Generated GLSL")) {
if (g_dag_glsl.empty()) {
ImGui::TextDisabled("(DAG not compiled yet — focus the DAG Pipeline window)");
} else {
ImVec2 avail = ImGui::GetContentRegionAvail();
ImGui::InputTextMultiline("##dag_glsl",
const_cast<char*>(g_dag_glsl.c_str()),
g_dag_glsl.size() + 1,
avail,
ImGuiInputTextFlags_ReadOnly);
}
}
ImGui::End();
} }
int main() { int main() {
ensure_dag_default();
fn::AppConfig cfg; fn::AppConfig cfg;
cfg.title = "shaders_lab"; cfg.title = "shaders_lab";
cfg.width = 1400; cfg.width = 1400;