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:
@@ -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})
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user