feat(0132): add terminal_panel demo (oneshot, interactive, readonly)

Add demos_terminal.cpp con 3 demos del modulo terminal_panel:
- Demo 1: one-shot spawn bash, echo hello; date; ls /tmp
- Demo 2: terminal interactivo bash/cmd.exe con input box
- Demo 3: readonly tail -f con boton append

Actualiza CMakeLists.txt con ansi_parser + terminal_panel + -lutil (Linux).
Actualiza demos.h + main.cpp con entry "Terminal" en el sidebar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 23:34:56 +02:00
parent 61d21cc1ff
commit 9979d74217
4 changed files with 203 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
// demos_terminal.cpp — demos del modulo terminal_panel (issue 0132).
//
// Demo 1: One-shot — spawn bash que ejecuta "echo hello; date; ls /tmp | head"
// con auto-close cuando el proceso termina. Muestra el scrollback.
// Demo 2: Interactivo — bash -i (Linux) / cmd.exe (Windows) con input box.
// Demo 3: Readonly — tail simulado de un archivo de log.
#include "demos.h"
#include "demo.h"
#include "viz/terminal_panel/terminal_panel.h"
#include "imgui.h"
#include <cstring>
#include <string>
namespace gallery {
// ---------------------------------------------------------------------------
// Demo 1: One-shot con auto-close
// ---------------------------------------------------------------------------
void demo_terminal_oneshot() {
demo_header("terminal_panel", "v1.0.0",
"Emulador TTY embebible. Demo 1: one-shot — spawn bash, ejecuta comandos, auto-close.");
static fn_term::TerminalPanel s_term;
static bool s_started = false;
if (!s_started) {
ImGui::TextUnformatted("Pulsa 'Run' para lanzar el one-shot.");
if (ImGui::Button("Run")) {
if (!s_term.is_open()) {
s_term.shell = "/bin/bash";
s_term.scrollback_lines = 200;
s_term.readonly = true; // solo output, sin input
fn_term::open(s_term);
fn_term::send(s_term, "echo hello; date; ls /tmp | head -5; exit 0\n");
s_started = true;
}
}
return;
}
fn_term::render(s_term);
if (s_term.process_exited.load() && s_term.is_open()) {
ImGui::SameLine();
ImGui::TextDisabled("[done — exit %d]", s_term.exit_code);
}
if (ImGui::Button("Reset demo")) {
if (s_term.is_open()) fn_term::close(s_term);
s_started = false;
}
code_block(
"fn_term::TerminalPanel term;\n"
"term.shell = \"/bin/bash\";\n"
"term.readonly = true;\n"
"fn_term::open(term);\n"
"fn_term::send(term, \"echo hello; date; exit 0\\n\");\n"
"// En cada frame ImGui:\n"
"fn_term::render(term);\n"
"if (term.process_exited.load()) fn_term::close(term);"
);
}
// ---------------------------------------------------------------------------
// Demo 2: Interactivo
// ---------------------------------------------------------------------------
void demo_terminal_interactive() {
demo_header("terminal_panel", "v1.0.0",
"Demo 2: terminal interactivo — bash -i (Linux) / cmd.exe (Windows).");
static fn_term::TerminalPanel s_term;
if (!s_term.is_open()) {
if (ImGui::Button("Open shell")) {
#ifdef _WIN32
s_term.shell = "cmd.exe";
#else
s_term.shell = "/bin/bash";
#endif
s_term.scrollback_lines = 1000;
s_term.readonly = false;
fn_term::open(s_term);
}
return;
}
fn_term::render(s_term);
if (ImGui::Button("Close shell")) {
fn_term::close(s_term);
}
}
// ---------------------------------------------------------------------------
// Demo 3: Readonly tail
// ---------------------------------------------------------------------------
void demo_terminal_readonly() {
demo_header("terminal_panel", "v1.0.0",
"Demo 3: readonly — tail de /tmp/fn_gallery_test.log (creado al abrir).");
static fn_term::TerminalPanel s_term;
static bool s_log_created = false;
// Crear el archivo de log de prueba si no existe.
if (!s_log_created) {
if (ImGui::Button("Start tail")) {
// Escribir unas líneas de muestra al log.
FILE* f = fopen("/tmp/fn_gallery_test.log", "w");
if (f) {
for (int i = 0; i < 5; i++)
fprintf(f, "[sample] log line %d\n", i);
fclose(f);
}
s_term.shell = "/bin/bash";
s_term.readonly = true;
s_term.scrollback_lines = 500;
fn_term::open(s_term);
fn_term::send(s_term, "tail -f /tmp/fn_gallery_test.log\n");
s_log_created = true;
}
return;
}
fn_term::render(s_term);
ImGui::Separator();
if (ImGui::Button("Append line")) {
FILE* f = fopen("/tmp/fn_gallery_test.log", "a");
if (f) {
static int s_count = 0;
fprintf(f, "[appended] line %d\n", s_count++);
fclose(f);
}
}
ImGui::SameLine();
if (ImGui::Button("Stop")) {
if (s_term.is_open()) fn_term::close(s_term);
s_log_created = false;
}
code_block(
"fn_term::TerminalPanel term;\n"
"term.shell = \"/bin/bash\";\n"
"term.readonly = true; // sin input box\n"
"fn_term::open(term);\n"
"fn_term::send(term, \"tail -f /tmp/agent.log\\n\");\n"
"// En cada frame:\n"
"fn_term::render(term);"
);
}
// ---------------------------------------------------------------------------
// Entry point unificado (llamado desde main.cpp via demos.h)
// ---------------------------------------------------------------------------
void demo_terminal() {
static int s_tab = 0;
if (ImGui::BeginTabBar("##term_tabs")) {
if (ImGui::BeginTabItem("One-shot")) {
s_tab = 0;
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Interactive")) {
s_tab = 1;
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Readonly tail")) {
s_tab = 2;
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
switch (s_tab) {
case 0: demo_terminal_oneshot(); break;
case 1: demo_terminal_interactive(); break;
case 2: demo_terminal_readonly(); break;
}
}
} // namespace gallery