feat(dev): issues 0100-0104 — dev_console binary + work_tab + DoD user-facing + frontmatter migration de 146 issues + taxonomia canonica

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 02:44:04 +02:00
parent d010a03b44
commit be26bfea89
3 changed files with 67 additions and 85 deletions
+3
View File
@@ -1,5 +1,8 @@
add_imgui_app(app_hub_launcher add_imgui_app(app_hub_launcher
main.cpp main.cpp
${CMAKE_SOURCE_DIR}/functions/core/app_card.cpp
${CMAKE_SOURCE_DIR}/functions/gfx/gl_texture_load.cpp
${CMAKE_SOURCE_DIR}/vendor/stb/stb_image_impl.cpp
) )
target_include_directories(app_hub_launcher PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(app_hub_launcher PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+3 -13
View File
@@ -5,19 +5,9 @@ domain: tools
description: "Hub launcher: lista y arranca apps C++ desplegadas en Windows Desktop" description: "Hub launcher: lista y arranca apps C++ desplegadas en Windows Desktop"
tags: [launcher, hub, suite, imgui] tags: [launcher, hub, suite, imgui]
uses_functions: uses_functions:
# Uncomment when using data_table::render() — provided via fn_table_viz: - app_card_cpp_core
# - data_table_cpp_viz - gl_texture_load_cpp_gfx
# - viz_render_cpp_viz - gl_loader_cpp_gfx
# - compute_stage_cpp_core
# - compute_pipeline_cpp_core
# - compute_column_stats_cpp_core
# - auto_detect_type_cpp_core
# - tql_emit_cpp_core
# - tql_apply_cpp_core
# - lua_engine_cpp_core
# - join_tables_cpp_core
# - tql_to_sql_cpp_core
# - llm_anthropic_cpp_core
uses_types: [] uses_types: []
framework: "imgui" framework: "imgui"
entry_point: "main.cpp" entry_point: "main.cpp"
+61 -72
View File
@@ -1,9 +1,10 @@
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h>
#include "app_base.h" #include "app_base.h"
#include "core/panel_menu.h" #include "core/panel_menu.h"
#include "core/icons_tabler.h" #include "core/icons_tabler.h"
#include "core/logger.h" #include "core/logger.h"
#include "core/app_card.h"
#include "gfx/gl_texture_load.h"
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
@@ -36,7 +37,8 @@ struct AppEntry {
AppMeta meta; AppMeta meta;
}; };
static std::vector<AppEntry> g_apps; static std::vector<AppEntry> g_apps;
static std::unordered_map<std::string, fn::GlTexture> g_icons;
static bool g_show_main = true; static bool g_show_main = true;
static char g_filter[128] = {0}; static char g_filter[128] = {0};
static std::string g_last_status; static std::string g_last_status;
@@ -143,6 +145,35 @@ static void scan_apps() {
g_apps.size(), root.string().c_str()); g_apps.size(), root.string().c_str());
} }
static void load_icons() {
fs::path icons_dir = fs::path(fn::local_dir()) / "icons";
std::error_code ec;
if (!fs::is_directory(icons_dir, ec)) {
fn_log::log_warn("hub: icons dir missing: %s", icons_dir.string().c_str());
return;
}
int loaded = 0;
for (auto const& e : g_apps) {
auto it = g_icons.find(e.name);
if (it != g_icons.end() && it->second.ok()) continue;
fs::path png = icons_dir / (e.name + ".png");
if (!fs::exists(png, ec)) continue;
fn::GlTexture tex = fn::gl_texture_load(png.string().c_str(),
/*flip_y=*/false, /*srgb=*/false);
if (tex.ok()) {
g_icons[e.name] = tex;
++loaded;
} else {
fn_log::log_warn("hub: failed to load %s: %s",
png.string().c_str(), fn::gl_texture_last_error());
}
}
if (loaded > 0) {
fn_log::log_info("hub: loaded %d icon textures (cache=%zu)",
loaded, g_icons.size());
}
}
static bool launch_app(AppEntry const& app) { static bool launch_app(AppEntry const& app) {
#ifdef _WIN32 #ifdef _WIN32
std::wstring wexe(app.exe_path.wstring()); std::wstring wexe(app.exe_path.wstring());
@@ -180,70 +211,22 @@ static bool matches_filter(AppEntry const& app) {
lower(app.meta.description).find(f) != std::string::npos; lower(app.meta.description).find(f) != std::string::npos;
} }
static ImU32 with_alpha(ImU32 c, float a) { static void draw_card(AppEntry const& app, float card_w, float card_h) {
ImVec4 v = ImGui::ColorConvertU32ToFloat4(c); fn_ui::AppCardData data;
v.w = a; data.id = app.name.c_str();
return ImGui::ColorConvertFloat4ToU32(v); data.title = app.meta.display_name.c_str();
} data.description = app.meta.description.c_str();
data.accent = app.meta.accent;
static ImU32 lighten(ImU32 c, float amount) { auto it = g_icons.find(app.name);
ImVec4 v = ImGui::ColorConvertU32ToFloat4(c); data.icon = (it != g_icons.end() && it->second.ok())
v.x = v.x + (1.0f - v.x) * amount; ? (ImTextureID)(intptr_t)it->second.id
v.y = v.y + (1.0f - v.y) * amount; : (ImTextureID)0;
v.z = v.z + (1.0f - v.z) * amount; bool clicked = fn_ui::app_card(data, ImVec2(card_w, card_h));
return ImGui::ColorConvertFloat4ToU32(v); if (ImGui::IsItemHovered()) {
}
static void draw_card(int idx, AppEntry const& app, float card_w, float card_h) {
ImGui::PushID(idx);
ImVec2 pos = ImGui::GetCursorScreenPos();
ImGui::InvisibleButton("card", ImVec2(card_w, card_h));
bool hovered = ImGui::IsItemHovered();
bool clicked = ImGui::IsItemClicked();
bool held = ImGui::IsItemActive();
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 p0 = pos;
ImVec2 p1 = ImVec2(pos.x + card_w, pos.y + card_h);
ImU32 bg = with_alpha(app.meta.accent, hovered ? 0.28f : 0.16f);
if (held) bg = with_alpha(app.meta.accent, 0.40f);
ImU32 border = with_alpha(app.meta.accent, hovered ? 0.95f : 0.55f);
float rounding = 8.0f;
dl->AddRectFilled(p0, p1, bg, rounding);
dl->AddRect(p0, p1, border, rounding, 0, 1.5f);
// Accent stripe on left
float stripe_w = 6.0f;
dl->AddRectFilled(p0, ImVec2(p0.x + stripe_w, p1.y), app.meta.accent, rounding,
ImDrawFlags_RoundCornersLeft);
float pad_x = stripe_w + 12.0f;
float pad_y = 10.0f;
ImVec2 text_pos = ImVec2(p0.x + pad_x, p0.y + pad_y);
// Title
ImU32 title_col = IM_COL32(245, 245, 250, 255);
dl->AddText(text_pos, title_col, app.meta.display_name.c_str());
// Description (wrapped) below
if (!app.meta.description.empty()) {
ImVec2 desc_pos = ImVec2(text_pos.x,
text_pos.y + ImGui::GetTextLineHeight() + 4.0f);
ImU32 desc_col = IM_COL32(195, 200, 210, 230);
float wrap_w = card_w - pad_x - 10.0f;
dl->AddText(NULL, 0.0f, desc_pos, desc_col,
app.meta.description.c_str(), NULL, wrap_w);
}
if (hovered) {
ImGui::SetTooltip("%s\n%s", app.name.c_str(), ImGui::SetTooltip("%s\n%s", app.name.c_str(),
app.exe_path.string().c_str()); app.exe_path.string().c_str());
} }
if (clicked) launch_app(app); if (clicked) launch_app(app);
ImGui::PopID();
} }
static void draw_main() { static void draw_main() {
@@ -258,6 +241,7 @@ static void draw_main() {
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 90.0f); ImGui::SameLine(ImGui::GetContentRegionAvail().x - 90.0f);
if (ImGui::Button(TI_REFRESH " Refresh")) { if (ImGui::Button(TI_REFRESH " Refresh")) {
scan_apps(); scan_apps();
load_icons();
} }
ImGui::Separator(); ImGui::Separator();
ImGui::SetNextItemWidth(-1.0f); ImGui::SetNextItemWidth(-1.0f);
@@ -265,18 +249,17 @@ static void draw_main() {
g_filter, sizeof(g_filter)); g_filter, sizeof(g_filter));
ImGui::Spacing(); ImGui::Spacing();
float card_w = 280.0f; float card_w = 320.0f;
float card_h = 96.0f; float card_h = 110.0f;
float spacing = 12.0f; float spacing = 12.0f;
float avail = ImGui::GetContentRegionAvail().x; float avail = ImGui::GetContentRegionAvail().x;
int cols = std::max(1, (int)((avail + spacing) / (card_w + spacing))); int cols = std::max(1, (int)((avail + spacing) / (card_w + spacing)));
int shown = 0; int shown = 0;
int idx = 0;
for (auto const& app : g_apps) { for (auto const& app : g_apps) {
if (!matches_filter(app)) continue; if (!matches_filter(app)) continue;
if (shown % cols != 0) ImGui::SameLine(0.0f, spacing); if (shown % cols != 0) ImGui::SameLine(0.0f, spacing);
draw_card(idx++, app, card_w, card_h); draw_card(app, card_w, card_h);
++shown; ++shown;
if (shown % cols == 0) ImGui::Dummy(ImVec2(0.0f, spacing - 4.0f)); if (shown % cols == 0) ImGui::Dummy(ImVec2(0.0f, spacing - 4.0f));
} }
@@ -291,6 +274,11 @@ static void draw_main() {
} }
static void render() { static void render() {
static bool icons_loaded_once = false;
if (!icons_loaded_once) {
load_icons();
icons_loaded_once = true;
}
if (g_show_main) draw_main(); if (g_show_main) draw_main();
} }
@@ -300,11 +288,12 @@ int main(int /*argc*/, char** /*argv*/) {
}; };
scan_apps(); scan_apps();
fn::AppConfig cfg; fn::AppConfig cfg;
cfg.title = "App Hub Launcher"; cfg.title = "App Hub Launcher";
cfg.about = { "app_hub_launcher", "0.2.0", cfg.about = { "app_hub_launcher", "0.3.0",
"Lista y arranca apps C++ desplegadas en Windows Desktop" }; "Lista y arranca apps C++ desplegadas en Windows Desktop" };
cfg.log = { "app_hub_launcher.log", 1 }; cfg.log = { "app_hub_launcher.log", 1 };
cfg.panels = panels; cfg.panels = panels;
cfg.panel_count = sizeof(panels) / sizeof(panels[0]); cfg.panel_count = sizeof(panels) / sizeof(panels[0]);
cfg.init_gl_loader = true; // needed for gl_texture_load
return fn::run_app(cfg, render); return fn::run_app(cfg, render);
} }