From c974eaa604046fa15d3230e4bc10b39b739394dc Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Fri, 24 Apr 2026 21:27:50 +0200 Subject: [PATCH] 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) --- cpp/apps/shaders_lab/main.cpp | 178 ++++++++++++++++------------------ 1 file changed, 85 insertions(+), 93 deletions(-) diff --git a/cpp/apps/shaders_lab/main.cpp b/cpp/apps/shaders_lab/main.cpp index e2d5e0f8..a177da63 100644 --- a/cpp/apps/shaders_lab/main.cpp +++ b/cpp/apps/shaders_lab/main.cpp @@ -13,12 +13,14 @@ #include "seed_shaders.h" #include +#include +#include #include #include -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 std::string g_source = PLASMA; static std::string g_last_err; @@ -31,7 +33,7 @@ static std::vector g_pipeline; static std::string g_dag_glsl; static void try_compile() { - if (g_mode == AppMode::Code) { + if (g_active == Source::Code) { auto r = fn::gfx::compile_fragment(g_source); if (r.ok) { 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); auto r = fn::gfx::compile_fragment(g_dag_glsl); if (r.ok) { - g_descs.clear(); - g_store.values.clear(); fn::gfx::canvas_set_program(g_canvas, r.program); g_last_err.clear(); g_last_err_line = -1; @@ -64,6 +64,13 @@ static void mark_dirty() { 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) { g_source = src; mark_dirty(); @@ -78,11 +85,21 @@ static void ensure_dag_default() { step.name = def->name; step.params = def->param_defaults; 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() { if (!g_canvas.initialized) fn::gfx::canvas_init(g_canvas); @@ -97,90 +114,56 @@ static void render() { ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport()); - // --- Left panel: Code or DAG --- - const char* panel_title = (g_mode == AppMode::Code) ? "Code" : "DAG"; - if (ImGui::Begin(panel_title)) { - // Mode toggle topbar - { - 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(); + // --- Code window --- + if (ImGui::Begin("Code")) { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) { + set_active(Source::Code); } - if (g_mode == AppMode::Code) { - if (ImGui::Button("Plasma")) { load_preset(PLASMA); } - ImGui::SameLine(); - if (ImGui::Button("Circle")) { load_preset(CIRCLE); } - ImGui::SameLine(); - if (ImGui::Button("Checker")) { load_preset(CHECKER); } + if (ImGui::Button("Plasma")) { load_preset(PLASMA); } + ImGui::SameLine(); + if (ImGui::Button("Circle")) { load_preset(CIRCLE); } + ImGui::SameLine(); + if (ImGui::Button("Checker")) { load_preset(CHECKER); } - ImVec2 avail = ImGui::GetContentRegionAvail(); - float footer_height = g_last_err.empty() ? 0.0f : ImGui::GetTextLineHeightWithSpacing() + 8.0f; - ImVec2 editor_size(avail.x, avail.y - footer_height); + bool has_err_here = !g_last_err.empty() && g_active == Source::Code; + ImVec2 avail = ImGui::GetContentRegionAvail(); + 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]; - size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1; - memcpy(buf, g_source.c_str(), copy_len); - buf[copy_len] = '\0'; + char buf[1 << 16]; + size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1; + memcpy(buf, g_source.c_str(), copy_len); + buf[copy_len] = '\0'; - ImGui::PushFont(nullptr); - if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size, - ImGuiInputTextFlags_AllowTabInput)) { - g_source = buf; - 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 (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size, + ImGuiInputTextFlags_AllowTabInput)) { + g_source = buf; + mark_dirty(); } + + if (has_err_here) draw_err_footer(); } ImGui::End(); - // --- Canvas panel --- - if (ImGui::Begin("Canvas")) { - if (g_mode == AppMode::Code) { + // --- DAG Pipeline window --- + if (ImGui::Begin("DAG Pipeline")) { + 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(ImGui::GetTime()), [](unsigned int program) { fn::gfx::uniforms_apply(g_store, g_descs, program); @@ -194,28 +177,37 @@ static void render() { } ImGui::End(); - // --- Controls panel --- + // --- Controls window (Code uniforms) --- if (ImGui::Begin("Controls")) { - if (g_mode == AppMode::Code) { - fn::gfx::uniforms_panel(g_store, g_descs); + if (g_descs.empty()) { + ImGui::TextDisabled("No uniforms declared in Code."); + ImGui::TextDisabled("Use // @slider, @color, @toggle, @xy annotations."); } else { - // Show generated GLSL read-only - if (ImGui::CollapsingHeader("Generated GLSL")) { - ImVec2 avail = ImGui::GetContentRegionAvail(); - ImGui::InputTextMultiline("##dag_glsl", - const_cast(g_dag_glsl.c_str()), - g_dag_glsl.size() + 1, - ImVec2(avail.x, std::min(avail.y, 400.0f)), - ImGuiInputTextFlags_ReadOnly); - } + fn::gfx::uniforms_panel(g_store, g_descs); } ImGui::Spacing(); fps_overlay(); } 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(g_dag_glsl.c_str()), + g_dag_glsl.size() + 1, + avail, + ImGuiInputTextFlags_ReadOnly); + } + } + ImGui::End(); } int main() { + ensure_dag_default(); fn::AppConfig cfg; cfg.title = "shaders_lab"; cfg.width = 1400;