fix(0132): cross-platform demos + auto-launch + black bg prompt

- Demo 1 (one-shot): auto-launch al primer frame, sin boton Run.
  Linux: bash; Windows: cmd.exe con "echo hello && date /t && dir /b %TEMP%"
- Demo 2 (interactive): auto-launch al entrar al tab. Sin boton Open shell.
  Linux: bash; Windows: cmd.exe. Boton Close/Reopen post-exit.
- Demo 3 (readonly): Linux tail -f igual que antes.
  Windows: panel readonly con cmd.exe one-shot + mensaje informativo.
- shell_exists() helper para mostrar error si el binario no existe.
- Render: fondo negro + prompt "$ " implementados en terminal_panel.cpp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 23:48:29 +02:00
parent 9979d74217
commit a3928cf2db
+139 -26
View File
@@ -1,9 +1,17 @@
// 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.
// Demo 1: One-shot — auto-launch al primer frame. Cross-platform:
// Linux → bash "echo hello; date; ls /tmp | head"
// Windows → cmd.exe "echo hello && date /t && dir /b %TEMP%"
// Demo 2: Interactivo — auto-launch al entrar al tab.
// Linux → bash -i
// Windows → cmd.exe
// Demo 3: Readonly — tail simulado en Linux / mensaje en Windows.
//
// Cambios issue 0132:
// - Fondo negro + prompt input: en terminal_panel.cpp.
// - Cross-platform: _WIN32 en todos los demos.
// - Auto-launch: sin boton "Open shell" / "Run" en Demo 1 y Demo 2.
#include "demos.h"
#include "demo.h"
@@ -13,31 +21,64 @@
#include <cstring>
#include <string>
// Para verificar que el binario del shell existe antes de abrir.
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
static bool shell_exists(const char* path) {
return GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES;
}
#else
# include <unistd.h>
static bool shell_exists(const char* path) {
return access(path, X_OK) == 0;
}
#endif
namespace gallery {
// ---------------------------------------------------------------------------
// Demo 1: One-shot con auto-close
// Demo 1: One-shot con auto-launch
// ---------------------------------------------------------------------------
void demo_terminal_oneshot() {
demo_header("terminal_panel", "v1.0.0",
"Emulador TTY embebible. Demo 1: one-shot — spawn bash, ejecuta comandos, auto-close.");
"Emulador TTY embebible. Demo 1: one-shot — auto-launch al primer frame, cross-platform.");
static fn_term::TerminalPanel s_term;
static bool s_started = false;
static bool s_launched = false;
static bool s_shell_missing = 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";
if (!s_launched && !s_shell_missing) {
#ifdef _WIN32
const char* sh = "cmd.exe";
const char* cmd = "echo hello && date /t && dir /b %TEMP% 2>nul\r\n";
#else
const char* sh = "/bin/bash";
const char* cmd = "echo hello; date; ls /tmp | head -5; exit 0\n";
#endif
if (!shell_exists(sh)) {
s_shell_missing = true;
} else {
s_term.shell = sh;
s_term.scrollback_lines = 200;
s_term.readonly = true; // solo output, sin input
s_term.readonly = true;
fn_term::open(s_term);
fn_term::send(s_term, "echo hello; date; ls /tmp | head -5; exit 0\n");
s_started = true;
fn_term::send(s_term, cmd);
s_launched = true;
}
}
if (s_shell_missing) {
#ifdef _WIN32
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"Shell not available: cmd.exe");
#else
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"Shell not available: /bin/bash");
#endif
return;
}
@@ -50,10 +91,21 @@ void demo_terminal_oneshot() {
if (ImGui::Button("Reset demo")) {
if (s_term.is_open()) fn_term::close(s_term);
s_started = false;
s_launched = false;
s_shell_missing = false;
}
code_block(
#ifdef _WIN32
"fn_term::TerminalPanel term;\n"
"term.shell = \"cmd.exe\";\n"
"term.readonly = true;\n"
"fn_term::open(term);\n"
"fn_term::send(term, \"echo hello && date /t\\r\\n\");\n"
"// En cada frame ImGui:\n"
"fn_term::render(term);\n"
"if (term.process_exited.load()) fn_term::close(term);"
#else
"fn_term::TerminalPanel term;\n"
"term.shell = \"/bin/bash\";\n"
"term.readonly = true;\n"
@@ -62,55 +114,115 @@ void demo_terminal_oneshot() {
"// En cada frame ImGui:\n"
"fn_term::render(term);\n"
"if (term.process_exited.load()) fn_term::close(term);"
#endif
);
}
// ---------------------------------------------------------------------------
// Demo 2: Interactivo
// Demo 2: Interactivo con auto-launch
// ---------------------------------------------------------------------------
void demo_terminal_interactive() {
demo_header("terminal_panel", "v1.0.0",
"Demo 2: terminal interactivo — bash -i (Linux) / cmd.exe (Windows).");
"Demo 2: terminal interactivo — auto-launch al entrar al tab. "
"Linux: bash -i / Windows: cmd.exe.");
static fn_term::TerminalPanel s_term;
static bool s_launched = false;
static bool s_shell_missing = false;
if (!s_term.is_open()) {
if (ImGui::Button("Open shell")) {
// Auto-launch en el primer frame que se renderiza esta demo.
if (!s_launched && !s_shell_missing) {
#ifdef _WIN32
s_term.shell = "cmd.exe";
const char* sh = "cmd.exe";
#else
s_term.shell = "/bin/bash";
const char* sh = "/bin/bash";
#endif
if (!shell_exists(sh)) {
s_shell_missing = true;
} else {
s_term.shell = sh;
s_term.scrollback_lines = 1000;
s_term.readonly = false;
fn_term::open(s_term);
s_launched = true;
}
}
if (s_shell_missing) {
#ifdef _WIN32
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"Shell not available: cmd.exe");
#else
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
"Shell not available: /bin/bash");
#endif
return;
}
fn_term::render(s_term);
if (s_term.is_open()) {
ImGui::Spacing();
if (ImGui::Button("Close shell")) {
fn_term::close(s_term);
s_launched = false;
}
} else if (s_launched) {
ImGui::TextDisabled("[shell exited — click Reopen]");
ImGui::SameLine();
if (ImGui::Button("Reopen")) {
s_launched = false;
s_shell_missing = false;
}
}
}
// ---------------------------------------------------------------------------
// Demo 3: Readonly tail
// Demo 3: Readonly tail (Linux) / mensaje informativo (Windows)
// ---------------------------------------------------------------------------
void demo_terminal_readonly() {
demo_header("terminal_panel", "v1.0.0",
"Demo 3: readonly — tail de /tmp/fn_gallery_test.log (creado al abrir).");
"Demo 3: readonly — "
#ifdef _WIN32
"demo disponible solo en Linux (tail -f). En Windows se muestra un mensaje.");
#else
"tail de /tmp/fn_gallery_test.log (creado al abrir).");
#endif
#ifdef _WIN32
// En Windows nativo, tail -f no existe y /tmp tampoco.
// Mostramos un panel readonly con mensaje estático para seguir ejercitando el render.
static fn_term::TerminalPanel s_term;
static bool s_launched = false;
if (!s_launched) {
s_term.shell = "cmd.exe";
s_term.readonly = true;
s_term.scrollback_lines = 100;
fn_term::open(s_term);
// Imprimir unas líneas de muestra y terminar el proceso.
fn_term::send(s_term,
"echo [terminal_panel demo 3 - readonly] && "
"echo This demo uses tail -f on Linux. && "
"echo On Windows the panel renders static output. && "
"exit 0\r\n");
s_launched = true;
}
fn_term::render(s_term);
if (ImGui::Button("Reset demo")) {
if (s_term.is_open()) fn_term::close(s_term);
s_launched = false;
}
#else
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++)
@@ -153,6 +265,7 @@ void demo_terminal_readonly() {
"// En cada frame:\n"
"fn_term::render(term);"
);
#endif
}
// ---------------------------------------------------------------------------