From be26bfea89d32d7e5f1101323da95ebd5b51a6a4 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sun, 17 May 2026 02:44:04 +0200 Subject: [PATCH] =?UTF-8?q?feat(dev):=20issues=200100-0104=20=E2=80=94=20d?= =?UTF-8?q?ev=5Fconsole=20binary=20+=20work=5Ftab=20+=20DoD=20user-facing?= =?UTF-8?q?=20+=20frontmatter=20migration=20de=20146=20issues=20+=20taxono?= =?UTF-8?q?mia=20canonica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- CMakeLists.txt | 3 ++ app.md | 16 ++---- main.cpp | 133 +++++++++++++++++++++++-------------------------- 3 files changed, 67 insertions(+), 85 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da5bcf9..d6797d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/app.md b/app.md index cc6b5c2..ce48765 100644 --- a/app.md +++ b/app.md @@ -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" diff --git a/main.cpp b/main.cpp index 615c1b8..97bde4f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,10 @@ #include -#include #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 #include @@ -36,7 +37,8 @@ struct AppEntry { AppMeta meta; }; -static std::vector g_apps; +static std::vector g_apps; +static std::unordered_map 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(); } @@ -300,11 +288,12 @@ int main(int /*argc*/, char** /*argv*/) { }; scan_apps(); fn::AppConfig cfg; - cfg.title = "App Hub Launcher"; - cfg.about = { "app_hub_launcher", "0.2.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.title = "App Hub Launcher"; + 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); }