feat(cpp/core): logger + log_window + selectable_text widgets
Logger global thread-safe con ring buffer in-memory de 2000 entradas + escritura opcional a archivo. log_window flotante consume el ring buffer con filtros por nivel, busqueda y autoscroll; se abre desde Settings -> Logs en la menubar. selectable_text cubre el patron drag-to-select + Ctrl+C en cualquier ventana. app_menubar y framework run_app integran log_window_render() en el frame loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+150
-2
@@ -316,8 +316,20 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
// Menubar canonica (View / Layouts / Settings / About) si la app la
|
||||
// configuro en AppConfig. Se renderiza ANTES del render_fn para que
|
||||
// el render_fn pueda hacer DockSpaceOverViewport debajo.
|
||||
if (config.panels != nullptr || config.layouts_cb != nullptr) {
|
||||
fn_ui::app_menubar(config.panels, config.panel_count, config.layouts_cb);
|
||||
if (config.panels != nullptr || config.layouts_cb != nullptr ||
|
||||
(bool)config.view_extras) {
|
||||
// Adapter: std::function<bool()> -> ViewMenuExtrasFn(void*).
|
||||
fn_ui::ViewMenuExtrasFn extras_fn = nullptr;
|
||||
void* extras_user = nullptr;
|
||||
if ((bool)config.view_extras) {
|
||||
extras_fn = [](void* ud) -> bool {
|
||||
auto* fn_ptr = static_cast<std::function<bool()>*>(ud);
|
||||
return (*fn_ptr) ? (*fn_ptr)() : false;
|
||||
};
|
||||
extras_user = (void*)&config.view_extras;
|
||||
}
|
||||
fn_ui::app_menubar(config.panels, config.panel_count,
|
||||
config.layouts_cb, extras_fn, extras_user);
|
||||
}
|
||||
|
||||
render_fn();
|
||||
@@ -385,3 +397,139 @@ int run_app(std::function<void()> render_fn) {
|
||||
}
|
||||
|
||||
} // namespace fn
|
||||
|
||||
#ifdef IMGUI_ENABLE_TEST_ENGINE
|
||||
#include "imgui_te_engine.h"
|
||||
#include "imgui_te_ui.h"
|
||||
#include "imgui_te_context.h"
|
||||
#include "imgui_te_exporters.h"
|
||||
|
||||
namespace fn {
|
||||
|
||||
int run_app_test(AppConfig config,
|
||||
std::function<void()> render_fn,
|
||||
std::function<void(::ImGuiTestEngine*)> register_tests,
|
||||
const char* filter) {
|
||||
if (!register_tests) {
|
||||
fprintf(stderr, "run_app_test: register_tests callback is null\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
if (!glfwInit()) { fprintf(stderr, "GLFW init failed\n"); return 1; }
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
|
||||
GLFWwindow* window = glfwCreateWindow(
|
||||
config.width, config.height,
|
||||
config.title ? config.title : "fn_test", nullptr, nullptr);
|
||||
if (!window) { glfwTerminate(); fprintf(stderr, "createWindow failed\n"); return 1; }
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(0); // tests run as fast as possible — no vsync
|
||||
|
||||
if (config.init_gl_loader) {
|
||||
if (!fn::gfx::gl_loader_init()) {
|
||||
glfwDestroyWindow(window); glfwTerminate();
|
||||
fprintf(stderr, "gl_loader_init failed\n"); return 1;
|
||||
}
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImPlot::CreateContext();
|
||||
ImPlot3D::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
// No viewports in tests — the engine drives the main window only.
|
||||
io.IniFilename = nullptr; // tests don't persist .ini
|
||||
|
||||
fn_ui::settings_load();
|
||||
fn_ui::load_fonts_from_settings();
|
||||
|
||||
switch (config.theme) {
|
||||
case ThemeMode::FnDark: fn_tokens::apply_dark_theme(); break;
|
||||
case ThemeMode::ImGuiDark: ImGui::StyleColorsDark(); break;
|
||||
case ThemeMode::ImGuiLight: ImGui::StyleColorsLight(); break;
|
||||
case ThemeMode::None: break;
|
||||
}
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 330");
|
||||
|
||||
// --- Test engine setup ---
|
||||
ImGuiTestEngine* engine = ImGuiTestEngine_CreateContext();
|
||||
ImGuiTestEngineIO& te_io = ImGuiTestEngine_GetIO(engine);
|
||||
te_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info;
|
||||
te_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug;
|
||||
te_io.ConfigRunSpeed = ImGuiTestRunSpeed_Fast;
|
||||
te_io.ConfigStopOnError = false;
|
||||
te_io.ConfigCaptureEnabled = false;
|
||||
te_io.ConfigSavedSettings = false;
|
||||
|
||||
register_tests(engine);
|
||||
|
||||
ImGuiTestEngine_Start(engine, ImGui::GetCurrentContext());
|
||||
ImGuiTestEngine_QueueTests(engine, ImGuiTestGroup_Tests, filter,
|
||||
ImGuiTestRunFlags_RunFromCommandLine);
|
||||
|
||||
// --- Loop until tests finish ---
|
||||
bool tests_queued_done = false;
|
||||
int frames_after_done = 0;
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
glfwPollEvents();
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
render_fn();
|
||||
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(config.bg_r, config.bg_g, config.bg_b, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
|
||||
if (!tests_queued_done && ImGuiTestEngine_IsTestQueueEmpty(engine)) {
|
||||
tests_queued_done = true;
|
||||
}
|
||||
if (tests_queued_done) {
|
||||
// Let the engine flush its final state for a few frames before exit.
|
||||
if (++frames_after_done > 2) break;
|
||||
}
|
||||
}
|
||||
|
||||
int count_tested = 0, count_success = 0;
|
||||
ImGuiTestEngine_GetResult(engine, count_tested, count_success);
|
||||
bool all_passed = (count_tested > 0) && (count_tested == count_success);
|
||||
|
||||
ImGuiTestEngine_PrintResultSummary(engine);
|
||||
ImGuiTestEngine_Stop(engine);
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImPlot3D::DestroyContext();
|
||||
ImPlot::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
ImGuiTestEngine_DestroyContext(engine);
|
||||
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
|
||||
fprintf(stdout, "\n[fn::run_app_test] %d/%d tests passed%s\n",
|
||||
count_success, count_tested, all_passed ? "" : " — FAILED");
|
||||
return all_passed ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace fn
|
||||
#endif // IMGUI_ENABLE_TEST_ENGINE
|
||||
|
||||
Reference in New Issue
Block a user