auto(0129): agents_dashboard — secret_store_cpp_infra + CMakeLists register #4
@@ -7,6 +7,7 @@ add_imgui_app(primitives_gallery
|
||||
demos_gfx.cpp
|
||||
demos_text_editor.cpp
|
||||
demos_gl_texture.cpp
|
||||
demos_extras.cpp
|
||||
# text_editor + file_watcher (issue 0025)
|
||||
${CMAKE_SOURCE_DIR}/functions/core/text_editor.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/file_watcher.cpp
|
||||
@@ -34,6 +35,10 @@ add_imgui_app(primitives_gallery
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/scatter_plot.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/histogram.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/sparkline.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/candlestick.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/gauge.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/heatmap.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/table_view.cpp
|
||||
# Graph stack (instanced GPU + Barnes-Hut + spatial hash)
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/graph_types.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/graph_renderer.cpp
|
||||
|
||||
@@ -18,6 +18,9 @@ void demo_badge();
|
||||
void demo_empty_state();
|
||||
void demo_page_header();
|
||||
void demo_dashboard_panel();
|
||||
void demo_text_editor(); // wave 1, issue 0025
|
||||
void demo_file_watcher(); // wave 1, issue 0025
|
||||
void demo_process_runner();
|
||||
|
||||
// --- Viz ---
|
||||
void demo_bar_chart();
|
||||
@@ -27,12 +30,13 @@ void demo_scatter_plot();
|
||||
void demo_histogram();
|
||||
void demo_sparkline();
|
||||
void demo_graph();
|
||||
void demo_candlestick();
|
||||
void demo_gauge();
|
||||
void demo_heatmap();
|
||||
void demo_table_view();
|
||||
|
||||
// --- Gfx ---
|
||||
void demo_shader_canvas();
|
||||
void demo_gl_texture();
|
||||
|
||||
// --- Core (combined demo: text_editor + file_watcher) ---
|
||||
void demo_text_editor();
|
||||
void demo_gl_texture(); // wave 1, issue 0026
|
||||
|
||||
} // namespace gallery
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
// Demos faltantes: process_runner (Core), candlestick / gauge / heatmap /
|
||||
// table_view (Viz). Aniade cobertura sobre los primitivos del registry que
|
||||
// no tenian su entry en la gallery.
|
||||
|
||||
#include "demos.h"
|
||||
#include "demo.h"
|
||||
|
||||
#include "core/process_runner.h"
|
||||
#include "viz/candlestick.h"
|
||||
#include "viz/gauge.h"
|
||||
#include "viz/heatmap.h"
|
||||
#include "viz/table_view.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace gallery {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// process_runner (Core)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_process_runner() {
|
||||
demo_header("process_runner", "v1.0.0",
|
||||
"Ejecuta una tarea en std::thread en background y expone estado thread-safe "
|
||||
"(idle/running/success/error). El widget runner_status() dibuja inline un "
|
||||
"spinner mientras corre y un mensaje de Success/Error al terminar.");
|
||||
|
||||
static fn_ui::ProcessRunner runner;
|
||||
|
||||
section("Tarea simulada (sleep 2s)");
|
||||
{
|
||||
if (ImGui::Button("Run task")) {
|
||||
if (!runner.is_busy()) {
|
||||
fn_ui::runner_trigger(runner, [](std::string& out) -> bool {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
out = "task done in 2s";
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Run failing task")) {
|
||||
if (!runner.is_busy()) {
|
||||
fn_ui::runner_trigger(runner, [](std::string& out) -> bool {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
out = "simulated failure";
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset")) runner.reset();
|
||||
fn_ui::runner_status(runner, "Working...");
|
||||
}
|
||||
|
||||
code_block(
|
||||
"static fn_ui::ProcessRunner r;\n"
|
||||
"if (button(\"Run\", Primary) && !r.is_busy()) {\n"
|
||||
" fn_ui::runner_trigger(r, [](std::string& out) -> bool {\n"
|
||||
" return do_work(&out);\n"
|
||||
" });\n"
|
||||
"}\n"
|
||||
"fn_ui::runner_status(r, \"Working...\");"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// candlestick (Viz)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_candlestick() {
|
||||
demo_header("candlestick", "v1.0.0",
|
||||
"Grafico de velas OHLC con ImPlot custom rendering. Verde si close >= open, "
|
||||
"rojo si bajista. Tooltip al hover muestra OHLC del dia.");
|
||||
|
||||
section("OHLC sintetico (30 dias)");
|
||||
{
|
||||
static std::vector<double> dates, opens, closes, lows, highs;
|
||||
if (dates.empty()) {
|
||||
dates.reserve(30); opens.reserve(30); closes.reserve(30);
|
||||
lows.reserve(30); highs.reserve(30);
|
||||
double price = 100.0;
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
double drift = std::sin(i * 0.4) * 1.2;
|
||||
double o = price;
|
||||
double c = price + drift + (i % 3 == 0 ? -0.6 : 0.4);
|
||||
double l = std::min(o, c) - 0.8 - (i % 5) * 0.1;
|
||||
double h = std::max(o, c) + 0.8 + (i % 4) * 0.1;
|
||||
dates.push_back(double(i));
|
||||
opens.push_back(o);
|
||||
closes.push_back(c);
|
||||
lows.push_back(l);
|
||||
highs.push_back(h);
|
||||
price = c;
|
||||
}
|
||||
}
|
||||
candlestick("##cs", dates.data(), opens.data(), closes.data(),
|
||||
lows.data(), highs.data(), int(dates.size()));
|
||||
}
|
||||
|
||||
code_block(
|
||||
"candlestick(\"##cs\", dates, opens, closes, lows, highs, n,\n"
|
||||
" /*width_percent=*/0.25f, /*tooltip=*/true);"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// gauge (Viz)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_gauge() {
|
||||
demo_header("gauge", "v1.0.0",
|
||||
"Indicador circular tipo velocimetro con ImGui DrawList. Color interpolado "
|
||||
"verde -> amarillo -> rojo segun el valor normalizado.");
|
||||
|
||||
static float v_cpu = 0.32f, v_mem = 0.78f, v_gpu = 0.55f;
|
||||
|
||||
section("Tres gauges con sliders");
|
||||
{
|
||||
ImGui::SliderFloat("cpu", &v_cpu, 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("mem", &v_mem, 0.0f, 1.0f);
|
||||
ImGui::SliderFloat("gpu", &v_gpu, 0.0f, 1.0f);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::BeginGroup();
|
||||
gauge("CPU", v_cpu, 0.0f, 1.0f, 60.0f);
|
||||
ImGui::EndGroup();
|
||||
ImGui::SameLine(0.0f, 24.0f);
|
||||
ImGui::BeginGroup();
|
||||
gauge("MEM", v_mem, 0.0f, 1.0f, 60.0f);
|
||||
ImGui::EndGroup();
|
||||
ImGui::SameLine(0.0f, 24.0f);
|
||||
ImGui::BeginGroup();
|
||||
gauge("GPU", v_gpu, 0.0f, 1.0f, 60.0f);
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
|
||||
code_block("gauge(\"CPU\", 0.32f, 0.0f, 1.0f, 60.0f);");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// heatmap (Viz)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_heatmap() {
|
||||
demo_header("heatmap", "v1.0.0",
|
||||
"Mapa de calor 2D con ImPlot. Datos row-major. Util para correlation "
|
||||
"matrices, attention maps, distribuciones 2D discretas.");
|
||||
|
||||
constexpr int R = 12;
|
||||
constexpr int C = 12;
|
||||
static float values[R * C] = {0};
|
||||
static bool init = false;
|
||||
if (!init) {
|
||||
for (int r = 0; r < R; ++r) {
|
||||
for (int c = 0; c < C; ++c) {
|
||||
float dx = (c - C * 0.5f) / float(C);
|
||||
float dy = (r - R * 0.5f) / float(R);
|
||||
values[r * C + c] = std::exp(-(dx * dx + dy * dy) * 6.0f);
|
||||
}
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
section("Gaussian 12x12");
|
||||
{
|
||||
heatmap("##hm", values, R, C, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
code_block(
|
||||
"float values[R * C];\n"
|
||||
"// fill row-major: values[r * C + c] = ...\n"
|
||||
"heatmap(\"##hm\", values, R, C, /*min=*/0.0f, /*max=*/1.0f);"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// table_view (Viz)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void demo_table_view() {
|
||||
demo_header("table_view", "v1.0.0",
|
||||
"Tabla interactiva con sorting indicators y scroll usando la ImGui Tables API. "
|
||||
"Headers + cells row-major. Util para dashboards y inspectores.");
|
||||
|
||||
section("Lenguajes del registry");
|
||||
{
|
||||
const char* headers[] = {"id", "lang", "domain", "purity"};
|
||||
// 6 filas x 4 cols, row-major
|
||||
const char* cells[] = {
|
||||
"filter_slice_go_core", "go", "core", "pure",
|
||||
"metabase_setup_py_infra", "py", "infra", "impure",
|
||||
"rsync_deploy_bash_infra", "sh", "infra", "impure",
|
||||
"button_cpp_core", "cpp", "core", "pure",
|
||||
"gl_texture_load_cpp_gfx", "cpp", "gfx", "impure",
|
||||
"audio_fft_cpp_core", "cpp", "core", "pure",
|
||||
};
|
||||
const int row_count = 6;
|
||||
const int col_count = 4;
|
||||
table_view("##tbl", headers, col_count, cells, row_count);
|
||||
}
|
||||
|
||||
code_block(
|
||||
"const char* headers[] = {\"id\", \"lang\", \"domain\"};\n"
|
||||
"const char* cells[] = {/* row-major: r0c0,r0c1,r0c2, r1c0,... */};\n"
|
||||
"table_view(\"##tbl\", headers, 3, cells, n_rows);"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace gallery
|
||||
@@ -1,10 +1,8 @@
|
||||
// Demo combinada: text_editor + file_watcher.
|
||||
// Demos individuales de text_editor y file_watcher (Wave 1, issue 0025).
|
||||
//
|
||||
// Layout (split horizontal):
|
||||
// - Izquierda: text_editor con CodeLang::GLSL precargado con un fragment
|
||||
// shader simple. Boton "Save to /tmp/fn_demo.glsl".
|
||||
// - Derecha: panel de info — dirty flag, ultimo error, lista scrollable de
|
||||
// eventos del watcher activo sobre /tmp/fn_demo.glsl.
|
||||
// Aunque las dos primitivas estan diseñadas para componerse, en gallery se
|
||||
// muestran por separado para que cada entry exhiba un solo primitivo y su
|
||||
// API minima.
|
||||
|
||||
#include "demos.h"
|
||||
#include "demo.h"
|
||||
@@ -25,13 +23,15 @@
|
||||
|
||||
namespace gallery {
|
||||
|
||||
// ===========================================================================
|
||||
// text_editor — editor de codigo con syntax highlighting
|
||||
// ===========================================================================
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kDemoPath = "/tmp/fn_demo.glsl";
|
||||
|
||||
const char* kInitialGLSL =
|
||||
const char* kSampleGLSL =
|
||||
"#version 330\n"
|
||||
"// Demo fragment shader (text_editor + file_watcher).\n"
|
||||
"// fragment shader demo\n"
|
||||
"out vec4 frag_color;\n"
|
||||
"uniform vec2 u_resolution;\n"
|
||||
"uniform float u_time;\n"
|
||||
@@ -42,40 +42,144 @@ const char* kInitialGLSL =
|
||||
" frag_color = vec4(col, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
struct EventLogEntry {
|
||||
double t_seconds; // tiempo relativo al primer evento mostrado
|
||||
std::string label;
|
||||
const char* kSampleSQL =
|
||||
"-- fts5 search sobre el registry\n"
|
||||
"SELECT id, kind, purity, description\n"
|
||||
"FROM functions\n"
|
||||
"WHERE id IN (\n"
|
||||
" SELECT id FROM functions_fts\n"
|
||||
" WHERE functions_fts MATCH 'name:slic* OR description:slic*'\n"
|
||||
")\n"
|
||||
"ORDER BY name\n"
|
||||
"LIMIT 50;\n";
|
||||
|
||||
const char* kSampleCpp =
|
||||
"#include <imgui.h>\n"
|
||||
"namespace fn {\n"
|
||||
" bool button(const char* label, ButtonVariant v) {\n"
|
||||
" auto& tk = tokens::current();\n"
|
||||
" ImGui::PushStyleColor(ImGuiCol_Button, tk.bg_for(v));\n"
|
||||
" bool clicked = ImGui::Button(label);\n"
|
||||
" ImGui::PopStyleColor();\n"
|
||||
" return clicked;\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
|
||||
struct EditorState {
|
||||
fn::TextEditorState* ed = nullptr;
|
||||
fn::CodeLang lang = fn::CodeLang::GLSL;
|
||||
};
|
||||
|
||||
struct DemoState {
|
||||
fn::TextEditorState* editor = nullptr;
|
||||
fn::FileWatcher* watcher = nullptr;
|
||||
std::deque<EventLogEntry> events;
|
||||
std::string save_status;
|
||||
std::string watch_error;
|
||||
bool watcher_active = false;
|
||||
};
|
||||
|
||||
DemoState& state() {
|
||||
static DemoState s;
|
||||
EditorState& editor_state() {
|
||||
static EditorState s;
|
||||
return s;
|
||||
}
|
||||
|
||||
void ensure_init() {
|
||||
auto& s = state();
|
||||
if (!s.editor) {
|
||||
s.editor = fn::text_editor_create(fn::CodeLang::GLSL);
|
||||
fn::text_editor_set_text(s.editor, kInitialGLSL);
|
||||
void ensure_editor() {
|
||||
auto& s = editor_state();
|
||||
if (!s.ed) {
|
||||
s.ed = fn::text_editor_create(s.lang);
|
||||
fn::text_editor_set_text(s.ed, kSampleGLSL);
|
||||
}
|
||||
if (!s.watcher) {
|
||||
s.watcher = fn::file_watcher_create();
|
||||
// Si /tmp/fn_demo.glsl no existe aun, file_watcher_add fallara —
|
||||
// se reintenta tras el primer Save.
|
||||
s.watcher_active = fn::file_watcher_add(s.watcher, kDemoPath);
|
||||
if (!s.watcher_active) {
|
||||
s.watch_error = fn::file_watcher_last_error(s.watcher);
|
||||
}
|
||||
|
||||
void apply_language(fn::CodeLang next) {
|
||||
auto& s = editor_state();
|
||||
if (next == s.lang) return;
|
||||
fn::text_editor_destroy(s.ed);
|
||||
s.ed = fn::text_editor_create(next);
|
||||
s.lang = next;
|
||||
switch (next) {
|
||||
case fn::CodeLang::GLSL: fn::text_editor_set_text(s.ed, kSampleGLSL); break;
|
||||
case fn::CodeLang::SQL: fn::text_editor_set_text(s.ed, kSampleSQL); break;
|
||||
case fn::CodeLang::Cpp: fn::text_editor_set_text(s.ed, kSampleCpp); break;
|
||||
case fn::CodeLang::Generic: fn::text_editor_set_text(s.ed, ""); break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void demo_text_editor() {
|
||||
using namespace fn_tokens;
|
||||
|
||||
demo_header("text_editor", "v1.0.0",
|
||||
"Editor de codigo embebido en ImGui con syntax highlighting (GLSL/SQL/Cpp/Generic). "
|
||||
"Wrapper PIMPL sobre ImGuiColorTextEdit (MIT). API: create / set_text / get_text / "
|
||||
"render / is_dirty.");
|
||||
|
||||
ensure_editor();
|
||||
auto& s = editor_state();
|
||||
|
||||
section("language");
|
||||
{
|
||||
const char* labels[] = {"GLSL", "SQL", "Cpp", "Generic"};
|
||||
const fn::CodeLang langs[] = {
|
||||
fn::CodeLang::GLSL, fn::CodeLang::SQL, fn::CodeLang::Cpp, fn::CodeLang::Generic
|
||||
};
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i > 0) ImGui::SameLine();
|
||||
bool active = (s.lang == langs[i]);
|
||||
if (active) ImGui::PushStyleColor(ImGuiCol_Button, colors::primary);
|
||||
if (ImGui::Button(labels[i])) apply_language(langs[i]);
|
||||
if (active) ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
section("editor");
|
||||
{
|
||||
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
float h = avail.y - 60.0f;
|
||||
if (h < 220.0f) h = 220.0f;
|
||||
fn::text_editor_render(s.ed, "##fn_text_editor_solo", ImVec2(-1, h));
|
||||
|
||||
if (fn::text_editor_is_dirty(s.ed)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::warning);
|
||||
ImGui::TextUnformatted("(modified)");
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("clear dirty##te_solo")) fn::text_editor_clear_dirty(s.ed);
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
|
||||
ImGui::TextUnformatted("(clean)");
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
|
||||
code_block(
|
||||
"auto* ed = fn::text_editor_create(fn::CodeLang::GLSL);\n"
|
||||
"fn::text_editor_set_text(ed, src);\n"
|
||||
"if (fn::text_editor_render(ed, \"##ed\", {600, 400}))\n"
|
||||
" on_changed(fn::text_editor_get_text(ed));"
|
||||
);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// file_watcher — watcher cross-platform no bloqueante
|
||||
// ===========================================================================
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kWatchPath = "/tmp/fn_demo.glsl";
|
||||
|
||||
struct WatcherDemoState {
|
||||
fn::FileWatcher* fw = nullptr;
|
||||
bool active = false;
|
||||
std::string err;
|
||||
std::deque<std::string> events;
|
||||
};
|
||||
|
||||
WatcherDemoState& watcher_state() {
|
||||
static WatcherDemoState s;
|
||||
return s;
|
||||
}
|
||||
|
||||
void ensure_watcher() {
|
||||
auto& s = watcher_state();
|
||||
if (!s.fw) {
|
||||
s.fw = fn::file_watcher_create();
|
||||
s.active = fn::file_watcher_add(s.fw, kWatchPath);
|
||||
if (!s.active) s.err = fn::file_watcher_last_error(s.fw);
|
||||
}
|
||||
}
|
||||
|
||||
const char* kind_label(fn::FileEvent::Kind k) {
|
||||
@@ -88,132 +192,88 @@ const char* kind_label(fn::FileEvent::Kind k) {
|
||||
}
|
||||
|
||||
void poll_and_log() {
|
||||
auto& s = state();
|
||||
if (!s.watcher) return;
|
||||
auto evs = fn::file_watcher_poll(s.watcher);
|
||||
if (evs.empty()) return;
|
||||
double now = (double)std::time(nullptr);
|
||||
auto& s = watcher_state();
|
||||
if (!s.fw) return;
|
||||
auto evs = fn::file_watcher_poll(s.fw);
|
||||
for (auto& e : evs) {
|
||||
char buf[512];
|
||||
std::snprintf(buf, sizeof(buf), "[%s] %s", kind_label(e.kind), e.path.c_str());
|
||||
s.events.push_back({now, buf});
|
||||
s.events.push_back(buf);
|
||||
}
|
||||
while (s.events.size() > 200) s.events.pop_front();
|
||||
}
|
||||
|
||||
bool save_to_disk() {
|
||||
auto& s = state();
|
||||
FILE* f = std::fopen(kDemoPath, "w");
|
||||
if (!f) {
|
||||
s.save_status = std::string("save failed: ") + std::strerror(errno);
|
||||
return false;
|
||||
}
|
||||
const char* txt = fn::text_editor_get_text(s.editor);
|
||||
std::fputs(txt, f);
|
||||
bool touch_demo_file(std::string& err_out) {
|
||||
FILE* f = std::fopen(kWatchPath, "a");
|
||||
if (!f) { err_out = std::strerror(errno); return false; }
|
||||
std::fprintf(f, "// touch %ld\n", (long)std::time(nullptr));
|
||||
std::fclose(f);
|
||||
fn::text_editor_clear_dirty(s.editor);
|
||||
s.save_status = std::string("saved -> ") + kDemoPath;
|
||||
|
||||
// Si el watcher no estaba activo (archivo no existia al iniciar), reintentar.
|
||||
if (!s.watcher_active) {
|
||||
s.watcher_active = fn::file_watcher_add(s.watcher, kDemoPath);
|
||||
if (!s.watcher_active) s.watch_error = fn::file_watcher_last_error(s.watcher);
|
||||
else s.watch_error.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void demo_text_editor() {
|
||||
void demo_file_watcher() {
|
||||
using namespace fn_tokens;
|
||||
|
||||
demo_header("text_editor + file_watcher", "v1.0.0",
|
||||
"Editor de codigo GLSL con syntax highlighting (PIMPL sobre ImGuiColorTextEdit) "
|
||||
"+ watcher de archivos no bloqueante (inotify Linux / ReadDirectoryChangesW Win). "
|
||||
"Edita, pulsa Save y observa el evento llegar al panel derecho.");
|
||||
demo_header("file_watcher", "v1.0.0",
|
||||
"Watcher de archivos cross-platform no bloqueante. Linux: inotify. Windows: "
|
||||
"ReadDirectoryChangesW. API: create / add / poll (drain) / destroy. Cap del "
|
||||
"buffer de eventos: 200.");
|
||||
|
||||
ensure_init();
|
||||
ensure_watcher();
|
||||
poll_and_log();
|
||||
|
||||
auto& s = state();
|
||||
auto& s = watcher_state();
|
||||
|
||||
// Layout: two-column table. Editor a la izquierda, info a la derecha.
|
||||
if (ImGui::BeginTable("##te_layout", 2,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp)) {
|
||||
ImGui::TableSetupColumn("editor", ImGuiTableColumnFlags_WidthStretch, 0.62f);
|
||||
ImGui::TableSetupColumn("info", ImGuiTableColumnFlags_WidthStretch, 0.38f);
|
||||
ImGui::TableNextRow();
|
||||
section("watcher state");
|
||||
ImGui::Text("path: %s", kWatchPath);
|
||||
ImGui::Text("active: %s", s.active ? "yes" : "no");
|
||||
if (!s.err.empty()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::error);
|
||||
ImGui::TextWrapped("err: %s", s.err.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// ---------- Columna izquierda: editor ----------
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
|
||||
section("editor (CodeLang::GLSL)");
|
||||
|
||||
ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||
float editor_h = avail.y - 60.0f;
|
||||
if (editor_h < 200.0f) editor_h = 200.0f;
|
||||
fn::text_editor_render(s.editor, "##fn_text_editor", ImVec2(-1, editor_h));
|
||||
|
||||
ImGui::Spacing();
|
||||
if (fn_ui::button("Save to /tmp/fn_demo.glsl", fn_ui::ButtonVariant::Primary)) {
|
||||
save_to_disk();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (fn::text_editor_is_dirty(s.editor)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::warning);
|
||||
ImGui::TextUnformatted("(modified)");
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
|
||||
ImGui::TextUnformatted("(clean)");
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
if (!s.save_status.empty()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
|
||||
ImGui::TextUnformatted(s.save_status.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// ---------- Columna derecha: info + eventos ----------
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
section("watcher state");
|
||||
|
||||
ImGui::Text("path: %s", kDemoPath);
|
||||
ImGui::Text("active: %s", s.watcher_active ? "yes" : "no");
|
||||
|
||||
if (!s.watch_error.empty()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::error);
|
||||
ImGui::TextWrapped("err: %s", s.watch_error.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
section("events");
|
||||
|
||||
ImGui::Text("captured: %d", (int)s.events.size());
|
||||
ImGui::SameLine();
|
||||
if (fn_ui::button("clear##evlog", fn_ui::ButtonVariant::Subtle, fn_ui::ButtonSize::Sm)) {
|
||||
s.events.clear();
|
||||
}
|
||||
|
||||
ImGui::BeginChild("##evlog", ImVec2(0, 0), ImGuiChildFlags_Borders);
|
||||
if (s.events.empty()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
|
||||
ImGui::TextWrapped("Sin eventos. Modifica el editor + Save, "
|
||||
"o desde otro terminal: echo hi >> %s", kDemoPath);
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
for (auto it = s.events.rbegin(); it != s.events.rend(); ++it) {
|
||||
ImGui::TextUnformatted(it->label.c_str());
|
||||
section("trigger events");
|
||||
{
|
||||
if (ImGui::Button("touch (append timestamp)")) {
|
||||
std::string e;
|
||||
if (!touch_demo_file(e)) s.err = "touch failed: " + e;
|
||||
else s.err.clear();
|
||||
// Si el archivo no existia al inicio, reintenta el add.
|
||||
if (!s.active) {
|
||||
s.active = fn::file_watcher_add(s.fw, kWatchPath);
|
||||
if (!s.active) s.err = fn::file_watcher_last_error(s.fw);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTable();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("clear events")) s.events.clear();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(o desde otro terminal: echo hi >> %s)", kWatchPath);
|
||||
}
|
||||
|
||||
section("event log");
|
||||
ImGui::Text("captured: %d", (int)s.events.size());
|
||||
ImGui::BeginChild("##fw_evlog", ImVec2(0, 0), ImGuiChildFlags_Borders);
|
||||
if (s.events.empty()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
|
||||
ImGui::TextWrapped("Sin eventos. Pulsa touch o modifica el path desde otro terminal.");
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
for (auto it = s.events.rbegin(); it != s.events.rend(); ++it) {
|
||||
ImGui::TextUnformatted(it->c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
code_block(
|
||||
"auto* fw = fn::file_watcher_create();\n"
|
||||
"fn::file_watcher_add(fw, \"/tmp/foo.glsl\");\n"
|
||||
"for (auto& e : fn::file_watcher_poll(fw)) {\n"
|
||||
" handle_event(e.path, e.kind);\n"
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace gallery
|
||||
|
||||
@@ -46,7 +46,9 @@ static const DemoEntry k_demos[] = {
|
||||
{"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},
|
||||
{"text_editor", "text_editor", "Core", &gallery::demo_text_editor}, // wave 1
|
||||
{"file_watcher", "file_watcher", "Core", &gallery::demo_file_watcher}, // wave 1
|
||||
{"process_runner", "process_runner", "Core", &gallery::demo_process_runner},
|
||||
// Viz
|
||||
{"bar_chart", "bar_chart", "Viz", &gallery::demo_bar_chart},
|
||||
{"pie_chart", "pie_chart", "Viz", &gallery::demo_pie_chart},
|
||||
@@ -55,9 +57,13 @@ static const DemoEntry k_demos[] = {
|
||||
{"histogram", "histogram", "Viz", &gallery::demo_histogram},
|
||||
{"sparkline", "sparkline", "Viz", &gallery::demo_sparkline},
|
||||
{"graph_viewport", "graph_viewport", "Viz", &gallery::demo_graph},
|
||||
{"candlestick", "candlestick", "Viz", &gallery::demo_candlestick},
|
||||
{"gauge", "gauge", "Viz", &gallery::demo_gauge},
|
||||
{"heatmap", "heatmap", "Viz", &gallery::demo_heatmap},
|
||||
{"table_view", "table_view", "Viz", &gallery::demo_table_view},
|
||||
// Gfx (shaders_lab core)
|
||||
{"shader_canvas", "shader_canvas", "Gfx", &gallery::demo_shader_canvas},
|
||||
{"gl_texture", "gl_texture_load", "Gfx", &gallery::demo_gl_texture},
|
||||
{"gl_texture", "gl_texture_load", "Gfx", &gallery::demo_gl_texture}, // wave 1
|
||||
};
|
||||
static constexpr int k_demo_count = sizeof(k_demos) / sizeof(k_demos[0]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user