feat(registry): index cpp/apps/* + e2e test infrastructure
registry/indexer.go ahora escanea <lang>/apps/*/app.md ademas de apps/ y projects/*/apps/. cpp/apps/chart_demo y cpp/apps/shaders_lab pasan a estar en registry.db con sus manifests. Infraestructura de tests e2e (opt-in con -DFN_BUILD_TESTS=ON): - vendor de Dear ImGui Test Engine (personal/open-source license). - chart_demo_tests target con tests/chart_demo_tests.cpp. - /e2e-cpp slash command para crear y ejecutar tests e2e. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Build artefacts y caches
|
||||
.pytest_cache/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Runtime Python embebido (issue 0033 fase B) — generado por
|
||||
# tools/freeze_python_runtime.sh. Cada PC lo regenera con su
|
||||
# /compile o paso de build.
|
||||
runtime/
|
||||
|
||||
# DBs locales (generadas por la app, vinculadas a un PC concreto)
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
*.ini
|
||||
|
||||
# Carpetas de proyectos (cada proyecto tiene su propia operations.db
|
||||
# y graph_explorer.db).
|
||||
projects/
|
||||
|
||||
# Vendoring de funciones Python (issue 0033b) — generado por
|
||||
# tools/vendor_enricher_python.sh.
|
||||
enrichers/*/_vendored/
|
||||
enrichers/*/.vendor.lock
|
||||
|
||||
# Binarios de enrichers Go (issue 0034) — generados por su build.sh.
|
||||
enrichers/*/run
|
||||
enrichers/*/run.exe
|
||||
@@ -7,6 +7,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# --- Options ---
|
||||
option(TRACY_ENABLE "Enable Tracy profiling" OFF)
|
||||
option(FN_BUILD_TESTS "Build C++ e2e tests with Dear ImGui Test Engine" OFF)
|
||||
|
||||
# --- Vendor: Dear ImGui ---
|
||||
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/imgui)
|
||||
@@ -24,6 +25,44 @@ target_include_directories(imgui PUBLIC
|
||||
${IMGUI_DIR}/backends
|
||||
)
|
||||
|
||||
# When tests are enabled, imgui must be compiled with hooks for the test engine.
|
||||
# The hooks compile to no-ops if the engine is never started, so this is safe to
|
||||
# leave on but we still gate it to keep release builds identical to today.
|
||||
if(FN_BUILD_TESTS)
|
||||
target_compile_definitions(imgui PUBLIC IMGUI_ENABLE_TEST_ENGINE)
|
||||
endif()
|
||||
|
||||
# --- Vendor: Dear ImGui Test Engine (opt-in via FN_BUILD_TESTS) ---
|
||||
# Personal/open-source license (see vendor/imgui_test_engine/LICENSE.txt).
|
||||
if(FN_BUILD_TESTS)
|
||||
set(IMTE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/imgui_test_engine)
|
||||
add_library(imgui_test_engine STATIC
|
||||
${IMTE_DIR}/imgui_te_engine.cpp
|
||||
${IMTE_DIR}/imgui_te_context.cpp
|
||||
${IMTE_DIR}/imgui_te_coroutine.cpp
|
||||
${IMTE_DIR}/imgui_te_exporters.cpp
|
||||
${IMTE_DIR}/imgui_te_perftool.cpp
|
||||
${IMTE_DIR}/imgui_te_ui.cpp
|
||||
${IMTE_DIR}/imgui_te_utils.cpp
|
||||
${IMTE_DIR}/imgui_capture_tool.cpp
|
||||
)
|
||||
target_include_directories(imgui_test_engine PUBLIC
|
||||
${IMTE_DIR}
|
||||
${IMTE_DIR}/thirdparty
|
||||
)
|
||||
# Use std::thread for coroutines so apps don't have to provide their own.
|
||||
target_compile_definitions(imgui_test_engine PUBLIC
|
||||
IMGUI_ENABLE_TEST_ENGINE
|
||||
IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1
|
||||
IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION=1
|
||||
)
|
||||
target_link_libraries(imgui_test_engine PUBLIC imgui)
|
||||
if(UNIX)
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(imgui_test_engine PUBLIC Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# --- Vendor: ImPlot ---
|
||||
set(IMPLOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/vendor/implot)
|
||||
add_library(implot STATIC
|
||||
@@ -173,6 +212,11 @@ target_link_libraries(fn_framework PUBLIC imgui implot implot3d SQLite::SQLite3)
|
||||
if(TRACY_ENABLE)
|
||||
target_link_libraries(fn_framework PUBLIC tracy)
|
||||
endif()
|
||||
if(FN_BUILD_TESTS)
|
||||
# Public so apps that include fn_framework headers see the same hooks.
|
||||
target_compile_definitions(fn_framework PUBLIC IMGUI_ENABLE_TEST_ENGINE)
|
||||
target_link_libraries(fn_framework PUBLIC imgui_test_engine)
|
||||
endif()
|
||||
|
||||
# --- OpenMP (opcional) ---
|
||||
# Habilita #pragma omp en las funciones del registry que lo declaren bajo
|
||||
|
||||
@@ -6,3 +6,17 @@ add_imgui_app(chart_demo
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/heatmap.cpp
|
||||
# fps_overlay vive en fn_framework
|
||||
)
|
||||
|
||||
# --- E2E tests (opt-in via -DFN_BUILD_TESTS=ON) ---
|
||||
if(FN_BUILD_TESTS)
|
||||
add_imgui_app(chart_demo_tests
|
||||
main.cpp
|
||||
tests/chart_demo_tests.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/line_plot.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/scatter_plot.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/bar_chart.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/heatmap.cpp
|
||||
)
|
||||
# Excludes int main() from main.cpp so the test target provides its own.
|
||||
target_compile_definitions(chart_demo_tests PRIVATE FN_TEST_BUILD)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: chart_demo
|
||||
lang: cpp
|
||||
domain: viz
|
||||
description: "Demo ImGui de primitivos viz del registry: line_plot, scatter_plot, bar_chart, heatmap. Cada chart en su propia tab del TabBar. Usado como showcase y como build gate de las funciones viz/."
|
||||
tags: [imgui, demo, charts, viz, showcase]
|
||||
uses_functions:
|
||||
- line_plot_cpp_viz
|
||||
- scatter_plot_cpp_viz
|
||||
- bar_chart_cpp_viz
|
||||
- heatmap_cpp_viz
|
||||
# logger, app_menubar viven en fn_framework — no se listan aqui
|
||||
uses_types: []
|
||||
framework: "imgui"
|
||||
entry_point: "main.cpp"
|
||||
dir_path: "cpp/apps/chart_demo"
|
||||
repo_url: ""
|
||||
---
|
||||
|
||||
## Que hace
|
||||
|
||||
App de una sola ventana con cuatro tabs (Line / Scatter / Bar / Heatmap) que
|
||||
renderiza datos sinteticos para mostrar el aspecto y la API de los primitivos
|
||||
viz del registry. Sirve como:
|
||||
|
||||
- **Showcase visual** de las funciones viz existentes — al añadir una nueva
|
||||
primitiva, anadir su tab aqui es la forma natural de probar el binding.
|
||||
- **Build gate**: si una de las funciones rompe API, esta app deja de
|
||||
compilar y lo cazamos sin tener que tocar `registry_dashboard` o
|
||||
`graph_explorer`.
|
||||
|
||||
## Estructura
|
||||
|
||||
`main.cpp` (~93 lineas):
|
||||
|
||||
- `init_data()` — genera arrays sinteticos una vez (estado modulo).
|
||||
- `render()` — DockSpaceOverViewport + TabBar con 4 tabs, cada una invoca
|
||||
un primitivo del registry.
|
||||
- `main()` → `fn::run_app(...)` con AppConfig estandar (titulo, tamaño,
|
||||
about, log).
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cd cpp && cmake -B build/linux -S . && cmake --build build/linux --target chart_demo
|
||||
|
||||
# Windows (cross-compile)
|
||||
cd cpp && cmake -B build/windows -S . -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw-w64.cmake \
|
||||
&& cmake --build build/windows --target chart_demo
|
||||
```
|
||||
|
||||
## Decisiones
|
||||
|
||||
- `viewports = true` (default de `fn::run_app`): las ventanas se pueden
|
||||
arrastrar fuera del main window.
|
||||
- `init_gl_loader = false`: solo usa ImGui/ImPlot, sin gl* directo.
|
||||
- Sin persistencia propia (no abre BD).
|
||||
- `log: file_path = "chart_demo.log"` con nivel Debug — el `init_data`
|
||||
emite info+debug para verificar que el logger funciona.
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "viz/bar_chart.h"
|
||||
#include "viz/heatmap.h"
|
||||
#include "core/app_menubar.h"
|
||||
#include "core/logger.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
@@ -23,6 +24,7 @@ static bool data_initialized = false;
|
||||
|
||||
static void init_data() {
|
||||
if (data_initialized) return;
|
||||
fn_log::log_info("init_data: generando %d puntos sin/cos, 200 scatter, 10x10 heatmap", N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
xs[i] = static_cast<float>(i) * 0.02f;
|
||||
ys_sin[i] = sinf(xs[i]);
|
||||
@@ -37,9 +39,10 @@ static void init_data() {
|
||||
heat_data[i] = sinf(r * 0.5f) * cosf(c * 0.5f);
|
||||
}
|
||||
data_initialized = true;
|
||||
fn_log::log_debug("init_data: ok");
|
||||
}
|
||||
|
||||
static void render() {
|
||||
void render() {
|
||||
init_data();
|
||||
|
||||
// MainMenuBar (solo Settings — chart_demo no tiene paneles toggleables)
|
||||
@@ -76,6 +79,7 @@ static void render() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
#ifndef FN_TEST_BUILD
|
||||
int main() {
|
||||
return fn::run_app({
|
||||
.title = "fn_registry — Chart Demo",
|
||||
@@ -83,6 +87,9 @@ int main() {
|
||||
.height = 900,
|
||||
.about = {.name = "chart demo",
|
||||
.version = "0.2.0",
|
||||
.description = "Demo de primitivos viz: line, scatter, bar, heatmap. AppConfig estandar + multi-viewport."}
|
||||
.description = "Demo de primitivos viz: line, scatter, bar, heatmap. AppConfig estandar + multi-viewport."},
|
||||
.log = {.file_path = "chart_demo.log",
|
||||
.level = static_cast<int>(fn_log::Level::Debug)}
|
||||
}, render);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// E2E tests for chart_demo — Dear ImGui Test Engine.
|
||||
// Built only when -DFN_BUILD_TESTS=ON. The same main.cpp from chart_demo is
|
||||
// compiled here with FN_TEST_BUILD defined so its int main() is excluded and
|
||||
// only render() is reused.
|
||||
|
||||
#include "app_base.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_te_engine.h"
|
||||
#include "imgui_te_context.h"
|
||||
|
||||
void render(); // defined in chart_demo/main.cpp
|
||||
|
||||
static void register_tests(ImGuiTestEngine* e) {
|
||||
ImGuiTest* t = nullptr;
|
||||
|
||||
// Smoke test: the main window appears and is non-empty.
|
||||
t = IM_REGISTER_TEST(e, "chart_demo", "smoke_window_visible");
|
||||
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||
ctx->SetRef("fn_registry \xe2\x80\x94 Chart Demo"); // em-dash
|
||||
IM_CHECK(ctx->WindowInfo("").ID != 0);
|
||||
};
|
||||
|
||||
// Cycle through all four tabs. Test engine fails the test if any tab item
|
||||
// is not found or cannot be activated — that is our implicit assertion.
|
||||
t = IM_REGISTER_TEST(e, "chart_demo", "tabs_cycle_all");
|
||||
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||
ctx->SetRef("fn_registry \xe2\x80\x94 Chart Demo");
|
||||
ctx->ItemClick("##charts/Line Plot");
|
||||
ctx->ItemClick("##charts/Scatter Plot");
|
||||
ctx->ItemClick("##charts/Bar Chart");
|
||||
ctx->ItemClick("##charts/Heatmap");
|
||||
};
|
||||
}
|
||||
|
||||
int main() {
|
||||
fn::AppConfig cfg{};
|
||||
cfg.title = "chart_demo_tests";
|
||||
cfg.width = 1280;
|
||||
cfg.height = 800;
|
||||
return fn::run_app_test(cfg, render, register_tests);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: shaders_lab
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
description: "Live GLSL shader playground con DAG pipeline. Editor de codigo con compilacion en caliente, panel DAG con paleta de generadores/filtros/output, dos canvas (Code y DAG), parseo de uniforms anotados (// @slider, @color, @xy) que se convierten en controles, persistencia de generators en shaders_lab.db, y guardado/carga de layouts ImGui."
|
||||
tags: [imgui, opengl, glsl, shaders, dag, live-coding, playground, sqlite]
|
||||
uses_functions:
|
||||
# gfx
|
||||
- gl_loader_cpp_gfx
|
||||
- gl_shader_cpp_gfx
|
||||
- gl_framebuffer_cpp_gfx
|
||||
- fullscreen_quad_cpp_gfx
|
||||
- shader_canvas_cpp_gfx
|
||||
- uniform_parser_cpp_gfx
|
||||
- uniform_panel_cpp_gfx
|
||||
- dag_catalog_cpp_gfx
|
||||
- dag_compile_cpp_gfx
|
||||
- dag_uniforms_cpp_gfx
|
||||
- dag_panel_cpp_gfx
|
||||
- dag_node_editor_cpp_gfx
|
||||
- dag_palette_cpp_gfx
|
||||
- dag_node_previews_cpp_gfx
|
||||
- shaderlab_db_cpp_gfx
|
||||
- code_to_generator_cpp_gfx
|
||||
# core (modal Save-as-generator)
|
||||
- modal_dialog_cpp_core
|
||||
- text_input_cpp_core
|
||||
- button_cpp_core
|
||||
uses_types:
|
||||
- dag_types_cpp_gfx
|
||||
framework: "imgui"
|
||||
entry_point: "main.cpp"
|
||||
dir_path: "cpp/apps/shaders_lab"
|
||||
repo_url: ""
|
||||
---
|
||||
|
||||
## Arquitectura
|
||||
|
||||
App ImGui de live-coding GLSL con dos modos en paralelo:
|
||||
|
||||
1. **Code panel** — editor de fragment shader libre. Las anotaciones en
|
||||
uniforms (`// @slider`, `// @color`, `// @xy`, `// @toggle`) se parsean y
|
||||
convierten en controles del panel **Controls** que escriben en un
|
||||
`UniformStore` aplicado al programa cada frame.
|
||||
2. **DAG panel** — pipeline node-based con catalogo de generadores
|
||||
(plasma, voronoi, etc.) y filtros (blur, threshold, etc.) que se
|
||||
compilan a un fragment shader unificado y se renderizan en **Canvas DAG**.
|
||||
|
||||
Al guardar un Code shader como "generator" se traduce a un `DagNodeDef` y se
|
||||
persiste en `shaders_lab.db` (tabla via `shaderlab_db`), apareciendo en la
|
||||
paleta del DAG junto a los builtins.
|
||||
|
||||
## Capas
|
||||
|
||||
| Archivo | Responsabilidad |
|
||||
|---|---|
|
||||
| `main.cpp` | UI shell, paneles, modal save-as, layouts, AppConfig |
|
||||
| `compiler.cpp` | `compile_code()`, `compile_dag()`, `mark_code_dirty()` con debounce 250ms |
|
||||
|
||||
`main.cpp` mantiene estado global de sesion (g_source, g_pipeline, g_descs,
|
||||
g_store, g_layouts...) — ImGui retained-mode obliga a que persista entre
|
||||
frames. Toda la logica pura de compilacion vive en `compiler.cpp` y en las
|
||||
funciones `dag_compile`, `code_to_generator`, `uniform_parser` del registry.
|
||||
|
||||
## Persistencia
|
||||
|
||||
- **`shaders_lab.db`** (junto al .exe) — tabla de generators de usuario via
|
||||
`shaderlab_db_*`, ademas de `imgui_layouts` (creada por `layout_storage`).
|
||||
- `imgui.ini` y `app_settings.ini` — gestionados por `fn::run_app` en
|
||||
`<exe_dir>/local_files/`.
|
||||
|
||||
## Paneles
|
||||
|
||||
| Panel | Atajo | Que muestra |
|
||||
|---|---|---|
|
||||
| Code | Ctrl+1 | Editor del fragment shader + boton "Save as generator" |
|
||||
| DAG Pipeline | Ctrl+2 | Node editor con la pipeline |
|
||||
| Canvas Code | Ctrl+3 | Render del Code shader |
|
||||
| Canvas DAG | Ctrl+4 | Render del shader compilado del DAG |
|
||||
| Controls | Ctrl+5 | Sliders/color pickers de uniforms anotados |
|
||||
| Functions | Ctrl+6 | Paleta del DAG (generators + filters + output) |
|
||||
| Generated GLSL | Ctrl+7 | GLSL final del DAG con uniforms baked como const array |
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cd cpp && cmake -B build/linux -S . && cmake --build build/linux --target shaders_lab
|
||||
|
||||
# Windows (cross-compile)
|
||||
cd cpp && cmake -B build/windows -S . -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw-w64.cmake \
|
||||
&& cmake --build build/windows --target shaders_lab
|
||||
```
|
||||
|
||||
## Decisiones
|
||||
|
||||
- `init_gl_loader = true` (via `fn::run_app` por default cuando se enlaza
|
||||
con OpenGL) — `shader_canvas`, `gl_shader`, `gl_framebuffer` llaman gl*.
|
||||
- `viewports = true` — los Canvas se pueden arrastrar fuera del main.
|
||||
- DAG default: arranca con un nodo "plasma" + "output" si la paleta los
|
||||
encuentra; persiste el INI con `layout_storage`.
|
||||
- El boton "Save as generator" valida snake_case, evita colisionar con
|
||||
builtins, traduce con `code_to_generator`, persiste con `shaderlab_db_save_generator`,
|
||||
y registra el nodo nuevo en el catalogo en vivo (`dag_register_node`).
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
Dear ImGui Test Engine License (v1.04)
|
||||
Copyright (c) 2018-2026 Omar Cornut
|
||||
|
||||
This document is a legal agreement ("License") between you ("Licensee") and
|
||||
DISCO HELLO ("Licensor") that governs your use of Dear ImGui Test Engine ("Software").
|
||||
|
||||
1. LICENSE MODELS
|
||||
|
||||
1.1. Free license
|
||||
|
||||
The Licensor grants you a free license ("Free License") if you meet ANY of the following
|
||||
criterion:
|
||||
|
||||
- You are a natural person;
|
||||
- You are not a legal entity, or you are a not-for-profit legal entity;
|
||||
- You are using this Software for educational purposes;
|
||||
- You are using this Software to create Derivative Software released publicly and under
|
||||
an Open Source license, as defined by the Open Source Initiative;
|
||||
- You are a legal entity with a turnover inferior to 2 million USD (or equivalent) during
|
||||
your last fiscal year.
|
||||
|
||||
1.2. Paid license
|
||||
|
||||
If you do not meet any criterion of Article 1.1, Licensor grants you a trial period of a
|
||||
maximum of 45 days, at no charge. Following this trial period, you must subscribe to a paid
|
||||
license ("Paid License") with the Licensor to continue using the Software.
|
||||
Paid Licenses are exclusively sold by DISCO HELLO. Paid Licenses and the associated
|
||||
information are available at the following URL: http://www.dearimgui.com/licenses
|
||||
|
||||
2. GRANT OF LICENSE
|
||||
|
||||
2.1. License scope
|
||||
|
||||
A limited and non-exclusive worldwide license is hereby granted, to the Licensee,
|
||||
to reproduce, execute, publicly perform, and display, use, copy, modify, merge,
|
||||
distribute, or create derivative works based on and/or derived from the Software
|
||||
("Derivative Software").
|
||||
|
||||
2.2. Right of distribution
|
||||
|
||||
License holders may also publish and/or distribute the Software or any Derivative
|
||||
Software. The above copyright notice and this license shall be included in all copies
|
||||
or substantial portions of the Software and/or Derivative Software.
|
||||
|
||||
License holders may add their own attribution notices within the Derivative Software
|
||||
that they distribute. Such attribution notices must not directly or indirectly imply a
|
||||
modification of the License. License holders may provide to their modifications their
|
||||
own copyright and/or additional or different terms and conditions, providing such
|
||||
conditions complies with this License.
|
||||
|
||||
3. DISCLAIMER
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
+1130
File diff suppressed because it is too large
Load Diff
+183
@@ -0,0 +1,183 @@
|
||||
// dear imgui test engine
|
||||
// (screen/video capture tool)
|
||||
// This is usable as a standalone applet or controlled by the test engine.
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Need "imgui_te_engine.h" included for ImFuncPtr
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Forward declarations
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Our types
|
||||
struct ImGuiCaptureArgs; // Parameters for Capture
|
||||
struct ImGuiCaptureContext; // State of an active capture tool
|
||||
struct ImGuiCaptureImageBuf; // Simple helper to store an RGBA image in memory
|
||||
struct ImGuiCaptureToolUI; // Capture tool instance + UI window
|
||||
|
||||
typedef unsigned int ImGuiCaptureFlags; // See enum: ImGuiCaptureFlags_
|
||||
|
||||
// Capture function which needs to be provided by user application
|
||||
typedef bool (ImGuiScreenCaptureFunc)(ImGuiID viewport_id, int x, int y, int w, int h, unsigned int* pixels, void* user_data);
|
||||
|
||||
// External types
|
||||
struct ImGuiWindow; // imgui.h
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// [Internal]
|
||||
// Helper class for simple bitmap manipulation (not particularly efficient!)
|
||||
struct IMGUI_API ImGuiCaptureImageBuf
|
||||
{
|
||||
int Width;
|
||||
int Height;
|
||||
unsigned int* Data; // RGBA8
|
||||
|
||||
ImGuiCaptureImageBuf() { Width = Height = 0; Data = nullptr; }
|
||||
~ImGuiCaptureImageBuf() { Clear(); }
|
||||
|
||||
void Clear(); // Free allocated memory buffer if such exists.
|
||||
void CreateEmpty(int w, int h); // Reallocate buffer for pixel data and zero it.
|
||||
bool SaveFile(const char* filename); // Save pixel data to specified image file.
|
||||
void RemoveAlpha(); // Clear alpha channel from all pixels.
|
||||
};
|
||||
|
||||
enum ImGuiCaptureFlags_ : unsigned int
|
||||
{
|
||||
ImGuiCaptureFlags_None = 0,
|
||||
ImGuiCaptureFlags_StitchAll = 1 << 0, // Capture entire window scroll area (by scrolling and taking multiple screenshot). Only works for a single window.
|
||||
ImGuiCaptureFlags_IncludeOtherWindows = 1 << 1, // Disable hiding other windows (when CaptureAddWindow has been called by default other windows are hidden)
|
||||
ImGuiCaptureFlags_IncludePopups = 1 << 2, // Expand capture area to automatically include visible popups (Unused if ImGuiCaptureFlags_IncludeOtherWindows is set)
|
||||
ImGuiCaptureFlags_HideMouseCursor = 1 << 3, // Hide render software mouse cursor during capture.
|
||||
ImGuiCaptureFlags_Instant = 1 << 4, // Perform capture on very same frame. Only works when capturing a rectangular region. Unsupported features: content stitching, window hiding, window relocation.
|
||||
ImGuiCaptureFlags_NoSave = 1 << 5 // Do not save output image.
|
||||
};
|
||||
|
||||
// Defines input and output arguments for capture process.
|
||||
// When capturing from tests you can usually use the ImGuiTestContext::CaptureXXX() helpers functions.
|
||||
struct ImGuiCaptureArgs
|
||||
{
|
||||
// [Input]
|
||||
ImGuiCaptureFlags InFlags = 0; // Flags for customizing behavior of screenshot tool.
|
||||
ImVector<ImGuiWindow*> InCaptureWindows; // Windows to capture. All other windows will be hidden. May be used with InCaptureRect to capture only some windows in specified rect.
|
||||
ImRect InCaptureRect; // Screen rect to capture. Does not include padding.
|
||||
float InPadding = 16.0f; // Extra padding at the edges of the screenshot. Ensure that there is available space around capture rect horizontally, also vertically if ImGuiCaptureFlags_StitchAll is not used.
|
||||
char InOutputFile[256] = ""; // Output will be saved to a file if InOutputImageBuf is nullptr.
|
||||
ImGuiCaptureImageBuf* InOutputImageBuf = nullptr; // _OR_ Output will be saved to image buffer if specified.
|
||||
int InRecordFPSTarget = 30; // FPS target for recording videos.
|
||||
int InSizeAlign = 0; // Resolution alignment (0 = auto, 1 = no alignment, >= 2 = align width/height to be multiple of given value)
|
||||
|
||||
// [Output]
|
||||
ImVec2 OutImageSize; // Produced image size.
|
||||
};
|
||||
|
||||
enum ImGuiCaptureStatus
|
||||
{
|
||||
ImGuiCaptureStatus_InProgress,
|
||||
ImGuiCaptureStatus_Done,
|
||||
ImGuiCaptureStatus_Error
|
||||
};
|
||||
|
||||
struct ImGuiCaptureWindowData
|
||||
{
|
||||
ImGuiWindow* Window;
|
||||
ImRect BackupRect;
|
||||
ImVec2 PosDuringCapture;
|
||||
};
|
||||
|
||||
// Implements functionality for capturing images
|
||||
struct IMGUI_API ImGuiCaptureContext
|
||||
{
|
||||
// IO
|
||||
ImFuncPtr(ImGuiScreenCaptureFunc) ScreenCaptureFunc = nullptr; // Graphics backend specific function that captures specified portion of framebuffer and writes RGBA data to `pixels` buffer.
|
||||
void* ScreenCaptureUserData = nullptr; // Custom user pointer which is passed to ScreenCaptureFunc. (Optional)
|
||||
char* VideoCaptureEncoderPath = nullptr; // Video encoder path (not owned, stored externally).
|
||||
int VideoCaptureEncoderPathSize = 0; // Optional. Set in order to edit this parameter from UI.
|
||||
char* VideoCaptureEncoderParams = nullptr; // Video encoder params (not owned, stored externally).
|
||||
int VideoCaptureEncoderParamsSize = 0; // Optional. Set in order to edit this parameter from UI.
|
||||
char* GifCaptureEncoderParams = nullptr; // Video encoder params for GIF output (not owned, stored externally).
|
||||
int GifCaptureEncoderParamsSize = 0; // Optional. Set in order to edit this parameter from UI.
|
||||
|
||||
// [Internal]
|
||||
ImRect _CaptureRect; // Viewport rect that is being captured.
|
||||
ImRect _CapturedWindowRect; // Top-left corner of region that covers all windows included in capture. This is not same as _CaptureRect.Min when capturing explicitly specified rect.
|
||||
int _ChunkNo = 0; // Number of chunk that is being captured when capture spans multiple frames.
|
||||
int _FrameNo = 0; // Frame number during capture process that spans multiple frames.
|
||||
ImVec2 _MouseRelativeToWindowPos; // Mouse cursor position relative to captured window (when _StitchAll is in use).
|
||||
ImGuiWindow* _HoveredWindow = nullptr; // Window which was hovered at capture start.
|
||||
ImGuiCaptureImageBuf _CaptureBuf; // Output image buffer.
|
||||
const ImGuiCaptureArgs* _CaptureArgs = nullptr; // Current capture args. Set only if capture is in progress.
|
||||
ImVector<ImGuiCaptureWindowData> _WindowsData; // Backup windows that will have their rect modified and restored. args->InCaptureWindows can not be used because popups may get closed during capture and no longer appear in that list.
|
||||
|
||||
// [Internal] Video recording
|
||||
bool _VideoRecording = false; // Flag indicating that video recording is in progress.
|
||||
double _VideoLastFrameTime = 0; // Time when last video frame was recorded.
|
||||
FILE* _VideoEncoderPipe = nullptr; // File writing to stdin of video encoder process.
|
||||
|
||||
// [Internal] Backups
|
||||
bool _BackupMouseDrawCursor = false; // Initial value of g.IO.MouseDrawCursor
|
||||
ImVec2 _BackupDisplayWindowPadding; // Backup padding. We set it to {0, 0} during capture.
|
||||
ImVec2 _BackupDisplaySafeAreaPadding; // Backup padding. We set it to {0, 0} during capture.
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Functions
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
ImGuiCaptureContext(ImGuiScreenCaptureFunc capture_func = nullptr) { ScreenCaptureFunc = capture_func; _MouseRelativeToWindowPos = ImVec2(-FLT_MAX, -FLT_MAX); }
|
||||
|
||||
// These functions should be called from appropriate context hooks. See ImGui::AddContextHook() for more info.
|
||||
// (ImGuiTestEngine automatically calls that for you, so this only apply to independently created instance)
|
||||
void PreNewFrame();
|
||||
void PreRender();
|
||||
void PostRender();
|
||||
|
||||
// Update capturing. If this function returns true then it should be called again with same arguments on the next frame.
|
||||
ImGuiCaptureStatus CaptureUpdate(ImGuiCaptureArgs* args);
|
||||
void RestoreBackedUpData();
|
||||
void ClearState();
|
||||
|
||||
// Begin video capture. Call CaptureUpdate() every frame afterwards until it returns false.
|
||||
void BeginVideoCapture(ImGuiCaptureArgs* args);
|
||||
void EndVideoCapture();
|
||||
bool IsCapturingVideo();
|
||||
bool IsCapturing();
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// ImGuiCaptureToolUI
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Implements UI for capturing images
|
||||
// (when using ImGuiTestEngine scripting API you may not need to use this at all)
|
||||
struct IMGUI_API ImGuiCaptureToolUI
|
||||
{
|
||||
float SnapGridSize = 32.0f; // Size of the grid cell for "snap to grid" functionality.
|
||||
char OutputLastFilename[256] = ""; // File name of last captured file.
|
||||
char* VideoCaptureExtension = nullptr; // Video file extension (e.g. ".gif" or ".mp4")
|
||||
int VideoCaptureExtensionSize = 0; // Optional. Set in order to edit this parameter from UI.
|
||||
|
||||
ImGuiCaptureArgs _CaptureArgs; // Capture args
|
||||
bool _StateIsPickingWindow = false;
|
||||
bool _StateIsCapturing = false;
|
||||
ImVector<ImGuiID> _SelectedWindows;
|
||||
char _OutputFileTemplate[256] = ""; //
|
||||
int _FileCounter = 0; // Counter which may be appended to file name when saving. By default, counting starts from 1. When done this field holds number of saved files.
|
||||
|
||||
// Public
|
||||
ImGuiCaptureToolUI();
|
||||
void ShowCaptureToolWindow(ImGuiCaptureContext* context, bool* p_open = nullptr); // Render a capture tool window with various options and utilities.
|
||||
|
||||
// [Internal]
|
||||
void _CaptureWindowPicker(ImGuiCaptureArgs* args); // Render a window picker that captures picked window to file specified in file_name.
|
||||
void _CaptureWindowsSelector(ImGuiCaptureContext* context, ImGuiCaptureArgs* args); // Render a selector for selecting multiple windows for capture.
|
||||
void _SnapWindowsToGrid(float cell_size); // Snap edges of all visible windows to a virtual grid.
|
||||
bool _InitializeOutputFile(); // Format output file template into capture args struct and ensure target directory exists.
|
||||
bool _ShowEncoderConfigFields(ImGuiCaptureContext* context);
|
||||
};
|
||||
|
||||
#define IMGUI_CAPTURE_DEFAULT_VIDEO_PARAMS_FOR_FFMPEG "-hide_banner -loglevel error -r $FPS -f rawvideo -pix_fmt rgba -s $WIDTHx$HEIGHT -i - -threads 0 -y -preset ultrafast -pix_fmt yuv420p -crf 20 $OUTPUT"
|
||||
#define IMGUI_CAPTURE_DEFAULT_GIF_PARAMS_FOR_FFMPEG "-hide_banner -loglevel error -r $FPS -f rawvideo -pix_fmt rgba -s $WIDTHx$HEIGHT -i - -threads 0 -y -filter_complex \"split=2 [a] [b]; [a] palettegen [pal]; [b] [pal] paletteuse\" $OUTPUT"
|
||||
+4543
File diff suppressed because it is too large
Load Diff
+681
@@ -0,0 +1,681 @@
|
||||
// dear imgui test engine
|
||||
// (context when a running test + end user automation API)
|
||||
// This is the main (if not only) interface that your Tests will be using.
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h" // ImGuiAxis, ImGuiItemStatusFlags, ImGuiInputSource, ImGuiWindow
|
||||
#include "imgui_te_engine.h" // ImGuiTestStatus, ImGuiTestRunFlags, ImGuiTestActiveFunc, ImGuiTestItemInfo, ImGuiTestLogFlags
|
||||
|
||||
/*
|
||||
|
||||
Index of this file:
|
||||
// [SECTION] Header mess, warnings
|
||||
// [SECTION] Forward declarations
|
||||
// [SECTION] ImGuiTestRef
|
||||
// [SECTION] Helper keys
|
||||
// [SECTION] ImGuiTestContext related Flags/Enumerations
|
||||
// [SECTION] ImGuiTestGenericVars, ImGuiTestGenericItemStatus
|
||||
// [SECTION] ImGuiTestContext
|
||||
// [SECTION] Debugging macros: IM_SUSPEND_TESTFUNC()
|
||||
// [SECTION] Testing/Checking macros: IM_CHECK(), IM_ERRORF() etc.
|
||||
|
||||
*/
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] Header mess, warnings
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Undo some of the damage done by <windows.h>
|
||||
#ifdef Yield
|
||||
#undef Yield
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] Forward declarations
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// This file
|
||||
typedef int ImGuiTestOpFlags; // Flags: See ImGuiTestOpFlags_
|
||||
|
||||
// External: imgui
|
||||
struct ImGuiDockNode;
|
||||
struct ImGuiTabBar;
|
||||
struct ImGuiWindow;
|
||||
|
||||
// External: test engine
|
||||
struct ImGuiTest; // A test registered with IM_REGISTER_TEST()
|
||||
struct ImGuiTestEngine; // Test Engine Instance (opaque)
|
||||
struct ImGuiTestEngineIO; // Test Engine IO structure (configuration flags, state)
|
||||
struct ImGuiTestItemInfo; // Information gathered about an item: label, status, bounding box etc.
|
||||
struct ImGuiTestItemList; // Result of an GatherItems() query
|
||||
struct ImGuiTestInputs; // Test Engine Simulated Inputs structure (opaque)
|
||||
struct ImGuiTestGatherTask; // Test Engine task for scanning/finding items
|
||||
struct ImGuiCaptureArgs; // Parameters for ctx->CaptureXXX functions
|
||||
enum ImGuiTestVerboseLevel : int;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] ImGuiTestRef
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Weak reference to an Item/Window given an hashed ID _or_ a string path ID.
|
||||
// This is most often passed as argument to function and generally has a very short lifetime.
|
||||
// Documentation: https://github.com/ocornut/imgui_test_engine/wiki/Named-References
|
||||
// (SUGGESTION: add those constructors to "VA Step Filter" (Visual Assist) or a .natstepfilter file (Visual Studio) so they are skipped by F11 (StepInto)
|
||||
struct IMGUI_API ImGuiTestRef
|
||||
{
|
||||
ImGuiID ID; // Pre-hashed ID
|
||||
const char* Path; // Relative or absolute path (string pointed to, not owned, as our lifetime is very short)
|
||||
|
||||
ImGuiTestRef() { ID = 0; Path = nullptr; }
|
||||
ImGuiTestRef(ImGuiID id) { ID = id; Path = nullptr; }
|
||||
ImGuiTestRef(const char* path) { ID = 0; Path = path; }
|
||||
bool IsEmpty() const { return ID == 0 && (Path == nullptr || Path[0] == 0); }
|
||||
};
|
||||
|
||||
// Debug helper to output a string showing the Path, ID or Debug Label based on what is available (some items only have ID as we couldn't find/store a Path)
|
||||
// (The size is arbitrary, this is only used for logging info the user/debugger)
|
||||
struct IMGUI_API ImGuiTestRefDesc
|
||||
{
|
||||
char Buf[80];
|
||||
|
||||
const char* c_str() { return Buf; }
|
||||
ImGuiTestRefDesc(const ImGuiTestRef& ref);
|
||||
ImGuiTestRefDesc(const ImGuiTestRef& ref, const ImGuiTestItemInfo& item);
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] ImGuiTestContext related Flags/Enumerations
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Named actions. Generally you will call the named helpers e.g. ItemClick(). This is used by shared/low-level functions such as ItemAction().
|
||||
enum ImGuiTestAction
|
||||
{
|
||||
ImGuiTestAction_Unknown = 0,
|
||||
ImGuiTestAction_Hover, // Move mouse
|
||||
ImGuiTestAction_Click, // Move mouse and click
|
||||
ImGuiTestAction_DoubleClick, // Move mouse and double-click
|
||||
ImGuiTestAction_Check, // Check item if unchecked (Checkbox, MenuItem or any widget reporting ImGuiItemStatusFlags_Checkable)
|
||||
ImGuiTestAction_Uncheck, // Uncheck item if checked
|
||||
ImGuiTestAction_Open, // Open item if closed (TreeNode, BeginMenu or any widget reporting ImGuiItemStatusFlags_Openable)
|
||||
ImGuiTestAction_Close, // Close item if opened
|
||||
ImGuiTestAction_Input, // Start text inputing into a field (e.g. CTRL+Click on Drags/Slider, click on InputText etc.)
|
||||
ImGuiTestAction_NavActivate, // Activate item with navigation
|
||||
ImGuiTestAction_COUNT
|
||||
};
|
||||
|
||||
// Generic flags for many ImGuiTestContext functions
|
||||
// Some flags are only supported by a handful of functions. Check function headers for list of supported flags.
|
||||
enum ImGuiTestOpFlags_
|
||||
{
|
||||
ImGuiTestOpFlags_None = 0,
|
||||
ImGuiTestOpFlags_NoCheckHoveredId = 1 << 1, // Don't check for HoveredId after aiming for a widget. This is automatic when there's an active item, typically in drag and drop operation. Otherwise, a few situations may want this e.g. for items that don't use ItemHoverable(), or when intently aiming for an item behind a popup/modal inhibition layer.
|
||||
ImGuiTestOpFlags_NoError = 1 << 2, // Don't abort/error e.g. if the item cannot be found or the operation doesn't succeed.
|
||||
ImGuiTestOpFlags_NoFocusWindow = 1 << 3, // Don't focus window when aiming at an item
|
||||
ImGuiTestOpFlags_NoAutoUncollapse = 1 << 4, // Disable automatically uncollapsing windows (useful when specifically testing Collapsing behaviors)
|
||||
ImGuiTestOpFlags_NoAutoOpenFullPath = 1 << 5, // Disable automatically opening intermediaries (e.g. ItemClick("Hello/OK") will automatically first open "Hello" if "OK" isn't found. Only works if ref is a string path.
|
||||
ImGuiTestOpFlags_NoYield = 1 << 6, // Don't yield (only supported by a few functions), in case you need to manage rigorous per-frame timing.
|
||||
ImGuiTestOpFlags_IsSecondAttempt = 1 << 7, // Used by recursing functions to indicate a second attempt
|
||||
ImGuiTestOpFlags_MoveToEdgeL = 1 << 8, // Simple Dumb aiming helpers to test widget that care about clicking position. May need to replace will better functionalities.
|
||||
ImGuiTestOpFlags_MoveToEdgeR = 1 << 9,
|
||||
ImGuiTestOpFlags_MoveToEdgeU = 1 << 10,
|
||||
ImGuiTestOpFlags_MoveToEdgeD = 1 << 11,
|
||||
ImGuiTestOpFlags_NoScroll = 1 << 12, // Disable automatically scrolling to reach an item.
|
||||
};
|
||||
|
||||
// Advanced filtering for ItemActionAll()
|
||||
struct IMGUI_API ImGuiTestActionFilter
|
||||
{
|
||||
int MaxDepth;
|
||||
int MaxPasses;
|
||||
const int* MaxItemCountPerDepth;
|
||||
ImGuiItemStatusFlags RequireAllStatusFlags;
|
||||
ImGuiItemStatusFlags RequireAnyStatusFlags;
|
||||
|
||||
ImGuiTestActionFilter() { MaxDepth = -1; MaxPasses = -1; MaxItemCountPerDepth = nullptr; RequireAllStatusFlags = RequireAnyStatusFlags = 0; }
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] ImGuiTestGenericVars, ImGuiTestGenericItemStatus
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Helper struct to store various query-able state of an item.
|
||||
// This facilitate interactions between GuiFunc and TestFunc, since those state are frequently used.
|
||||
struct IMGUI_API ImGuiTestGenericItemStatus
|
||||
{
|
||||
int RetValue; // return value
|
||||
int Hovered; // result of IsItemHovered()
|
||||
int HoveredAllowDisabled; // result of IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)
|
||||
int Active; // result of IsItemActive()
|
||||
int Focused; // result of IsItemFocused()
|
||||
int Clicked; // result of IsItemClicked()
|
||||
int Visible; // result of IsItemVisible()
|
||||
int Edited; // result of IsItemEdited()
|
||||
int Activated; // result of IsItemActivated()
|
||||
int Deactivated; // result of IsItemDeactivated()
|
||||
int DeactivatedAfterEdit; // result of IsItemDeactivatedAfterEdit()
|
||||
|
||||
ImGuiTestGenericItemStatus() { Clear(); }
|
||||
void Clear() { memset(this, 0, sizeof(*this)); }
|
||||
void QuerySet(bool ret_val = false) { Clear(); QueryInc(ret_val); }
|
||||
void QueryInc(bool ret_val = false) { RetValue += ret_val; Hovered += ImGui::IsItemHovered(); HoveredAllowDisabled += ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled); Active += ImGui::IsItemActive(); Focused += ImGui::IsItemFocused(); Clicked += ImGui::IsItemClicked(); Visible += ImGui::IsItemVisible(); Edited += ImGui::IsItemEdited(); Activated += ImGui::IsItemActivated(); Deactivated += ImGui::IsItemDeactivated(); DeactivatedAfterEdit += ImGui::IsItemDeactivatedAfterEdit(); }
|
||||
void Draw() { ImGui::Text("Ret: %d, Hovered: %d, Active: %d, Focused: %d\nClicked: %d, Visible: %d, Edited: %d\nActivated: %d, Deactivated: %d, DeactivatedAfterEdit: %d", RetValue, Hovered, Active, Focused, Clicked, Visible, Edited, Activated, Deactivated, DeactivatedAfterEdit); }
|
||||
};
|
||||
|
||||
// Generic structure with various storage fields.
|
||||
// This is useful for tests to quickly share data between GuiFunc and TestFunc without creating custom data structure.
|
||||
// If those fields are not enough: using test->SetVarsDataType<>() + ctx->GetVars<>() it is possible to store custom data.
|
||||
struct IMGUI_API ImGuiTestGenericVars
|
||||
{
|
||||
// Generic storage with a bit of semantic to make user/test code look neater
|
||||
int Step;
|
||||
int Count;
|
||||
ImGuiID DockId;
|
||||
ImGuiID OwnerId;
|
||||
ImVec2 WindowSize;
|
||||
ImGuiWindowFlags WindowFlags;
|
||||
ImGuiTableFlags TableFlags;
|
||||
ImGuiPopupFlags PopupFlags;
|
||||
ImGuiInputTextFlags InputTextFlags;
|
||||
ImGuiTestGenericItemStatus Status;
|
||||
bool ShowWindow1, ShowWindow2;
|
||||
bool UseClipper;
|
||||
bool UseViewports;
|
||||
float Width;
|
||||
ImVec2 Pos;
|
||||
ImVec2 Pivot;
|
||||
ImVec2 ItemSize;
|
||||
ImVec4 Color1, Color2;
|
||||
|
||||
// Generic unnamed storage
|
||||
int Int1, Int2, IntArray[10];
|
||||
float Float1, Float2, FloatArray[10];
|
||||
bool Bool1, Bool2, BoolArray[10];
|
||||
ImGuiID Id, IdArray[10];
|
||||
char Str1[256], Str2[256];
|
||||
|
||||
ImGuiTestGenericVars() { Clear(); }
|
||||
void Clear() { memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] ImGuiTestContext
|
||||
// Context for a running ImGuiTest
|
||||
// This is the interface that most tests will interact with.
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
struct IMGUI_API ImGuiTestContext
|
||||
{
|
||||
// User variables
|
||||
ImGuiTestGenericVars GenericVars; // Generic variables holder for convenience.
|
||||
void* UserVars = nullptr; // Access using ctx->GetVars<Type>(). Setup with test->SetVarsDataType<>().
|
||||
|
||||
// Public fields
|
||||
ImGuiContext* UiContext = nullptr; // UI context
|
||||
ImGuiTestEngineIO* EngineIO = nullptr; // Test Engine IO/settings
|
||||
ImGuiTest* Test = nullptr; // Test currently running
|
||||
ImGuiTestOutput* TestOutput = nullptr; // Test output (generally == &Test->Output while executing TestFunc)
|
||||
ImGuiTestOpFlags OpFlags = ImGuiTestOpFlags_None; // Flags affecting all operation (supported: ImGuiTestOpFlags_NoAutoUncollapse)
|
||||
int PerfStressAmount = 0; // Convenience copy of engine->IO.PerfStressAmount
|
||||
int FrameCount = 0; // Test frame count (restarts from zero every time)
|
||||
int FirstTestFrameCount = 0; // First frame where TestFunc is running (after warm-up frame). This is generally -1 or 0 depending on whether we have warm up enabled
|
||||
bool FirstGuiFrame = false;
|
||||
bool HasDock = false; // #ifdef IMGUI_HAS_DOCK expressed in an easier to test value
|
||||
ImGuiCaptureArgs* CaptureArgs = nullptr; // Capture settings used by ctx->Capture*() functions
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [Internal Fields]
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
ImGuiTestEngine* Engine = nullptr;
|
||||
ImGuiTestInputs* Inputs = nullptr;
|
||||
ImGuiTestRunFlags RunFlags = ImGuiTestRunFlags_None;
|
||||
ImGuiTestActiveFunc ActiveFunc = ImGuiTestActiveFunc_None; // None/GuiFunc/TestFunc
|
||||
double RunningTime = 0.0; // Amount of wall clock time the Test has been running. Used by safety watchdog.
|
||||
int ActionDepth = 0; // Nested depth of ctx-> function calls (used to decorate log)
|
||||
int CaptureCounter = 0; // Number of captures
|
||||
int ErrorCounter = 0; // Number of errors (generally this maxxes at 1 as most functions will early out)
|
||||
bool Abort = false;
|
||||
double PerfRefDt = -1.0;
|
||||
int PerfIterations = 400; // Number of frames for PerfCapture() measurements
|
||||
char RefStr[256] = { 0 }; // Reference window/path over which all named references are based
|
||||
ImGuiID RefID = 0; // Reference ID over which all named references are based
|
||||
ImGuiID RefWindowID = 0; // ID of a window that contains RefID item
|
||||
ImGuiInputSource InputMode = ImGuiInputSource_Mouse; // Prefer interacting with mouse/keyboard/gamepad
|
||||
ImVector<char> TempString;
|
||||
ImVector<char> Clipboard; // Private clipboard for the test instance
|
||||
ImVector<ImGuiWindow*> ForeignWindowsToHide;
|
||||
ImGuiTestItemInfo DummyItemInfoNull; // Storage for ItemInfoNull()
|
||||
bool CachedLinesPrintedToTTY = false;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Public API
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Main control
|
||||
void Finish(ImGuiTestStatus status = ImGuiTestStatus_Success); // Set test status and stop running. Usually called when running test logic from GuiFunc() only.
|
||||
ImGuiTestStatus RunChildTest(const char* test_name, ImGuiTestRunFlags flags = 0); // [Experimental] Run another test from the current test.
|
||||
template <typename T> T& GetVars() { IM_ASSERT(UserVars != nullptr); return *(T*)(UserVars); }// Campanion to using t->SetVarsDataType<>(). FIXME: Assert to compare sizes
|
||||
|
||||
// Main status queries
|
||||
bool IsError() const { return TestOutput->Status == ImGuiTestStatus_Error || Abort; }
|
||||
bool IsWarmUpGuiFrame() const { return FrameCount < FirstTestFrameCount; } // Unless test->Flags has ImGuiTestFlags_NoGuiWarmUp, we run GuiFunc() twice before running TestFunc(). Those frames are called "WarmUp" frames.
|
||||
bool IsFirstGuiFrame() const { return FirstGuiFrame; }
|
||||
bool IsFirstTestFrame() const { return FrameCount == FirstTestFrameCount; } // First frame where TestFunc is running (after warm-up frame).
|
||||
bool IsGuiFuncOnly() const { return (RunFlags & ImGuiTestRunFlags_GuiFuncOnly) != 0; }
|
||||
|
||||
// Debugging
|
||||
bool SuspendTestFunc(const char* file = nullptr, int line = 0); // [DEBUG] Generally called via IM_SUSPEND_TESTFUNC
|
||||
|
||||
// Logging
|
||||
void LogEx(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, ...) IM_FMTARGS(4);
|
||||
void LogExV(ImGuiTestVerboseLevel level, ImGuiTestLogFlags flags, const char* fmt, va_list args) IM_FMTLIST(4);
|
||||
void LogToTTY(ImGuiTestVerboseLevel level, const char* message, const char* message_end = nullptr);
|
||||
void LogToDebugger(ImGuiTestVerboseLevel level, const char* message);
|
||||
void LogDebug(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Debug or ImGuiTestVerboseLevel_Trace depending on context depth
|
||||
void LogInfo(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Info
|
||||
void LogWarning(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Warning
|
||||
void LogError(const char* fmt, ...) IM_FMTARGS(2); // ImGuiTestVerboseLevel_Error
|
||||
void LogBasicUiState();
|
||||
void LogItemList(ImGuiTestItemList* list);
|
||||
|
||||
// Yield, Timing
|
||||
void Yield(int count = 1);
|
||||
void Sleep(float time_in_second); // Sleep for a given simulation time, unless in Fast mode
|
||||
void SleepShort(); // Standard short delay of io.ActionDelayShort (~0.15f), unless in Fast mode.
|
||||
void SleepStandard(); // Standard regular delay of io.ActionDelayStandard (~0.40f), unless in Fast mode.
|
||||
void SleepNoSkip(float time_in_second, float framestep_in_second);
|
||||
|
||||
// Base Reference
|
||||
// - ItemClick("Window/Button") --> click "Window/Button"
|
||||
// - SetRef("Window"), ItemClick("Button") --> click "Window/Button"
|
||||
// - SetRef("Window"), ItemClick("/Button") --> click "Window/Button"
|
||||
// - SetRef("Window"), ItemClick("//Button") --> click "/Button"
|
||||
// - SetRef("//$FOCUSED"), ItemClick("Button") --> click "Button" in focused window.
|
||||
// See https://github.com/ocornut/imgui_test_engine/wiki/Named-References about using ImGuiTestRef in all ImGuiTestContext functions.
|
||||
// Note: SetRef() may take multiple frames to complete if specified ref is an item id.
|
||||
// Note: SetRef() ignores current reference, so they are always absolute path.
|
||||
void SetRef(ImGuiTestRef ref);
|
||||
void SetRef(ImGuiWindow* window); // Shortcut to SetRef(window->Name) which works for ChildWindow (see code)
|
||||
ImGuiTestRef GetRef();
|
||||
|
||||
// Windows
|
||||
// - Use WindowInfo() to access path to child windows, since the paths are internally mangled.
|
||||
// - SetRef(WindowInfo("Parent/Child")->Window) --> set ref to child window.
|
||||
ImGuiTestItemInfo WindowInfo(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void WindowClose(ImGuiTestRef window_ref);
|
||||
void WindowCollapse(ImGuiTestRef window_ref, bool collapsed);
|
||||
void WindowFocus(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void WindowBringToFront(ImGuiTestRef window_ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void WindowMove(ImGuiTestRef window_ref, ImVec2 pos, ImVec2 pivot = ImVec2(0.0f, 0.0f), ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void WindowResize(ImGuiTestRef window_ref, ImVec2 sz);
|
||||
bool WindowTeleportToMakePosVisible(ImGuiTestRef window_ref, ImVec2 pos_in_window);
|
||||
ImGuiWindow*GetWindowByRef(ImGuiTestRef window_ref);
|
||||
|
||||
// Popups
|
||||
void PopupCloseOne();
|
||||
void PopupCloseAll();
|
||||
ImGuiID PopupGetWindowID(ImGuiTestRef ref);
|
||||
|
||||
// Get hash for a decorated ID Path.
|
||||
// Note: for windows you may use WindowInfo()
|
||||
ImGuiID GetID(ImGuiTestRef ref);
|
||||
ImGuiID GetID(ImGuiTestRef ref, ImGuiTestRef seed_ref);
|
||||
|
||||
// Miscellaneous helpers
|
||||
ImVec2 GetPosOnVoid(ImGuiViewport* viewport); // Find a point that has no windows // FIXME: This needs error return and flag to enable/disable forcefully finding void.
|
||||
ImVec2 GetWindowTitlebarPoint(ImGuiTestRef window_ref); // Return a clickable point on window title-bar (window tab for docked windows) that will e.g. move this single window.
|
||||
ImVec2 GetMainMonitorWorkPos(); // Work pos and size of main viewport when viewports are disabled, or work pos and size of monitor containing main viewport when viewports are enabled.
|
||||
ImVec2 GetMainMonitorWorkSize();
|
||||
|
||||
// Screenshot/Video Captures
|
||||
void CaptureReset(); // Reset state (use when doing multiple captures)
|
||||
void CaptureSetExtension(const char* ext); // Set capture file format (otherwise for video this default to EngineIO->VideoCaptureExtension)
|
||||
bool CaptureAddWindow(ImGuiTestRef ref); // Add window to be captured (default to capture everything)
|
||||
void CaptureScreenshotWindow(ImGuiTestRef ref, int capture_flags = 0); // Trigger a screen capture of a single window (== CaptureAddWindow() + CaptureScreenshot())
|
||||
bool CaptureScreenshot(int capture_flags = 0); // Trigger a screen capture
|
||||
bool CaptureBeginVideo(); // Start a video capture
|
||||
bool CaptureEndVideo();
|
||||
|
||||
// Mouse inputs
|
||||
void MouseMove(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void MouseMoveToPos(ImVec2 pos);
|
||||
void MouseTeleportToPos(ImVec2 pos, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void MouseClick(ImGuiMouseButton button = 0);
|
||||
void MouseClickMulti(ImGuiMouseButton button, int count);
|
||||
void MouseDoubleClick(ImGuiMouseButton button = 0);
|
||||
void MouseDown(ImGuiMouseButton button = 0);
|
||||
void MouseUp(ImGuiMouseButton button = 0);
|
||||
void MouseLiftDragThreshold(ImGuiMouseButton button = 0);
|
||||
void MouseDragWithDelta(ImVec2 delta, ImGuiMouseButton button = 0);
|
||||
void MouseWheel(ImVec2 delta);
|
||||
void MouseWheelX(float dx) { MouseWheel(ImVec2(dx, 0.0f)); }
|
||||
void MouseWheelY(float dy) { MouseWheel(ImVec2(0.0f, dy)); } // +1: up, -1: down
|
||||
void MouseMoveToVoid(ImGuiViewport* viewport = nullptr);
|
||||
void MouseClickOnVoid(ImGuiMouseButton button = 0, ImGuiViewport* viewport = nullptr);
|
||||
ImGuiWindow*FindHoveredWindowAtPos(const ImVec2& pos);
|
||||
bool FindExistingVoidPosOnViewport(ImGuiViewport* viewport, ImVec2* out);
|
||||
|
||||
// Mouse inputs: Viewports
|
||||
// - This is automatically called by SetRef() and any mouse action taking an item reference (e.g. ItemClick("button"), MouseClick("button"))
|
||||
// - But when using raw position directy e.g. MouseMoveToPos() / MouseTeleportToPos() without referring to the parent window before, this needs to be set.
|
||||
void MouseSetViewport(ImGuiWindow* window);
|
||||
void MouseSetViewportID(ImGuiID viewport_id);
|
||||
|
||||
// Keyboard inputs
|
||||
void KeyDown(ImGuiKeyChord key_chord);
|
||||
void KeyUp(ImGuiKeyChord key_chord);
|
||||
void KeyPress(ImGuiKeyChord key_chord, int count = 1);
|
||||
void KeyHold(ImGuiKeyChord key_chord, float time);
|
||||
void KeySetEx(ImGuiKeyChord key_chord, bool is_down, float time);
|
||||
void KeyChars(const char* chars); // Input characters
|
||||
void KeyCharsAppend(const char* chars); // Input characters at end of field
|
||||
void KeyCharsAppendEnter(const char* chars); // Input characters at end of field, press Enter
|
||||
void KeyCharsReplace(const char* chars); // Delete existing field then input characters
|
||||
void KeyCharsReplaceEnter(const char* chars); // Delete existing field then input characters, press Enter
|
||||
|
||||
// Navigation inputs
|
||||
// FIXME: Need some redesign/refactoring:
|
||||
// - This was initially intended to: replace mouse action with keyboard/gamepad
|
||||
// - Abstract keyboard vs gamepad actions
|
||||
// However this is widely inconsistent and unfinished at this point.
|
||||
void SetInputMode(ImGuiInputSource input_mode); // Mouse or Keyboard or Gamepad. In Keyboard or Gamepad mode, actions such as ItemClick or ItemInput are using nav facilities instead of Mouse.
|
||||
void NavMoveTo(ImGuiTestRef ref);
|
||||
void NavActivate(); // Activate current selected item: activate button, tweak sliders/drags. Equivalent of pressing Space on keyboard, ImGuiKey_GamepadFaceUp on a gamepad.
|
||||
void NavInput(); // Input into select item: input sliders/drags. Equivalent of pressing Enter on keyboard, ImGuiKey_GamepadFaceDown on a gamepad.
|
||||
|
||||
// Scrolling
|
||||
void ScrollTo(ImGuiTestRef ref, ImGuiAxis axis, float scroll_v, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void ScrollToX(ImGuiTestRef ref, float scroll_x) { ScrollTo(ref, ImGuiAxis_X, scroll_x); }
|
||||
void ScrollToY(ImGuiTestRef ref, float scroll_y) { ScrollTo(ref, ImGuiAxis_Y, scroll_y); }
|
||||
void ScrollToTop(ImGuiTestRef ref);
|
||||
void ScrollToBottom(ImGuiTestRef ref);
|
||||
void ScrollToPos(ImGuiTestRef window_ref, float pos_v, ImGuiAxis axis, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void ScrollToPosX(ImGuiTestRef window_ref, float pos_x);
|
||||
void ScrollToPosY(ImGuiTestRef window_ref, float pos_y);
|
||||
void ScrollToItem(ImGuiTestRef ref, ImGuiAxis axis, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
void ScrollToItemX(ImGuiTestRef ref);
|
||||
void ScrollToItemY(ImGuiTestRef ref);
|
||||
void ScrollToTabItem(ImGuiTabBar* tab_bar, ImGuiID tab_id);
|
||||
|
||||
// Low-level queries
|
||||
// - ItemInfo queries never returns nullptr! Instead they return an empty instance (info->IsEmpty(), info->ID == 0) and set contexted as errored.
|
||||
// - You can use ImGuiTestOpFlags_NoError to do a query without marking context as errored. This is what ItemExists() does.
|
||||
ImGuiTestItemInfo ItemInfo(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
ImGuiTestItemInfo ItemInfoOpenFullPath(ImGuiTestRef ref, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
ImGuiID ItemInfoHandleWildcardSearch(const char* wildcard_prefix_start, const char* wildcard_prefix_end, const char* wildcard_suffix_start);
|
||||
ImGuiTestItemInfo ItemInfoNull() { return ImGuiTestItemInfo(); }
|
||||
void GatherItems(ImGuiTestItemList* out_list, ImGuiTestRef parent, int depth = -1);
|
||||
|
||||
// Item/Widgets manipulation
|
||||
void ItemAction(ImGuiTestAction action, ImGuiTestRef ref, ImGuiTestOpFlags flags = 0, void* action_arg = nullptr);
|
||||
void ItemClick(ImGuiTestRef ref, ImGuiMouseButton button = 0, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Click, ref, flags, (void*)(size_t)button); }
|
||||
void ItemDoubleClick(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_DoubleClick, ref, flags); }
|
||||
void ItemCheck(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Check, ref, flags); }
|
||||
void ItemUncheck(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Uncheck, ref, flags); }
|
||||
void ItemOpen(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Open, ref, flags); }
|
||||
void ItemClose(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Close, ref, flags); }
|
||||
void ItemInput(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_Input, ref, flags); }
|
||||
void ItemNavActivate(ImGuiTestRef ref, ImGuiTestOpFlags flags = 0) { ItemAction(ImGuiTestAction_NavActivate, ref, flags); }
|
||||
|
||||
// Item/Widgets: Batch actions over an entire scope
|
||||
void ItemActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent, const ImGuiTestActionFilter* filter = nullptr);
|
||||
void ItemOpenAll(ImGuiTestRef ref_parent, int depth = -1, int passes = -1);
|
||||
void ItemCloseAll(ImGuiTestRef ref_parent, int depth = -1, int passes = -1);
|
||||
|
||||
// Item/Widgets: Helpers to easily set a value
|
||||
void ItemInputValue(ImGuiTestRef ref, int v);
|
||||
void ItemInputValue(ImGuiTestRef ref, float f);
|
||||
void ItemInputValue(ImGuiTestRef ref, const char* str);
|
||||
|
||||
// Item/Widgets: Helpers to easily read a value by selecting Slider/Drag/Input text, copying it and parsing it.
|
||||
// - This requires the item to be selectable (we will later provide helpers that works in more general manner)
|
||||
// - (this temporarily use the internal test clipboard, but original clipboard value is restored afterwards)
|
||||
// See https://github.com/ocornut/imgui_test_engine/wiki/Automation-API#accessing-your-data
|
||||
int ItemReadAsInt(ImGuiTestRef ref);
|
||||
float ItemReadAsFloat(ImGuiTestRef ref);
|
||||
bool ItemReadAsScalar(ImGuiTestRef ref, ImGuiDataType data_type, void* out_data, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None);
|
||||
const char* ItemReadAsString(ImGuiTestRef ref);
|
||||
size_t ItemReadAsString(ImGuiTestRef ref, char* out_buf, size_t out_buf_size);
|
||||
|
||||
// Item/Widgets: Status query
|
||||
bool ItemExists(ImGuiTestRef ref);
|
||||
bool ItemIsChecked(ImGuiTestRef ref);
|
||||
bool ItemIsOpened(ImGuiTestRef ref);
|
||||
bool ItemIsVisible(ImGuiTestRef ref);
|
||||
void ItemVerifyCheckedIfAlive(ImGuiTestRef ref, bool checked);
|
||||
|
||||
// Item/Widgets: Drag and Mouse operations
|
||||
void ItemHold(ImGuiTestRef ref, float time);
|
||||
void ItemHoldForFrames(ImGuiTestRef ref, int frames);
|
||||
void ItemDragOverAndHold(ImGuiTestRef ref_src, ImGuiTestRef ref_dst);
|
||||
void ItemDragAndDrop(ImGuiTestRef ref_src, ImGuiTestRef ref_dst, ImGuiMouseButton button = 0);
|
||||
void ItemDragWithDelta(ImGuiTestRef ref_src, ImVec2 pos_delta);
|
||||
|
||||
// Helpers for Tab Bars widgets
|
||||
void TabClose(ImGuiTestRef ref);
|
||||
bool TabBarCompareOrder(ImGuiTabBar* tab_bar, const char** tab_order);
|
||||
|
||||
// Helpers for MenuBar and Menus widgets
|
||||
// - e.g. MenuCheck("File/Options/Enable grid"); // Access menu in current ref window.
|
||||
// - e.g. MenuClick("//Window/File/Quit"); // Access menu in another window.
|
||||
void MenuAction(ImGuiTestAction action, ImGuiTestRef ref);
|
||||
void MenuActionAll(ImGuiTestAction action, ImGuiTestRef ref_parent);
|
||||
void MenuClick(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Click, ref); }
|
||||
void MenuCheck(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Check, ref); }
|
||||
void MenuUncheck(ImGuiTestRef ref) { MenuAction(ImGuiTestAction_Uncheck, ref); }
|
||||
void MenuCheckAll(ImGuiTestRef ref_parent) { MenuActionAll(ImGuiTestAction_Check, ref_parent); }
|
||||
void MenuUncheckAll(ImGuiTestRef ref_parent) { MenuActionAll(ImGuiTestAction_Uncheck, ref_parent); }
|
||||
|
||||
// Helpers for Combo Boxes
|
||||
void ComboClick(ImGuiTestRef ref);
|
||||
void ComboClickAll(ImGuiTestRef ref);
|
||||
|
||||
// Helpers for Tables
|
||||
void TableOpenContextMenu(ImGuiTestRef ref, int column_n = -1);
|
||||
ImGuiSortDirection TableClickHeader(ImGuiTestRef ref, const char* label, ImGuiKeyChord key_mods = 0);
|
||||
void TableSetColumnEnabled(ImGuiTestRef ref, int column_n, bool enabled);
|
||||
void TableSetColumnEnabled(ImGuiTestRef ref, const char* label, bool enabled);
|
||||
void TableResizeColumn(ImGuiTestRef ref, int column_n, float width);
|
||||
const ImGuiTableSortSpecs* TableGetSortSpecs(ImGuiTestRef ref);
|
||||
|
||||
// Viewports
|
||||
// IMPORTANT: Those function may alter Platform state (unless using the "Mock Viewport" backend). Use carefully.
|
||||
// Those are mostly useful to simulate OS actions and testing of viewport-specific features, may not be useful to most users.
|
||||
#ifdef IMGUI_HAS_VIEWPORT
|
||||
void ViewportPlatform_SetWindowPos(ImGuiViewport* viewport, const ImVec2& pos);
|
||||
void ViewportPlatform_SetWindowSize(ImGuiViewport* viewport, const ImVec2& size);
|
||||
void ViewportPlatform_SetWindowFocus(ImGuiViewport* viewport);
|
||||
void ViewportPlatform_CloseWindow(ImGuiViewport* viewport);
|
||||
#endif
|
||||
|
||||
// Docking
|
||||
#ifdef IMGUI_HAS_DOCK
|
||||
void DockClear(const char* window_name, ...);
|
||||
void DockInto(ImGuiTestRef src_id, ImGuiTestRef dst_id, ImGuiDir split_dir = ImGuiDir_None, bool is_outer_docking = false, ImGuiTestOpFlags flags = 0);
|
||||
void UndockNode(ImGuiID dock_id);
|
||||
void UndockWindow(const char* window_name);
|
||||
bool WindowIsUndockedOrStandalone(ImGuiWindow* window);
|
||||
bool DockIdIsUndockedOrStandalone(ImGuiID dock_id);
|
||||
void DockNodeHideTabBar(ImGuiDockNode* node, bool hidden);
|
||||
//ImVec2 GetDockNodeTitlebarPos(ImGuiDockNode* node);
|
||||
#endif
|
||||
|
||||
// Performances Measurement (use along with Dear ImGui Perf Tool)
|
||||
void PerfCalcRef();
|
||||
void PerfCapture(const char* category = nullptr, const char* test_name = nullptr, const char* csv_file = nullptr);
|
||||
|
||||
// Obsolete functions
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
// Obsoleted 2025/01/20
|
||||
bool ItemSelectAndReadValue(ImGuiTestRef ref, ImGuiDataType data_type, void* out_data, ImGuiTestOpFlags flags = ImGuiTestOpFlags_None) { return ItemReadAsScalar(ref, data_type, out_data, flags); }
|
||||
void ItemSelectAndReadValue(ImGuiTestRef ref, int* out_v) { int v = ItemReadAsInt(ref); *out_v = v; }
|
||||
void ItemSelectAndReadValue(ImGuiTestRef ref, float* out_v) { float v = ItemReadAsFloat(ref); *out_v = v; }
|
||||
// Obsoleted 2024/05/21
|
||||
void YieldUntil(int frame_count) { while (FrameCount < frame_count) { Yield(); } }
|
||||
// Obsoleted 2022/10/11
|
||||
ImGuiID GetIDByInt(int n); // Prefer using "$$123"
|
||||
ImGuiID GetIDByInt(int n, ImGuiTestRef seed_ref);
|
||||
ImGuiID GetIDByPtr(void* p); // Prefer using "$$(ptr)0xFFFFFFFF"
|
||||
ImGuiID GetIDByPtr(void* p, ImGuiTestRef seed_ref);
|
||||
// Obsoleted 2022/09/26
|
||||
//void KeyModDown(ImGuiModFlags mods) { KeyDown(mods); }
|
||||
//void KeyModUp(ImGuiModFlags mods) { KeyUp(mods); }
|
||||
//void KeyModPress(ImGuiModFlags mods) { KeyPress(mods); }
|
||||
#endif
|
||||
|
||||
// [Internal]
|
||||
void _ScrollVerifyScrollMax(ImGuiTestRef ref);
|
||||
void _MakeAimingSpaceOverPos(ImGuiViewport* viewport, ImGuiWindow* over_window, const ImVec2& over_pos); // Move windows covering 'window' at pos.
|
||||
void _ForeignWindowsHideOverPos(const ImVec2& pos, ImGuiWindow** ignore_list); // FIXME: Aim to remove this system...
|
||||
void _ForeignWindowsUnhideAll(); // FIXME: Aim to remove this system...
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] Debugging macros (IM_SUSPEND_TESTFUNC)
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Debug: Temporarily suspend TestFunc to let user interactively inspect the GUI state (user will need to press the "Continue" button to resume TestFunc execution)
|
||||
#define IM_SUSPEND_TESTFUNC() do { if (ctx->SuspendTestFunc(__FILE__, __LINE__)) return; } while (0)
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] Testing/Checking macros: IM_CHECK(), IM_ERRORF() etc.
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Helpers used by IM_CHECK_OP() macros.
|
||||
// ImGuiTestEngine_GetTempStringBuilder() returns a shared instance of ImGuiTextBuffer to recycle memory allocations
|
||||
template<typename T> void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, T v) { buf->append("???"); IM_UNUSED(v); } // FIXME-TESTS: Could improve with some template magic
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, const char* v) { if (v) buf->appendf("\"%s\"", v); else buf->append("nullptr"); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, bool v) { buf->append(v ? "true" : "false"); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS8 v) { buf->appendf("%d", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU8 v) { buf->appendf("%u", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS16 v) { buf->appendf("%hd", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU16 v) { buf->appendf("%hu", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS32 v) { buf->appendf("%d", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU32 v) { buf->appendf("0x%08X", v); } // Assuming ImGuiID
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImS64 v) { buf->appendf("%lld", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImU64 v) { buf->appendf("%llu", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, float v) { buf->appendf("%.3f", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, double v) { buf->appendf("%f", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImVec2 v) { buf->appendf("(%.3f, %.3f)", v.x, v.y); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, const void* v) { buf->appendf("%p", v); }
|
||||
template<> inline void ImGuiTestEngineUtil_appendf_auto(ImGuiTextBuffer* buf, ImGuiWindow* v){ if (v) buf->appendf("\"%s\"", v->Name); else buf->append("nullptr"); }
|
||||
|
||||
// We embed every macro in a do {} while(0) statement as a trick to allow using them as regular single statement, e.g. if (XXX) IM_CHECK(A); else IM_CHECK(B)
|
||||
// We leave the IM_DEBUG_BREAK() outside of the check function to step out faster when using a debugger. It also has the benefit of being lighter than an IM_ASSERT().
|
||||
#define IM_CHECK(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return; } while (0)
|
||||
#define IM_CHECK_NO_RET(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } } while (0)
|
||||
#define IM_CHECK_SILENT(_EXPR) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_SilentSuccess, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return; } while (0)
|
||||
#define IM_CHECK_RETV(_EXPR,_RETV) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return _RETV; } while (0)
|
||||
#define IM_CHECK_SILENT_RETV(_EXPR,_RETV) do { bool res = (bool)(_EXPR); if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_SilentSuccess, res, #_EXPR)) { IM_DEBUG_BREAK(); } if (!res) return _RETV; } while (0)
|
||||
#define IM_ERRORF(_FMT,...) do { if (ImGuiTestEngine_Error(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, _FMT, __VA_ARGS__)) { IM_DEBUG_BREAK(); } } while (0)
|
||||
#define IM_ERRORF_NOHDR(_FMT,...) do { if (ImGuiTestEngine_Error(nullptr, nullptr, 0, ImGuiTestCheckFlags_None, _FMT, __VA_ARGS__)) { IM_DEBUG_BREAK(); } } while (0)
|
||||
|
||||
// Those macros allow us to print out the values of both LHS and RHS expressions involved in a check.
|
||||
#define IM_CHECK_OP(_LHS, _RHS, _OP, _RETURN) \
|
||||
do \
|
||||
{ \
|
||||
auto __lhs = _LHS; /* Cache to avoid side effects */ \
|
||||
auto __rhs = _RHS; \
|
||||
bool __res = __lhs _OP __rhs; \
|
||||
ImGuiTextBuffer* expr_buf = ImGuiTestEngine_GetTempStringBuilder(); \
|
||||
expr_buf->append(#_LHS " ["); \
|
||||
ImGuiTestEngineUtil_appendf_auto(expr_buf, __lhs); \
|
||||
expr_buf->append("] " #_OP " " #_RHS " ["); \
|
||||
ImGuiTestEngineUtil_appendf_auto(expr_buf, __rhs); \
|
||||
expr_buf->append("]"); \
|
||||
if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, __res, expr_buf->c_str())) \
|
||||
IM_ASSERT(__res); \
|
||||
if (_RETURN && !__res) \
|
||||
return; \
|
||||
} while (0)
|
||||
|
||||
#define IM_CHECK_STR_OP(_LHS, _RHS, _OP, _RETURN, _FLAGS) \
|
||||
do \
|
||||
{ \
|
||||
bool __res; \
|
||||
if (ImGuiTestEngine_CheckOpStr(__FILE__, __func__, __LINE__, _FLAGS, #_OP, #_LHS, _LHS, #_RHS, _RHS, &__res)) \
|
||||
IM_ASSERT(__res); \
|
||||
if (_RETURN && !__res) \
|
||||
return; \
|
||||
} while (0)
|
||||
|
||||
#define IM_CHECK_FLOAT_OP_CUSTOM(_LHS, _RHS, _DISPLAY_OP, _ACTUAL_COMPARE, _RETURN) \
|
||||
do \
|
||||
{ \
|
||||
auto __lhs = _LHS; /* Cache to avoid side effects */ \
|
||||
auto __rhs = _RHS; \
|
||||
bool __res = _ACTUAL_COMPARE; \
|
||||
ImGuiTextBuffer* expr_buf = ImGuiTestEngine_GetTempStringBuilder(); \
|
||||
expr_buf->append(#_LHS " ["); \
|
||||
ImGuiTestEngineUtil_appendf_auto(expr_buf, __lhs); \
|
||||
expr_buf->append("] " #_DISPLAY_OP " " #_RHS " ["); \
|
||||
ImGuiTestEngineUtil_appendf_auto(expr_buf, __rhs); \
|
||||
expr_buf->append("] (using epsilon)"); \
|
||||
if (ImGuiTestEngine_Check(__FILE__, __func__, __LINE__, ImGuiTestCheckFlags_None, __res, expr_buf->c_str())) \
|
||||
IM_ASSERT(__res); \
|
||||
if (_RETURN && !__res) \
|
||||
return; \
|
||||
} while(0)
|
||||
|
||||
|
||||
// Scalar compares
|
||||
#define IM_CHECK_EQ(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, ==, true) // Equal
|
||||
#define IM_CHECK_NE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, !=, true) // Not Equal
|
||||
#define IM_CHECK_LT(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, < , true) // Less Than
|
||||
#define IM_CHECK_LE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, <=, true) // Less or Equal
|
||||
#define IM_CHECK_GT(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, > , true) // Greater Than
|
||||
#define IM_CHECK_GE(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, >=, true) // Greater or Equal
|
||||
|
||||
// Scalar compares, without return on failure
|
||||
#define IM_CHECK_EQ_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, ==, false) // Equal
|
||||
#define IM_CHECK_NE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, !=, false) // Not Equal
|
||||
#define IM_CHECK_LT_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, < , false) // Less Than
|
||||
#define IM_CHECK_LE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, <=, false) // Less or Equal
|
||||
#define IM_CHECK_GT_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, > , false) // Greater Than
|
||||
#define IM_CHECK_GE_NO_RET(_LHS, _RHS) IM_CHECK_OP(_LHS, _RHS, >=, false) // Greater or Equal
|
||||
|
||||
// String compares
|
||||
#define IM_CHECK_STR_EQ(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, true, ImGuiTestCheckFlags_None)
|
||||
#define IM_CHECK_STR_NE(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, !=, true, ImGuiTestCheckFlags_None)
|
||||
#define IM_CHECK_STR_EQ_NO_RET(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, false, ImGuiTestCheckFlags_None)
|
||||
#define IM_CHECK_STR_NE_NO_RET(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, !=, false, ImGuiTestCheckFlags_None)
|
||||
#define IM_CHECK_STR_EQ_SILENT(_LHS, _RHS) IM_CHECK_STR_OP(_LHS, _RHS, ==, true, ImGuiTestCheckFlags_SilentSuccess)
|
||||
|
||||
// Floating point compares using an epsilon
|
||||
#define IM_CHECK_FLOAT_EQ(_LHS, _RHS) IM_CHECK_FLOAT_OP_CUSTOM(_LHS, _RHS, ==, ((__lhs-__rhs) <= FLT_EPSILON && (__lhs-__rhs) >= -FLT_EPSILON), true) // Float Equal (w/ epsilon)
|
||||
#define IM_CHECK_FLOAT_NE(_LHS, _RHS) IM_CHECK_FLOAT_OP_CUSTOM(_LHS, _RHS, !=, ((__lhs-__rhs) > FLT_EPSILON || (__lhs-__rhs) < -FLT_EPSILON), true) // Float Not Equal (w/ epsilon)
|
||||
#define IM_CHECK_FLOAT_NEAR_EQ(_LHS, _RHS, _EPS) IM_CHECK_FLOAT_OP_CUSTOM(_LHS, _RHS, ==, ((__lhs-__rhs) <= _EPS && (__lhs-__rhs) >= -_EPS), true) // Float Equal (w/ custom epsilon)
|
||||
#define IM_CHECK_DOUBLE_EQ(_LHS, _RHS) IM_CHECK_FLOAT_OP_CUSTOM(_LHS, _RHS, ==, ((__lhs-__rhs) <= DBL_EPSILON && (__lhs-__rhs) >= -DBL_EPSILON), true) // Double Equal (w/ epsilon)
|
||||
#define IM_CHECK_DOUBLE_NE(_LHS, _RHS) IM_CHECK_FLOAT_OP_CUSTOM(_LHS, _RHS, !=, ((__lhs-__rhs) > DBL_EPSILON || (__lhs-__rhs) < -DBL_EPSILON), true) // Double Not Equal (w/ epsilon)
|
||||
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
// Obsoleted in favor of simpler names
|
||||
#define IM_CHECK_FLOAT_EQ_EPS IM_CHECK_FLOAT_EQ
|
||||
#define IM_CHECK_FLOAT_NE_EPS IM_CHECK_FLOAT_NE
|
||||
|
||||
// Obsoleted because ambiguous/confusing
|
||||
#define IM_CHECK_FLOAT_NEAR(_LHS,_RHS,_EPS) IM_CHECK_LE(ImFabs(_LHS - (_RHS)), _EPS) // Float Near a value (custom epsilon)
|
||||
#define IM_CHECK_FLOAT_NEAR_NO_RET(_LHS,_RHS,_EPS) IM_CHECK_LE_NO_RET(ImFabs(_LHS - (_RHS)), _EPS) // Float Near a value (custom epsilon)
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
// dear imgui test engine
|
||||
// (coroutine interface + optional implementation)
|
||||
// Read https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#include "imgui_te_coroutine.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Coroutine implementation using std::thread
|
||||
// This implements a coroutine using std::thread, with a helper thread for each coroutine (with serialised execution, so threads never actually run concurrently)
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
|
||||
|
||||
#include "imgui_te_utils.h"
|
||||
#include "thirdparty/Str/Str.h"
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
struct Coroutine_ImplStdThreadData
|
||||
{
|
||||
std::thread* Thread; // The thread this coroutine is using
|
||||
std::condition_variable StateChange; // Condition variable notified when the coroutine state changes
|
||||
std::mutex StateMutex; // Mutex to protect coroutine state
|
||||
bool CoroutineRunning; // Is the coroutine currently running? Lock StateMutex before access and notify StateChange on change
|
||||
bool CoroutineTerminated; // Has the coroutine terminated? Lock StateMutex before access and notify StateChange on change
|
||||
Str64 Name; // The name of this coroutine
|
||||
};
|
||||
|
||||
// The coroutine executing on the current thread (if it is a coroutine thread)
|
||||
static thread_local Coroutine_ImplStdThreadData* GThreadCoroutine = nullptr;
|
||||
|
||||
// The main function for a coroutine thread
|
||||
static void CoroutineThreadMain(Coroutine_ImplStdThreadData* data, ImGuiTestCoroutineMainFunc func, void* ctx)
|
||||
{
|
||||
// Set our thread name
|
||||
ImThreadSetCurrentThreadDescription(data->Name.c_str());
|
||||
|
||||
// Set the thread coroutine
|
||||
GThreadCoroutine = data;
|
||||
|
||||
// Wait for initial Run()
|
||||
while (1)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(data->StateMutex);
|
||||
if (data->CoroutineRunning)
|
||||
break;
|
||||
data->StateChange.wait(lock);
|
||||
}
|
||||
|
||||
// Run user code, which will then call Yield() when it wants to yield control
|
||||
func(ctx);
|
||||
|
||||
// Mark as terminated
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data->StateMutex);
|
||||
|
||||
data->CoroutineTerminated = true;
|
||||
data->CoroutineRunning = false;
|
||||
data->StateChange.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ImGuiTestCoroutineHandle Coroutine_ImplStdThread_Create(ImGuiTestCoroutineMainFunc* func, const char* name, void* ctx)
|
||||
{
|
||||
Coroutine_ImplStdThreadData* data = new Coroutine_ImplStdThreadData();
|
||||
|
||||
data->Name = name;
|
||||
data->CoroutineRunning = false;
|
||||
data->CoroutineTerminated = false;
|
||||
data->Thread = new std::thread(CoroutineThreadMain, data, func, ctx);
|
||||
|
||||
return (ImGuiTestCoroutineHandle)data;
|
||||
}
|
||||
|
||||
static void Coroutine_ImplStdThread_Destroy(ImGuiTestCoroutineHandle handle)
|
||||
{
|
||||
Coroutine_ImplStdThreadData* data = (Coroutine_ImplStdThreadData*)handle;
|
||||
|
||||
IM_ASSERT(data->CoroutineTerminated); // The coroutine needs to run to termination otherwise it may leak all sorts of things and this will deadlock
|
||||
if (data->Thread)
|
||||
{
|
||||
data->Thread->join();
|
||||
|
||||
delete data->Thread;
|
||||
data->Thread = nullptr;
|
||||
}
|
||||
|
||||
delete data;
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
// Run the coroutine until the next call to Yield(). Returns TRUE if the coroutine yielded, FALSE if it terminated (or had previously terminated)
|
||||
static bool Coroutine_ImplStdThread_Run(ImGuiTestCoroutineHandle handle)
|
||||
{
|
||||
Coroutine_ImplStdThreadData* data = (Coroutine_ImplStdThreadData*)handle;
|
||||
|
||||
// Wake up coroutine thread
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data->StateMutex);
|
||||
|
||||
if (data->CoroutineTerminated)
|
||||
return false; // Coroutine has already finished
|
||||
|
||||
data->CoroutineRunning = true;
|
||||
data->StateChange.notify_all();
|
||||
}
|
||||
|
||||
// Wait for coroutine to stop
|
||||
while (1)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(data->StateMutex);
|
||||
if (!data->CoroutineRunning)
|
||||
{
|
||||
// Breakpoint here to catch the point where we return from the coroutine
|
||||
if (data->CoroutineTerminated)
|
||||
return false; // Coroutine finished
|
||||
break;
|
||||
}
|
||||
data->StateChange.wait(lock);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Yield the current coroutine (can only be called from a coroutine)
|
||||
static void Coroutine_ImplStdThread_Yield()
|
||||
{
|
||||
IM_ASSERT(GThreadCoroutine); // This can only be called from a coroutine thread
|
||||
|
||||
Coroutine_ImplStdThreadData* data = GThreadCoroutine;
|
||||
|
||||
// Flag that we are not running any more
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(data->StateMutex);
|
||||
data->CoroutineRunning = false;
|
||||
data->StateChange.notify_all();
|
||||
}
|
||||
|
||||
// At this point the thread that called RunCoroutine() will leave the "Wait for coroutine to stop" loop
|
||||
// Wait until we get started up again
|
||||
while (1)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(data->StateMutex);
|
||||
if (data->CoroutineRunning)
|
||||
break; // Breakpoint here if you want to catch the point where execution of this coroutine resumes
|
||||
data->StateChange.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiTestCoroutineInterface* Coroutine_ImplStdThread_GetInterface()
|
||||
{
|
||||
static ImGuiTestCoroutineInterface intf;
|
||||
intf.CreateFunc = Coroutine_ImplStdThread_Create;
|
||||
intf.DestroyFunc = Coroutine_ImplStdThread_Destroy;
|
||||
intf.RunFunc = Coroutine_ImplStdThread_Run;
|
||||
intf.YieldFunc = Coroutine_ImplStdThread_Yield;
|
||||
return &intf;
|
||||
}
|
||||
|
||||
#endif // #if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
|
||||
@@ -0,0 +1,59 @@
|
||||
// dear imgui test engine
|
||||
// (coroutine interface + optional implementation)
|
||||
// Read https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef IMGUI_VERSION
|
||||
#include "imgui.h"
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Coroutine abstraction
|
||||
//------------------------------------------------------------------------
|
||||
// Coroutines should be used like this:
|
||||
// ImGuiTestCoroutineHandle handle = CoroutineCreate(<func>, <name>, <ctx>); // name being for debugging, and ctx being an arbitrary user context pointer
|
||||
// while (CoroutineRun(handle)) { <do other stuff };
|
||||
// CoroutineDestroy(handle);
|
||||
// The coroutine code itself should call CoroutineYieldFunc() whenever it wants to yield control back to the main thread.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
// An arbitrary handle used internally to represent coroutines (nullptr indicates no handle)
|
||||
typedef void* ImGuiTestCoroutineHandle;
|
||||
|
||||
// A coroutine main function
|
||||
typedef void (ImGuiTestCoroutineMainFunc)(void* data);
|
||||
|
||||
// Coroutine support interface
|
||||
// Your app needs to return and implement this.
|
||||
// You can '#define IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL 1' in your imconfig file to use a default implementation using std::thread
|
||||
// Documentation: https://github.com/ocornut/imgui_test_engine/wiki/Setting-Up
|
||||
struct IMGUI_API ImGuiTestCoroutineInterface
|
||||
{
|
||||
// Create a new coroutine
|
||||
ImGuiTestCoroutineHandle (*CreateFunc)(ImGuiTestCoroutineMainFunc* func, const char* name, void* data);
|
||||
|
||||
// Destroy a coroutine (which must have completed first)
|
||||
void (*DestroyFunc)(ImGuiTestCoroutineHandle handle);
|
||||
|
||||
// Run a coroutine until it yields or finishes, returning false if finished
|
||||
bool (*RunFunc)(ImGuiTestCoroutineHandle handle);
|
||||
|
||||
// Yield from a coroutine back to the caller, preserving coroutine state
|
||||
void (*YieldFunc)();
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Coroutine implementation using std::thread
|
||||
// The "coroutine" thread and user's main thread will always block on each other (both threads will NEVER run in parallel)
|
||||
// It is just an implementation convenience that we provide an implementation using std::thread as it is widely available/standard.
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
#if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
|
||||
|
||||
IMGUI_API ImGuiTestCoroutineInterface* Coroutine_ImplStdThread_GetInterface();
|
||||
|
||||
#endif // #if IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
|
||||
+2720
File diff suppressed because it is too large
Load Diff
+491
@@ -0,0 +1,491 @@
|
||||
// dear imgui test engine
|
||||
// (core)
|
||||
// This is the interface that your initial setup (app init, main loop) will mostly be using.
|
||||
// Actual tests will mostly use the interface of imgui_te_context.h
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h" // ImPool<>, ImRect, ImGuiItemStatusFlags, ImFormatString
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
|
||||
#endif
|
||||
#ifdef Status // X11 headers
|
||||
#undef Status
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Function Pointers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION
|
||||
#include <functional>
|
||||
#define ImFuncPtr(FUNC_TYPE) std::function<FUNC_TYPE>
|
||||
#else
|
||||
#define ImFuncPtr(FUNC_TYPE) FUNC_TYPE*
|
||||
#endif
|
||||
|
||||
#include "imgui_capture_tool.h" // ImGuiScreenCaptureFunc
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Forward Declarations
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
struct ImGuiTest; // Data for a test registered with IM_REGISTER_TEST()
|
||||
struct ImGuiTestContext; // Context while a test is running
|
||||
struct ImGuiTestCoroutineInterface; // Interface to expose coroutine functions (imgui_te_coroutine provides a default implementation for C++11 using std::thread, but you may use your own)
|
||||
struct ImGuiTestEngine; // Test engine instance
|
||||
struct ImGuiTestEngineIO; // Test engine public I/O
|
||||
struct ImGuiTestEngineResultSummary;// Output of ImGuiTestEngine_GetResultSummary()
|
||||
struct ImGuiTestItemInfo; // Info queried from item (id, geometry, status flags, debug label)
|
||||
struct ImGuiTestItemList; // A list of items
|
||||
struct ImGuiTestInputs; // Simulated user inputs (will be fed into ImGuiIO by the test engine)
|
||||
struct ImGuiTestRunTask; // A queued test (test + runflags)
|
||||
|
||||
typedef int ImGuiTestFlags; // Flags: See ImGuiTestFlags_
|
||||
typedef int ImGuiTestCheckFlags; // Flags: See ImGuiTestCheckFlags_
|
||||
typedef int ImGuiTestLogFlags; // Flags: See ImGuiTestLogFlags_
|
||||
typedef int ImGuiTestRunFlags; // Flags: See ImGuiTestRunFlags_
|
||||
|
||||
enum ImGuiTestActiveFunc : int;
|
||||
enum ImGuiTestGroup : int;
|
||||
enum ImGuiTestRunSpeed : int;
|
||||
enum ImGuiTestStatus : int;
|
||||
enum ImGuiTestVerboseLevel : int;
|
||||
enum ImGuiTestEngineExportFormat : int;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Types
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Stored in ImGuiTestContext: where we are currently running GuiFunc or TestFunc
|
||||
enum ImGuiTestActiveFunc : int
|
||||
{
|
||||
ImGuiTestActiveFunc_None,
|
||||
ImGuiTestActiveFunc_GuiFunc, // == GuiFunc() handler
|
||||
ImGuiTestActiveFunc_TestFunc, // == TestFunc() handler
|
||||
ImGuiTestActiveFunc_TeardownFunc, // == TeardownFunc() handler
|
||||
};
|
||||
|
||||
enum ImGuiTestRunSpeed : int
|
||||
{
|
||||
ImGuiTestRunSpeed_Fast = 0, // Run tests as fast as possible (teleport mouse, skip delays, etc.)
|
||||
ImGuiTestRunSpeed_Normal = 1, // Run tests at human watchable speed (for debugging)
|
||||
ImGuiTestRunSpeed_Cinematic = 2, // Run tests with pauses between actions (for e.g. tutorials)
|
||||
ImGuiTestRunSpeed_COUNT
|
||||
};
|
||||
|
||||
enum ImGuiTestVerboseLevel : int
|
||||
{
|
||||
ImGuiTestVerboseLevel_Silent = 0, // -v0
|
||||
ImGuiTestVerboseLevel_Error = 1, // -v1
|
||||
ImGuiTestVerboseLevel_Warning = 2, // -v2
|
||||
ImGuiTestVerboseLevel_Info = 3, // -v3
|
||||
ImGuiTestVerboseLevel_Debug = 4, // -v4
|
||||
ImGuiTestVerboseLevel_Trace = 5,
|
||||
ImGuiTestVerboseLevel_COUNT
|
||||
};
|
||||
|
||||
// Test status (stored in ImGuiTest)
|
||||
enum ImGuiTestStatus : int
|
||||
{
|
||||
ImGuiTestStatus_Unknown = 0,
|
||||
ImGuiTestStatus_Success = 1,
|
||||
ImGuiTestStatus_Queued = 2,
|
||||
ImGuiTestStatus_Running = 3,
|
||||
ImGuiTestStatus_Error = 4,
|
||||
ImGuiTestStatus_Suspended = 5,
|
||||
ImGuiTestStatus_COUNT
|
||||
};
|
||||
|
||||
// Test group: this is mostly used to categorize tests in our testing UI. (Stored in ImGuiTest)
|
||||
enum ImGuiTestGroup : int
|
||||
{
|
||||
ImGuiTestGroup_Unknown = -1,
|
||||
ImGuiTestGroup_Tests = 0,
|
||||
ImGuiTestGroup_Perfs = 1,
|
||||
ImGuiTestGroup_COUNT
|
||||
};
|
||||
|
||||
// Flags (stored in ImGuiTest)
|
||||
enum ImGuiTestFlags_
|
||||
{
|
||||
ImGuiTestFlags_None = 0,
|
||||
ImGuiTestFlags_NoGuiWarmUp = 1 << 0, // Disable running the GUI func for 2 frames before starting test code. For tests which absolutely need to start before GuiFunc.
|
||||
ImGuiTestFlags_NoAutoFinish = 1 << 1, // By default, tests with no TestFunc (only a GuiFunc) will end after warmup. Setting this require test to call ctx->Finish().
|
||||
ImGuiTestFlags_NoRecoveryWarnings = 1 << 2 // Error/recovery warnings (missing End/Pop calls etc.) will be displayed as normal debug entries, for tests which may rely on those.
|
||||
//ImGuiTestFlags_RequireViewports = 1 << 10
|
||||
};
|
||||
|
||||
// Flags for IM_CHECK* macros.
|
||||
enum ImGuiTestCheckFlags_
|
||||
{
|
||||
ImGuiTestCheckFlags_None = 0,
|
||||
ImGuiTestCheckFlags_SilentSuccess = 1 << 0
|
||||
};
|
||||
|
||||
// Flags for ImGuiTestContext::Log* functions.
|
||||
enum ImGuiTestLogFlags_
|
||||
{
|
||||
ImGuiTestLogFlags_None = 0,
|
||||
ImGuiTestLogFlags_NoHeader = 1 << 0 // Do not display frame count and depth padding
|
||||
};
|
||||
|
||||
enum ImGuiTestRunFlags_
|
||||
{
|
||||
ImGuiTestRunFlags_None = 0,
|
||||
ImGuiTestRunFlags_GuiFuncDisable = 1 << 0, // Used internally to temporarily disable the GUI func (at the end of a test, etc)
|
||||
ImGuiTestRunFlags_GuiFuncOnly = 1 << 1, // Set when user selects "Run GUI func"
|
||||
ImGuiTestRunFlags_NoSuccessMsg = 1 << 2,
|
||||
ImGuiTestRunFlags_EnableRawInputs = 1 << 3, // Disable input submission to let test submission raw input event (in order to test e.g. IO queue)
|
||||
ImGuiTestRunFlags_RunFromGui = 1 << 4, // Test ran manually from GUI, will disable watchdog.
|
||||
ImGuiTestRunFlags_RunFromCommandLine= 1 << 5, // Test queued from command-line.
|
||||
|
||||
// Flags for ImGuiTestContext::RunChildTest()
|
||||
ImGuiTestRunFlags_NoError = 1 << 10,
|
||||
ImGuiTestRunFlags_ShareVars = 1 << 11, // Share generic vars and custom vars between child and parent tests (custom vars need to be same type)
|
||||
ImGuiTestRunFlags_ShareTestContext = 1 << 12, // Share ImGuiTestContext instead of creating a new one (unsure what purpose this may be useful for yet)
|
||||
// TODO: Add GuiFunc options
|
||||
};
|
||||
|
||||
struct ImGuiTestEngineResultSummary
|
||||
{
|
||||
int CountTested = 0; // Number of tests executed
|
||||
int CountSuccess = 0; // Number of tests succeeded
|
||||
int CountInQueue = 0; // Number of tests remaining in queue (e.g. aborted, crashed)
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Functions
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Legacy version support
|
||||
#if IMGUI_VERSION_NUM < 19256 && !defined(IM_COUNTOF)
|
||||
#define IM_COUNTOF IM_ARRAYSIZE
|
||||
#endif
|
||||
|
||||
// Hooks for core imgui/ library (generally called via macros)
|
||||
extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, ImGuiID id, const ImRect& bb, const ImGuiLastItemData* item_data);
|
||||
#if IMGUI_VERSION_NUM < 18934
|
||||
extern void ImGuiTestEngineHook_ItemAdd(ImGuiContext* ui_ctx, const ImRect& bb, ImGuiID id);
|
||||
#endif
|
||||
#ifdef IMGUI_HAS_IMSTR
|
||||
extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, ImStrv label, ImGuiItemStatusFlags flags);
|
||||
#else
|
||||
extern void ImGuiTestEngineHook_ItemInfo(ImGuiContext* ui_ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags);
|
||||
#endif
|
||||
extern void ImGuiTestEngineHook_Log(ImGuiContext* ui_ctx, const char* fmt, ...);
|
||||
extern const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ui_ctx, ImGuiID id);
|
||||
|
||||
// Functions (generally called via IM_CHECK() macros)
|
||||
IMGUI_API bool ImGuiTestEngine_Check(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, bool result, const char* expr);
|
||||
IMGUI_API bool ImGuiTestEngine_CheckOpStr(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* op, const char* lhs_desc, const char* lhs_value, const char* rhs_desc, const char* rhs_value, bool* out_result);
|
||||
IMGUI_API bool ImGuiTestEngine_Error(const char* file, const char* func, int line, ImGuiTestCheckFlags flags, const char* fmt, ...);
|
||||
IMGUI_API void ImGuiTestEngine_AssertLog(const char* expr, const char* file, const char* function, int line);
|
||||
IMGUI_API ImGuiTextBuffer* ImGuiTestEngine_GetTempStringBuilder();
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// ImGuiTestEngine API
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Functions: Initialization
|
||||
IMGUI_API ImGuiTestEngine* ImGuiTestEngine_CreateContext(); // Create test engine
|
||||
IMGUI_API void ImGuiTestEngine_DestroyContext(ImGuiTestEngine* engine); // Destroy test engine. Call after ImGui::DestroyContext() so test engine specific ini data gets saved.
|
||||
IMGUI_API void ImGuiTestEngine_Start(ImGuiTestEngine* engine, ImGuiContext* ui_ctx); // Bind to a dear imgui context. Start coroutine.
|
||||
IMGUI_API void ImGuiTestEngine_Stop(ImGuiTestEngine* engine); // Stop coroutine and export if any. (Unbind will lazily happen on context shutdown)
|
||||
IMGUI_API void ImGuiTestEngine_PostSwap(ImGuiTestEngine* engine); // Call every frame after framebuffer swap, will process screen capture and call test_io.ScreenCaptureFunc()
|
||||
IMGUI_API ImGuiTestEngineIO& ImGuiTestEngine_GetIO(ImGuiTestEngine* engine);
|
||||
|
||||
// Macros: Register Test
|
||||
#define IM_REGISTER_TEST(_ENGINE, _CATEGORY, _NAME) ImGuiTestEngine_RegisterTest(_ENGINE, _CATEGORY, _NAME, __FILE__, __LINE__)
|
||||
IMGUI_API ImGuiTest* ImGuiTestEngine_RegisterTest(ImGuiTestEngine* engine, const char* category, const char* name, const char* src_file = nullptr, int src_line = 0); // Prefer calling IM_REGISTER_TEST()
|
||||
IMGUI_API void ImGuiTestEngine_UnregisterTest(ImGuiTestEngine* engine, ImGuiTest* test);
|
||||
IMGUI_API void ImGuiTestEngine_UnregisterAllTests(ImGuiTestEngine* engine);
|
||||
|
||||
// Functions: Main
|
||||
IMGUI_API void ImGuiTestEngine_QueueTest(ImGuiTestEngine* engine, ImGuiTest* test, ImGuiTestRunFlags run_flags = 0);
|
||||
IMGUI_API void ImGuiTestEngine_QueueTests(ImGuiTestEngine* engine, ImGuiTestGroup group, const char* filter = nullptr, ImGuiTestRunFlags run_flags = 0);
|
||||
IMGUI_API bool ImGuiTestEngine_TryAbortEngine(ImGuiTestEngine* engine);
|
||||
IMGUI_API void ImGuiTestEngine_AbortCurrentTest(ImGuiTestEngine* engine);
|
||||
IMGUI_API ImGuiTest* ImGuiTestEngine_FindTestByName(ImGuiTestEngine* engine, const char* category, const char* name);
|
||||
|
||||
// Functions: Status Queries
|
||||
// FIXME: Clarify API to avoid function calls vs raw bools in ImGuiTestEngineIO
|
||||
IMGUI_API bool ImGuiTestEngine_IsTestQueueEmpty(ImGuiTestEngine* engine);
|
||||
IMGUI_API bool ImGuiTestEngine_IsUsingSimulatedInputs(ImGuiTestEngine* engine);
|
||||
IMGUI_API void ImGuiTestEngine_GetResultSummary(ImGuiTestEngine* engine, ImGuiTestEngineResultSummary* out_results);
|
||||
IMGUI_API void ImGuiTestEngine_GetTestList(ImGuiTestEngine* engine, ImVector<ImGuiTest*>* out_tests);
|
||||
IMGUI_API void ImGuiTestEngine_GetTestQueue(ImGuiTestEngine* engine, ImVector<ImGuiTestRunTask>* out_tests);
|
||||
|
||||
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
// Obsoleted 2025/03/17
|
||||
static inline void ImGuiTestEngine_GetResult(ImGuiTestEngine* engine, int& out_count_tested, int& out_count_success) { ImGuiTestEngineResultSummary summary; ImGuiTestEngine_GetResultSummary(engine, &summary); out_count_tested = summary.CountTested; out_count_success = summary.CountSuccess; }
|
||||
#endif
|
||||
|
||||
// Functions: Crash Handling
|
||||
// Ensure past test results are properly exported even if application crash during a test.
|
||||
IMGUI_API void ImGuiTestEngine_InstallDefaultCrashHandler(); // Install default crash handler (if you don't have one)
|
||||
IMGUI_API void ImGuiTestEngine_CrashHandler(); // Default crash handler, should be called from a custom crash handler if such exists
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// IO structure to configure the test engine
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Function to capture raw serial log (otherwise you can peek into Output.Log of individual tests)
|
||||
typedef void (ImGuiTestEngineLogFunc)(ImGuiTestEngine* engine, ImGuiTestContext* test_ctx, ImGuiTestVerboseLevel level, const char* message, void* user_data);
|
||||
|
||||
// Function bound to right-clicking on a test and selecting "Open source" in the UI
|
||||
// - Easy: you can make this function call OS shell to "open" the file (e.g. ImOsOpenInShell() helper).
|
||||
// - Better: bind this function to a custom setup which can pass line number to a text editor (e.g. see 'imgui_test_suite/tools/win32_open_with_sublime.cmd' example)
|
||||
typedef void (ImGuiTestEngineSrcFileOpenFunc)(const char* filename, int line_no, void* user_data);
|
||||
|
||||
struct IMGUI_API ImGuiTestEngineIO
|
||||
{
|
||||
//-------------------------------------------------------------------------
|
||||
// Functions
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Options: Functions
|
||||
ImGuiTestCoroutineInterface* CoroutineFuncs = nullptr; // (Required) Coroutine functions (see imgui_te_coroutines.h)
|
||||
ImFuncPtr(ImGuiTestEngineSrcFileOpenFunc) SrcFileOpenFunc = nullptr; // (Optional) To open source files from test engine UI (otherwise default to open file in shell)
|
||||
ImFuncPtr(ImGuiScreenCaptureFunc) ScreenCaptureFunc = nullptr; // (Optional) To capture graphics output (application _MUST_ call ImGuiTestEngine_PostSwap() function after swapping is framebuffer)
|
||||
void* SrcFileOpenUserData = nullptr; // (Optional) User data for SrcFileOpenFunc
|
||||
void* ScreenCaptureUserData = nullptr; // (Optional) User data for ScreenCaptureFunc
|
||||
|
||||
// Options: Main
|
||||
bool ConfigSavedSettings = true; // Load/Save settings in main context .ini file.
|
||||
ImGuiTestRunSpeed ConfigRunSpeed = ImGuiTestRunSpeed_Fast; // Run tests in fast/normal/cinematic mode
|
||||
bool ConfigStopOnError = false; // Stop queued tests on test error
|
||||
bool ConfigBreakOnError = false; // Break debugger on test error by calling IM_DEBUG_BREAK()
|
||||
bool ConfigKeepGuiFunc = false; // Keep test GUI running at the end of the test
|
||||
bool ConfigRestoreFocusAfterTests = true;// Restore focus back after running tests
|
||||
bool ConfigCaptureEnabled = true; // Master enable flags for capturing and saving captures. Disable to avoid e.g. lengthy saving of large PNG files.
|
||||
bool ConfigCaptureOnError = false;
|
||||
bool ConfigNoThrottle = false; // Disable vsync for performance measurement or fast test running
|
||||
bool ConfigMouseDrawCursor = true; // Enable drawing of Dear ImGui software mouse cursor when running tests
|
||||
float ConfigFixedDeltaTime = 0.0f; // Use fixed delta time instead of calculating it from wall clock
|
||||
int PerfStressAmount = 1; // Integer to scale the amount of items submitted in test
|
||||
char GitBranchName[64] = ""; // e.g. fill in branch name (e.g. recorded in perf samples .csv)
|
||||
|
||||
// Options: Logging
|
||||
ImGuiTestVerboseLevel ConfigVerboseLevel = ImGuiTestVerboseLevel_Warning;
|
||||
ImGuiTestVerboseLevel ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Info;
|
||||
bool ConfigLogToTTY = false; // Output log entries to TTY (in addition to Test Engine UI)
|
||||
bool ConfigLogToDebugger = false; // Output log entries to Debugger (in addition to Test Engine UI)
|
||||
ImFuncPtr(ImGuiTestEngineLogFunc) ConfigLogToFunc = nullptr; // Hook for logging of full serial log (vs peeking into ImGuiTest->Output.Log for a single test)
|
||||
void* ConfigLogToFuncUserData = NULL;
|
||||
|
||||
// Options: Speed of user simulation
|
||||
float MouseSpeed = 600.0f; // Mouse speed (pixel/second) when not running in fast mode
|
||||
float MouseWobble = 0.25f; // (0.0f..1.0f) How much wobble to apply to the mouse (pixels per pixel of move distance) when not running in fast mode
|
||||
float ScrollSpeed = 1400.0f; // Scroll speed (pixel/second) when not running in fast mode
|
||||
float TypingSpeed = 20.0f; // Char input speed (characters/second) when not running in fast mode
|
||||
float ActionDelayShort = 0.15f; // Time between short actions
|
||||
float ActionDelayStandard = 0.40f; // Time between most actions
|
||||
|
||||
// Options: Screen/video capture
|
||||
char VideoCaptureEncoderPath[256] = ""; // Video encoder executable path, e.g. "path/to/ffmpeg.exe".
|
||||
char VideoCaptureEncoderParams[256] = "";// Video encoder parameters for .MP4 captures, e.g. see IMGUI_CAPTURE_DEFAULT_VIDEO_PARAMS_FOR_FFMPEG
|
||||
char GifCaptureEncoderParams[512] = ""; // Video encoder parameters for .GIF captures, e.g. see IMGUI_CAPTURE_DEFAULT_GIF_PARAMS_FOR_FFMPEG
|
||||
char VideoCaptureExtension[8] = ".mp4"; // Video file extension (default, may be overridden by test).
|
||||
|
||||
// Options: Watchdog. Set values to FLT_MAX to disable.
|
||||
// Interactive GUI applications that may be slower tend to use higher values.
|
||||
float ConfigWatchdogWarning = 30.0f; // Warn when a test exceed this time (in second)
|
||||
float ConfigWatchdogKillTest = 60.0f; // Attempt to stop running a test when exceeding this time (in second)
|
||||
float ConfigWatchdogKillApp = FLT_MAX; // Stop application when exceeding this time (in second)
|
||||
|
||||
// Options: Export
|
||||
// While you can manually call ImGuiTestEngine_Export(), registering filename/format here ensure the crash handler will always export if application crash.
|
||||
const char* ExportResultsFilename = nullptr;
|
||||
ImGuiTestEngineExportFormat ExportResultsFormat = (ImGuiTestEngineExportFormat)0;
|
||||
|
||||
// Options: Sanity Checks
|
||||
bool CheckDrawDataIntegrity = false; // Check ImDrawData integrity (buffer count, etc.). Currently cheap but may become a slow operation.
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Output
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Output: State of test engine
|
||||
bool IsRunningTests = false;
|
||||
bool IsRequestingMaxAppSpeed = false; // When running in fast mode: request app to skip vsync or even skip rendering if it wants
|
||||
bool IsCapturing = false; // Capture is in progress
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// ImGuiTestItemInfo
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Information about a given item or window, result of an ItemInfo() or WindowInfo() query
|
||||
struct ImGuiTestItemInfo
|
||||
{
|
||||
ImGuiID ID = 0; // Item ID
|
||||
char DebugLabel[32] = {}; // Shortened/truncated label for debugging and convenience purpose
|
||||
ImGuiWindow* Window = nullptr; // Item Window
|
||||
unsigned int NavLayer : 1; // Nav layer of the item (ImGuiNavLayer)
|
||||
int Depth : 16; // Depth from requested parent id. 0 == ID is immediate child of requested parent id.
|
||||
int TimestampMain; // Timestamp of main result (all fields)
|
||||
int TimestampStatus; // Timestamp of StatusFlags
|
||||
ImGuiID ParentID = 0; // Item Parent ID (value at top of the ID stack)
|
||||
ImRect RectFull = ImRect(); // Item Rectangle
|
||||
ImRect RectClipped = ImRect(); // Item Rectangle (clipped with window->ClipRect at time of item submission)
|
||||
ImGuiItemFlags ItemFlags = 0; // Item flags
|
||||
//ImGuiItemFlags InFlags = 0; // Item flags (OBSOLETE: before 2024/10/17 ItemFlags was called InFlags)
|
||||
ImGuiItemStatusFlags StatusFlags = 0; // Item Status flags (fully updated for some items only, compare TimestampStatus to FrameCount)
|
||||
|
||||
ImGuiTestItemInfo() { memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
// Result of an GatherItems() query
|
||||
struct IMGUI_API ImGuiTestItemList
|
||||
{
|
||||
ImPool<ImGuiTestItemInfo> Pool;
|
||||
|
||||
void Clear() { Pool.Clear(); }
|
||||
void Reserve(int capacity) { Pool.Reserve(capacity); }
|
||||
int GetSize() const { return Pool.GetMapSize(); }
|
||||
const ImGuiTestItemInfo* GetByIndex(int n) { return Pool.GetByIndex(n); }
|
||||
const ImGuiTestItemInfo* GetByID(ImGuiID id) { return Pool.GetByKey(id); }
|
||||
|
||||
// For range-for
|
||||
size_t size() const { return (size_t)Pool.GetMapSize(); }
|
||||
const ImGuiTestItemInfo* begin() const { return Pool.Buf.begin(); }
|
||||
const ImGuiTestItemInfo* end() const { return Pool.Buf.end(); }
|
||||
const ImGuiTestItemInfo* operator[] (size_t n) { return &Pool.Buf[(int)n]; }
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// ImGuiTestLog: store textual output of one given Test.
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
struct IMGUI_API ImGuiTestLogLineInfo
|
||||
{
|
||||
ImGuiTestVerboseLevel Level;
|
||||
int LineOffset;
|
||||
};
|
||||
|
||||
struct IMGUI_API ImGuiTestLog
|
||||
{
|
||||
ImGuiTextBuffer Buffer;
|
||||
ImVector<ImGuiTestLogLineInfo> LineInfo;
|
||||
int CountPerLevel[ImGuiTestVerboseLevel_COUNT] = {};
|
||||
|
||||
// Functions
|
||||
ImGuiTestLog() {}
|
||||
bool IsEmpty() const { return Buffer.empty(); }
|
||||
const char* GetText() { return Buffer.c_str(); }
|
||||
int GetTextLen() { return Buffer.size(); }
|
||||
void Clear();
|
||||
|
||||
// Extract log contents filtered per log-level.
|
||||
// Output:
|
||||
// - If 'buffer != nullptr': all extracted lines are appended to 'buffer'. Use 'buffer->c_str()' on your side to obtain the text.
|
||||
// - Return value: number of lines extracted (should be equivalent to number of '\n' inside buffer->c_str()).
|
||||
// - You may call the function with buffer == nullptr to only obtain a count without getting the data.
|
||||
// Verbose levels are inclusive:
|
||||
// - To get ONLY Error: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Error
|
||||
// - To get ONLY Error and Warnings: Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Warning
|
||||
// - To get All Errors, Warnings, Debug... Use level_min == ImGuiTestVerboseLevel_Error, level_max = ImGuiTestVerboseLevel_Trace
|
||||
int ExtractLinesForVerboseLevels(ImGuiTestVerboseLevel level_min, ImGuiTestVerboseLevel level_max, ImGuiTextBuffer* out_buffer);
|
||||
|
||||
// [Internal]
|
||||
void UpdateLineOffsets(ImGuiTestEngineIO* engine_io, ImGuiTestVerboseLevel level, const char* start);
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// ImGuiTest
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
typedef void (ImGuiTestGuiFunc)(ImGuiTestContext* ctx);
|
||||
typedef void (ImGuiTestTestFunc)(ImGuiTestContext* ctx);
|
||||
|
||||
// Wraps a placement new of a given type (where 'buffer' is the allocated memory)
|
||||
typedef void (ImGuiTestVarsConstructor)(void* buffer);
|
||||
typedef void (ImGuiTestVarsPostConstructor)(ImGuiTestContext* ctx, void* ptr, void* fn);
|
||||
typedef void (ImGuiTestVarsDestructor)(void* ptr);
|
||||
|
||||
// Storage for the output of a test run
|
||||
struct IMGUI_API ImGuiTestOutput
|
||||
{
|
||||
ImGuiTestStatus Status = ImGuiTestStatus_Unknown;
|
||||
ImGuiTestLog Log;
|
||||
ImU64 StartTime = 0;
|
||||
ImU64 EndTime = 0;
|
||||
};
|
||||
|
||||
// Storage for one test
|
||||
struct IMGUI_API ImGuiTest
|
||||
{
|
||||
// Test Definition
|
||||
const char* Category = nullptr; // Literal, not owned
|
||||
const char* Name = nullptr; // Literal, generally not owned unless NameOwned=true
|
||||
ImGuiTestGroup Group = ImGuiTestGroup_Unknown; // Coarse groups: 'Tests' or 'Perf'
|
||||
bool NameOwned = false; //
|
||||
int ArgVariant = 0; // User parameter. Generally we use it to run variations of a same test by sharing GuiFunc/TestFunc
|
||||
ImGuiTestFlags Flags = ImGuiTestFlags_None; // See ImGuiTestFlags_
|
||||
ImFuncPtr(ImGuiTestGuiFunc) GuiFunc = nullptr; // GUI function (optional if your test are running over an existing GUI application)
|
||||
ImFuncPtr(ImGuiTestTestFunc) TestFunc = nullptr; // Test driving function
|
||||
ImFuncPtr(ImGuiTestTestFunc) TeardownFunc = nullptr; // Teardown driving function, executed after TestFunc _regardless_ of TestFunc failing.
|
||||
void* UserData = nullptr; // General purpose user data (if assigning capturing lambdas on GuiFunc/TestFunc you may not need to use this)
|
||||
//ImVector<ImGuiTestRunTask> Dependencies; // Registered via AddDependencyTest(), ran automatically before our test. This is a simpler wrapper to calling ctx->RunChildTest()
|
||||
|
||||
// Sources information (exposed in UI)
|
||||
const char* SourceFile = nullptr; // __FILE__
|
||||
int SourceLine = 0; // __LINE__
|
||||
int SourceLineEnd = 0; // end of line (when calculated by ImGuiTestEngine_StartCalcSourceLineEnds())
|
||||
|
||||
// Last Test Output/Status
|
||||
// (this is the only part that may change after registration)
|
||||
ImGuiTestOutput Output;
|
||||
|
||||
// User variables (which are instantiated when running the test)
|
||||
// Setup after test registration with SetVarsDataType<>(), access instance during test with GetVars<>().
|
||||
// This is mostly useful to communicate between GuiFunc and TestFunc. If you don't use both you may not want to use it!
|
||||
size_t VarsSize = 0;
|
||||
ImGuiTestVarsConstructor* VarsConstructor = nullptr;
|
||||
ImGuiTestVarsPostConstructor* VarsPostConstructor = nullptr; // To override constructor default (in case the default are problematic on the first GuiFunc frame)
|
||||
void* VarsPostConstructorUserFn = nullptr;
|
||||
ImGuiTestVarsDestructor* VarsDestructor = nullptr;
|
||||
|
||||
// Functions
|
||||
ImGuiTest() {}
|
||||
~ImGuiTest();
|
||||
|
||||
void SetOwnedName(const char* name);
|
||||
|
||||
template <typename T>
|
||||
void SetVarsDataType(void(*post_initialize)(ImGuiTestContext* ctx, T& vars) = nullptr)
|
||||
{
|
||||
VarsSize = sizeof(T);
|
||||
VarsConstructor = [](void* ptr) { IM_PLACEMENT_NEW(ptr) T; };
|
||||
VarsDestructor = [](void* ptr) { IM_UNUSED(ptr); reinterpret_cast<T*>(ptr)->~T(); };
|
||||
if (post_initialize != nullptr)
|
||||
{
|
||||
VarsPostConstructorUserFn = (void*)post_initialize;
|
||||
VarsPostConstructor = [](ImGuiTestContext* ctx, void* ptr, void* fn) { ((void (*)(ImGuiTestContext*, T&))(fn))(ctx, *(T*)ptr); };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Stored in test queue
|
||||
struct IMGUI_API ImGuiTestRunTask
|
||||
{
|
||||
ImGuiTest* Test = nullptr;
|
||||
ImGuiTestRunFlags RunFlags = ImGuiTestRunFlags_None;
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
// dear imgui test engine
|
||||
// (result exporters)
|
||||
// Read https://github.com/ocornut/imgui_test_engine/wiki/Exporting-Results
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#include "imgui_te_exporters.h"
|
||||
#include "imgui_te_engine.h"
|
||||
#include "imgui_te_internal.h"
|
||||
#include "thirdparty/Str/Str.h"
|
||||
|
||||
// Warnings
|
||||
#if defined(__clang__)
|
||||
#if __has_warning("-Wunknown-warning-option")
|
||||
#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!
|
||||
#endif
|
||||
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
||||
#elif defined(__GNUC__)
|
||||
#pragma GCC diagnostic ignored "-Wsign-conversion" // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] FORWARD DECLARATIONS
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
static void ImGuiTestEngine_ExportJUnitXml(ImGuiTestEngine* engine, const char* output_file);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// [SECTION] TEST ENGINE EXPORTER FUNCTIONS
|
||||
//-------------------------------------------------------------------------
|
||||
// - ImGuiTestEngine_PrintResultSummary()
|
||||
// - ImGuiTestEngine_Export()
|
||||
// - ImGuiTestEngine_ExportEx()
|
||||
// - ImGuiTestEngine_ExportJUnitXml()
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
void ImGuiTestEngine_PrintResultSummary(ImGuiTestEngine* engine)
|
||||
{
|
||||
ImGuiTestEngineResultSummary summary;
|
||||
ImGuiTestEngine_GetResultSummary(engine, &summary);
|
||||
|
||||
if (summary.CountSuccess < summary.CountTested)
|
||||
{
|
||||
printf("\nFailing tests:\n");
|
||||
for (ImGuiTest* test : engine->TestsAll)
|
||||
if (test->Output.Status == ImGuiTestStatus_Error)
|
||||
printf("- %s\n", test->Name);
|
||||
}
|
||||
|
||||
bool success = (summary.CountSuccess == summary.CountTested);
|
||||
ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, success ? ImOsConsoleTextColor_BrightGreen : ImOsConsoleTextColor_BrightRed);
|
||||
printf("\nTests Result: %s\n", success ? "OK" : "Errors");
|
||||
printf("(%d/%d tests passed)\n", summary.CountSuccess, summary.CountTested);
|
||||
if (summary.CountInQueue > 0)
|
||||
printf("(%d queued tests remaining)\n", summary.CountInQueue);
|
||||
ImOsConsoleSetTextColor(ImOsConsoleStream_StandardOutput, ImOsConsoleTextColor_White);
|
||||
}
|
||||
|
||||
// This is mostly a copy of ImGuiTestEngine_PrintResultSummary with few additions.
|
||||
static void ImGuiTestEngine_ExportResultSummary(ImGuiTestEngine* engine, FILE* fp, int indent_count, ImGuiTestGroup group)
|
||||
{
|
||||
int count_tested = 0;
|
||||
int count_success = 0;
|
||||
|
||||
for (ImGuiTest* test : engine->TestsAll)
|
||||
{
|
||||
if (test->Group != group)
|
||||
continue;
|
||||
if (test->Output.Status != ImGuiTestStatus_Unknown)
|
||||
count_tested++;
|
||||
if (test->Output.Status == ImGuiTestStatus_Success)
|
||||
count_success++;
|
||||
}
|
||||
|
||||
Str64 indent_str;
|
||||
indent_str.reserve(indent_count + 1);
|
||||
memset(indent_str.c_str(), ' ', indent_count);
|
||||
indent_str[indent_count] = 0;
|
||||
const char* indent = indent_str.c_str();
|
||||
|
||||
if (count_success < count_tested)
|
||||
{
|
||||
fprintf(fp, "\n%sFailing tests:\n", indent);
|
||||
for (ImGuiTest* test : engine->TestsAll)
|
||||
{
|
||||
if (test->Group != group)
|
||||
continue;
|
||||
if (test->Output.Status == ImGuiTestStatus_Error)
|
||||
fprintf(fp, "%s- %s\n", indent, test->Name);
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
fprintf(fp, "%sTests Result: %s\n", indent, (count_success == count_tested) ? "OK" : "Errors");
|
||||
fprintf(fp, "%s(%d/%d tests passed)\n", indent, count_success, count_tested);
|
||||
}
|
||||
|
||||
static bool ImGuiTestEngine_HasAnyLogLines(ImGuiTestLog* test_log, ImGuiTestVerboseLevel level)
|
||||
{
|
||||
for (auto& line_info : test_log->LineInfo)
|
||||
if (line_info.Level <= level)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ImGuiTestEngine_PrintLogLines(FILE* fp, ImGuiTestLog* test_log, int indent, ImGuiTestVerboseLevel level)
|
||||
{
|
||||
Str128 log_line;
|
||||
for (auto& line_info : test_log->LineInfo)
|
||||
{
|
||||
if (line_info.Level > level)
|
||||
continue;
|
||||
const char* line_start = test_log->Buffer.c_str() + line_info.LineOffset;
|
||||
const char* line_end = strstr(line_start, "\n"); // FIXME: Incorrect.
|
||||
log_line.set(line_start, line_end);
|
||||
ImStrXmlEscape(&log_line); // FIXME: Should not be here considering the function name.
|
||||
|
||||
// Some users may want to disable indenting?
|
||||
fprintf(fp, "%*s%s\n", indent, "", log_line.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Export using settings stored in ImGuiTestEngineIO
|
||||
// This is called by ImGuiTestEngine_CrashHandler().
|
||||
void ImGuiTestEngine_Export(ImGuiTestEngine* engine)
|
||||
{
|
||||
ImGuiTestEngineIO& io = engine->IO;
|
||||
ImGuiTestEngine_ExportEx(engine, io.ExportResultsFormat, io.ExportResultsFilename);
|
||||
}
|
||||
|
||||
// Export using custom settings.
|
||||
void ImGuiTestEngine_ExportEx(ImGuiTestEngine* engine, ImGuiTestEngineExportFormat format, const char* filename)
|
||||
{
|
||||
if (format == ImGuiTestEngineExportFormat_None)
|
||||
return;
|
||||
IM_ASSERT(filename != nullptr);
|
||||
|
||||
if (format == ImGuiTestEngineExportFormat_JUnitXml)
|
||||
ImGuiTestEngine_ExportJUnitXml(engine, filename);
|
||||
else
|
||||
IM_ASSERT(0);
|
||||
}
|
||||
|
||||
void ImGuiTestEngine_ExportJUnitXml(ImGuiTestEngine* engine, const char* output_file)
|
||||
{
|
||||
IM_ASSERT(engine != nullptr);
|
||||
IM_ASSERT(output_file != nullptr);
|
||||
|
||||
FILE* fp = fopen(output_file, "w+b");
|
||||
if (fp == nullptr)
|
||||
{
|
||||
fprintf(stderr, "Writing '%s' failed.\n", output_file);
|
||||
return;
|
||||
}
|
||||
|
||||
// Per-testsuite test statistics.
|
||||
struct
|
||||
{
|
||||
const char* Name = nullptr;
|
||||
int Tests = 0;
|
||||
int Failures = 0;
|
||||
int Disabled = 0;
|
||||
} testsuites[ImGuiTestGroup_COUNT];
|
||||
testsuites[ImGuiTestGroup_Tests].Name = "tests";
|
||||
testsuites[ImGuiTestGroup_Perfs].Name = "perfs";
|
||||
|
||||
for (int n = 0; n < engine->TestsAll.Size; n++)
|
||||
{
|
||||
ImGuiTest* test = engine->TestsAll[n];
|
||||
auto* stats = &testsuites[test->Group];
|
||||
stats->Tests += 1;
|
||||
if (test->Output.Status == ImGuiTestStatus_Error)
|
||||
stats->Failures += 1;
|
||||
else if (test->Output.Status == ImGuiTestStatus_Unknown)
|
||||
stats->Disabled += 1;
|
||||
}
|
||||
|
||||
// Attributes for <testsuites> tag.
|
||||
const char* testsuites_name = "Dear ImGui";
|
||||
int testsuites_failures = 0;
|
||||
int testsuites_tests = 0;
|
||||
int testsuites_disabled = 0;
|
||||
float testsuites_time = (float)((double)(engine->BatchEndTime - engine->BatchStartTime) / 1000000.0);
|
||||
for (int testsuite_id = ImGuiTestGroup_Tests; testsuite_id < ImGuiTestGroup_COUNT; testsuite_id++)
|
||||
{
|
||||
testsuites_tests += testsuites[testsuite_id].Tests;
|
||||
testsuites_failures += testsuites[testsuite_id].Failures;
|
||||
testsuites_disabled += testsuites[testsuite_id].Disabled;
|
||||
}
|
||||
|
||||
// FIXME: "errors" attribute and <error> tag in <testcase> may be supported if we have means to catch unexpected errors like assertions.
|
||||
fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<testsuites disabled=\"%d\" errors=\"0\" failures=\"%d\" name=\"%s\" tests=\"%d\" time=\"%.3f\">\n",
|
||||
testsuites_disabled, testsuites_failures, testsuites_name, testsuites_tests, testsuites_time);
|
||||
|
||||
for (int testsuite_id = ImGuiTestGroup_Tests; testsuite_id < ImGuiTestGroup_COUNT; testsuite_id++)
|
||||
{
|
||||
// Attributes for <testsuite> tag.
|
||||
auto* testsuite = &testsuites[testsuite_id];
|
||||
float testsuite_time = testsuites_time; // FIXME: We do not differentiate between tests and perfs, they are executed in one big batch.
|
||||
Str30 testsuite_timestamp = "";
|
||||
ImTimestampToISO8601(engine->BatchStartTime, &testsuite_timestamp);
|
||||
fprintf(fp, " <testsuite name=\"%s\" tests=\"%d\" disabled=\"%d\" errors=\"0\" failures=\"%d\" hostname=\"\" id=\"%d\" package=\"\" skipped=\"0\" time=\"%.3f\" timestamp=\"%s\">\n",
|
||||
testsuite->Name, testsuite->Tests, testsuite->Disabled, testsuite->Failures, testsuite_id, testsuite_time, testsuite_timestamp.c_str());
|
||||
|
||||
for (int n = 0; n < engine->TestsAll.Size; n++)
|
||||
{
|
||||
ImGuiTest* test = engine->TestsAll[n];
|
||||
if (test->Group != testsuite_id)
|
||||
continue;
|
||||
|
||||
ImGuiTestOutput* test_output = &test->Output;
|
||||
ImGuiTestLog* test_log = &test_output->Log;
|
||||
|
||||
// Attributes for <testcase> tag.
|
||||
const char* testcase_name = test->Name;
|
||||
const char* testcase_classname = test->Category;
|
||||
const char* testcase_status = ImGuiTestEngine_GetStatusName(test_output->Status);
|
||||
const float testcase_time = (float)((double)(test_output->EndTime - test_output->StartTime) / 1000000.0);
|
||||
|
||||
fprintf(fp, " <testcase name=\"%s\" assertions=\"0\" classname=\"%s\" status=\"%s\" time=\"%.3f\">\n",
|
||||
testcase_name, testcase_classname, testcase_status, testcase_time);
|
||||
|
||||
if (test_output->Status == ImGuiTestStatus_Error)
|
||||
{
|
||||
// Skip last error message because it is generic information that test failed.
|
||||
Str128 log_line;
|
||||
for (int i = test_log->LineInfo.Size - 2; i >= 0; i--)
|
||||
{
|
||||
ImGuiTestLogLineInfo* line_info = &test_log->LineInfo[i];
|
||||
if (line_info->Level > engine->IO.ConfigVerboseLevelOnError)
|
||||
continue;
|
||||
if (line_info->Level == ImGuiTestVerboseLevel_Error)
|
||||
{
|
||||
const char* line_start = test_log->Buffer.c_str() + line_info->LineOffset;
|
||||
const char* line_end = strstr(line_start, "\n");
|
||||
log_line.set(line_start, line_end);
|
||||
ImStrXmlEscape(&log_line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Failing tests save their "on error" log output in text element of <failure> tag.
|
||||
fprintf(fp, " <failure message=\"%s\" type=\"error\">\n", log_line.c_str());
|
||||
ImGuiTestEngine_PrintLogLines(fp, test_log, 8, engine->IO.ConfigVerboseLevelOnError);
|
||||
fprintf(fp, " </failure>\n");
|
||||
}
|
||||
|
||||
if (test_output->Status == ImGuiTestStatus_Unknown)
|
||||
{
|
||||
fprintf(fp, " <skipped message=\"Skipped\" />\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Succeeding tests save their default log output output as "stdout".
|
||||
if (ImGuiTestEngine_HasAnyLogLines(test_log, engine->IO.ConfigVerboseLevel))
|
||||
{
|
||||
fprintf(fp, " <system-out>\n");
|
||||
ImGuiTestEngine_PrintLogLines(fp, test_log, 8, engine->IO.ConfigVerboseLevel);
|
||||
fprintf(fp, " </system-out>\n");
|
||||
}
|
||||
|
||||
// Save error messages as "stderr".
|
||||
if (ImGuiTestEngine_HasAnyLogLines(test_log, ImGuiTestVerboseLevel_Error))
|
||||
{
|
||||
fprintf(fp, " <system-err>\n");
|
||||
ImGuiTestEngine_PrintLogLines(fp, test_log, 8, ImGuiTestVerboseLevel_Error);
|
||||
fprintf(fp, " </system-err>\n");
|
||||
}
|
||||
}
|
||||
fprintf(fp, " </testcase>\n");
|
||||
}
|
||||
|
||||
if (testsuites[testsuite_id].Disabled < testsuites[testsuite_id].Tests) // Any tests executed
|
||||
{
|
||||
// Log all log messages as "stdout".
|
||||
fprintf(fp, " <system-out>\n");
|
||||
for (int n = 0; n < engine->TestsAll.Size; n++)
|
||||
{
|
||||
ImGuiTest* test = engine->TestsAll[n];
|
||||
ImGuiTestOutput* test_output = &test->Output;
|
||||
if (test->Group != testsuite_id)
|
||||
continue;
|
||||
if (test_output->Status == ImGuiTestStatus_Unknown)
|
||||
continue;
|
||||
fprintf(fp, " [0000] Test: '%s' '%s'..\n", test->Category, test->Name);
|
||||
ImGuiTestVerboseLevel level = test_output->Status == ImGuiTestStatus_Error ? engine->IO.ConfigVerboseLevelOnError : engine->IO.ConfigVerboseLevel;
|
||||
ImGuiTestEngine_PrintLogLines(fp, &test_output->Log, 6, level);
|
||||
}
|
||||
ImGuiTestEngine_ExportResultSummary(engine, fp, 6, (ImGuiTestGroup)testsuite_id);
|
||||
fprintf(fp, " </system-out>\n");
|
||||
|
||||
// Log all warning and error messages as "stderr".
|
||||
fprintf(fp, " <system-err>\n");
|
||||
for (int n = 0; n < engine->TestsAll.Size; n++)
|
||||
{
|
||||
ImGuiTest* test = engine->TestsAll[n];
|
||||
ImGuiTestOutput* test_output = &test->Output;
|
||||
if (test->Group != testsuite_id)
|
||||
continue;
|
||||
if (test_output->Status == ImGuiTestStatus_Unknown)
|
||||
continue;
|
||||
fprintf(fp, " [0000] Test: '%s' '%s'..\n", test->Category, test->Name);
|
||||
ImGuiTestEngine_PrintLogLines(fp, &test_output->Log, 6, ImGuiTestVerboseLevel_Warning);
|
||||
}
|
||||
ImGuiTestEngine_ExportResultSummary(engine, fp, 6, (ImGuiTestGroup)testsuite_id);
|
||||
fprintf(fp, " </system-err>\n");
|
||||
}
|
||||
fprintf(fp, " </testsuite>\n");
|
||||
}
|
||||
fprintf(fp, "</testsuites>\n");
|
||||
fclose(fp);
|
||||
fprintf(stdout, "Saved test results to '%s' successfully.\n", output_file);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// dear imgui test engine
|
||||
// (result exporters)
|
||||
// Read https://github.com/ocornut/imgui_test_engine/wiki/Exporting-Results
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Description
|
||||
//-------------------------------------------------------------------------
|
||||
//
|
||||
// Test results may be exported in one of supported formats.
|
||||
// To enable result exporting please configure test engine as follows:
|
||||
//
|
||||
// ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine);
|
||||
// test_io.ExportResultsFile = "output_file.xml";
|
||||
// test_io.ExportResultsFormat = ImGuiTestEngineExportFormat_<...>;
|
||||
//
|
||||
// JUnit XML format
|
||||
//------------------
|
||||
// JUnit XML format described at https://llg.cubic.org/docs/junit/. Many
|
||||
// third party applications support consumption of this format. Some of
|
||||
// of them are listed here:
|
||||
// - Jenkins
|
||||
// - Installation guide: https://www.jenkins.io/doc/book/installing/docker/
|
||||
// - JUnit plugin: https://plugins.jenkins.io/junit/
|
||||
// - xunit-viewer
|
||||
// - Project: https://github.com/lukejpreston/xunit-viewer
|
||||
// - Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
|
||||
// - Install viewer and view test results:
|
||||
// npm install xunit-viewer
|
||||
// imgui_test_suite -nopause -v2 -ve4 -nogui -export-file junit.xml tests
|
||||
// node_modules/xunit-viewer/bin/xunit-viewer -r junit.xml -o junit.html
|
||||
// - Open junit.html
|
||||
//
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Forward Declarations
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
struct ImGuiTestEngine;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Types
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
enum ImGuiTestEngineExportFormat : int
|
||||
{
|
||||
ImGuiTestEngineExportFormat_None = 0,
|
||||
ImGuiTestEngineExportFormat_JUnitXml,
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Functions
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
void ImGuiTestEngine_PrintResultSummary(ImGuiTestEngine* engine);
|
||||
|
||||
void ImGuiTestEngine_Export(ImGuiTestEngine* engine);
|
||||
void ImGuiTestEngine_ExportEx(ImGuiTestEngine* engine, ImGuiTestEngineExportFormat format, const char* filename);
|
||||
@@ -0,0 +1,67 @@
|
||||
// dear imgui test engine
|
||||
// (template for compile-time configuration)
|
||||
// Replicate or #include this file in your imconfig.h to enable test engine.
|
||||
|
||||
// Compile Dear ImGui with test engine hooks
|
||||
// (Important: This is a value-less define, to be consistent with other defines used in core dear imgui.)
|
||||
#define IMGUI_ENABLE_TEST_ENGINE
|
||||
|
||||
// [Optional, default 0] Enable plotting of perflog data for comparing performance of different runs.
|
||||
// This feature requires ImPlot to be linked in the application.
|
||||
#ifndef IMGUI_TEST_ENGINE_ENABLE_IMPLOT
|
||||
#define IMGUI_TEST_ENGINE_ENABLE_IMPLOT 0
|
||||
#endif
|
||||
|
||||
// [Optional, default 1] Enable screen capture and PNG/GIF saving functionalities
|
||||
// There's not much point to disable this but we provide it to reassure user that the dependencies on imstb_image_write.h and ffmpeg are technically optional.
|
||||
#ifndef IMGUI_TEST_ENGINE_ENABLE_CAPTURE
|
||||
#define IMGUI_TEST_ENGINE_ENABLE_CAPTURE 1
|
||||
#endif
|
||||
|
||||
// [Optional, default 0] Using std::function and <functional> for function pointers such as ImGuiTest::TestFunc and ImGuiTest::GuiFunc
|
||||
#ifndef IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION
|
||||
#define IMGUI_TEST_ENGINE_ENABLE_STD_FUNCTION 0
|
||||
#endif
|
||||
|
||||
// [Optional, default 0] Automatically fill ImGuiTestEngineIO::CoroutineFuncs with a default implementation using std::thread
|
||||
#ifndef IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL
|
||||
#define IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL 0
|
||||
#endif
|
||||
|
||||
// [Optional, default 0] Disable calls that do not make sense on game consoles
|
||||
// (Disable: system(), popen(), sigaction(), colored TTY output)
|
||||
#ifndef IMGUI_TEST_ENGINE_IS_GAME_CONSOLE
|
||||
#if defined(__ORBIS__) || defined(__PROSPERO__) || defined(_GAMING_XBOX) || defined(_DURANGO)
|
||||
#define IMGUI_TEST_ENGINE_IS_GAME_CONSOLE 1
|
||||
#else
|
||||
#define IMGUI_TEST_ENGINE_IS_GAME_CONSOLE 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Define IM_DEBUG_BREAK macros so it is accessible in imgui.h
|
||||
// (this is a conveniance for app using test engine may define an IM_ASSERT() that uses this instead of an actual assert)
|
||||
// (this is a copy of the block in imgui_internal.h. if the one in imgui_internal.h were to be defined at the top of imgui.h we wouldn't need this)
|
||||
#ifndef IM_DEBUG_BREAK
|
||||
#if defined (_MSC_VER)
|
||||
#define IM_DEBUG_BREAK() __debugbreak()
|
||||
#elif defined(__clang__)
|
||||
#define IM_DEBUG_BREAK() __builtin_debugtrap()
|
||||
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
#define IM_DEBUG_BREAK() __asm__ volatile("int $0x03")
|
||||
#elif defined(__GNUC__) && defined(__thumb__)
|
||||
#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xde01")
|
||||
#elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__)
|
||||
#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xe7f001f0");
|
||||
#else
|
||||
#define IM_DEBUG_BREAK() IM_ASSERT(0) // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger!
|
||||
#endif
|
||||
#endif // #ifndef IMGUI_DEBUG_BREAK
|
||||
|
||||
// [Options] We provide custom assert macro used by our our test suite, which you may use:
|
||||
// - Calling IM_DEBUG_BREAK() instead of an actual assert, so we can easily recover and step over (compared to many assert implementations).
|
||||
// - If a test is running, test name will be included in the log.
|
||||
// - Macro is calling IM_DEBUG_BREAK() inline to get debugger to break in the calling function (instead of a deeper callstack level).
|
||||
// - Macro is using comma operator instead of an if() to avoid "conditional expression is constant" warnings.
|
||||
extern void ImGuiTestEngine_AssertLog(const char* expr, const char* file, const char* func, int line);
|
||||
#define IM_TEST_ENGINE_ASSERT(_EXPR) do { if ((void)0, !(_EXPR)) { ImGuiTestEngine_AssertLog(#_EXPR, __FILE__, __func__, __LINE__); IM_DEBUG_BREAK(); } } while (0)
|
||||
// V_ASSERT_CONTRACT, assertMacro:IM_ASSERT
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
// dear imgui test engine
|
||||
// (internal api)
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imgui_te_coroutine.h"
|
||||
#include "imgui_te_utils.h" // ImMovingAverage
|
||||
#include "imgui_capture_tool.h" // ImGuiCaptureTool // FIXME
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// FORWARD DECLARATIONS
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
class Str; // Str<> from thirdparty/Str/Str.h
|
||||
struct ImGuiPerfTool;
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// DATA STRUCTURES
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Query item position/window/state given ID.
|
||||
struct ImGuiTestInfoTask
|
||||
{
|
||||
// Input
|
||||
ImGuiID ID = 0;
|
||||
int FrameCount = -1; // Timestamp of request
|
||||
char DebugName[64] = ""; // Debug string representing the queried ID
|
||||
|
||||
// Output
|
||||
ImGuiTestItemInfo Result;
|
||||
};
|
||||
|
||||
// Gather item list in given parent ID.
|
||||
struct ImGuiTestGatherTask
|
||||
{
|
||||
// Input
|
||||
ImGuiID InParentID = 0;
|
||||
int InMaxDepth = 0;
|
||||
short InLayerMask = 0;
|
||||
|
||||
// Output/Temp
|
||||
ImGuiTestItemList* OutList = nullptr;
|
||||
ImGuiTestItemInfo* LastItemInfo = nullptr;
|
||||
|
||||
void Clear() { memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
// Find item ID given a label and a parent id
|
||||
// Usually used by queries with wildcards such as ItemInfo("hello/**/foo/bar")
|
||||
struct ImGuiTestFindByLabelTask
|
||||
{
|
||||
// Input
|
||||
ImGuiID InPrefixId = 0; // A known base ID which appears BEFORE the wildcard ID (for "hello/**/foo/bar" it would be hash of "hello")
|
||||
int InSuffixDepth = 0; // Number of labels in a path, after unknown base ID (for "hello/**/foo/bar" it would be 2)
|
||||
const char* InSuffix = nullptr; // A label string which appears on ID stack after unknown base ID (for "hello/**/foo/bar" it would be "foo/bar")
|
||||
const char* InSuffixLastItem = nullptr; // A last label string (for "hello/**/foo/bar" it would be "bar")
|
||||
ImGuiID InSuffixLastItemHash = 0;
|
||||
ImGuiItemStatusFlags InFilterItemStatusFlags = 0; // Flags required for item to be returned
|
||||
|
||||
// Output
|
||||
ImGuiID OutItemId = 0; // Result item ID
|
||||
};
|
||||
|
||||
enum ImGuiTestInputType
|
||||
{
|
||||
ImGuiTestInputType_None,
|
||||
ImGuiTestInputType_Key,
|
||||
ImGuiTestInputType_Char,
|
||||
ImGuiTestInputType_ViewportFocus,
|
||||
ImGuiTestInputType_ViewportSetPos,
|
||||
ImGuiTestInputType_ViewportSetSize,
|
||||
ImGuiTestInputType_ViewportClose
|
||||
};
|
||||
|
||||
// FIXME: May want to strip further now that core imgui is using its own input queue
|
||||
struct ImGuiTestInput
|
||||
{
|
||||
ImGuiTestInputType Type = ImGuiTestInputType_None;
|
||||
ImGuiKeyChord KeyChord = ImGuiKey_None;
|
||||
ImWchar Char = 0;
|
||||
bool Down = false;
|
||||
ImGuiID ViewportId = 0;
|
||||
ImVec2 ViewportPosSize;
|
||||
|
||||
static ImGuiTestInput ForKeyChord(ImGuiKeyChord key_chord, bool down)
|
||||
{
|
||||
ImGuiTestInput inp;
|
||||
inp.Type = ImGuiTestInputType_Key;
|
||||
inp.KeyChord = key_chord;
|
||||
inp.Down = down;
|
||||
return inp;
|
||||
}
|
||||
|
||||
static ImGuiTestInput ForChar(ImWchar v)
|
||||
{
|
||||
ImGuiTestInput inp;
|
||||
inp.Type = ImGuiTestInputType_Char;
|
||||
inp.Char = v;
|
||||
return inp;
|
||||
}
|
||||
|
||||
static ImGuiTestInput ForViewportFocus(ImGuiID viewport_id)
|
||||
{
|
||||
ImGuiTestInput inp;
|
||||
inp.Type = ImGuiTestInputType_ViewportFocus;
|
||||
inp.ViewportId = viewport_id;
|
||||
return inp;
|
||||
}
|
||||
|
||||
static ImGuiTestInput ForViewportSetPos(ImGuiID viewport_id, const ImVec2& pos)
|
||||
{
|
||||
ImGuiTestInput inp;
|
||||
inp.Type = ImGuiTestInputType_ViewportSetPos;
|
||||
inp.ViewportId = viewport_id;
|
||||
inp.ViewportPosSize = pos;
|
||||
return inp;
|
||||
}
|
||||
|
||||
static ImGuiTestInput ForViewportSetSize(ImGuiID viewport_id, const ImVec2& size)
|
||||
{
|
||||
ImGuiTestInput inp;
|
||||
inp.Type = ImGuiTestInputType_ViewportSetSize;
|
||||
inp.ViewportId = viewport_id;
|
||||
inp.ViewportPosSize = size;
|
||||
return inp;
|
||||
}
|
||||
|
||||
static ImGuiTestInput ForViewportClose(ImGuiID viewport_id)
|
||||
{
|
||||
ImGuiTestInput inp;
|
||||
inp.Type = ImGuiTestInputType_ViewportClose;
|
||||
inp.ViewportId = viewport_id;
|
||||
return inp;
|
||||
}
|
||||
};
|
||||
|
||||
struct ImGuiTestInputs
|
||||
{
|
||||
ImVec2 MousePosValue; // Own non-rounded copy of MousePos in order facilitate simulating mouse movement very slow speed and high-framerate
|
||||
ImVec2 MouseWheel;
|
||||
ImGuiID MouseHoveredViewport = 0;
|
||||
int MouseButtonsValue = 0x00; // FIXME-TESTS: Use simulated_io.MouseDown[] ?
|
||||
ImVector<ImGuiTestInput> Queue;
|
||||
bool HostEscDown = false;
|
||||
float HostEscDownDuration = -1.0f; // Maintain our own DownDuration for host/backend ESC key so we can abort.
|
||||
ImVec2 HostMousePos;
|
||||
};
|
||||
|
||||
// [Internal] Test Engine Context
|
||||
struct ImGuiTestEngine
|
||||
{
|
||||
ImGuiTestEngineIO IO;
|
||||
ImGuiContext* UiContextTarget = nullptr; // imgui context for testing
|
||||
ImGuiContext* UiContextActive = nullptr; // imgui context for testing == UiContextTarget or nullptr
|
||||
|
||||
bool Started = false;
|
||||
bool UiContextHasHooks = false;
|
||||
ImU64 BatchStartTime = 0;
|
||||
ImU64 BatchEndTime = 0;
|
||||
int FrameCount = 0;
|
||||
float OverrideDeltaTime = -1.0f; // Inject custom delta time into imgui context to simulate clock passing faster than wall clock time.
|
||||
ImVector<ImGuiTest*> TestsAll;
|
||||
ImVector<ImGuiTestRunTask> TestsQueue;
|
||||
ImGuiTestContext* TestContext = nullptr; // Running test context
|
||||
bool TestsSourceLinesDirty = false;
|
||||
ImVector<ImGuiTestInfoTask*>InfoTasks;
|
||||
ImGuiTestGatherTask GatherTask;
|
||||
ImGuiTestFindByLabelTask FindByLabelTask;
|
||||
ImGuiTestCoroutineHandle TestQueueCoroutine = nullptr; // Coroutine to run the test queue
|
||||
bool TestQueueCoroutineShouldExit = false; // Flag to indicate that we are shutting down and the test queue coroutine should stop
|
||||
ImGuiTextBuffer StringBuilderForChecks;
|
||||
|
||||
// Inputs
|
||||
ImGuiTestInputs Inputs;
|
||||
|
||||
// UI support
|
||||
bool Abort = false;
|
||||
ImGuiTest* UiSelectAndScrollToTest = nullptr;
|
||||
ImGuiTest* UiSelectedTest = nullptr;
|
||||
Str* UiFilterTests;
|
||||
Str* UiFilterPerfs;
|
||||
ImU32 UiFilterByStatusMask = ~0u;
|
||||
bool UiMetricsOpen = false;
|
||||
bool UiDebugLogOpen = false;
|
||||
bool UiCaptureToolOpen = false;
|
||||
bool UiStackToolOpen = false;
|
||||
bool UiPerfToolOpen = false;
|
||||
float UiLogHeight = 150.0f;
|
||||
|
||||
// Performance Monitor
|
||||
double PerfRefDeltaTime;
|
||||
ImMovingAverage<double> PerfDeltaTime100;
|
||||
ImMovingAverage<double> PerfDeltaTime500;
|
||||
ImGuiPerfTool* PerfTool = nullptr;
|
||||
|
||||
// Screen/Video Capturing
|
||||
ImGuiCaptureToolUI CaptureTool; // Capture tool UI
|
||||
ImGuiCaptureContext CaptureContext; // Capture context used in tests
|
||||
ImGuiCaptureArgs* CaptureCurrentArgs = nullptr;
|
||||
|
||||
// Tools
|
||||
bool PostSwapCalled = false;
|
||||
bool ToolDebugRebootUiContext = false; // Completely shutdown and recreate the dear imgui context in place
|
||||
bool ToolSlowDown = false;
|
||||
int ToolSlowDownMs = 100;
|
||||
ImGuiTestRunSpeed BackupConfigRunSpeed = ImGuiTestRunSpeed_Fast;
|
||||
bool BackupConfigNoThrottle = false;
|
||||
|
||||
// Functions
|
||||
ImGuiTestEngine();
|
||||
~ImGuiTestEngine();
|
||||
};
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// INTERNAL FUNCTIONS
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
ImGuiTestItemInfo* ImGuiTestEngine_FindItemInfo(ImGuiTestEngine* engine, ImGuiID id, const char* debug_id);
|
||||
void ImGuiTestEngine_Yield(ImGuiTestEngine* engine);
|
||||
void ImGuiTestEngine_SetDeltaTime(ImGuiTestEngine* engine, float delta_time);
|
||||
int ImGuiTestEngine_GetFrameCount(ImGuiTestEngine* engine);
|
||||
bool ImGuiTestEngine_PassFilter(ImGuiTest* test, const char* filter);
|
||||
void ImGuiTestEngine_RunTest(ImGuiTestEngine* engine, ImGuiTestContext* ctx, ImGuiTest* test, ImGuiTestRunFlags run_flags);
|
||||
|
||||
void ImGuiTestEngine_BindImGuiContext(ImGuiTestEngine* engine, ImGuiContext* ui_ctx);
|
||||
void ImGuiTestEngine_UnbindImGuiContext(ImGuiTestEngine* engine, ImGuiContext* ui_ctx);
|
||||
|
||||
void ImGuiTestEngine_RebootUiContext(ImGuiTestEngine* engine);
|
||||
ImGuiPerfTool* ImGuiTestEngine_GetPerfTool(ImGuiTestEngine* engine);
|
||||
void ImGuiTestEngine_UpdateTestsSourceLines(ImGuiTestEngine* engine);
|
||||
|
||||
// Screen/Video Capturing
|
||||
bool ImGuiTestEngine_CaptureScreenshot(ImGuiTestEngine* engine, ImGuiCaptureArgs* args);
|
||||
bool ImGuiTestEngine_CaptureBeginVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args);
|
||||
bool ImGuiTestEngine_CaptureEndVideo(ImGuiTestEngine* engine, ImGuiCaptureArgs* args);
|
||||
|
||||
// Helper functions
|
||||
const char* ImGuiTestEngine_GetStatusName(ImGuiTestStatus v);
|
||||
const char* ImGuiTestEngine_GetRunSpeedName(ImGuiTestRunSpeed v);
|
||||
const char* ImGuiTestEngine_GetVerboseLevelName(ImGuiTestVerboseLevel v);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
+1978
File diff suppressed because it is too large
Load Diff
+134
@@ -0,0 +1,134 @@
|
||||
// dear imgui test engine
|
||||
// (performance tool)
|
||||
// Browse and visualize samples recorded by ctx->PerfCapture() calls.
|
||||
// User access via 'Test Engine UI -> Tools -> Perf Tool'
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
// Forward Declaration
|
||||
struct ImGuiPerfToolColumnInfo;
|
||||
struct ImGuiTestEngine;
|
||||
struct ImGuiCsvParser;
|
||||
|
||||
// Configuration
|
||||
#define IMGUI_PERFLOG_DEFAULT_FILENAME "output/imgui_perflog.csv"
|
||||
|
||||
// [Internal] Perf log entry. Changes to this struct should be reflected in ImGuiTestContext::PerfCapture() and ImGuiTestEngine_Start().
|
||||
// This struct assumes strings stored here will be available until next ImGuiPerfTool::Clear() call. Fortunately we do not have to actively
|
||||
// manage lifetime of these strings. New entries are created only in two cases:
|
||||
// 1. ImGuiTestEngine_PerfToolAppendToCSV() call after perf test has run. This call receives ImGuiPerfToolEntry with const strings stored indefinitely by application.
|
||||
// 2. As a consequence of ImGuiPerfTool::LoadCSV() call, we persist the ImGuiCSVParser instance, which keeps parsed CSV text, from which strings are referenced.
|
||||
// As a result our solution also doesn't make many allocations.
|
||||
struct IMGUI_API ImGuiPerfToolEntry
|
||||
{
|
||||
ImU64 Timestamp = 0; // Title of a particular batch of perftool entries.
|
||||
const char* Category = nullptr; // Name of category perf test is in.
|
||||
const char* TestName = nullptr; // Name of perf test.
|
||||
double DtDeltaMs = 0.0; // Result of perf test.
|
||||
double DtDeltaMsMin = +FLT_MAX; // May be used by perftool.
|
||||
double DtDeltaMsMax = -FLT_MAX; // May be used by perftool.
|
||||
int NumSamples = 1; // Number aggregated samples.
|
||||
int PerfStressAmount = 0; //
|
||||
const char* GitBranchName = nullptr; // Build information.
|
||||
const char* BuildType = nullptr; //
|
||||
const char* Cpu = nullptr; //
|
||||
const char* OS = nullptr; //
|
||||
const char* Compiler = nullptr; //
|
||||
const char* Date = nullptr; // Date of this entry or min date of combined entries.
|
||||
//const char* DateMax = nullptr; // Max date of combined entries, or nullptr.
|
||||
double VsBaseline = 0.0; // Percent difference vs baseline.
|
||||
int LabelIndex = 0; // Index of TestName in ImGuiPerfTool::_LabelsVisible.
|
||||
|
||||
ImGuiPerfToolEntry() { }
|
||||
ImGuiPerfToolEntry(const ImGuiPerfToolEntry& rhs) { Set(rhs); }
|
||||
ImGuiPerfToolEntry& operator=(const ImGuiPerfToolEntry& rhs){ Set(rhs); return *this; }
|
||||
void Set(const ImGuiPerfToolEntry& rhs);
|
||||
};
|
||||
|
||||
// [Internal] Perf log batch.
|
||||
struct ImGuiPerfToolBatch
|
||||
{
|
||||
ImU64 BatchID = 0; // Timestamp of the batch, or unique ID of the build in combined mode.
|
||||
int NumSamples = 0; // A number of unique batches aggregated.
|
||||
int BranchIndex = 0; // For per-branch color mapping.
|
||||
ImVector<ImGuiPerfToolEntry> Entries; // Aggregated perf test entries. Order follows ImGuiPerfTool::_LabelsVisible order.
|
||||
~ImGuiPerfToolBatch() { Entries.clear_destruct(); } // FIXME: Misleading: nothing to destruct in that struct?
|
||||
};
|
||||
|
||||
enum ImGuiPerfToolDisplayType : int
|
||||
{
|
||||
ImGuiPerfToolDisplayType_Simple, // Each run will be displayed individually.
|
||||
ImGuiPerfToolDisplayType_PerBranchColors, // Use one bar color per branch.
|
||||
ImGuiPerfToolDisplayType_CombineByBuildInfo, // Entries with same build information will be averaged.
|
||||
};
|
||||
|
||||
//
|
||||
struct IMGUI_API ImGuiPerfTool
|
||||
{
|
||||
ImVector<ImGuiPerfToolEntry>_SrcData; // Raw entries from CSV file (with string pointer into CSV data).
|
||||
ImVector<const char*> _Labels;
|
||||
ImVector<const char*> _LabelsVisible; // ImPlot requires a pointer of all labels beforehand. Always contains a dummy "" entry at the end!
|
||||
ImVector<ImGuiPerfToolBatch> _Batches;
|
||||
ImGuiStorage _LabelBarCounts; // Number bars each label will render.
|
||||
int _NumVisibleBuilds = 0; // Cached number of visible builds.
|
||||
int _NumUniqueBuilds = 0; // Cached number of unique builds.
|
||||
ImGuiPerfToolDisplayType _DisplayType = ImGuiPerfToolDisplayType_CombineByBuildInfo;
|
||||
int _BaselineBatchIndex = 0; // Index of baseline build.
|
||||
ImU64 _BaselineTimestamp = 0;
|
||||
ImU64 _BaselineBuildId = 0;
|
||||
char _Filter[128]; // Context menu filtering substring.
|
||||
char _FilterDateFrom[11] = {};
|
||||
char _FilterDateTo[11] = {};
|
||||
float _InfoTableHeight = 180.0f;
|
||||
int _AlignStress = 0; // Alignment values for build info components, so they look aligned in the legend.
|
||||
int _AlignType = 0;
|
||||
int _AlignOs = 0;
|
||||
int _AlignCpu = 0;
|
||||
int _AlignCompiler = 0;
|
||||
int _AlignBranch = 0;
|
||||
int _AlignSamples = 0;
|
||||
bool _InfoTableSortDirty = false;
|
||||
ImVector<ImU64> _InfoTableSort; // _InfoTableSort[_LabelsVisible.Size * _Batches.Size]. Contains sorted batch indices for each label.
|
||||
const ImGuiTableSortSpecs* _InfoTableSortSpecs = nullptr; // Current table sort specs.
|
||||
ImGuiStorage _TempSet; // Used as a set
|
||||
int _TableHoveredTest = -1; // Index within _VisibleLabelPointers array.
|
||||
int _TableHoveredBatch = -1;
|
||||
int _PlotHoverTest = -1;
|
||||
int _PlotHoverBatch = -1;
|
||||
bool _PlotHoverTestLabel = false;
|
||||
bool _ReportGenerating = false;
|
||||
ImGuiStorage _Visibility;
|
||||
ImGuiCsvParser* _CsvParser = nullptr; // We keep this around and point to its fields
|
||||
|
||||
ImGuiPerfTool();
|
||||
~ImGuiPerfTool();
|
||||
|
||||
void Clear();
|
||||
bool LoadCSV(const char* filename = nullptr);
|
||||
void AddEntry(ImGuiPerfToolEntry* entry);
|
||||
|
||||
void ShowPerfToolWindow(ImGuiTestEngine* engine, bool* p_open);
|
||||
void ViewOnly(const char* perf_name);
|
||||
void ViewOnly(const char** perf_names);
|
||||
ImGuiPerfToolEntry* GetEntryByBatchIdx(int idx, const char* perf_name = nullptr);
|
||||
bool SaveHtmlReport(const char* file_name, const char* image_file = nullptr);
|
||||
inline bool Empty() { return _SrcData.empty(); }
|
||||
|
||||
void _Rebuild();
|
||||
bool _IsVisibleBuild(ImGuiPerfToolBatch* batch);
|
||||
bool _IsVisibleBuild(ImGuiPerfToolEntry* batch);
|
||||
bool _IsVisibleTest(const char* test_name);
|
||||
void _CalculateLegendAlignment();
|
||||
void _ShowEntriesPlot();
|
||||
void _ShowEntriesTable();
|
||||
void _SetBaseline(int batch_index);
|
||||
void _AddSettingsHandler();
|
||||
void _UnpackSortedKey(ImU64 key, int* batch_index, int* entry_index, int* monotonic_index = nullptr);
|
||||
};
|
||||
|
||||
IMGUI_API void ImGuiTestEngine_PerfToolAppendToCSV(ImGuiPerfTool* perf_log, ImGuiPerfToolEntry* entry, const char* filename = nullptr);
|
||||
+953
@@ -0,0 +1,953 @@
|
||||
// dear imgui test engine
|
||||
// (ui)
|
||||
// If you run tests in an interactive or visible application, you may want to call ImGuiTestEngine_ShowTestEngineWindows()
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include "imgui_te_ui.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "imgui_te_engine.h"
|
||||
#include "imgui_te_context.h"
|
||||
#include "imgui_te_internal.h"
|
||||
#include "imgui_te_perftool.h"
|
||||
#include "thirdparty/Str/Str.h"
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// TEST ENGINE: USER INTERFACE
|
||||
//-------------------------------------------------------------------------
|
||||
// - DrawTestLog() [internal]
|
||||
// - GetVerboseLevelName() [internal]
|
||||
// - ShowTestGroup() [internal]
|
||||
// - ImGuiTestEngine_ShowTestEngineWindows()
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Look for " filename:number " in the string and add menu option to open source.
|
||||
static bool ParseLineAndDrawFileOpenItemForSourceFile(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end)
|
||||
{
|
||||
const char* separator = ImStrchrRange(line_start, line_end, ':');
|
||||
if (separator == nullptr)
|
||||
return false;
|
||||
|
||||
const char* path_end = separator;
|
||||
const char* path_begin = separator - 1;
|
||||
while (path_begin > line_start&& path_begin[-1] != ' ')
|
||||
path_begin--;
|
||||
if (path_begin == path_end)
|
||||
return false;
|
||||
|
||||
int line_no = -1;
|
||||
sscanf(separator + 1, "%d ", &line_no);
|
||||
if (line_no == -1)
|
||||
return false;
|
||||
|
||||
Str256f buf("Open '%.*s' at line %d", (int)(path_end - path_begin), path_begin, line_no);
|
||||
if (ImGui::MenuItem(buf.c_str()))
|
||||
{
|
||||
// FIXME-TESTS: Assume folder is same as folder of test->SourceFile!
|
||||
const char* src_path = test->SourceFile;
|
||||
const char* src_name = ImPathFindFilename(src_path);
|
||||
buf.setf("%.*s%.*s", (int)(src_name - src_path), src_path, (int)(path_end - path_begin), path_begin);
|
||||
|
||||
ImGuiTestEngine_OpenSourceFile(e, buf.c_str(), line_no);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Look for "[ ,"]filename.png" in the string and add menu option to open image.
|
||||
static bool ParseLineAndDrawFileOpenItemForImageFile(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end, const char* file_ext)
|
||||
{
|
||||
IM_UNUSED(e);
|
||||
IM_UNUSED(test);
|
||||
|
||||
const char* extension = ImStristr(line_start, line_end, file_ext, nullptr);
|
||||
if (extension == nullptr)
|
||||
return false;
|
||||
|
||||
const char* path_end = extension + strlen(file_ext);
|
||||
const char* path_begin = extension - 1;
|
||||
while (path_begin > line_start && path_begin[-1] != ' ' && path_begin[-1] != '\'' && path_begin[-1] != '\"')
|
||||
path_begin--;
|
||||
if (path_begin == path_end)
|
||||
return false;
|
||||
|
||||
Str256 buf;
|
||||
|
||||
// Open file
|
||||
buf.setf("Open file: %.*s", (int)(path_end - path_begin), path_begin);
|
||||
if (ImGui::MenuItem(buf.c_str()))
|
||||
{
|
||||
buf.setf("%.*s", (int)(path_end - path_begin), path_begin);
|
||||
ImPathFixSeparatorsForCurrentOS(buf.c_str());
|
||||
ImOsOpenInShell(buf.c_str());
|
||||
}
|
||||
|
||||
// Open folder
|
||||
const char* folder_begin = path_begin;
|
||||
const char* folder_end = ImPathFindFilename(path_begin, path_end);
|
||||
buf.setf("Open folder: %.*s", (int)(folder_end - folder_begin), path_begin);
|
||||
if (ImGui::MenuItem(buf.c_str()))
|
||||
{
|
||||
buf.setf("%.*s", (int)(folder_end - folder_begin), folder_begin);
|
||||
ImPathFixSeparatorsForCurrentOS(buf.c_str());
|
||||
ImOsOpenInShell(buf.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseLineAndDrawFileOpenItem(ImGuiTestEngine* e, ImGuiTest* test, const char* line_start, const char* line_end)
|
||||
{
|
||||
if (ParseLineAndDrawFileOpenItemForSourceFile(e, test, line_start, line_end))
|
||||
return true;
|
||||
if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".png"))
|
||||
return true;
|
||||
if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".gif"))
|
||||
return true;
|
||||
if (ParseLineAndDrawFileOpenItemForImageFile(e, test, line_start, line_end, ".mp4"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static float GetDpiScale()
|
||||
{
|
||||
#ifdef IMGUI_HAS_VIEWPORT
|
||||
return ImGui::GetWindowViewport()->DpiScale;
|
||||
#elif IMGUI_VERSION_NUM >= 19197
|
||||
return ImGui::GetStyle()._MainScale;
|
||||
#else
|
||||
return 1.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void DrawTestLog(ImGuiTestEngine* e, ImGuiTest* test)
|
||||
{
|
||||
const ImU32 error_col = IM_COL32(255, 150, 150, 255);
|
||||
const ImU32 warning_col = IM_COL32(240, 240, 150, 255);
|
||||
const ImU32 unimportant_col = IM_COL32(190, 190, 190, 255);
|
||||
const float dpi_scale = GetDpiScale();
|
||||
|
||||
ImGuiTestOutput* test_output = &test->Output;
|
||||
|
||||
ImGuiTestLog* log = &test_output->Log;
|
||||
const char* text = log->Buffer.begin();
|
||||
const char* text_end = log->Buffer.end();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 2.0f) * dpi_scale);
|
||||
ImGuiListClipper clipper;
|
||||
ImGuiTestVerboseLevel max_log_level = test_output->Status == ImGuiTestStatus_Error ? e->IO.ConfigVerboseLevelOnError : e->IO.ConfigVerboseLevel;
|
||||
int line_count = log->ExtractLinesForVerboseLevels(ImGuiTestVerboseLevel_Silent, max_log_level, nullptr);
|
||||
int current_index_clipped = -1;
|
||||
int current_index_abs = 0;
|
||||
clipper.Begin(line_count);
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
|
||||
{
|
||||
// Advance index_by_log_level to find log entry indicated by line_no.
|
||||
ImGuiTestLogLineInfo* line_info = nullptr;
|
||||
while (current_index_clipped < line_no)
|
||||
{
|
||||
line_info = &log->LineInfo[current_index_abs];
|
||||
if (line_info->Level <= max_log_level)
|
||||
current_index_clipped++;
|
||||
current_index_abs++;
|
||||
}
|
||||
|
||||
const char* line_start = text + line_info->LineOffset;
|
||||
const char* line_end = strchr(line_start, '\n');
|
||||
if (line_end == nullptr)
|
||||
line_end = text_end;
|
||||
|
||||
switch (line_info->Level)
|
||||
{
|
||||
case ImGuiTestVerboseLevel_Error:
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, error_col);
|
||||
break;
|
||||
case ImGuiTestVerboseLevel_Warning:
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, warning_col);
|
||||
break;
|
||||
case ImGuiTestVerboseLevel_Debug:
|
||||
case ImGuiTestVerboseLevel_Trace:
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, unimportant_col);
|
||||
break;
|
||||
default:
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32_WHITE);
|
||||
break;
|
||||
}
|
||||
#if IMGUI_VERSION_NUM >= 19072
|
||||
ImGui::DebugTextUnformattedWithLocateItem(line_start, line_end);
|
||||
#else
|
||||
ImGui::TextUnformatted(line_start, line_end);
|
||||
#endif
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::PushID(line_no);
|
||||
if (ImGui::BeginPopupContextItem("Context"))
|
||||
{
|
||||
if (!ParseLineAndDrawFileOpenItem(e, test, line_start, line_end))
|
||||
ImGui::MenuItem("No options", nullptr, false, false);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper
|
||||
if (test->Output.Status == ImGuiTestStatus_Error)
|
||||
{
|
||||
if (ImGui::TextLink("How to investigate a failing test?"))
|
||||
ImGui::OpenPopup("Help");
|
||||
if (ImGui::BeginPopup("Help"))
|
||||
{
|
||||
ImGui::BulletText("%s", "Click '[X] KeepGui' (io.ConfigKeepGuiFunc) to view and interact with failing state.");
|
||||
ImGui::BulletText("%s", "Click '[X] Break' (io.ConfigBreakOnError) to break in debugger.");
|
||||
ImGui::BulletText("%s", "Click '[X] Capture' (io.ConfigCaptureOnError) to capture image of failing state to disk.");
|
||||
ImGui::BulletText("%s", "Log: Right-click on a filename to see open options.");
|
||||
ImGui::BulletText("%s", "Log: Hover hex identifiers to locate items on the screen.");
|
||||
ImGui::BulletText("%s", "Log: Increase Verbose Level (top row of this window) to get a more detailed log.");
|
||||
ImGui::BulletText("%s", "Log: Use 'Tools->Debug Log->Configure Outputs..' to send IMGUI_DEBUG_LOG() output here.");
|
||||
ImGui::BulletText("%s", "Call IM_SUSPEND_TESTFUNC() from TestFunc to view and interact with state at any given point.");
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
#if IMGUI_VERSION_NUM <= 18963
|
||||
namespace ImGui
|
||||
{
|
||||
void SetItemTooltip(const char* fmt, ...)
|
||||
{
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
ImGui::SetTooltipV(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
} // namespace ImGui
|
||||
#endif
|
||||
|
||||
static bool ShowTestGroupFilterTest(ImGuiTestEngine* e, ImGuiTestGroup group, const char* filter, ImGuiTest* test)
|
||||
{
|
||||
if (test->Group != group)
|
||||
return false;
|
||||
if (!ImGuiTestEngine_PassFilter(test, *filter ? filter : "all"))
|
||||
return false;
|
||||
if ((e->UiFilterByStatusMask & (1 << test->Output.Status)) == 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void GetFailingTestsAsString(ImGuiTestEngine* e, ImGuiTestGroup group, char separator, Str* out_string)
|
||||
{
|
||||
IM_ASSERT(out_string != nullptr);
|
||||
bool first = true;
|
||||
for (int i = 0; i < e->TestsAll.Size; i++)
|
||||
{
|
||||
ImGuiTest* failing_test = e->TestsAll[i];
|
||||
Str* filter = (group == ImGuiTestGroup_Tests) ? e->UiFilterTests : e->UiFilterPerfs;
|
||||
if (failing_test->Group != group)
|
||||
continue;
|
||||
if (failing_test->Output.Status != ImGuiTestStatus_Error)
|
||||
continue;
|
||||
if (!ImGuiTestEngine_PassFilter(failing_test, filter->empty() ? "all" : filter->c_str()))
|
||||
continue;
|
||||
if (!first)
|
||||
out_string->append(separator);
|
||||
out_string->append(failing_test->Name);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void TestStatusButton(const char* id, const ImVec4& color, bool running, int display_counter)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop | ImGuiItemFlags_NoNav, true);
|
||||
ImGui::ColorButton(id, color, ImGuiColorEditFlags_NoTooltip);
|
||||
ImGui::PopItemFlag();
|
||||
if (running)
|
||||
{
|
||||
//ImRect r = g.LastItemData.Rect;
|
||||
ImVec2 center = g.LastItemData.Rect.GetCenter();
|
||||
float radius = ImFloor(ImMin(g.LastItemData.Rect.GetWidth(), g.LastItemData.Rect.GetHeight()) * 0.40f);
|
||||
float t = (float)(ImGui::GetTime() * 20.0f);
|
||||
ImVec2 off(ImCos(t) * radius, ImSin(t) * radius);
|
||||
ImGui::GetWindowDrawList()->AddLine(center - off, center + off, ImGui::GetColorU32(ImGuiCol_Text), 1.5f);
|
||||
//ImGui::RenderText(r.Min + style.FramePadding + ImVec2(0, 0), &"|\0/\0-\0\\"[(((ImGui::GetFrameCount() / 5) & 3) << 1)], nullptr);
|
||||
}
|
||||
else if (display_counter >= 0)
|
||||
{
|
||||
ImVec2 center = g.LastItemData.Rect.GetCenter();
|
||||
Str30f buf("%d", display_counter);
|
||||
ImGui::GetWindowDrawList()->AddText(center - ImGui::CalcTextSize(buf.c_str()) * 0.5f, ImGui::GetColorU32(ImGuiCol_Text), buf.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void ShowTestGroup(ImGuiTestEngine* e, ImGuiTestGroup group, Str* filter, bool run)
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
const float dpi_scale = GetDpiScale();
|
||||
|
||||
// Colored Status button: will be displayed later below
|
||||
// - Save position of test run status button and make space for it.
|
||||
const ImVec2 status_button_pos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeight() + style.ItemInnerSpacing.x);
|
||||
|
||||
//ImGui::Text("TESTS (%d)", engine->TestsAll.Size);
|
||||
#if IMGUI_VERSION_NUM >= 19066
|
||||
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_R, ImGuiInputFlags_Tooltip | ImGuiInputFlags_RouteFromRootWindow);
|
||||
run |= ImGui::Button("Run");
|
||||
#elif IMGUI_VERSION_NUM >= 18837
|
||||
run |= ImGui::Button("Run") || ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_R);
|
||||
#if IMGUI_VERSION_NUM > 18963
|
||||
ImGui::SetItemTooltip("Ctrl+R");
|
||||
#endif
|
||||
#else
|
||||
run |= ImGui::Button("Run");
|
||||
#endif
|
||||
if (run)
|
||||
{
|
||||
for (int n = 0; n < e->TestsAll.Size; n++)
|
||||
{
|
||||
ImGuiTest* test = e->TestsAll[n];
|
||||
if (!ShowTestGroupFilterTest(e, group, filter->c_str(), test))
|
||||
continue;
|
||||
ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_RunFromGui);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
{
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
|
||||
const char* filter_by_status_desc = "";
|
||||
if (e->UiFilterByStatusMask == ~0u)
|
||||
filter_by_status_desc = "All";
|
||||
else if (e->UiFilterByStatusMask == ~(1u << ImGuiTestStatus_Success))
|
||||
filter_by_status_desc = "Not OK";
|
||||
else if (e->UiFilterByStatusMask == (1u << ImGuiTestStatus_Error))
|
||||
filter_by_status_desc = "Errors";
|
||||
if (ImGui::BeginCombo("##filterbystatus", filter_by_status_desc))
|
||||
{
|
||||
if (ImGui::Selectable("All", e->UiFilterByStatusMask == ~0u))
|
||||
e->UiFilterByStatusMask = (ImU32)~0u;
|
||||
if (ImGui::Selectable("Not OK", e->UiFilterByStatusMask == ~(1u << ImGuiTestStatus_Success)))
|
||||
e->UiFilterByStatusMask = (ImU32)~(1u << ImGuiTestStatus_Success);
|
||||
if (ImGui::Selectable("Errors", e->UiFilterByStatusMask == (1u << ImGuiTestStatus_Error)))
|
||||
e->UiFilterByStatusMask = (ImU32)(1u << ImGuiTestStatus_Error);
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
const char* perflog_label = "Perf Tool";
|
||||
float filter_width = ImGui::GetContentRegionAvail().x;
|
||||
float perf_stress_factor_width = (30 * dpi_scale);
|
||||
if (group == ImGuiTestGroup_Perfs)
|
||||
{
|
||||
filter_width -= style.ItemSpacing.x + perf_stress_factor_width;
|
||||
filter_width -= style.ItemSpacing.x + style.FramePadding.x * 2 + ImGui::CalcTextSize(perflog_label).x;
|
||||
}
|
||||
filter_width -= ImGui::CalcTextSize("(?)").x + style.ItemSpacing.x;
|
||||
ImGui::SetNextItemWidth(ImMax(20.0f, filter_width));
|
||||
#if IMGUI_VERSION_NUM >= 19066
|
||||
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip | ImGuiInputFlags_RouteFromRootWindow);
|
||||
#endif
|
||||
ImGui::InputText("##filter", filter);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(?)");
|
||||
ImGui::SetItemTooltip("Query is composed of one or more comma-separated filter terms with optional modifiers.\n"
|
||||
"Available modifiers:\n"
|
||||
"- '-' prefix excludes tests matched by the term.\n"
|
||||
"- '^' prefix anchors term matching to the start of the string.\n"
|
||||
"- '$' suffix anchors term matching to the end of the string.");
|
||||
if (group == ImGuiTestGroup_Perfs)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(perf_stress_factor_width);
|
||||
ImGui::DragInt("##PerfStress", &e->IO.PerfStressAmount, 0.1f, 1, 20, "x%d");
|
||||
ImGui::SetItemTooltip("Increase workload of performance tests (higher means longer run)."); // FIXME: Move?
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(perflog_label))
|
||||
{
|
||||
e->UiPerfToolOpen = true;
|
||||
ImGui::FocusWindow(ImGui::FindWindowByName("Dear ImGui Perf Tool"));
|
||||
}
|
||||
}
|
||||
|
||||
int tests_completed = 0;
|
||||
int tests_succeeded = 0;
|
||||
int tests_failed = 0;
|
||||
ImVector<ImGuiTest*> tests_to_remove;
|
||||
|
||||
// Set table child window to use _NavFlattened. WIP/Undocumented. (#8280)
|
||||
#if IMGUI_VERSION_NUM >= 19183
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
if (!(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags))
|
||||
g.NextWindowData.ChildFlags = 0;
|
||||
g.NextWindowData.ChildFlags |= ImGuiChildFlags_NavFlattened;
|
||||
g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ImGui::BeginTable("Tests", 3, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_SizingFixedFit))
|
||||
{
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableSetupColumn("Status");
|
||||
ImGui::TableSetupColumn("Category");
|
||||
ImGui::TableSetupColumn("Test", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6, 4) * dpi_scale);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 0) * dpi_scale);
|
||||
//ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(100, 10) * dpi_scale);
|
||||
for (int test_n = 0; test_n < e->TestsAll.Size; test_n++)
|
||||
{
|
||||
ImGuiTest* test = e->TestsAll[test_n];
|
||||
if (!ShowTestGroupFilterTest(e, group, filter->c_str(), test))
|
||||
continue;
|
||||
|
||||
ImGuiTestOutput* test_output = &test->Output;
|
||||
ImGuiTestContext* test_context = (e->TestContext && e->TestContext->Test == test) ? e->TestContext : nullptr; // Running context, if any
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PushID(test_n);
|
||||
|
||||
// Colors match general test status colors defined below.
|
||||
ImVec4 status_color;
|
||||
switch (test_output->Status)
|
||||
{
|
||||
case ImGuiTestStatus_Error:
|
||||
status_color = ImVec4(0.9f, 0.1f, 0.1f, 1.0f);
|
||||
tests_completed++;
|
||||
tests_failed++;
|
||||
break;
|
||||
case ImGuiTestStatus_Success:
|
||||
status_color = ImVec4(0.1f, 0.9f, 0.1f, 1.0f);
|
||||
tests_completed++;
|
||||
tests_succeeded++;
|
||||
break;
|
||||
case ImGuiTestStatus_Queued:
|
||||
case ImGuiTestStatus_Running:
|
||||
case ImGuiTestStatus_Suspended:
|
||||
if (test_context && (test_context->RunFlags & ImGuiTestRunFlags_GuiFuncOnly))
|
||||
status_color = ImVec4(0.8f, 0.0f, 0.8f, 1.0f);
|
||||
else
|
||||
status_color = ImVec4(0.8f, 0.4f, 0.1f, 1.0f);
|
||||
break;
|
||||
default:
|
||||
status_color = ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
TestStatusButton("status", status_color, test_output->Status == ImGuiTestStatus_Running || test_output->Status == ImGuiTestStatus_Suspended, -1);
|
||||
ImGui::SameLine();
|
||||
|
||||
bool queue_test = false;
|
||||
bool queue_gui_func_toggle = false;
|
||||
bool select_test = false;
|
||||
|
||||
if (test_output->Status == ImGuiTestStatus_Suspended)
|
||||
{
|
||||
// Resume IM_SUSPEND_TESTFUNC
|
||||
// FIXME: Terrible user experience to have this here.
|
||||
if (ImGui::Button("Con###Run"))
|
||||
test_output->Status = ImGuiTestStatus_Running;
|
||||
ImGui::SetItemTooltip("CTRL+Space to continue.");
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Space) && io.KeyCtrl)
|
||||
test_output->Status = ImGuiTestStatus_Running;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::Button("Run###Run"))
|
||||
queue_test = select_test = true;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Selectable(test->Category, test == e->UiSelectedTest, ImGuiSelectableFlags_SpanAllColumns | (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnNav))
|
||||
select_test = true;
|
||||
|
||||
// Double-click to run test, CTRL+Double-click to run GUI function
|
||||
const bool is_running_gui_func = (test_context && (test_context->RunFlags & ImGuiTestRunFlags_GuiFuncOnly));
|
||||
const bool has_gui_func = (test->GuiFunc != nullptr);
|
||||
if ((ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) || (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter))) // FIXME: How to properly handle that with selectable
|
||||
{
|
||||
if (ImGui::GetIO().KeyCtrl)
|
||||
queue_gui_func_toggle = true;
|
||||
else
|
||||
queue_test = true;
|
||||
}
|
||||
|
||||
/*if (ImGui::IsItemHovered() && test->TestLog.size() > 0)
|
||||
{
|
||||
ImGui::BeginTooltip();
|
||||
DrawTestLog(engine, test, false);
|
||||
ImGui::EndTooltip();
|
||||
}*/
|
||||
|
||||
if (e->UiSelectAndScrollToTest == test)
|
||||
ImGui::SetScrollHereY();
|
||||
|
||||
bool view_source = false;
|
||||
if (ImGui::BeginPopupContextItem())
|
||||
{
|
||||
select_test = true;
|
||||
|
||||
if (ImGui::MenuItem("Run test"))
|
||||
queue_test = true;
|
||||
if (ImGui::MenuItem("Run GUI func", "Ctrl+DblClick", is_running_gui_func, has_gui_func))
|
||||
queue_gui_func_toggle = true;
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
const bool open_source_available = (test->SourceFile != nullptr) && (e->IO.SrcFileOpenFunc != nullptr);
|
||||
|
||||
Str128 buf;
|
||||
if (test->SourceFile != nullptr) // This is normally set by IM_REGISTER_TEST() but custom registration may omit it.
|
||||
buf.setf("Open source (%s:%d)", ImPathFindFilename(test->SourceFile), test->SourceLine);
|
||||
else
|
||||
buf.set("Open source");
|
||||
if (ImGui::MenuItem(buf.c_str(), nullptr, false, open_source_available))
|
||||
ImGuiTestEngine_OpenSourceFile(e, test->SourceFile, test->SourceLine);
|
||||
if (ImGui::MenuItem("View source...", nullptr, false, test->SourceFile != nullptr))
|
||||
view_source = true;
|
||||
|
||||
if (group == ImGuiTestGroup_Perfs && ImGui::MenuItem("View perflog"))
|
||||
{
|
||||
e->PerfTool->ViewOnly(test->Name);
|
||||
e->UiPerfToolOpen = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Copy name", nullptr, false))
|
||||
ImGui::SetClipboardText(test->Name);
|
||||
|
||||
if (test_output->Status == ImGuiTestStatus_Error)
|
||||
if (ImGui::MenuItem("Copy names of all failing tests"))
|
||||
{
|
||||
Str256 failing_tests;
|
||||
GetFailingTestsAsString(e, group, ',', &failing_tests);
|
||||
ImGui::SetClipboardText(failing_tests.c_str());
|
||||
}
|
||||
|
||||
ImGuiTestLog* test_log = &test_output->Log;
|
||||
if (ImGui::BeginMenu("Copy log", !test_log->IsEmpty()))
|
||||
{
|
||||
for (int level_n = ImGuiTestVerboseLevel_Error; level_n < ImGuiTestVerboseLevel_COUNT; level_n++)
|
||||
{
|
||||
ImGuiTestVerboseLevel level = (ImGuiTestVerboseLevel)level_n;
|
||||
int count = test_log->ExtractLinesForVerboseLevels((ImGuiTestVerboseLevel)0, level, nullptr);
|
||||
if (ImGui::MenuItem(Str64f("%s (%d lines)", ImGuiTestEngine_GetVerboseLevelName(level), count).c_str(), nullptr, false, count > 0))
|
||||
{
|
||||
ImGuiTextBuffer buffer;
|
||||
test_log->ExtractLinesForVerboseLevels((ImGuiTestVerboseLevel)0, level, &buffer);
|
||||
ImGui::SetClipboardText(buffer.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Clear log", nullptr, false, !test_log->IsEmpty()))
|
||||
test_log->Clear();
|
||||
|
||||
// [DEBUG] Simple way to exercise ImGuiTestEngine_UnregisterTest()
|
||||
//ImGui::Separator();
|
||||
//if (ImGui::MenuItem("Remove test"))
|
||||
// tests_to_remove.push_back(test);
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// Process source popup
|
||||
static ImGuiTextBuffer source_blurb;
|
||||
static int goto_line = -1;
|
||||
if (view_source)
|
||||
{
|
||||
source_blurb.clear();
|
||||
size_t file_size = 0;
|
||||
char* file_data = (char*)ImFileLoadToMemory(test->SourceFile, "rb", &file_size);
|
||||
if (file_data)
|
||||
source_blurb.append(file_data, file_data + file_size);
|
||||
else
|
||||
source_blurb.append("<Error loading sources>");
|
||||
goto_line = test->SourceLine;
|
||||
ImGui::OpenPopup("Source");
|
||||
}
|
||||
if (ImGui::BeginPopup("Source"))
|
||||
{
|
||||
const ImVec2 start_pos = ImGui::GetCursorScreenPos();
|
||||
const float line_height = ImGui::GetTextLineHeight();
|
||||
if (goto_line != -1)
|
||||
ImGui::SetScrollY(ImMax((goto_line - 5) * line_height, 0.0f));
|
||||
goto_line = -1;
|
||||
|
||||
ImRect r(0.0f, (test->SourceLine - 1) * line_height, ImGui::GetWindowWidth(), (test->SourceLineEnd - 1) * line_height);
|
||||
ImGui::GetWindowDrawList()->AddRectFilled(start_pos + r.Min, start_pos + r.Max, IM_COL32(80, 80, 150, 100));
|
||||
|
||||
ImGui::TextUnformatted(source_blurb.c_str(), source_blurb.end());
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(test->Name);
|
||||
|
||||
// Process selection
|
||||
if (select_test)
|
||||
e->UiSelectedTest = test;
|
||||
|
||||
// Process queuing
|
||||
if (queue_gui_func_toggle && is_running_gui_func)
|
||||
ImGuiTestEngine_AbortCurrentTest(e);
|
||||
else if (queue_gui_func_toggle && !e->IO.IsRunningTests)
|
||||
ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_RunFromGui | ImGuiTestRunFlags_GuiFuncOnly);
|
||||
if (queue_test)
|
||||
{
|
||||
if (e->IO.IsRunningTests)
|
||||
ImGuiTestEngine_AbortCurrentTest(e);
|
||||
else
|
||||
ImGuiTestEngine_QueueTest(e, test, ImGuiTestRunFlags_RunFromGui);
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
// Process removal
|
||||
for (ImGuiTest* test : tests_to_remove)
|
||||
ImGuiTestEngine_UnregisterTest(e, test);
|
||||
|
||||
// Display test status recap (colors match per-test run button colors defined above)
|
||||
{
|
||||
ImVec4 status_color;
|
||||
if (tests_failed > 0)
|
||||
status_color = ImVec4(0.9f, 0.1f, 0.1f, 1.0f); // Red
|
||||
else if (e->IO.IsRunningTests)
|
||||
status_color = ImVec4(0.8f, 0.4f, 0.1f, 1.0f);
|
||||
else if (tests_succeeded > 0 && tests_completed == tests_succeeded)
|
||||
status_color = ImVec4(0.1f, 0.9f, 0.1f, 1.0f);
|
||||
else
|
||||
status_color = ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
|
||||
//ImVec2 cursor_pos_bkp = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPos(status_button_pos);
|
||||
TestStatusButton("status", status_color, false, tests_failed > 0 ? tests_failed : -1);// e->IO.IsRunningTests);
|
||||
ImGui::SetItemTooltip("Filtered: %d\n- OK: %d\n- Errors: %d", tests_completed, tests_succeeded, tests_failed);
|
||||
//ImGui::SetCursorPos(cursor_pos_bkp); // Restore cursor position for rendering further widgets
|
||||
}
|
||||
}
|
||||
|
||||
static void ImGuiTestEngine_ShowLogAndTools(ImGuiTestEngine* engine)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
const float dpi_scale = GetDpiScale();
|
||||
|
||||
if (!ImGui::BeginTabBar("##tools"))
|
||||
return;
|
||||
|
||||
if (ImGui::BeginTabItem("LOG"))
|
||||
{
|
||||
ImGuiTest* selected_test = engine->UiSelectedTest;
|
||||
|
||||
if (selected_test != nullptr)
|
||||
ImGui::Text("Log for '%s' '%s'", selected_test->Category, selected_test->Name);
|
||||
else
|
||||
ImGui::Text("N/A");
|
||||
if (ImGui::SmallButton("Clear"))
|
||||
if (selected_test)
|
||||
selected_test->Output.Log.Clear();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Copy to clipboard"))
|
||||
if (engine->UiSelectedTest)
|
||||
ImGui::SetClipboardText(selected_test->Output.Log.Buffer.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::BeginChild("Log");
|
||||
if (engine->UiSelectedTest)
|
||||
{
|
||||
DrawTestLog(engine, engine->UiSelectedTest);
|
||||
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
|
||||
ImGui::SetScrollHereY();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
// Options
|
||||
if (ImGui::BeginTabItem("OPTIONS"))
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::Text("%.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
||||
ImGui::Text("TestEngine: HookItems: %d, HookPushId: %d, InfoTasks: %d, MaxAppSpeed: %d", g.TestEngineHookItems,
|
||||
#if IMGUI_VERSION_NUM < 19229
|
||||
g.DebugHookIdInfo != 0,
|
||||
#else
|
||||
g.DebugHookIdInfoId != 0,
|
||||
#endif
|
||||
engine->InfoTasks.Size,
|
||||
engine->IO.IsRequestingMaxAppSpeed);
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Reboot UI context"))
|
||||
engine->ToolDebugRebootUiContext = true;
|
||||
|
||||
const ImGuiInputTextCallback filter_callback = [](ImGuiInputTextCallbackData* data) { return (data->EventChar == ',' || data->EventChar == ';') ? 1 : 0; };
|
||||
ImGui::InputText("Branch/Annotation", engine->IO.GitBranchName, IM_COUNTOF(engine->IO.GitBranchName), ImGuiInputTextFlags_CallbackCharFilter, filter_callback, nullptr);
|
||||
ImGui::SetItemTooltip("This will be stored in the CSV file for performance tools.");
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::TreeNode("Screen/video capture"))
|
||||
{
|
||||
ImGui::Checkbox("Capture when requested by API", &engine->IO.ConfigCaptureEnabled);
|
||||
ImGui::SetItemTooltip("Enable or disable screen capture API completely.");
|
||||
ImGui::Checkbox("Capture screen on error", &engine->IO.ConfigCaptureOnError);
|
||||
ImGui::SetItemTooltip("Capture a screenshot on test failure.");
|
||||
|
||||
// Fields modified by in this call will be synced to engine->CaptureContext.
|
||||
engine->CaptureTool._ShowEncoderConfigFields(&engine->CaptureContext);
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNode("Performances"))
|
||||
{
|
||||
ImGui::Checkbox("Slow down whole app", &engine->ToolSlowDown);
|
||||
ImGui::SameLine(); ImGui::SetNextItemWidth(70 * dpi_scale);
|
||||
ImGui::SliderInt("##ms", &engine->ToolSlowDownMs, 0, 400, "%d ms");
|
||||
|
||||
// FIXME-TESTS: Need to be visualizing the samples/spikes.
|
||||
double dt_1 = 1.0 / ImGui::GetIO().Framerate;
|
||||
double fps_now = 1.0 / dt_1;
|
||||
double dt_100 = engine->PerfDeltaTime100.GetAverage();
|
||||
double dt_500 = engine->PerfDeltaTime500.GetAverage();
|
||||
|
||||
//if (engine->PerfRefDeltaTime <= 0.0 && engine->PerfRefDeltaTime.IsFull())
|
||||
// engine->PerfRefDeltaTime = dt_2000;
|
||||
|
||||
ImGui::Checkbox("Unthrolled", &engine->IO.ConfigNoThrottle);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Pick ref dt"))
|
||||
engine->PerfRefDeltaTime = dt_500;
|
||||
|
||||
double dt_ref = engine->PerfRefDeltaTime;
|
||||
ImGui::Text("[ref dt] %6.3f ms", engine->PerfRefDeltaTime * 1000);
|
||||
ImGui::Text("[last 001] %6.3f ms (%.1f FPS) ++ %6.3f ms", dt_1 * 1000.0, 1.0 / dt_1, (dt_1 - dt_ref) * 1000);
|
||||
ImGui::Text("[last 100] %6.3f ms (%.1f FPS) ++ %6.3f ms ~ converging in %.1f secs", dt_100 * 1000.0, 1.0 / dt_100, (dt_1 - dt_ref) * 1000, 100.0 / fps_now);
|
||||
ImGui::Text("[last 500] %6.3f ms (%.1f FPS) ++ %6.3f ms ~ converging in %.1f secs", dt_500 * 1000.0, 1.0 / dt_500, (dt_1 - dt_ref) * 1000, 500.0 / fps_now);
|
||||
|
||||
//ImGui::PlotLines("Last 100", &engine->PerfDeltaTime100.Samples.Data, engine->PerfDeltaTime100.Samples.Size, engine->PerfDeltaTime100.Idx, nullptr, 0.0f, dt_1000 * 1.10f, ImVec2(0.0f, ImGui::GetFontSize()));
|
||||
ImVec2 plot_size(0.0f, ImGui::GetFrameHeight() * 3);
|
||||
ImMovingAverage<double>* ma = &engine->PerfDeltaTime500;
|
||||
ImGui::PlotLines("Last 500",
|
||||
[](void* data, int n) { ImMovingAverage<double>* ma = (ImMovingAverage<double>*)data; return (float)(ma->Samples[n] * 1000); },
|
||||
ma, ma->Samples.Size, 0 * ma->Idx, nullptr, 0.0f, (float)(ImMax(dt_100, dt_500) * 1000.0 * 1.2f), plot_size);
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNode("Dear ImGui Configuration Flags"))
|
||||
{
|
||||
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableKeyboard", &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);
|
||||
ImGui::CheckboxFlags("io.ConfigFlags: NavEnableGamepad", &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad);
|
||||
#ifdef IMGUI_HAS_DOCK
|
||||
ImGui::Checkbox("io.ConfigDockingAlwaysTabBar", &io.ConfigDockingAlwaysTabBar);
|
||||
#endif
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
static void ImGuiTestEngine_ShowTestTool(ImGuiTestEngine* engine, bool* p_open)
|
||||
{
|
||||
const float dpi_scale = GetDpiScale();
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(ImGui::GetFontSize() * 50, ImGui::GetFontSize() * 40), ImGuiCond_FirstUseEver);
|
||||
if (!ImGui::Begin("Dear ImGui Test Engine", p_open, ImGuiWindowFlags_MenuBar))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
bool run = false;
|
||||
if (ImGui::BeginMenuBar())
|
||||
{
|
||||
if (ImGui::BeginMenu("Tests"))
|
||||
{
|
||||
// FIXME: This idiom showcases an issue with menus vs shortcuts. Would be nice if e.g. we could activate a shortcut?
|
||||
run = ImGui::MenuItem("Run Visible", "Ctrl+R");
|
||||
ImGui::MenuItem("Filter", "Ctrl+F");
|
||||
if (p_open != NULL && ImGui::MenuItem("Close"))
|
||||
*p_open = false;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Tools"))
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGui::MenuItem("Metrics/Debugger", "", &engine->UiMetricsOpen);
|
||||
ImGui::MenuItem("Debug Log", "", &engine->UiDebugLogOpen);
|
||||
ImGui::MenuItem("Stack Tool", "", &engine->UiStackToolOpen);
|
||||
ImGui::MenuItem("Item Picker", "", &g.DebugItemPickerActive);
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Capture Tool", "", &engine->UiCaptureToolOpen);
|
||||
ImGui::MenuItem("Perf Tool", "", &engine->UiPerfToolOpen);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
// Run Speed
|
||||
{
|
||||
ImGui::SetNextItemWidth(90 * dpi_scale);
|
||||
if (ImGui::BeginCombo("##RunSpeed", ImGuiTestEngine_GetRunSpeedName(engine->IO.ConfigRunSpeed), ImGuiComboFlags_None))
|
||||
{
|
||||
for (ImGuiTestRunSpeed level = (ImGuiTestRunSpeed)0; level < ImGuiTestRunSpeed_COUNT; level = (ImGuiTestRunSpeed)(level + 1))
|
||||
if (ImGui::Selectable(ImGuiTestEngine_GetRunSpeedName(level), engine->IO.ConfigRunSpeed == level))
|
||||
engine->IO.ConfigRunSpeed = level;
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip(
|
||||
"Running speed\n"
|
||||
"- Fast: Run tests as fast as possible (no delay/vsync, teleport mouse, etc.).\n"
|
||||
"- Normal: Run tests at human watchable speed (for debugging).\n"
|
||||
"- Cinematic: Run tests with pauses between actions (for e.g. tutorials)."
|
||||
);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
// Verbose Level
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGui::SetNextItemWidth(ImGui::CalcTextSize(ImGuiTestEngine_GetVerboseLevelName(ImGuiTestVerboseLevel_Warning)).x + style.FramePadding.x * 4.0f + ImGui::GetFontSize());
|
||||
if (ImGui::BeginCombo("##Verbose", ImGuiTestEngine_GetVerboseLevelName(engine->IO.ConfigVerboseLevel), ImGuiComboFlags_None))
|
||||
{
|
||||
for (ImGuiTestVerboseLevel level = (ImGuiTestVerboseLevel)0; level < ImGuiTestVerboseLevel_COUNT; level = (ImGuiTestVerboseLevel)(level + 1))
|
||||
if (ImGui::Selectable(ImGuiTestEngine_GetVerboseLevelName(level), engine->IO.ConfigVerboseLevel == level))
|
||||
engine->IO.ConfigVerboseLevel = engine->IO.ConfigVerboseLevelOnError = level;
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SetItemTooltip("Verbose level.");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
|
||||
// (Would be good if we exposed horizontal layout mode..)
|
||||
ImGui::Checkbox("Stop", &engine->IO.ConfigStopOnError);
|
||||
ImGui::SetItemTooltip("When hitting an error:\n- Stop running other tests.");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Break", &engine->IO.ConfigBreakOnError);
|
||||
ImGui::SetItemTooltip("When hitting an error:\n- Break in debugger.");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Capture", &engine->IO.ConfigCaptureOnError);
|
||||
ImGui::SetItemTooltip("When hitting an error:\n- Capture screen to PNG. Right-click filename in Test Log to open.");
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Checkbox("KeepGui", &engine->IO.ConfigKeepGuiFunc);
|
||||
ImGui::SetItemTooltip("After running single test or hitting an error:\n- Keep GUI function visible and interactive.\n- Hold ESC to abort a running GUI function.");
|
||||
ImGui::SameLine();
|
||||
bool keep_focus = !engine->IO.ConfigRestoreFocusAfterTests;
|
||||
if (ImGui::Checkbox("KeepFocus", &keep_focus))
|
||||
engine->IO.ConfigRestoreFocusAfterTests = !keep_focus;
|
||||
ImGui::SetItemTooltip("After running tests:\n- Keep GUI current focus, instead of restoring focus to this window.");
|
||||
|
||||
//ImGui::PopStyleVar();
|
||||
ImGui::Separator();
|
||||
|
||||
// SPLITTER
|
||||
// FIXME-OPT: A better splitter API supporting arbitrary number of splits would be useful.
|
||||
float list_height = 0.0f;
|
||||
float& log_height = engine->UiLogHeight;
|
||||
ImGui::Splitter("splitter", &list_height, &log_height, ImGuiAxis_Y, +1);
|
||||
|
||||
// TESTS
|
||||
ImGui::BeginChild("List", ImVec2(0, list_height), false, ImGuiWindowFlags_NoScrollbar);
|
||||
if (ImGui::BeginTabBar("##Tests", ImGuiTabBarFlags_NoTooltip)) // Add _NoPushId flag in TabBar?
|
||||
{
|
||||
if (ImGui::BeginTabItem("TESTS", nullptr, ImGuiTabItemFlags_NoPushId))
|
||||
{
|
||||
ShowTestGroup(engine, ImGuiTestGroup_Tests, engine->UiFilterTests, run);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("PERFS", nullptr, ImGuiTabItemFlags_NoPushId))
|
||||
{
|
||||
ShowTestGroup(engine, ImGuiTestGroup_Perfs, engine->UiFilterPerfs, run);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
engine->UiSelectAndScrollToTest = nullptr;
|
||||
|
||||
// LOG & TOOLS
|
||||
ImGui::BeginChild("Log", ImVec2(0, log_height));
|
||||
ImGuiTestEngine_ShowLogAndTools(engine);
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ImGuiTestEngine_ShowTestEngineWindows(ImGuiTestEngine* e, bool* p_open)
|
||||
{
|
||||
if (e->TestsSourceLinesDirty)
|
||||
ImGuiTestEngine_UpdateTestsSourceLines(e);
|
||||
|
||||
// Test Tool
|
||||
ImGuiTestEngine_ShowTestTool(e, p_open);
|
||||
|
||||
// Stack Tool
|
||||
#if IMGUI_VERSION_NUM < 18993
|
||||
if (e->UiStackToolOpen)
|
||||
ImGui::ShowStackToolWindow(&e->UiStackToolOpen);
|
||||
#else
|
||||
if (e->UiStackToolOpen)
|
||||
ImGui::ShowIDStackToolWindow(&e->UiStackToolOpen);
|
||||
#endif
|
||||
|
||||
// Capture Tool
|
||||
if (e->UiCaptureToolOpen)
|
||||
e->CaptureTool.ShowCaptureToolWindow(&e->CaptureContext, &e->UiCaptureToolOpen);
|
||||
|
||||
// Performance tool
|
||||
if (e->UiPerfToolOpen)
|
||||
e->PerfTool->ShowPerfToolWindow(e, &e->UiPerfToolOpen);;
|
||||
|
||||
// Show Dear ImGui windows
|
||||
// (we cannot show demo window here because it could lead to duplicate display, which demo windows isn't guarded for)
|
||||
if (e->UiMetricsOpen)
|
||||
ImGui::ShowMetricsWindow(&e->UiMetricsOpen);
|
||||
if (e->UiDebugLogOpen)
|
||||
ImGui::ShowDebugLogWindow(&e->UiDebugLogOpen);
|
||||
}
|
||||
|
||||
void ImGuiTestEngine_OpenSourceFile(ImGuiTestEngine* e, const char* source_filename, int source_line_no)
|
||||
{
|
||||
ImGuiTestEngineIO& e_io = ImGuiTestEngine_GetIO(e);
|
||||
if (e_io.SrcFileOpenFunc == nullptr)
|
||||
ImOsOpenInShell(source_filename); // This is never used by imgui_test_suite but we provide it as a second layer of convenience for test engine users.
|
||||
else
|
||||
e_io.SrcFileOpenFunc(source_filename, source_line_no, e_io.SrcFileOpenUserData);
|
||||
|
||||
// Debugger output which may be double-clicked
|
||||
// Print after opener so it appears in a neat place below e.g. DLL loading.
|
||||
if (ImGui::GetIO().ConfigDebugIsDebuggerPresent)
|
||||
ImOsOutputDebugString(Str256f("%s(%d): opening from user action.\n", source_filename, source_line_no).c_str());
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// dear imgui test engine
|
||||
// (ui)
|
||||
// If you run tests in an interactive or visible application, you may want to call ImGuiTestEngine_ShowTestEngineWindows()
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
// Provide access to:
|
||||
// - "Dear ImGui Test Engine" main interface
|
||||
// - "Dear ImGui Capture Tool"
|
||||
// - "Dear ImGui Perf Tool"
|
||||
// - other core debug functions: Metrics, Debug Log
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef IMGUI_VERSION
|
||||
#include "imgui.h" // IMGUI_API
|
||||
#endif
|
||||
|
||||
// Forward declarations
|
||||
struct ImGuiTestEngine;
|
||||
|
||||
// Functions
|
||||
IMGUI_API void ImGuiTestEngine_ShowTestEngineWindows(ImGuiTestEngine* engine, bool* p_open);
|
||||
IMGUI_API void ImGuiTestEngine_OpenSourceFile(ImGuiTestEngine* engine, const char* source_filename, int source_line_no);
|
||||
+1425
File diff suppressed because it is too large
Load Diff
+218
@@ -0,0 +1,218 @@
|
||||
// dear imgui test engine
|
||||
// (helpers/utilities. do NOT use this as a general purpose library)
|
||||
|
||||
// This file is governed by the "Dear ImGui Test Engine License".
|
||||
// Details of the license are provided in the LICENSE.txt file in the same directory.
|
||||
|
||||
#pragma once
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Includes
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include <math.h> // fabsf
|
||||
#include <stdint.h> // uint64_t
|
||||
#include <stdio.h> // FILE*
|
||||
#include "imgui.h" // ImGuiID, ImGuiKey
|
||||
class Str; // Str<> from thirdparty/Str/Str.h
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Hashing Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ImGuiID ImHashDecoratedPath(const char* str, const char* str_end = nullptr, ImGuiID seed = 0);
|
||||
const char* ImFindNextDecoratedPartInPath(const char* str, const char* str_end = nullptr);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// File/Directory Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool ImFileExist(const char* filename);
|
||||
bool ImFileDelete(const char* filename);
|
||||
bool ImFileCreateDirectoryChain(const char* path, const char* path_end = nullptr);
|
||||
bool ImFileFindInParents(const char* sub_path, int max_parent_count, Str* output);
|
||||
bool ImFileLoadSourceBlurb(const char* filename, int line_no_start, int line_no_end, ImGuiTextBuffer* out_buf);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Path Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Those are strictly string manipulation functions
|
||||
const char* ImPathFindFilename(const char* path, const char* path_end = nullptr); // Return value always between path and path_end
|
||||
const char* ImPathFindExtension(const char* path, const char* path_end = nullptr); // Return value always between path and path_end
|
||||
void ImPathFixSeparatorsForCurrentOS(char* buf);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// String Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ImStrReplace(Str* s, const char* find, const char* repl);
|
||||
const char* ImStrchrRangeWithEscaping(const char* str, const char* str_end, char find_c);
|
||||
void ImStrXmlEscape(Str* s);
|
||||
int ImStrBase64Encode(const unsigned char* src, char* dst, int length);
|
||||
void ImStrTrimTrailingZeroesFromFloat(char* buf, char* buf_end);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Parsing Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ImParseExtractArgcArgvFromCommandLine(int* out_argc, char const*** out_argv, const char* cmd_line);
|
||||
bool ImParseFindIniSection(const char* ini_config, const char* header, ImVector<char>* result);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Time Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
uint64_t ImTimeGetInMicroseconds();
|
||||
void ImTimestampToISO8601(uint64_t timestamp, Str* out_date);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Threading Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void ImThreadSleepInMilliseconds(int ms);
|
||||
void ImThreadSetCurrentThreadDescription(const char* description);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Build Info helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// All the pointers are expect to be literals/persistent
|
||||
struct ImBuildInfo
|
||||
{
|
||||
const char* Type = "";
|
||||
const char* Cpu = "";
|
||||
const char* OS = "";
|
||||
const char* Compiler = "";
|
||||
char Date[32]; // "YYYY-MM-DD"
|
||||
const char* Time = "";
|
||||
};
|
||||
|
||||
const ImBuildInfo* ImBuildGetCompilationInfo();
|
||||
bool ImBuildFindGitBranchName(const char* git_repo_path, Str* branch_name);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Operating System Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
enum ImOsConsoleStream
|
||||
{
|
||||
ImOsConsoleStream_StandardOutput,
|
||||
ImOsConsoleStream_StandardError,
|
||||
};
|
||||
|
||||
enum ImOsConsoleTextColor
|
||||
{
|
||||
ImOsConsoleTextColor_Black,
|
||||
ImOsConsoleTextColor_White,
|
||||
ImOsConsoleTextColor_BrightWhite,
|
||||
ImOsConsoleTextColor_BrightRed,
|
||||
ImOsConsoleTextColor_BrightGreen,
|
||||
ImOsConsoleTextColor_BrightBlue,
|
||||
ImOsConsoleTextColor_BrightYellow,
|
||||
};
|
||||
|
||||
bool ImOsCreateProcess(const char* cmd_line);
|
||||
FILE* ImOsPOpen(const char* cmd_line, const char* mode);
|
||||
void ImOsPClose(FILE* fp);
|
||||
void ImOsOpenInShell(const char* path);
|
||||
bool ImOsIsDebuggerPresent();
|
||||
void ImOsOutputDebugString(const char* message);
|
||||
void ImOsConsoleSetTextColor(ImOsConsoleStream stream, ImOsConsoleTextColor color);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Miscellaneous functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Tables functions
|
||||
struct ImGuiTable;
|
||||
ImGuiID TableGetHeaderID(ImGuiTable* table, const char* column, int instance_no = 0);
|
||||
ImGuiID TableGetHeaderID(ImGuiTable* table, int column_n, int instance_no = 0);
|
||||
void TableDiscardInstanceAndSettings(ImGuiID table_id);
|
||||
|
||||
// DrawData functions
|
||||
void DrawDataVerifyMatchingBufferCount(ImDrawData* draw_data);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helper: maintain/calculate moving average
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template<typename TYPE>
|
||||
struct ImMovingAverage
|
||||
{
|
||||
// Internal Fields
|
||||
ImVector<TYPE> Samples;
|
||||
TYPE Accum;
|
||||
int Idx;
|
||||
int FillAmount;
|
||||
|
||||
// Functions
|
||||
ImMovingAverage() { Accum = (TYPE)0; Idx = FillAmount = 0; }
|
||||
void Init(int count) { Samples.resize(count); memset(Samples.Data, 0, (size_t)Samples.Size * sizeof(TYPE)); Accum = (TYPE)0; Idx = FillAmount = 0; }
|
||||
void AddSample(TYPE v) { Accum += v - Samples[Idx]; Samples[Idx] = v; if (++Idx == Samples.Size) Idx = 0; if (FillAmount < Samples.Size) FillAmount++; }
|
||||
TYPE GetAverage() const { return Accum / (TYPE)FillAmount; }
|
||||
int GetSampleCount() const { return Samples.Size; }
|
||||
bool IsFull() const { return FillAmount == Samples.Size; }
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helper: Simple/dumb CSV parser
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
struct ImGuiCsvParser
|
||||
{
|
||||
// Public fields
|
||||
int Columns = 0; // Number of columns in CSV file.
|
||||
int Rows = 0; // Number of rows in CSV file.
|
||||
|
||||
// Internal fields
|
||||
char* _Data = nullptr; // CSV file data.
|
||||
ImVector<char*> _Index; // CSV table: _Index[row * _Columns + col].
|
||||
|
||||
// Functions
|
||||
ImGuiCsvParser(int columns = -1) { Columns = columns; }
|
||||
~ImGuiCsvParser() { Clear(); }
|
||||
bool Load(const char* file_name); // Open and parse a CSV file.
|
||||
void Clear(); // Free allocated buffers.
|
||||
const char* GetCell(int row, int col) { IM_ASSERT(0 <= row && row < Rows && 0 <= col && col < Columns); return _Index[row * Columns + col]; }
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Misc Dear ImGui extensions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if IMGUI_VERSION_NUM < 18924
|
||||
struct ImGuiTabBar;
|
||||
struct ImGuiTabItem;
|
||||
#endif
|
||||
|
||||
namespace ImGui
|
||||
{
|
||||
|
||||
IMGUI_API void ItemErrorFrame(ImU32 col);
|
||||
|
||||
#if IMGUI_VERSION_NUM < 18927
|
||||
ImGuiID TableGetInstanceID(ImGuiTable* table, int instance_no = 0);
|
||||
#endif
|
||||
|
||||
// Str support for InputText()
|
||||
IMGUI_API bool InputText(const char* label, Str* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr);
|
||||
IMGUI_API bool InputTextWithHint(const char* label, const char* hint, Str* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr);
|
||||
IMGUI_API bool InputTextMultiline(const char* label, Str* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr);
|
||||
|
||||
// Splitter
|
||||
IMGUI_API bool Splitter(const char* id, float* value_1, float* value_2, int axis, int anchor = 0, float min_size_0 = -1.0f, float min_size_1 = -1.0f);
|
||||
|
||||
// Misc
|
||||
IMGUI_API ImFont* FindFontByPrefix(const char* name);
|
||||
|
||||
// Legacy version support
|
||||
#if IMGUI_VERSION_NUM < 18924
|
||||
IMGUI_API const char* TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
|
||||
#endif
|
||||
|
||||
#if IMGUI_VERSION_NUM < 19256 && !defined(IM_COUNTOF)
|
||||
#define IM_COUNTOF IM_ARRAYSIZE
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
## Third party libraries used by Test Engine
|
||||
|
||||
Always used:
|
||||
- `Str/Str.h` simple string type, used by `imgui_test_engine` (Public Domain)
|
||||
|
||||
Used if `IMGUI_TEST_ENGINE_ENABLE_CAPTURE` is defined to 1 (default: 1)
|
||||
- `stb/imstb_image_write.h` image writer, used by `imgui_capture_tool` (MIT Licence OR Public Domain)
|
||||
@@ -0,0 +1,5 @@
|
||||
[*.{h,cpp}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
```
|
||||
Str
|
||||
Simple C++ string type with an optional local buffer, by Omar Cornut
|
||||
https://github.com/ocornut/str
|
||||
|
||||
LICENSE
|
||||
This software is in the public domain. Where that dedication is not
|
||||
recognized, you are granted a perpetual, irrevocable license to copy,
|
||||
distribute, and modify this file as you see fit.
|
||||
|
||||
USAGE
|
||||
Include Str.h in whatever places need to refer to it.
|
||||
In ONE .cpp file, write '#define STR_IMPLEMENTATION' before the #include.
|
||||
This expands out the actual implementation into that C/C++ file.
|
||||
|
||||
NOTES
|
||||
- This isn't a fully featured string class.
|
||||
- It is a simple, bearable replacement to std::string that isn't heap abusive nor bloated (can actually be debugged by humans!).
|
||||
- String are mutable. We don't maintain size so length() is not-constant time.
|
||||
- Maximum string size currently limited to 2 MB (we allocate 21 bits to hold capacity).
|
||||
- Local buffer size is currently limited to 1023 bytes (we allocate 10 bits to hold local buffer size).
|
||||
- We could easily raise those limits if we are ok to increase the structure overhead in 32-bits mode.
|
||||
- In "non-owned" mode for literals/reference we don't do any tracking/counting of references.
|
||||
- Overhead is 8-bytes in 32-bits, 16-bytes in 64-bits (12 + alignment).
|
||||
- I'm using this code but it hasn't been tested thoroughly.
|
||||
|
||||
The idea is that you can provide an arbitrary sized local buffer if you expect string to fit
|
||||
most of the time, and then you avoid using costly heap.
|
||||
|
||||
No local buffer, always use heap, sizeof()==8~16 (depends if your pointers are 32-bits or 64-bits)
|
||||
|
||||
Str s = "hey"; // use heap
|
||||
|
||||
With a local buffer of 16 bytes, sizeof() == 8~16 + 16 bytes.
|
||||
|
||||
Str16 s = "filename.h"; // copy into local buffer
|
||||
Str16 s = "long_filename_not_very_long_but_longer_than_expected.h"; // use heap
|
||||
|
||||
With a local buffer of 256 bytes, sizeof() == 8~16 + 256 bytes.
|
||||
|
||||
Str256 s = "long_filename_not_very_long_but_longer_than_expected.h"; // copy into local buffer
|
||||
|
||||
Common sizes are defined at the bottom of Str.h, you may define your own.
|
||||
|
||||
Functions:
|
||||
|
||||
Str256 s;
|
||||
s.set("hello sailor"); // set (copy)
|
||||
s.setf("%s/%s.tmp", folder, filename); // set (w/format)
|
||||
s.append("hello"); // append. cost a length() calculation!
|
||||
s.appendf("hello %d", 42); // append (w/format). cost a length() calculation!
|
||||
s.set_ref("Hey!"); // set (literal/reference, just copy pointer, no tracking)
|
||||
|
||||
Constructor helper for format string: add a trailing 'f' to the type. Underlying type is the same.
|
||||
|
||||
Str256f filename("%s/%s.tmp", folder, filename); // construct (w/format)
|
||||
fopen(Str256f("%s/%s.tmp, folder, filename).c_str(), "rb"); // construct (w/format), use as function param, destruct
|
||||
|
||||
Constructor helper for reference/literal:
|
||||
|
||||
StrRef ref("literal"); // copy pointer, no allocation, no string copy
|
||||
StrRef ref2(GetDebugName()); // copy pointer. no tracking of anything whatsoever, know what you are doing!
|
||||
|
||||
All StrXXX types derives from Str and instance hold the local buffer capacity.
|
||||
So you can pass e.g. Str256* to a function taking base type Str* and it will be functional!
|
||||
|
||||
void MyFunc(Str& s) { s = "Hello"; } // will use local buffer if available in Str instance
|
||||
|
||||
(Using a template e.g. Str<N> we could remove the LocalBufSize storage but it would make passing typed Str<> to functions tricky.
|
||||
Instead we don't use template so you can pass them around as the base type Str*. Also, templates are ugly.)
|
||||
```
|
||||
+660
@@ -0,0 +1,660 @@
|
||||
// Str v0.33
|
||||
// Simple C++ string type with an optional local buffer, by Omar Cornut
|
||||
// https://github.com/ocornut/str
|
||||
|
||||
// LICENSE
|
||||
// This software is in the public domain. Where that dedication is not
|
||||
// recognized, you are granted a perpetual, irrevocable license to copy,
|
||||
// distribute, and modify this file as you see fit.
|
||||
|
||||
// USAGE
|
||||
// Include this file in whatever places need to refer to it.
|
||||
// In ONE .cpp file, write '#define STR_IMPLEMENTATION' before the #include of this file.
|
||||
// This expands out the actual implementation into that C/C++ file.
|
||||
|
||||
|
||||
/*
|
||||
- This isn't a fully featured string class.
|
||||
- It is a simple, bearable replacement to std::string that isn't heap abusive nor bloated (can actually be debugged by humans).
|
||||
- String are mutable. We don't maintain size so length() is not-constant time.
|
||||
- Maximum string size currently limited to 2 MB (we allocate 21 bits to hold capacity).
|
||||
- Local buffer size is currently limited to 1023 bytes (we allocate 10 bits to hold local buffer size).
|
||||
- In "non-owned" mode for literals/reference we don't do any tracking/counting of references.
|
||||
- Overhead is 8-bytes in 32-bits, 16-bytes in 64-bits (12 + alignment).
|
||||
- This code hasn't been tested very much. it is probably incomplete or broken. Made it for my own use.
|
||||
|
||||
The idea is that you can provide an arbitrary sized local buffer if you expect string to fit
|
||||
most of the time, and then you avoid using costly heap.
|
||||
|
||||
No local buffer, always use heap, sizeof()==8~16 (depends if your pointers are 32-bits or 64-bits)
|
||||
|
||||
Str s = "hey";
|
||||
|
||||
With a local buffer of 16 bytes, sizeof() == 8~16 + 16 bytes.
|
||||
|
||||
Str16 s = "filename.h"; // copy into local buffer
|
||||
Str16 s = "long_filename_not_very_long_but_longer_than_expected.h"; // use heap
|
||||
|
||||
With a local buffer of 256 bytes, sizeof() == 8~16 + 256 bytes.
|
||||
|
||||
Str256 s = "long_filename_not_very_long_but_longer_than_expected.h"; // copy into local buffer
|
||||
|
||||
Common sizes are defined at the bottom of Str.h, you may define your own.
|
||||
|
||||
Functions:
|
||||
|
||||
Str256 s;
|
||||
s.set("hello sailor"); // set (copy)
|
||||
s.setf("%s/%s.tmp", folder, filename); // set (w/format)
|
||||
s.append("hello"); // append. cost a length() calculation!
|
||||
s.appendf("hello %d", 42); // append (w/format). cost a length() calculation!
|
||||
s.set_ref("Hey!"); // set (literal/reference, just copy pointer, no tracking)
|
||||
|
||||
Constructor helper for format string: add a trailing 'f' to the type. Underlying type is the same.
|
||||
|
||||
Str256f filename("%s/%s.tmp", folder, filename); // construct (w/format)
|
||||
fopen(Str256f("%s/%s.tmp, folder, filename).c_str(), "rb"); // construct (w/format), use as function param, destruct
|
||||
|
||||
Constructor helper for reference/literal:
|
||||
|
||||
StrRef ref("literal"); // copy pointer, no allocation, no string copy
|
||||
StrRef ref2(GetDebugName()); // copy pointer. no tracking of anything whatsoever, know what you are doing!
|
||||
|
||||
All StrXXX types derives from Str and instance hold the local buffer capacity. So you can pass e.g. Str256* to a function taking base type Str* and it will be functional.
|
||||
|
||||
void MyFunc(Str& s) { s = "Hello"; } // will use local buffer if available in Str instance
|
||||
|
||||
(Using a template e.g. Str<N> we could remove the LocalBufSize storage but it would make passing typed Str<> to functions tricky.
|
||||
Instead we don't use template so you can pass them around as the base type Str*. Also, templates are ugly.)
|
||||
*/
|
||||
|
||||
/*
|
||||
CHANGELOG
|
||||
0.33 - fixed capacity() return value to match standard. e.g. a Str256's capacity() now returns 255, not 256.
|
||||
0.32 - added owned() accessor.
|
||||
0.31 - fixed various warnings.
|
||||
0.30 - turned into a single header file, removed Str.cpp.
|
||||
0.29 - fixed bug when calling reserve on non-owned strings (ie. when using StrRef or set_ref), and fixed <string> include.
|
||||
0.28 - breaking change: replaced Str32 by Str30 to avoid collision with Str32 from MacTypes.h .
|
||||
0.27 - added STR_API and basic .natvis file.
|
||||
0.26 - fixed set(cont char* src, const char* src_end) writing null terminator to the wrong position.
|
||||
0.25 - allow set(const char* NULL) or operator= NULL to clear the string. note that set() from range or other types are not allowed.
|
||||
0.24 - allow set_ref(const char* NULL) to clear the string. include fixes for linux.
|
||||
0.23 - added append(char). added append_from(int idx, XXX) functions. fixed some compilers warnings.
|
||||
0.22 - documentation improvements, comments. fixes for some compilers.
|
||||
0.21 - added StrXXXf() constructor to construct directly from a format string.
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO
|
||||
- Since we lose 4-bytes of padding on 64-bits architecture, perhaps just spread the header to 8-bytes and lift size limits?
|
||||
- More functions/helpers.
|
||||
*/
|
||||
|
||||
#ifndef STR_INCLUDED
|
||||
#define STR_INCLUDED
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// CONFIGURATION
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
#ifndef STR_MEMALLOC
|
||||
#define STR_MEMALLOC malloc
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifndef STR_MEMFREE
|
||||
#define STR_MEMFREE free
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifndef STR_ASSERT
|
||||
#define STR_ASSERT assert
|
||||
#include <assert.h>
|
||||
#endif
|
||||
#ifndef STR_API
|
||||
#define STR_API
|
||||
#endif
|
||||
#include <stdarg.h> // for va_list
|
||||
#include <string.h> // for strlen, strcmp, memcpy, etc.
|
||||
|
||||
// Configuration: #define STR_SUPPORT_STD_STRING 0 to disable setters variants using const std::string& (on by default)
|
||||
#ifndef STR_SUPPORT_STD_STRING
|
||||
#define STR_SUPPORT_STD_STRING 1
|
||||
#endif
|
||||
|
||||
// Configuration: #define STR_DEFINE_STR32 1 to keep defining Str32/Str32f, but be warned: on macOS/iOS, MacTypes.h also defines a type named Str32.
|
||||
#ifndef STR_DEFINE_STR32
|
||||
#define STR_DEFINE_STR32 0
|
||||
#endif
|
||||
|
||||
#if STR_SUPPORT_STD_STRING
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// HEADERS
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// This is the base class that you can pass around
|
||||
// Footprint is 8-bytes (32-bits arch) or 16-bytes (64-bits arch)
|
||||
class STR_API Str
|
||||
{
|
||||
char* Data; // Point to LocalBuf() or heap allocated
|
||||
int Capacity : 21; // Max 2 MB. Exclude zero terminator.
|
||||
int LocalBufSize : 10; // Max 1023 bytes
|
||||
unsigned int Owned : 1; // Set when we have ownership of the pointed data (most common, unless using set_ref() method or StrRef constructor)
|
||||
|
||||
public:
|
||||
inline char* c_str() { return Data; }
|
||||
inline const char* c_str() const { return Data; }
|
||||
inline bool empty() const { return Data[0] == 0; }
|
||||
inline int length() const { return (int)strlen(Data); } // by design, allow user to write into the buffer at any time
|
||||
inline int capacity() const { return Capacity; }
|
||||
inline bool owned() const { return Owned ? true : false; }
|
||||
|
||||
inline void set_ref(const char* src);
|
||||
int setf(const char* fmt, ...);
|
||||
int setfv(const char* fmt, va_list args);
|
||||
int setf_nogrow(const char* fmt, ...);
|
||||
int setfv_nogrow(const char* fmt, va_list args);
|
||||
int append(char c);
|
||||
int append(const char* s, const char* s_end = NULL);
|
||||
int appendf(const char* fmt, ...);
|
||||
int appendfv(const char* fmt, va_list args);
|
||||
int append_from(int idx, char c);
|
||||
int append_from(int idx, const char* s, const char* s_end = NULL); // If you know the string length or want to append from a certain point
|
||||
int appendf_from(int idx, const char* fmt, ...);
|
||||
int appendfv_from(int idx, const char* fmt, va_list args);
|
||||
|
||||
void clear();
|
||||
void reserve(int cap);
|
||||
void reserve_discard(int cap);
|
||||
void shrink_to_fit();
|
||||
|
||||
inline char& operator[](size_t i) { return Data[i]; }
|
||||
inline char operator[](size_t i) const { return Data[i]; }
|
||||
//explicit operator const char*() const{ return Data; }
|
||||
|
||||
inline Str();
|
||||
inline Str(const char* rhs);
|
||||
inline void set(const char* src);
|
||||
inline void set(const char* src, const char* src_end);
|
||||
inline Str& operator=(const char* rhs) { set(rhs); return *this; }
|
||||
inline bool operator==(const char* rhs) const { return strcmp(c_str(), rhs) == 0; }
|
||||
|
||||
inline Str(const Str& rhs);
|
||||
inline void set(const Str& src);
|
||||
inline Str& operator=(const Str& rhs) { set(rhs); return *this; }
|
||||
inline bool operator==(const Str& rhs) const { return strcmp(c_str(), rhs.c_str()) == 0; }
|
||||
|
||||
#if STR_SUPPORT_STD_STRING
|
||||
inline Str(const std::string& rhs);
|
||||
inline void set(const std::string& src);
|
||||
inline Str& operator=(const std::string& rhs) { set(rhs); return *this; }
|
||||
inline bool operator==(const std::string& rhs)const { return strcmp(c_str(), rhs.c_str()) == 0; }
|
||||
#endif
|
||||
|
||||
// Destructor for all variants
|
||||
inline ~Str()
|
||||
{
|
||||
if (Owned && !is_using_local_buf())
|
||||
STR_MEMFREE(Data);
|
||||
}
|
||||
|
||||
static char* EmptyBuffer;
|
||||
|
||||
protected:
|
||||
inline char* local_buf() { return (char*)this + sizeof(Str); }
|
||||
inline const char* local_buf() const { return (char*)this + sizeof(Str); }
|
||||
inline bool is_using_local_buf() const { return Data == local_buf() && LocalBufSize != 0; }
|
||||
|
||||
// Constructor for StrXXX variants with local buffer
|
||||
Str(unsigned short local_buf_size)
|
||||
{
|
||||
STR_ASSERT(local_buf_size < 1024);
|
||||
Data = local_buf();
|
||||
Data[0] = '\0';
|
||||
Capacity = local_buf_size ? local_buf_size - 1 : 0;
|
||||
LocalBufSize = local_buf_size;
|
||||
Owned = 1;
|
||||
}
|
||||
};
|
||||
|
||||
void Str::set(const char* src)
|
||||
{
|
||||
// We allow set(NULL) or via = operator to clear the string.
|
||||
if (src == NULL)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
int buf_len = (int)strlen(src);
|
||||
if (Capacity < buf_len)
|
||||
reserve_discard(buf_len);
|
||||
memcpy(Data, src, (size_t)(buf_len + 1));
|
||||
Owned = 1;
|
||||
}
|
||||
|
||||
void Str::set(const char* src, const char* src_end)
|
||||
{
|
||||
STR_ASSERT(src != NULL && src_end >= src);
|
||||
int buf_len = (int)(src_end - src);
|
||||
if ((int)Capacity < buf_len)
|
||||
reserve_discard(buf_len);
|
||||
memcpy(Data, src, (size_t)buf_len);
|
||||
Data[buf_len] = 0;
|
||||
Owned = 1;
|
||||
}
|
||||
|
||||
void Str::set(const Str& src)
|
||||
{
|
||||
int buf_len = (int)strlen(src.c_str());
|
||||
if ((int)Capacity < buf_len)
|
||||
reserve_discard(buf_len);
|
||||
memcpy(Data, src.c_str(), (size_t)(buf_len + 1));
|
||||
Owned = 1;
|
||||
}
|
||||
|
||||
#if STR_SUPPORT_STD_STRING
|
||||
void Str::set(const std::string& src)
|
||||
{
|
||||
int buf_len = (int)src.length();
|
||||
if ((int)Capacity < buf_len)
|
||||
reserve_discard(buf_len);
|
||||
memcpy(Data, src.c_str(), (size_t)(buf_len + 1));
|
||||
Owned = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void Str::set_ref(const char* src)
|
||||
{
|
||||
if (Owned && !is_using_local_buf())
|
||||
STR_MEMFREE(Data);
|
||||
Data = src ? (char*)src : EmptyBuffer;
|
||||
Capacity = 0;
|
||||
Owned = 0;
|
||||
}
|
||||
|
||||
Str::Str()
|
||||
{
|
||||
Data = EmptyBuffer; // Shared READ-ONLY initial buffer for 0 capacity
|
||||
Capacity = 0;
|
||||
LocalBufSize = 0;
|
||||
Owned = 0;
|
||||
}
|
||||
|
||||
Str::Str(const Str& rhs) : Str()
|
||||
{
|
||||
set(rhs);
|
||||
}
|
||||
|
||||
Str::Str(const char* rhs) : Str()
|
||||
{
|
||||
set(rhs);
|
||||
}
|
||||
|
||||
#if STR_SUPPORT_STD_STRING
|
||||
Str::Str(const std::string& rhs) : Str()
|
||||
{
|
||||
set(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Literal/reference string
|
||||
class StrRef : public Str
|
||||
{
|
||||
public:
|
||||
StrRef(const char* s) : Str() { set_ref(s); }
|
||||
};
|
||||
|
||||
// Types embedding a local buffer
|
||||
// NB: we need to override the constructor and = operator for both Str& and TYPENAME (without the later compiler will call a default copy operator)
|
||||
#if STR_SUPPORT_STD_STRING
|
||||
|
||||
#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
|
||||
class TYPENAME : public Str \
|
||||
{ \
|
||||
char local_buf[LOCALBUFSIZE]; \
|
||||
public: \
|
||||
TYPENAME() : Str(LOCALBUFSIZE) {} \
|
||||
TYPENAME(const Str& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
|
||||
TYPENAME(const char* rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
|
||||
TYPENAME(const TYPENAME& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
|
||||
TYPENAME(const std::string& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
|
||||
TYPENAME& operator=(const char* rhs) { set(rhs); return *this; } \
|
||||
TYPENAME& operator=(const Str& rhs) { set(rhs); return *this; } \
|
||||
TYPENAME& operator=(const TYPENAME& rhs) { set(rhs); return *this; } \
|
||||
TYPENAME& operator=(const std::string& rhs) { set(rhs); return *this; } \
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
|
||||
class TYPENAME : public Str \
|
||||
{ \
|
||||
char local_buf[LOCALBUFSIZE]; \
|
||||
public: \
|
||||
TYPENAME() : Str(LOCALBUFSIZE) {} \
|
||||
TYPENAME(const Str& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
|
||||
TYPENAME(const char* rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
|
||||
TYPENAME(const TYPENAME& rhs) : Str(LOCALBUFSIZE) { set(rhs); } \
|
||||
TYPENAME& operator=(const char* rhs) { set(rhs); return *this; } \
|
||||
TYPENAME& operator=(const Str& rhs) { set(rhs); return *this; } \
|
||||
TYPENAME& operator=(const TYPENAME& rhs) { set(rhs); return *this; } \
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// Disable PVS-Studio warning V730: Not all members of a class are initialized inside the constructor (local_buf is not initialized and that is fine)
|
||||
// -V:STR_DEFINETYPE:730
|
||||
|
||||
// Helper to define StrXXXf constructors
|
||||
#define STR_DEFINETYPE_F(TYPENAME, TYPENAME_F) \
|
||||
class TYPENAME_F : public TYPENAME \
|
||||
{ \
|
||||
public: \
|
||||
TYPENAME_F(const char* fmt, ...) : TYPENAME() { va_list args; va_start(args, fmt); setfv(fmt, args); va_end(args); } \
|
||||
};
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-private-field" // warning : private field 'local_buf' is not used
|
||||
#endif
|
||||
|
||||
// Declaring types for common sizes here
|
||||
STR_DEFINETYPE(Str16, 16)
|
||||
STR_DEFINETYPE(Str30, 30)
|
||||
STR_DEFINETYPE(Str64, 64)
|
||||
STR_DEFINETYPE(Str128, 128)
|
||||
STR_DEFINETYPE(Str256, 256)
|
||||
STR_DEFINETYPE(Str512, 512)
|
||||
|
||||
// Declaring helper constructors to pass in format strings in one statement
|
||||
STR_DEFINETYPE_F(Str16, Str16f)
|
||||
STR_DEFINETYPE_F(Str30, Str30f)
|
||||
STR_DEFINETYPE_F(Str64, Str64f)
|
||||
STR_DEFINETYPE_F(Str128, Str128f)
|
||||
STR_DEFINETYPE_F(Str256, Str256f)
|
||||
STR_DEFINETYPE_F(Str512, Str512f)
|
||||
|
||||
#if STR_DEFINE_STR32
|
||||
STR_DEFINETYPE(Str32, 32)
|
||||
STR_DEFINETYPE_F(Str32, Str32f)
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#endif // #ifndef STR_INCLUDED
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// IMPLEMENTATION
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
#ifdef STR_IMPLEMENTATION
|
||||
|
||||
#include <stdio.h> // for vsnprintf
|
||||
|
||||
// On some platform vsnprintf() takes va_list by reference and modifies it.
|
||||
// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
|
||||
#ifndef va_copy
|
||||
#define va_copy(dest, src) (dest = src)
|
||||
#endif
|
||||
|
||||
// Static empty buffer we can point to for empty strings
|
||||
// Pointing to a literal increases the like-hood of getting a crash if someone attempts to write in the empty string buffer.
|
||||
char* Str::EmptyBuffer = (char*)"\0NULL";
|
||||
|
||||
// Clear
|
||||
void Str::clear()
|
||||
{
|
||||
if (Owned && !is_using_local_buf())
|
||||
STR_MEMFREE(Data);
|
||||
if (LocalBufSize)
|
||||
{
|
||||
Data = local_buf();
|
||||
Data[0] = '\0';
|
||||
Capacity = LocalBufSize - 1;
|
||||
Owned = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Data = EmptyBuffer;
|
||||
Capacity = 0;
|
||||
Owned = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve memory, preserving the current of the buffer
|
||||
// Capacity doesn't include the zero terminator, so reserve(5) is enough to store "hello".
|
||||
void Str::reserve(int new_capacity)
|
||||
{
|
||||
if (new_capacity <= Capacity)
|
||||
return;
|
||||
|
||||
char* new_data;
|
||||
if (new_capacity <= LocalBufSize - 1)
|
||||
{
|
||||
// Disowned -> LocalBuf
|
||||
new_data = local_buf();
|
||||
new_capacity = LocalBufSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disowned or LocalBuf -> Heap
|
||||
new_data = (char*)STR_MEMALLOC((size_t)(new_capacity + 1) * sizeof(char));
|
||||
}
|
||||
|
||||
// string in Data might be longer than new_capacity if it wasn't owned, don't copy too much
|
||||
#ifdef _MSC_VER
|
||||
strncpy_s(new_data, (size_t)new_capacity + 1, Data, (size_t)new_capacity);
|
||||
#else
|
||||
strncpy(new_data, Data, (size_t)new_capacity);
|
||||
#endif
|
||||
new_data[new_capacity] = 0;
|
||||
|
||||
if (Owned && !is_using_local_buf())
|
||||
STR_MEMFREE(Data);
|
||||
|
||||
Data = new_data;
|
||||
Capacity = new_capacity;
|
||||
Owned = 1;
|
||||
}
|
||||
|
||||
// Reserve memory, discarding the current of the buffer (if we expect to be fully rewritten)
|
||||
void Str::reserve_discard(int new_capacity)
|
||||
{
|
||||
if (new_capacity <= Capacity)
|
||||
return;
|
||||
|
||||
if (Owned && !is_using_local_buf())
|
||||
STR_MEMFREE(Data);
|
||||
|
||||
if (new_capacity <= LocalBufSize - 1)
|
||||
{
|
||||
// Disowned -> LocalBuf
|
||||
Data = local_buf();
|
||||
Capacity = LocalBufSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disowned or LocalBuf -> Heap
|
||||
Data = (char*)STR_MEMALLOC((size_t)(new_capacity + 1) * sizeof(char));
|
||||
Capacity = new_capacity;
|
||||
}
|
||||
Owned = 1;
|
||||
}
|
||||
|
||||
void Str::shrink_to_fit()
|
||||
{
|
||||
if (!Owned || is_using_local_buf())
|
||||
return;
|
||||
int new_capacity = length();
|
||||
if (Capacity <= new_capacity)
|
||||
return;
|
||||
|
||||
char* new_data = (char*)STR_MEMALLOC((size_t)(new_capacity + 1) * sizeof(char));
|
||||
memcpy(new_data, Data, (size_t)(new_capacity + 1));
|
||||
STR_MEMFREE(Data);
|
||||
Data = new_data;
|
||||
Capacity = new_capacity;
|
||||
}
|
||||
|
||||
// FIXME: merge setfv() and appendfv()?
|
||||
int Str::setfv(const char* fmt, va_list args)
|
||||
{
|
||||
// Needed for portability on platforms where va_list are passed by reference and modified by functions
|
||||
va_list args2;
|
||||
va_copy(args2, args);
|
||||
|
||||
// MSVC returns -1 on overflow when writing, which forces us to do two passes
|
||||
// FIXME-OPT: Find a way around that.
|
||||
#ifdef _MSC_VER
|
||||
int len = vsnprintf(NULL, 0, fmt, args);
|
||||
STR_ASSERT(len >= 0);
|
||||
|
||||
if (Capacity < len)
|
||||
reserve_discard(len);
|
||||
len = vsnprintf(Data, (size_t)len + 1, fmt, args2);
|
||||
#else
|
||||
// First try
|
||||
int len = vsnprintf(Owned ? Data : NULL, Owned ? (size_t)(Capacity + 1): 0, fmt, args);
|
||||
STR_ASSERT(len >= 0);
|
||||
|
||||
if (Capacity < len)
|
||||
{
|
||||
reserve_discard(len);
|
||||
len = vsnprintf(Data, (size_t)len + 1, fmt, args2);
|
||||
}
|
||||
#endif
|
||||
|
||||
STR_ASSERT(Owned);
|
||||
return len;
|
||||
}
|
||||
|
||||
int Str::setf(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = setfv(fmt, args);
|
||||
va_end(args);
|
||||
return len;
|
||||
}
|
||||
|
||||
int Str::setfv_nogrow(const char* fmt, va_list args)
|
||||
{
|
||||
STR_ASSERT(Owned);
|
||||
|
||||
if (Capacity == 0)
|
||||
return 0;
|
||||
|
||||
int w = vsnprintf(Data, (size_t)(Capacity + 1), fmt, args);
|
||||
Data[Capacity] = 0;
|
||||
Owned = 1;
|
||||
return (w == -1) ? Capacity : w;
|
||||
}
|
||||
|
||||
int Str::setf_nogrow(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = setfv_nogrow(fmt, args);
|
||||
va_end(args);
|
||||
return len;
|
||||
}
|
||||
|
||||
int Str::append_from(int idx, char c)
|
||||
{
|
||||
int add_len = 1;
|
||||
if (Capacity < idx + add_len)
|
||||
reserve(idx + add_len);
|
||||
Data[idx] = c;
|
||||
Data[idx + add_len] = 0;
|
||||
STR_ASSERT(Owned);
|
||||
return add_len;
|
||||
}
|
||||
|
||||
int Str::append_from(int idx, const char* s, const char* s_end)
|
||||
{
|
||||
if (!s_end)
|
||||
s_end = s + strlen(s);
|
||||
int add_len = (int)(s_end - s);
|
||||
if (Capacity < idx + add_len)
|
||||
reserve(idx + add_len);
|
||||
memcpy(Data + idx, (const void*)s, (size_t)add_len);
|
||||
Data[idx + add_len] = 0; // Our source data isn't necessarily zero terminated
|
||||
STR_ASSERT(Owned);
|
||||
return add_len;
|
||||
}
|
||||
|
||||
// FIXME: merge setfv() and appendfv()?
|
||||
int Str::appendfv_from(int idx, const char* fmt, va_list args)
|
||||
{
|
||||
// Needed for portability on platforms where va_list are passed by reference and modified by functions
|
||||
va_list args2;
|
||||
va_copy(args2, args);
|
||||
|
||||
// MSVC returns -1 on overflow when writing, which forces us to do two passes
|
||||
// FIXME-OPT: Find a way around that.
|
||||
#ifdef _MSC_VER
|
||||
int add_len = vsnprintf(NULL, 0, fmt, args);
|
||||
STR_ASSERT(add_len >= 0);
|
||||
|
||||
if (Capacity < idx + add_len)
|
||||
reserve(idx + add_len);
|
||||
add_len = vsnprintf(Data + idx, add_len + 1, fmt, args2);
|
||||
#else
|
||||
// First try
|
||||
int add_len = vsnprintf(Owned ? Data + idx : NULL, Owned ? (size_t)(Capacity + 1 - idx) : 0, fmt, args);
|
||||
STR_ASSERT(add_len >= 0);
|
||||
|
||||
if (Capacity < idx + add_len)
|
||||
{
|
||||
reserve(idx + add_len);
|
||||
add_len = vsnprintf(Data + idx, (size_t)add_len + 1, fmt, args2);
|
||||
}
|
||||
#endif
|
||||
|
||||
STR_ASSERT(Owned);
|
||||
return add_len;
|
||||
}
|
||||
|
||||
int Str::appendf_from(int idx, const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = appendfv_from(idx, fmt, args);
|
||||
va_end(args);
|
||||
return len;
|
||||
}
|
||||
|
||||
int Str::append(char c)
|
||||
{
|
||||
int cur_len = length();
|
||||
return append_from(cur_len, c);
|
||||
}
|
||||
|
||||
int Str::append(const char* s, const char* s_end)
|
||||
{
|
||||
int cur_len = length();
|
||||
return append_from(cur_len, s, s_end);
|
||||
}
|
||||
|
||||
int Str::appendfv(const char* fmt, va_list args)
|
||||
{
|
||||
int cur_len = length();
|
||||
return appendfv_from(cur_len, fmt, args);
|
||||
}
|
||||
|
||||
int Str::appendf(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = appendfv(fmt, args);
|
||||
va_end(args);
|
||||
return len;
|
||||
}
|
||||
|
||||
#endif // #define STR_IMPLEMENTATION
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
|
||||
<Type Name="Str">
|
||||
<DisplayString>{ Data, na }</DisplayString>
|
||||
<Expand>
|
||||
<Item Name="Data">Data, na</Item>
|
||||
<Item Name="Size">strlen(Data)</Item>
|
||||
<Item Name="Capacity">Capacity</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user