commit 303b6d476dbe12961a6f2cc2d8602c4a516cadcf Author: Egutierrez Date: Wed Apr 8 00:42:29 2026 +0200 Initial commit: registry_dashboard — Dashboard ImGui para fn_registry diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c794538 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +operations.db +operations.db-wal +operations.db-shm +*.exe diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6ab60c8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +# SQLite3: use system library on Linux, vendored amalgamation on Windows cross-compile +find_package(SQLite3 QUIET) +if(NOT SQLite3_FOUND) + # Build from amalgamation + set(SQLITE3_AMALG_DIR ${CMAKE_SOURCE_DIR}/vendor/sqlite3) + add_library(sqlite3_vendored STATIC ${SQLITE3_AMALG_DIR}/sqlite3.c) + target_include_directories(sqlite3_vendored PUBLIC ${SQLITE3_AMALG_DIR}) + target_compile_definitions(sqlite3_vendored PRIVATE + SQLITE_THREADSAFE=1 + SQLITE_ENABLE_FTS5 + SQLITE_ENABLE_JSON1 + ) + # Alias so we can use the same target name + add_library(SQLite::SQLite3 ALIAS sqlite3_vendored) +endif() + +add_imgui_app(registry_dashboard + main.cpp + data.cpp + views.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/kpi_card.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/bar_chart.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/pie_chart.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/table_view.cpp + ${CMAKE_SOURCE_DIR}/functions/viz/sparkline.cpp + ${CMAKE_SOURCE_DIR}/functions/core/dashboard_panel.cpp + ${CMAKE_SOURCE_DIR}/functions/core/dashboard_grid.cpp + ${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp + ${CMAKE_SOURCE_DIR}/functions/core/fullscreen_window.cpp +) + +target_include_directories(registry_dashboard PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(registry_dashboard PRIVATE SQLite::SQLite3) diff --git a/app.md b/app.md new file mode 100644 index 0000000..b1fab9a --- /dev/null +++ b/app.md @@ -0,0 +1,70 @@ +--- +name: registry_dashboard +lang: cpp +domain: tui +description: "Dashboard ImGui para visualizar el estado del fn_registry: KPIs, charts, tablas, desglose por lenguaje/dominio/pureza" +tags: [dashboard, imgui, visualization, registry] +uses_functions: + - kpi_card_cpp_viz + - bar_chart_cpp_viz + - pie_chart_cpp_viz + - table_view_cpp_viz + - sparkline_cpp_viz + - dashboard_panel_cpp_core + - dashboard_grid_cpp_core + - fps_overlay_cpp_core + - fullscreen_window_cpp_core +uses_types: [] +framework: "imgui" +entry_point: "main.cpp" +dir_path: "apps/registry_dashboard" +repo_url: "" +--- + +## Arquitectura + +Dashboard C++ que lee `registry.db` directamente via SQLite C API y visualiza el estado completo del fn_registry. + +**Data layer** (`data.cpp`): Carga todas las stats, desgloses y listas en un solo paso desde SQLite. Soporte para reload en caliente. + +**Views** (`views.cpp`): Compone funciones del registry C++ para renderizar: +- 6 KPI cards: total functions, types, apps, analysis, tested%, pure% +- Bar charts: funciones por lenguaje, por dominio, actividad ultimos 30 dias +- Pie charts: pureza (pure/impure), kind (function/pipeline/component), tested/untested +- Tablas: ultimas 20 funciones, apps, analysis, tipos + +**Entrada**: Paths a `registry.db` como argumentos con fallback encadenado. + +## Build + +```bash +# Linux +cd cpp && cmake -B build/linux -S . && cmake --build build/linux --target registry_dashboard -j$(nproc) + +# Windows (cross-compile) +cd cpp && cmake -B build/windows -S . -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw-w64.cmake && cmake --build build/windows --target registry_dashboard -j$(nproc) +``` + +## Ejecucion + +```bash +# Linux +./cpp/build/linux/apps/registry_dashboard/registry_dashboard /home/lucas/fn_registry/registry.db + +# Windows (PowerShell) — intenta WSL mount, luego local +.\registry_dashboard.ps1 + +# Otra maquina — copiar .exe + .db al mismo dir +registry_dashboard.exe .\registry.db +``` + +## Roadmap + +- [ ] Integrar Go runtime (CGo) para ejecutar comandos `fn` desde el GUI +- [ ] Filtros interactivos por lenguaje/dominio en sidebar +- [ ] Busqueda FTS5 integrada +- [ ] Detalles de funcion al hacer click en tabla + +## Notas + +SQLite compilado estaticamente en Windows via amalgamation vendoreada. En Linux usa libsqlite3 del sistema. diff --git a/data.cpp b/data.cpp new file mode 100644 index 0000000..1b32d70 --- /dev/null +++ b/data.cpp @@ -0,0 +1,188 @@ +#include "data.h" +#include +#include + +// Helper: execute a query and call row_fn for each row +template +static bool query(sqlite3* db, const char* sql, F row_fn) { + sqlite3_stmt* stmt = nullptr; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { + fprintf(stderr, "SQL error: %s\n query: %s\n", sqlite3_errmsg(db), sql); + return false; + } + while (sqlite3_step(stmt) == SQLITE_ROW) { + row_fn(stmt); + } + sqlite3_finalize(stmt); + return true; +} + +static std::string col_str(sqlite3_stmt* s, int i) { + const char* t = reinterpret_cast(sqlite3_column_text(s, i)); + return t ? t : ""; +} + +static int col_int(sqlite3_stmt* s, int i) { + return sqlite3_column_int(s, i); +} + +void RegistryData::prepare_chart_data() { + lang_labels.clear(); lang_values.clear(); + for (auto& lc : by_lang) { + lang_labels.push_back(lc.lang); + lang_values.push_back(static_cast(lc.count)); + } + + domain_labels.clear(); domain_values.clear(); + for (auto& dc : by_domain) { + domain_labels.push_back(dc.domain); + domain_values.push_back(static_cast(dc.count)); + } + + kind_labels.clear(); kind_values.clear(); + for (auto& kc : by_kind) { + kind_labels.push_back(kc.kind); + kind_values.push_back(static_cast(kc.count)); + } + + date_labels.clear(); date_values.clear(); + for (auto& dc : by_date) { + date_labels.push_back(dc.date); + date_values.push_back(static_cast(dc.count)); + } +} + +bool load_registry_data(const char* db_path, RegistryData& out) { + sqlite3* db = nullptr; + if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) { + fprintf(stderr, "Cannot open %s: %s\n", db_path, sqlite3_errmsg(db)); + return false; + } + + // --- Counts --- + query(db, "SELECT COUNT(*) FROM functions", [&](sqlite3_stmt* s) { + out.stats.total_functions = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM types", [&](sqlite3_stmt* s) { + out.stats.total_types = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM apps", [&](sqlite3_stmt* s) { + out.stats.total_apps = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM analysis", [&](sqlite3_stmt* s) { + out.stats.total_analysis = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM unit_tests", [&](sqlite3_stmt* s) { + out.stats.total_unit_tests = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM proposals", [&](sqlite3_stmt* s) { + out.stats.total_proposals = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM functions WHERE tested = 1", [&](sqlite3_stmt* s) { + out.stats.tested_functions = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM functions WHERE purity = 'pure'", [&](sqlite3_stmt* s) { + out.stats.pure_functions = col_int(s, 0); + }); + query(db, "SELECT COUNT(*) FROM functions WHERE purity = 'impure'", [&](sqlite3_stmt* s) { + out.stats.impure_functions = col_int(s, 0); + }); + + // --- By language --- + out.by_lang.clear(); + query(db, "SELECT lang, COUNT(*) as cnt FROM functions GROUP BY lang ORDER BY cnt DESC", + [&](sqlite3_stmt* s) { + out.by_lang.push_back({col_str(s, 0), col_int(s, 1)}); + }); + + // --- By domain --- + out.by_domain.clear(); + query(db, "SELECT domain, COUNT(*) as cnt FROM functions GROUP BY domain ORDER BY cnt DESC", + [&](sqlite3_stmt* s) { + out.by_domain.push_back({col_str(s, 0), col_int(s, 1)}); + }); + + // --- By kind --- + out.by_kind.clear(); + query(db, "SELECT kind, COUNT(*) as cnt FROM functions GROUP BY kind ORDER BY cnt DESC", + [&](sqlite3_stmt* s) { + out.by_kind.push_back({col_str(s, 0), col_int(s, 1)}); + }); + + // --- By date (last 30 days) --- + out.by_date.clear(); + query(db, + "SELECT date(created_at) as d, COUNT(*) as cnt FROM functions " + "WHERE created_at >= date('now', '-30 days') " + "GROUP BY d ORDER BY d", + [&](sqlite3_stmt* s) { + out.by_date.push_back({col_str(s, 0), col_int(s, 1)}); + }); + + // --- Recent functions (last 20) --- + out.recent_funcs.clear(); + query(db, + "SELECT id, name, lang, domain, kind, purity, description, created_at, tested " + "FROM functions ORDER BY created_at DESC LIMIT 20", + [&](sqlite3_stmt* s) { + FunctionRow r; + r.id = col_str(s, 0); + r.name = col_str(s, 1); + r.lang = col_str(s, 2); + r.domain = col_str(s, 3); + r.kind = col_str(s, 4); + r.purity = col_str(s, 5); + r.description = col_str(s, 6); + r.created_at = col_str(s, 7); + r.tested = col_int(s, 8) != 0; + out.recent_funcs.push_back(std::move(r)); + }); + + // --- Apps --- + out.apps.clear(); + query(db, + "SELECT id, name, lang, domain, description, framework FROM apps ORDER BY name", + [&](sqlite3_stmt* s) { + AppRow r; + r.id = col_str(s, 0); + r.name = col_str(s, 1); + r.lang = col_str(s, 2); + r.domain = col_str(s, 3); + r.description = col_str(s, 4); + r.framework = col_str(s, 5); + out.apps.push_back(std::move(r)); + }); + + // --- Analysis --- + out.analyses.clear(); + query(db, + "SELECT id, name, domain, description FROM analysis ORDER BY name", + [&](sqlite3_stmt* s) { + AnalysisRow r; + r.id = col_str(s, 0); + r.name = col_str(s, 1); + r.domain = col_str(s, 2); + r.description = col_str(s, 3); + out.analyses.push_back(std::move(r)); + }); + + // --- Types --- + out.types.clear(); + query(db, + "SELECT id, name, lang, domain, algebraic, description FROM types ORDER BY name", + [&](sqlite3_stmt* s) { + TypeRow r; + r.id = col_str(s, 0); + r.name = col_str(s, 1); + r.lang = col_str(s, 2); + r.domain = col_str(s, 3); + r.algebraic = col_str(s, 4); + r.description = col_str(s, 5); + out.types.push_back(std::move(r)); + }); + + sqlite3_close(db); + + out.prepare_chart_data(); + return true; +} diff --git a/data.h b/data.h new file mode 100644 index 0000000..fc86959 --- /dev/null +++ b/data.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include + +struct RegistryStats { + int total_functions = 0; + int total_types = 0; + int total_apps = 0; + int total_analysis = 0; + int total_unit_tests = 0; + int total_proposals = 0; + int tested_functions = 0; + int pure_functions = 0; + int impure_functions = 0; +}; + +struct LangCount { + std::string lang; + int count = 0; +}; + +struct DomainCount { + std::string domain; + int count = 0; +}; + +struct KindCount { + std::string kind; + int count = 0; +}; + +struct DateCount { + std::string date; // YYYY-MM-DD + int count = 0; +}; + +struct FunctionRow { + std::string id; + std::string name; + std::string lang; + std::string domain; + std::string kind; + std::string purity; + std::string description; + std::string created_at; + bool tested = false; +}; + +struct AppRow { + std::string id; + std::string name; + std::string lang; + std::string domain; + std::string description; + std::string framework; +}; + +struct AnalysisRow { + std::string id; + std::string name; + std::string domain; + std::string description; +}; + +struct TypeRow { + std::string id; + std::string name; + std::string lang; + std::string domain; + std::string algebraic; + std::string description; +}; + +// All data loaded from registry.db in one shot +struct RegistryData { + RegistryStats stats; + std::vector by_lang; + std::vector by_domain; + std::vector by_kind; + std::vector by_date; // last 30 days + std::vector recent_funcs; // last 20 + std::vector apps; + std::vector analyses; + std::vector types; + + // For chart data (populated by prepare_chart_data) + std::vector lang_labels; + std::vector lang_values; + std::vector domain_labels; + std::vector domain_values; + std::vector kind_labels; + std::vector kind_values; + std::vector date_labels; + std::vector date_values; + + void prepare_chart_data(); +}; + +// Load all data from registry.db. Returns true on success. +bool load_registry_data(const char* db_path, RegistryData& out); diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..43b311a --- /dev/null +++ b/main.cpp @@ -0,0 +1,78 @@ +#include "app_base.h" +#include "imgui.h" +#include "core/fullscreen_window.h" +#include "data.h" +#include "views.h" + +#include +#include +#include +#include + +static RegistryData g_data; +static std::string g_db_path; +static bool g_loaded = false; + +static void reload_data() { + g_data = RegistryData{}; + g_loaded = load_registry_data(g_db_path.c_str(), g_data); + if (!g_loaded) { + fprintf(stderr, "Failed to load registry data from: %s\n", g_db_path.c_str()); + } +} + +static void render() { + if (ImGui::GetIO().UserData != nullptr) { + ImGui::GetIO().UserData = nullptr; + reload_data(); + } + + if (!g_loaded) { + fullscreen_window_begin("##error"); + ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), + "Could not open registry.db"); + ImGui::Spacing(); + ImGui::Text("Tried: %s", g_db_path.c_str()); + ImGui::Spacing(); + ImGui::TextWrapped("Usage: registry_dashboard [path2] [path3] ..."); + ImGui::Spacing(); + if (ImGui::Button("Retry")) { + reload_data(); + } + fullscreen_window_end(); + return; + } + + draw_dashboard(g_data); +} + +int main(int argc, char** argv) { + if (argc < 2) { + fprintf(stderr, "Usage: registry_dashboard [fallback_path ...]\n"); + fprintf(stderr, " Tries each path in order until one opens successfully.\n"); + return 1; + } + + // Try each argument in order + for (int i = 1; i < argc; i++) { + std::string candidate = argv[i]; + if (std::ifstream(candidate).good()) { + g_db_path = candidate; + fprintf(stdout, "Using: %s\n", g_db_path.c_str()); + break; + } + fprintf(stderr, "Not found: %s\n", candidate); + } + + if (g_db_path.empty()) { + // None found, use last arg so error screen shows something useful + g_db_path = argv[argc - 1]; + } + + reload_data(); + + return fn::run_app( + {.title = "fn_registry Dashboard", .width = 1600, .height = 1000, .viewports = true}, + render + ); +} diff --git a/registry_dashboard.ps1 b/registry_dashboard.ps1 new file mode 100644 index 0000000..d906aa0 --- /dev/null +++ b/registry_dashboard.ps1 @@ -0,0 +1,6 @@ +$exe = Join-Path $PSScriptRoot "registry_dashboard.exe" + +& $exe ` + "\\wsl.localhost\Ubuntu\home\lucas\fn_registry\registry.db" ` + "\\wsl$\Ubuntu\home\lucas\fn_registry\registry.db" ` + "$PSScriptRoot\registry.db" diff --git a/views.cpp b/views.cpp new file mode 100644 index 0000000..58c47be --- /dev/null +++ b/views.cpp @@ -0,0 +1,224 @@ +#include "views.h" +#include "imgui.h" +#include "implot.h" + +#include "viz/kpi_card.h" +#include "viz/bar_chart.h" +#include "viz/pie_chart.h" +#include "viz/table_view.h" +#include "viz/sparkline.h" +#include "core/dashboard_panel.h" +#include "core/dashboard_grid.h" +#include "core/fps_overlay.h" +#include "core/fullscreen_window.h" + +#include + +static std::vector to_cstr(const std::vector& v) { + std::vector out; + out.reserve(v.size()); + for (auto& s : v) out.push_back(s.c_str()); + return out; +} + +void draw_kpi_row(const RegistryStats& stats) { + float tested_pct = stats.total_functions > 0 + ? 100.0f * stats.tested_functions / stats.total_functions + : 0.0f; + float pure_pct = stats.total_functions > 0 + ? 100.0f * stats.pure_functions / stats.total_functions + : 0.0f; + + dashboard_grid_begin(6, 12.0f); + + kpi_card("Functions", static_cast(stats.total_functions), 0, nullptr, 0, "%.0f"); + dashboard_grid_next(); + kpi_card("Types", static_cast(stats.total_types), 0, nullptr, 0, "%.0f"); + dashboard_grid_next(); + kpi_card("Apps", static_cast(stats.total_apps), 0, nullptr, 0, "%.0f"); + dashboard_grid_next(); + kpi_card("Analysis", static_cast(stats.total_analysis), 0, nullptr, 0, "%.0f"); + dashboard_grid_next(); + kpi_card("Tested %", tested_pct, 0, nullptr, 0, "%.1f%%"); + dashboard_grid_next(); + kpi_card("Pure %", pure_pct, 0, nullptr, 0, "%.1f%%"); + + dashboard_grid_end(); +} + +void draw_charts(RegistryData& data) { + dashboard_grid_begin(2, 12.0f); + + if (dashboard_panel_begin("Functions by Language", 300, 280)) { + auto labels = to_cstr(data.lang_labels); + if (!labels.empty()) + bar_chart("##lang", labels.data(), data.lang_values.data(), static_cast(labels.size())); + } + dashboard_panel_end(); + dashboard_grid_next(); + + if (dashboard_panel_begin("Functions by Domain", 300, 280)) { + auto labels = to_cstr(data.domain_labels); + if (!labels.empty()) + bar_chart("##domain", labels.data(), data.domain_values.data(), static_cast(labels.size())); + } + dashboard_panel_end(); + dashboard_grid_next(); + + if (dashboard_panel_begin("Purity", 250, 280)) { + const char* labels[] = {"Pure", "Impure"}; + float values[] = {static_cast(data.stats.pure_functions), static_cast(data.stats.impure_functions)}; + pie_chart("##purity", labels, values, 2); + } + dashboard_panel_end(); + dashboard_grid_next(); + + if (dashboard_panel_begin("Kind", 250, 280)) { + auto labels = to_cstr(data.kind_labels); + if (!labels.empty()) + pie_chart("##kind", labels.data(), data.kind_values.data(), static_cast(labels.size())); + } + dashboard_panel_end(); + dashboard_grid_next(); + + if (dashboard_panel_begin("Activity (last 30 days)", 600, 280)) { + auto labels = to_cstr(data.date_labels); + if (!labels.empty()) + bar_chart("##dates", labels.data(), data.date_values.data(), static_cast(labels.size())); + else + ImGui::TextDisabled("No functions created in the last 30 days"); + } + dashboard_panel_end(); + dashboard_grid_next(); + + if (dashboard_panel_begin("Extras", 250, 280)) { + kpi_card("Unit Tests", static_cast(data.stats.total_unit_tests), 0, nullptr, 0, "%.0f"); + ImGui::Spacing(); + kpi_card("Proposals", static_cast(data.stats.total_proposals), 0, nullptr, 0, "%.0f"); + ImGui::Spacing(); + const char* t_labels[] = {"Tested", "Untested"}; + float t_values[] = {static_cast(data.stats.tested_functions), + static_cast(data.stats.total_functions - data.stats.tested_functions)}; + pie_chart("##tested", t_labels, t_values, 2); + } + dashboard_panel_end(); + + dashboard_grid_end(); +} + +void draw_recent_functions(const std::vector& funcs) { + const char* headers[] = {"Name", "Lang", "Domain", "Kind", "Purity", "Tested", "Created"}; + constexpr int cols = 7; + std::vector cell_strings; + cell_strings.reserve(funcs.size() * cols); + for (auto& f : funcs) { + cell_strings.push_back(f.name); + cell_strings.push_back(f.lang); + cell_strings.push_back(f.domain); + cell_strings.push_back(f.kind); + cell_strings.push_back(f.purity); + cell_strings.push_back(f.tested ? "yes" : "no"); + cell_strings.push_back(f.created_at.substr(0, 10)); + } + auto cells = to_cstr(cell_strings); + table_view("##recent", headers, cols, cells.data(), static_cast(funcs.size())); +} + +void draw_apps_list(const std::vector& apps) { + const char* headers[] = {"Name", "Lang", "Domain", "Framework", "Description"}; + constexpr int cols = 5; + std::vector cell_strings; + cell_strings.reserve(apps.size() * cols); + for (auto& a : apps) { + cell_strings.push_back(a.name); + cell_strings.push_back(a.lang); + cell_strings.push_back(a.domain); + cell_strings.push_back(a.framework); + cell_strings.push_back(a.description); + } + auto cells = to_cstr(cell_strings); + table_view("##apps", headers, cols, cells.data(), static_cast(apps.size())); +} + +void draw_analysis_list(const std::vector& analyses) { + const char* headers[] = {"Name", "Domain", "Description"}; + constexpr int cols = 3; + std::vector cell_strings; + cell_strings.reserve(analyses.size() * cols); + for (auto& a : analyses) { + cell_strings.push_back(a.name); + cell_strings.push_back(a.domain); + cell_strings.push_back(a.description); + } + auto cells = to_cstr(cell_strings); + table_view("##analysis", headers, cols, cells.data(), static_cast(analyses.size())); +} + +void draw_types_list(const std::vector& types) { + const char* headers[] = {"Name", "Lang", "Domain", "Algebraic", "Description"}; + constexpr int cols = 5; + std::vector cell_strings; + cell_strings.reserve(types.size() * cols); + for (auto& t : types) { + cell_strings.push_back(t.name); + cell_strings.push_back(t.lang); + cell_strings.push_back(t.domain); + cell_strings.push_back(t.algebraic); + cell_strings.push_back(t.description); + } + auto cells = to_cstr(cell_strings); + table_view("##types", headers, cols, cells.data(), static_cast(types.size())); +} + +void draw_dashboard(RegistryData& data) { + fps_overlay(); + + fullscreen_window_begin("##dashboard"); + + // Header + ImGui::Text("fn_registry Dashboard"); + ImGui::SameLine(ImGui::GetWindowWidth() - 100); + if (ImGui::Button("Reload")) { + ImGui::GetIO().UserData = reinterpret_cast(1); + } + + ImGui::Separator(); + ImGui::Spacing(); + + // KPIs + draw_kpi_row(data.stats); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // Charts + draw_charts(data); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // Tables in tabs + if (ImGui::BeginTabBar("##tables")) { + if (ImGui::BeginTabItem("Recent Functions")) { + draw_recent_functions(data.recent_funcs); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Apps")) { + draw_apps_list(data.apps); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Analysis")) { + draw_analysis_list(data.analyses); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Types")) { + draw_types_list(data.types); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + + fullscreen_window_end(); +} diff --git a/views.h b/views.h new file mode 100644 index 0000000..55f876f --- /dev/null +++ b/views.h @@ -0,0 +1,14 @@ +#pragma once + +#include "data.h" + +// Draw the full dashboard. Call every frame. +void draw_dashboard(RegistryData& data); + +// Individual views (called by draw_dashboard) +void draw_kpi_row(const RegistryStats& stats); +void draw_charts(RegistryData& data); +void draw_recent_functions(const std::vector& funcs); +void draw_apps_list(const std::vector& apps); +void draw_analysis_list(const std::vector& analyses); +void draw_types_list(const std::vector& types);