// Demos individuales de text_editor y file_watcher (Wave 1, issue 0025). // // 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" #include "core/text_editor.h" #include "core/file_watcher.h" #include "core/button.h" #include "core/tokens.h" #include #include #include #include #include #include #include namespace gallery { // =========================================================================== // text_editor — editor de codigo con syntax highlighting // =========================================================================== namespace { const char* kSampleGLSL = "#version 330\n" "// fragment shader demo\n" "out vec4 frag_color;\n" "uniform vec2 u_resolution;\n" "uniform float u_time;\n" "\n" "void main() {\n" " vec2 uv = gl_FragCoord.xy / u_resolution;\n" " vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0,2,4));\n" " frag_color = vec4(col, 1.0);\n" "}\n"; 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 \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; }; EditorState& editor_state() { static EditorState s; return s; } 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); } } 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 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) { switch (k) { case fn::FileEvent::Modified: return "MODIFIED"; case fn::FileEvent::Created: return "CREATED"; case fn::FileEvent::Deleted: return "DELETED"; } return "?"; } void poll_and_log() { 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(buf); } while (s.events.size() > 200) s.events.pop_front(); } 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); return true; } } // namespace void demo_file_watcher() { using namespace fn_tokens; 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_watcher(); poll_and_log(); auto& s = watcher_state(); 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(); } 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::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