feat(primitives_gallery): wire text_editor + file_watcher demo

- demos_text_editor.cpp: split horizontal con editor GLSL precargado a la
  izquierda (boton Save to /tmp/fn_demo.glsl + dirty indicator) y panel de
  eventos a la derecha (path, active flag, lista scrollable, boton clear).
  Watcher activo sobre /tmp/fn_demo.glsl; reintenta el add() tras el primer
  Save si el archivo no existia al iniciar.
- demos.h: declaracion de gallery::demo_text_editor()
- main.cpp: entry "text_editor"/"text_editor + watcher" en categoria Core
- CMakeLists.txt: anade demos_text_editor.cpp + sources de text_editor,
  file_watcher y vendor TextEditor.cpp + include path de imgui_text_edit

Nota: la primitives_gallery NO se construye en este branch (sus deps —
button.cpp, toolbar.cpp, etc. — son untracked en master). El subdirectorio
se anade pero protegido por FN_BUILD_GALLERY=OFF para no romper builds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 21:00:55 +02:00
parent dc079a815b
commit ed1ffd07b5
11 changed files with 1717 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
// primitives_gallery — catalogo visual interactivo de los primitivos UI
// del registry (cpp/functions/core y cpp/functions/viz).
//
// Sidebar izquierdo con lista de primitivos agrupados por dominio; panel
// derecho renderiza la demo del item seleccionado (+ snippet de codigo).
//
// Rol: smoke test visual + documentacion viva + build gate en CI.
// NO se conecta a sqlite_api ni a ningun backend. Datos sinteticos.
#include "app_base.h"
#include "imgui.h"
#include "core/fullscreen_window.h"
#include "core/tokens.h"
#include "core/page_header.h"
#include "core/toast.h"
#include "core/app_menubar.h"
#include "gfx/gl_loader.h"
#include "demos.h"
#include "demo.h"
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
struct DemoEntry {
const char* id; // id estable, apto para comparar seleccion
const char* label; // texto en sidebar
const char* category; // "Core" o "Viz"
void (*fn)(); // puntero a la demo_xxx
};
static const DemoEntry k_demos[] = {
// Core
{"button", "button", "Core", &gallery::demo_button},
{"icon_button", "icon_button", "Core", &gallery::demo_icon_button},
{"toolbar", "toolbar", "Core", &gallery::demo_toolbar},
{"modal_dialog", "modal_dialog", "Core", &gallery::demo_modal},
{"text_input", "text_input", "Core", &gallery::demo_text_input},
{"select", "select", "Core", &gallery::demo_select},
{"toast", "toast + inbox", "Core", &gallery::demo_toast},
{"tree_view", "tree_view", "Core", &gallery::demo_tree_view},
{"badge", "badge", "Core", &gallery::demo_badge},
{"empty_state", "empty_state", "Core", &gallery::demo_empty_state},
{"page_header", "page_header", "Core", &gallery::demo_page_header},
{"dashboard_panel", "dashboard_panel", "Core", &gallery::demo_dashboard_panel},
{"kpi_card", "kpi_card", "Core", &gallery::demo_kpi_card},
{"text_editor", "text_editor + watcher", "Core", &gallery::demo_text_editor},
// Viz
{"bar_chart", "bar_chart", "Viz", &gallery::demo_bar_chart},
{"pie_chart", "pie_chart", "Viz", &gallery::demo_pie_chart},
{"line_plot", "line_plot", "Viz", &gallery::demo_line_plot},
{"scatter_plot", "scatter_plot", "Viz", &gallery::demo_scatter_plot},
{"histogram", "histogram", "Viz", &gallery::demo_histogram},
{"sparkline", "sparkline", "Viz", &gallery::demo_sparkline},
{"graph_viewport", "graph_viewport", "Viz", &gallery::demo_graph},
// Gfx (shaders_lab core)
{"shader_canvas", "shader_canvas", "Gfx", &gallery::demo_shader_canvas},
};
static constexpr int k_demo_count = sizeof(k_demos) / sizeof(k_demos[0]);
static std::string g_selected_id = "button";
static const DemoEntry* find_demo(const std::string& id) {
for (int i = 0; i < k_demo_count; i++) {
if (id == k_demos[i].id) return &k_demos[i];
}
return &k_demos[0];
}
static void draw_sidebar() {
using namespace fn_tokens;
ImGui::BeginChild("##gallery_sidebar", ImVec2(220, 0),
ImGuiChildFlags_Borders);
const char* current_category = nullptr;
for (int i = 0; i < k_demo_count; i++) {
const auto& d = k_demos[i];
if (!current_category || std::strcmp(current_category, d.category) != 0) {
if (current_category) ImGui::Dummy(ImVec2(0, spacing::sm));
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
ImGui::TextUnformatted(d.category);
ImGui::PopStyleColor();
ImGui::Separator();
current_category = d.category;
}
const bool selected = (g_selected_id == d.id);
ImGui::PushStyleColor(ImGuiCol_Header, selected ? colors::surface_hover : ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, colors::surface_hover);
ImGui::PushStyleColor(ImGuiCol_HeaderActive, colors::surface);
ImGui::PushStyleColor(ImGuiCol_Text, selected ? colors::primary : colors::text);
char label[96];
std::snprintf(label, sizeof(label), "%s##sel_%s", d.label, d.id);
if (ImGui::Selectable(label, selected)) {
g_selected_id = d.id;
}
ImGui::PopStyleColor(4);
}
ImGui::EndChild();
}
static void render() {
static bool init_done = false;
if (!init_done) {
fn_tokens::apply_dark_theme();
// En Linux es no-op; en Windows resuelve los punteros GL via wglGetProcAddress.
// Imprescindible antes de invocar primitivos que usen OpenGL 2.0+ (graph_viewport,
// shader_canvas, etc).
fn::gfx::gl_loader_init();
init_done = true;
}
// MainMenuBar (solo Settings — la gallery no tiene paneles toggleables ni layouts)
fn_ui::app_menubar(nullptr, 0, nullptr);
fullscreen_window_begin("##gallery");
page_header_begin("Primitives Gallery",
"Visual catalog of fn_registry C++ UI primitives");
page_header_end();
if (ImGui::BeginTable("##layout", 2,
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("sidebar", ImGuiTableColumnFlags_WidthFixed, 220.0f);
ImGui::TableSetupColumn("content", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
draw_sidebar();
ImGui::TableSetColumnIndex(1);
ImGui::BeginChild("##gallery_content", ImVec2(0, 0),
ImGuiChildFlags_Borders,
ImGuiWindowFlags_HorizontalScrollbar);
const DemoEntry* d = find_demo(g_selected_id);
if (d && d->fn) d->fn();
ImGui::EndChild();
ImGui::EndTable();
}
fullscreen_window_end();
// Toasts se renderizan encima para que el demo de toast funcione aqui tambien.
fn_ui::toast_render();
}
int main(int /*argc*/, char** /*argv*/) {
return fn::run_app(
{.title = "fn_registry · Primitives Gallery",
.width = 1400, .height = 900, .viewports = true},
render
);
}