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
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})
+3 -13
View File
@@ -5,19 +5,9 @@ domain: tools
description: "Hub launcher: lista y arranca apps C++ desplegadas en Windows Desktop"
tags: [launcher, hub, suite, imgui]
uses_functions:
# Uncomment when using data_table::render() — provided via fn_table_viz:
# - data_table_cpp_viz
# - viz_render_cpp_viz
# - 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
- app_card_cpp_core
- gl_texture_load_cpp_gfx
- gl_loader_cpp_gfx
uses_types: []
framework: "imgui"
entry_point: "main.cpp"
+55 -66
View File
@@ -1,9 +1,10 @@
#include <imgui.h>
#include <imgui_internal.h>
#include "app_base.h"
#include "core/panel_menu.h"
#include "core/icons_tabler.h"
#include "core/logger.h"
#include "core/app_card.h"
#include "gfx/gl_texture_load.h"
#include <algorithm>
#include <cctype>
@@ -37,6 +38,7 @@ struct AppEntry {
};
static std::vector<AppEntry> g_apps;
static std::unordered_map<std::string, fn::GlTexture> g_icons;
static bool g_show_main = true;
static char g_filter[128] = {0};
static std::string g_last_status;
@@ -143,6 +145,35 @@ static void scan_apps() {
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) {
#ifdef _WIN32
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;
}
static ImU32 with_alpha(ImU32 c, float a) {
ImVec4 v = ImGui::ColorConvertU32ToFloat4(c);
v.w = a;
return ImGui::ColorConvertFloat4ToU32(v);
}
static ImU32 lighten(ImU32 c, float amount) {
ImVec4 v = ImGui::ColorConvertU32ToFloat4(c);
v.x = v.x + (1.0f - v.x) * amount;
v.y = v.y + (1.0f - v.y) * amount;
v.z = v.z + (1.0f - v.z) * amount;
return ImGui::ColorConvertFloat4ToU32(v);
}
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) {
static void draw_card(AppEntry const& app, float card_w, float card_h) {
fn_ui::AppCardData data;
data.id = app.name.c_str();
data.title = app.meta.display_name.c_str();
data.description = app.meta.description.c_str();
data.accent = app.meta.accent;
auto it = g_icons.find(app.name);
data.icon = (it != g_icons.end() && it->second.ok())
? (ImTextureID)(intptr_t)it->second.id
: (ImTextureID)0;
bool clicked = fn_ui::app_card(data, ImVec2(card_w, card_h));
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s\n%s", app.name.c_str(),
app.exe_path.string().c_str());
}
if (clicked) launch_app(app);
ImGui::PopID();
}
static void draw_main() {
@@ -258,6 +241,7 @@ static void draw_main() {
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 90.0f);
if (ImGui::Button(TI_REFRESH " Refresh")) {
scan_apps();
load_icons();
}
ImGui::Separator();
ImGui::SetNextItemWidth(-1.0f);
@@ -265,18 +249,17 @@ static void draw_main() {
g_filter, sizeof(g_filter));
ImGui::Spacing();
float card_w = 280.0f;
float card_h = 96.0f;
float card_w = 320.0f;
float card_h = 110.0f;
float spacing = 12.0f;
float avail = ImGui::GetContentRegionAvail().x;
int cols = std::max(1, (int)((avail + spacing) / (card_w + spacing)));
int shown = 0;
int idx = 0;
for (auto const& app : g_apps) {
if (!matches_filter(app)) continue;
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;
if (shown % cols == 0) ImGui::Dummy(ImVec2(0.0f, spacing - 4.0f));
}
@@ -291,6 +274,11 @@ static void draw_main() {
}
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();
}
@@ -301,10 +289,11 @@ int main(int /*argc*/, char** /*argv*/) {
scan_apps();
fn::AppConfig cfg;
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" };
cfg.log = { "app_hub_launcher.log", 1 };
cfg.panels = panels;
cfg.panel_count = sizeof(panels) / sizeof(panels[0]);
cfg.init_gl_loader = true; // needed for gl_texture_load
return fn::run_app(cfg, render);
}