chore: snapshot WIP previo + flow 0008 + 7 sub-issues (0112-0119)
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1 del flow 0008 (kanban_cpp + agent_runner_api + DoD schema). Incluye: - dev/flows/0008-kanban-cpp-and-agent-workflows.md - dev/issues/0112-0119*.md (7 sub-issues) - WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "version_generated.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include "implot.h"
|
||||
@@ -16,9 +17,11 @@
|
||||
#include "core/log_window.h"
|
||||
#include "core/layout_storage.h"
|
||||
#include "gfx/gl_loader.h"
|
||||
#include "app_modules.h"
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
@@ -26,6 +29,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
@@ -224,6 +228,43 @@ static void prune_dead_icon_attached() {
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta la title bar (caption + bordes) en oscuro via DWM, para que el
|
||||
// header del SO no se quede blanco mientras el cliente es dark. DWM la pinta
|
||||
// el OS, no GLFW/ImGui — sin esta llamada queda en blanco aunque el resto
|
||||
// este oscuro.
|
||||
//
|
||||
// Carga dwmapi.dll dinamicamente: evita anadir la dep al toolchain. Si la
|
||||
// DLL/atributo no existe (Win10 < 1809), no hace nada — silencioso.
|
||||
// Attr 20 = DWMWA_USE_IMMERSIVE_DARK_MODE (Win11 / Win10 >= build 18985).
|
||||
// Attr 19 = nombre antiguo (Win10 1809..18984). Probar 20 primero, fallback 19.
|
||||
// Force repaint con SWP_FRAMECHANGED — la ventana ya fue mostrada por GLFW
|
||||
// antes de que lleguemos aqui.
|
||||
typedef HRESULT (WINAPI *PFN_DwmSetWindowAttribute)(HWND, DWORD, LPCVOID, DWORD);
|
||||
static std::unordered_set<HWND> g_dark_titlebar_applied;
|
||||
static void attach_dark_titlebar_to_hwnd(HWND hwnd, bool dark) {
|
||||
if (!hwnd) return;
|
||||
if (g_dark_titlebar_applied.count(hwnd)) return; // idempotent
|
||||
static HMODULE h_dwmapi = LoadLibraryW(L"dwmapi.dll");
|
||||
if (!h_dwmapi) return;
|
||||
static auto p_set = (PFN_DwmSetWindowAttribute)GetProcAddress(h_dwmapi, "DwmSetWindowAttribute");
|
||||
if (!p_set) return;
|
||||
BOOL value = dark ? TRUE : FALSE;
|
||||
HRESULT hr = p_set(hwnd, 20 /* DWMWA_USE_IMMERSIVE_DARK_MODE */, &value, sizeof(value));
|
||||
if (FAILED(hr)) {
|
||||
p_set(hwnd, 19 /* legacy name on Win10 1809..18984 */, &value, sizeof(value));
|
||||
}
|
||||
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
|
||||
g_dark_titlebar_applied.insert(hwnd);
|
||||
}
|
||||
|
||||
static void prune_dead_dark_titlebar() {
|
||||
for (auto it = g_dark_titlebar_applied.begin(); it != g_dark_titlebar_applied.end();) {
|
||||
if (!IsWindow(*it)) it = g_dark_titlebar_applied.erase(it);
|
||||
else ++it;
|
||||
}
|
||||
}
|
||||
|
||||
static void install_sizemove_subclass(GLFWwindow* w) {
|
||||
if (!w) return;
|
||||
install_sizemove_subclass_hwnd(glfwGetWin32Window(w));
|
||||
@@ -391,6 +432,221 @@ const char* framework_description() {
|
||||
return FN_MODULE_FRAMEWORK_DESCRIPTION;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Header badge overlay — identidad por app en viewports secundarios.
|
||||
// ----------------------------------------------------------------------------
|
||||
// Cuando una app C++ tiene N panels y el usuario arrastra varios fuera del
|
||||
// main window, cada panel se convierte en su propio OS viewport. Sin marcas
|
||||
// visuales adicionales, si tienes 3 apps abiertas a la vez no sabes de cual
|
||||
// viene cada panel flotante. Este overlay dibuja un cuadrado redondeado de
|
||||
// ~18px con la inicial de la app en la esquina top-left de la title bar de
|
||||
// cada viewport secundario. Solo en secundarios — el main ya tiene icono
|
||||
// del SO en titlebar/taskbar (attach_app_icon_to_hwnd).
|
||||
//
|
||||
// Filosofia:
|
||||
// - Defaults producen identidad util sin tocar la app (color hash-derivado
|
||||
// desde about.name, glyph = primera letra).
|
||||
// - Apps con icon.accent en su app.md pueden pasar el mismo hex para
|
||||
// coherencia con App Hub.
|
||||
// - ForegroundDrawList: dibuja por encima del titlebar de ImGui sin
|
||||
// necesidad de envolver Begin() ni hookear el render del titulo.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Parsea "#RRGGBB" o "RRGGBB" a ImU32 ABGR. Devuelve 0 si invalido.
|
||||
static ImU32 parse_hex_color_abgr(const char* s) {
|
||||
if (!s || !*s) return 0;
|
||||
if (*s == '#') ++s;
|
||||
auto h = [](char c) -> int {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
};
|
||||
int v[6];
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
v[i] = h(s[i]);
|
||||
if (v[i] < 0) return 0;
|
||||
}
|
||||
int r = (v[0] << 4) | v[1];
|
||||
int g = (v[2] << 4) | v[3];
|
||||
int b = (v[4] << 4) | v[5];
|
||||
return IM_COL32(r, g, b, 255);
|
||||
}
|
||||
|
||||
// Hash-derivado: FNV-1a 32-bit -> H en [0,360), S=0.58, V=0.78 -> ImU32 ABGR.
|
||||
// Estable por nombre, distribuye razonablemente entre N apps.
|
||||
static ImU32 derive_color_from_name(const char* name) {
|
||||
if (!name || !*name) name = "fn_registry";
|
||||
unsigned h = 2166136261u;
|
||||
for (const char* p = name; *p; ++p) {
|
||||
h ^= (unsigned char)*p;
|
||||
h *= 16777619u;
|
||||
}
|
||||
float hue = (float)(h % 360u);
|
||||
float s = 0.58f, v = 0.78f;
|
||||
float c = v * s;
|
||||
float hp = hue / 60.0f;
|
||||
float x = c * (1.0f - std::fabs(std::fmod(hp, 2.0f) - 1.0f));
|
||||
float r=0, g=0, b=0;
|
||||
if (hp < 1) { r=c; g=x; }
|
||||
else if (hp < 2) { r=x; g=c; }
|
||||
else if (hp < 3) { g=c; b=x; }
|
||||
else if (hp < 4) { g=x; b=c; }
|
||||
else if (hp < 5) { r=x; b=c; }
|
||||
else { r=c; b=x; }
|
||||
float m = v - c;
|
||||
int R = (int)((r + m) * 255.0f + 0.5f);
|
||||
int G = (int)((g + m) * 255.0f + 0.5f);
|
||||
int B = (int)((b + m) * 255.0f + 0.5f);
|
||||
return IM_COL32(R, G, B, 255);
|
||||
}
|
||||
|
||||
// Decide string a renderizar como glyph. Devuelve puntero a buffer estatico
|
||||
// thread-local cuando hace falta normalizar la primera letra del nombre.
|
||||
static const char* resolve_badge_glyph(const AppConfig& cfg) {
|
||||
const char* g = cfg.header_badge.glyph;
|
||||
if (g && *g) return g;
|
||||
static thread_local char letter[8] = {0};
|
||||
const char* nm = (cfg.about.name && *cfg.about.name) ? cfg.about.name : cfg.title;
|
||||
char first = (nm && *nm) ? nm[0] : '?';
|
||||
if (first >= 'a' && first <= 'z') first = (char)(first - 'a' + 'A');
|
||||
letter[0] = first;
|
||||
letter[1] = '\0';
|
||||
return letter;
|
||||
}
|
||||
|
||||
// Color final con precedencia:
|
||||
// 1) Override explicito en cfg.header_badge.accent_hex (main.cpp)
|
||||
// 2) Codegen extern fn::app_header_accent_hex (icon.accent del app.md)
|
||||
// 3) Hash-derived desde about.name (siempre estable, da identidad gratis)
|
||||
static ImU32 resolve_badge_color(const AppConfig& cfg) {
|
||||
ImU32 c = parse_hex_color_abgr(cfg.header_badge.accent_hex);
|
||||
if (c != 0) return c;
|
||||
c = parse_hex_color_abgr(app_header_accent_hex);
|
||||
if (c != 0) return c;
|
||||
const char* nm = (cfg.about.name && *cfg.about.name) ? cfg.about.name : cfg.title;
|
||||
return derive_color_from_name(nm);
|
||||
}
|
||||
|
||||
// Textura GL del icono de la app — extraida del HICON embebido en el .exe
|
||||
// (resource ID 101 generado por add_imgui_app desde appicon.ico). Cargada
|
||||
// perezosamente al primer frame y reutilizada en cada draw. 0 = no disponible.
|
||||
static GLuint g_app_icon_texture = 0;
|
||||
static int g_app_icon_size = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
// Carga el icono embebido a 32x32 y sube como textura GL RGBA8. Linear
|
||||
// filtering para que escalar 32->18 sea suave. Devuelve 0 si falla.
|
||||
static GLuint upload_hicon_to_gl_texture() {
|
||||
const int sz = 32;
|
||||
HICON hicon = (HICON)LoadImageW(GetModuleHandleW(nullptr),
|
||||
MAKEINTRESOURCEW(FN_APP_ICON_RES_ID),
|
||||
IMAGE_ICON, sz, sz,
|
||||
LR_DEFAULTCOLOR);
|
||||
if (!hicon) return 0;
|
||||
|
||||
ICONINFO ii{};
|
||||
if (!GetIconInfo(hicon, &ii)) { DestroyIcon(hicon); return 0; }
|
||||
|
||||
HDC hdc = CreateCompatibleDC(nullptr);
|
||||
BITMAPINFO bmi{};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = sz;
|
||||
bmi.bmiHeader.biHeight = -sz; // top-down
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
std::vector<unsigned char> pixels(sz * sz * 4, 0);
|
||||
int ok = GetDIBits(hdc, ii.hbmColor, 0, sz, pixels.data(), &bmi, DIB_RGB_COLORS);
|
||||
DeleteDC(hdc);
|
||||
if (ii.hbmColor) DeleteObject(ii.hbmColor);
|
||||
if (ii.hbmMask) DeleteObject(ii.hbmMask);
|
||||
DestroyIcon(hicon);
|
||||
if (ok == 0) return 0;
|
||||
|
||||
// BGRA -> RGBA (Windows DIB es BGRA).
|
||||
for (int i = 0; i < sz * sz; ++i) {
|
||||
unsigned char b = pixels[i*4 + 0];
|
||||
unsigned char r = pixels[i*4 + 2];
|
||||
pixels[i*4 + 0] = r;
|
||||
pixels[i*4 + 2] = b;
|
||||
}
|
||||
|
||||
GLuint tex = 0;
|
||||
glGenTextures(1, &tex);
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, sz, sz, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
pixels.data());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return tex;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Itera todas las ventanas ImGui y pinta el badge en la title bar de las que
|
||||
// viven en un viewport secundario (panel arrastrado fuera del main window).
|
||||
// Usa imgui_internal.h para acceder a g.Windows y a TitleBarRect(), y dibuja
|
||||
// directamente en la DrawList propia de cada ventana — asi el badge va en
|
||||
// el mismo paso de render que el resto del panel, sin depender de
|
||||
// ForegroundDrawList del viewport (que no se renderiza en algunas combos de
|
||||
// backend + multi-viewport).
|
||||
//
|
||||
// Si tenemos icono GL cargado (Windows con appicon.ico embebido), se dibuja
|
||||
// el icono real (mismo bitmap que el taskbar). Sin icono, fallback a
|
||||
// cuadrado redondeado del color accent con la inicial blanca del app name.
|
||||
//
|
||||
// Filtro de ventanas:
|
||||
// - Activa, no Hidden, no Collapsed.
|
||||
// - No es child window.
|
||||
// - No es popup/tooltip/menu (NoTitleBar => skip).
|
||||
// - No esta dockeada en un nodo (DockIsActive => su titlebar es tabbar del
|
||||
// host; el host window aparece en g.Windows por separado y SI recibe badge).
|
||||
// - Su viewport != main viewport.
|
||||
static void draw_header_badge_on_floating_panels(const AppConfig& cfg) {
|
||||
if (!cfg.header_badge.enabled) return;
|
||||
ImGuiContext& g = *ImGui::GetCurrentContext();
|
||||
ImGuiViewport* main_vp = ImGui::GetMainViewport();
|
||||
|
||||
const ImU32 bg = resolve_badge_color(cfg);
|
||||
const char* glyph = resolve_badge_glyph(cfg);
|
||||
const float sz = cfg.header_badge.size_px > 4.0f ? cfg.header_badge.size_px : 18.0f;
|
||||
const float mg = cfg.header_badge.margin_px >= 0.0f ? cfg.header_badge.margin_px : 6.0f;
|
||||
const float round = sz * 0.22f;
|
||||
const bool has_texture = (g_app_icon_texture != 0);
|
||||
|
||||
for (int i = 0; i < g.Windows.Size; ++i) {
|
||||
ImGuiWindow* w = g.Windows[i];
|
||||
if (!w || !w->WasActive || w->Hidden) continue;
|
||||
if (w->Flags & ImGuiWindowFlags_ChildWindow) continue;
|
||||
if (w->Flags & ImGuiWindowFlags_NoTitleBar) continue;
|
||||
if (w->DockIsActive) continue; // titlebar reemplazado por tab bar del host
|
||||
if (w->Viewport == nullptr || w->Viewport == main_vp) continue;
|
||||
if (w->Collapsed) continue;
|
||||
|
||||
ImRect tb = w->TitleBarRect();
|
||||
ImVec2 p0(tb.Min.x + mg, tb.Min.y + (tb.GetHeight() - sz) * 0.5f);
|
||||
ImVec2 p1(p0.x + sz, p0.y + sz);
|
||||
ImDrawList* dl = w->DrawList;
|
||||
|
||||
if (has_texture) {
|
||||
dl->AddImageRounded((ImTextureID)(intptr_t)g_app_icon_texture,
|
||||
p0, p1, ImVec2(0,0), ImVec2(1,1),
|
||||
IM_COL32_WHITE, round);
|
||||
} else {
|
||||
dl->AddRectFilled(p0, p1, bg, round);
|
||||
ImVec2 ts = ImGui::CalcTextSize(glyph);
|
||||
ImVec2 tp(p0.x + (sz - ts.x) * 0.5f,
|
||||
p0.y + (sz - ts.y) * 0.5f);
|
||||
dl->AddText(tp, IM_COL32(255, 255, 255, 255), glyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
// Logger primero para capturar fallos del propio init (GLFW, ventana, GL).
|
||||
if (config.log.file_path != nullptr) {
|
||||
@@ -460,6 +716,14 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
// barra de tareas, Alt+Tab y title bar (GLFW no propaga el icono de
|
||||
// recursos del .exe a su WNDCLASS por defecto).
|
||||
attach_app_icon_to_hwnd(glfwGetWin32Window(window));
|
||||
|
||||
// Title bar oscuro (DWM) si el tema lo es. Sin esto el header del SO
|
||||
// queda blanco aunque el cliente sea dark.
|
||||
{
|
||||
const bool dark = (config.theme == ThemeMode::FnDark ||
|
||||
config.theme == ThemeMode::ImGuiDark);
|
||||
attach_dark_titlebar_to_hwnd(glfwGetWin32Window(window), dark);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Carga punteros a funciones GL >= 2.0 si la app lo pide. En Linux es
|
||||
@@ -484,6 +748,14 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
// Multi-viewport docking: payload viewport y target viewport swappean
|
||||
// buffers independientes, asi que los dock preview overlays (los rects
|
||||
// azul/gris que indican zonas droppeables) parecen vibrar 1px contra el
|
||||
// payload arrastrado. Con TransparentPayload el payload se vuelve
|
||||
// invisible al arrastrar y los rects solo se pintan en el target ->
|
||||
// ningun desync visible. Recomendado por upstream cuando "rendering of
|
||||
// multiple viewport cannot be synced".
|
||||
io.ConfigDockingTransparentPayload = true;
|
||||
// Title-bar-only move for ImGui windows. Critical for secondary viewports
|
||||
// (floating panels) whose entire OS window is a single borderless ImGui
|
||||
// window: without this flag, ImGui moves the window when the user drags
|
||||
@@ -625,6 +897,9 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
||||
prune_dead_subclassed();
|
||||
prune_dead_icon_attached();
|
||||
prune_dead_dark_titlebar();
|
||||
const bool dark_tb = (config.theme == ThemeMode::FnDark ||
|
||||
config.theme == ThemeMode::ImGuiDark);
|
||||
ImGuiPlatformIO& pio_sub = ImGui::GetPlatformIO();
|
||||
for (int i = 0; i < pio_sub.Viewports.Size; ++i) {
|
||||
ImGuiViewport* vp = pio_sub.Viewports[i];
|
||||
@@ -636,6 +911,9 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
// SetClassLongPtrW. WM_SETICON per-HWND es la unica forma de
|
||||
// que el taskbar/titlebar muestren el icono.
|
||||
attach_app_icon_to_hwnd(glfwGetWin32Window(gw));
|
||||
// Misma logica para el title bar oscuro — cada viewport
|
||||
// secundario tiene su propio HWND con caption pintado por DWM.
|
||||
attach_dark_titlebar_to_hwnd(glfwGetWin32Window(gw), dark_tb);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -750,6 +1028,17 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
fps_overlay();
|
||||
}
|
||||
|
||||
// Identidad por app en viewports secundarios — badge en el title bar
|
||||
// de cada panel arrastrado fuera del main window. Si Windows + tiene
|
||||
// appicon.ico embebido, dibuja el mismo icono que el taskbar (PNG
|
||||
// RGBA escalado). Si no, fallback a cuadrado accent + inicial.
|
||||
#ifdef _WIN32
|
||||
if (g_app_icon_texture == 0) {
|
||||
g_app_icon_texture = upload_hicon_to_gl_texture();
|
||||
}
|
||||
#endif
|
||||
draw_header_badge_on_floating_panels(config);
|
||||
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(window, &display_w, &display_h);
|
||||
@@ -800,6 +1089,10 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if (g_app_icon_texture != 0) {
|
||||
glDeleteTextures(1, &g_app_icon_texture);
|
||||
g_app_icon_texture = 0;
|
||||
}
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImPlot3D::DestroyContext();
|
||||
|
||||
@@ -171,6 +171,42 @@ struct AppConfig {
|
||||
// render_fn) las ventanas docked aparecen flotantes hasta el siguiente
|
||||
// ciclo. Default null = no-op.
|
||||
std::function<void()> pre_frame{};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Header badge — identidad visual en viewports secundarios (panels
|
||||
// arrastrados fuera del main window). Cuando un panel se separa, el
|
||||
// framework dibuja un pequeño cuadrado redondeado con la inicial de la
|
||||
// app (o un glyph custom) en la esquina top-left de la title bar de su
|
||||
// viewport, asi distingues de un vistazo de que app viene cada panel
|
||||
// flotante cuando tienes varias apps abiertas a la vez.
|
||||
//
|
||||
// Si todos los campos quedan por defecto, el framework auto-deriva color
|
||||
// estable desde about.name (hash -> HSV) y glyph desde la primera letra.
|
||||
// No requiere accion en la app; con solo declarar about.name ya hay
|
||||
// identidad. Apps que ya tengan icon.accent en su app.md deberian setear
|
||||
// header_badge.accent_hex con el mismo hex para coherencia visual con el
|
||||
// App Hub.
|
||||
// ------------------------------------------------------------------------
|
||||
struct AppHeaderBadge {
|
||||
// Color de fondo del badge en formato "#RRGGBB" o "RRGGBB" sRGB.
|
||||
// "" -> auto-derive desde about.name (hash estable).
|
||||
const char* accent_hex = "";
|
||||
|
||||
// Glyph dibujado en blanco encima del fondo. nullptr/"" -> primera
|
||||
// letra de about.name (uppercase). Soporta cualquier UTF-8 corto
|
||||
// (1-2 chars o un TI_* macro de cpp/functions/core/icons_tabler.h).
|
||||
const char* glyph = nullptr;
|
||||
|
||||
// Tamaño cuadrado del badge en pixels.
|
||||
float size_px = 18.0f;
|
||||
|
||||
// Margen desde top-left del viewport.
|
||||
float margin_px = 6.0f;
|
||||
|
||||
// false -> deshabilita el badge para esta app (capture mode, headless).
|
||||
bool enabled = true;
|
||||
};
|
||||
AppHeaderBadge header_badge{};
|
||||
};
|
||||
|
||||
// Run an ImGui application. The render_fn is called every frame
|
||||
|
||||
@@ -29,4 +29,19 @@ struct ModuleInfo {
|
||||
extern const ModuleInfo app_modules_array[];
|
||||
extern const unsigned long app_modules_count;
|
||||
|
||||
// App identity para el header badge en viewports secundarios (panels arrastrados
|
||||
// fuera del main window). Auto-generados desde el bloque `icon:` del app.md
|
||||
// por codegen_app_modules.py. Permiten que el framework dibuje el cuadrado
|
||||
// accent con la identidad visual de la app sin que main.cpp deba pasar el hex
|
||||
// manualmente.
|
||||
//
|
||||
// app_header_accent_hex: "#RRGGBB" desde icon.accent (default "" si no se
|
||||
// declara — framework cae a hash-derived).
|
||||
// app_header_glyph_name: nombre del glyph Phosphor desde icon.phosphor.
|
||||
// Hoy informativo: el framework C++ no tiene fuente
|
||||
// Phosphor cargada, asi que cae a primera letra de
|
||||
// about.name. Reservado para futuro mapping Tabler.
|
||||
extern const char* const app_header_accent_hex;
|
||||
extern const char* const app_header_glyph_name;
|
||||
|
||||
} // namespace fn
|
||||
|
||||
Reference in New Issue
Block a user