feat(framework): assets/ subfolder para distribuibles read-only

Refina la convencion de layout: el top de cada app distribuible
solo lleva el .exe + DLLs nativas; todo lo demas (TTFs, enrichers,
runtime Python, MCP servers) vive en <exe_dir>/assets/.

Cambios:
- cpp/CMakeLists.txt::add_imgui_app — copia las 5 TTFs (Karla,
  Roboto, DroidSans, Cousine, tabler-icons) a
  $<TARGET_FILE_DIR>/assets/ en lugar de junto al exe.
- framework/app_base: nuevas funciones fn::asset_dir() y
  fn::asset_path(name) que resuelven a <exe_dir>/assets/<name>.
- functions/core/icon_font.cpp::find_asset — anade
  fn::asset_path(filename) como PRIMERA ruta de busqueda, antes
  de las legacy ./<file> y ./assets/<file>. Mantiene los
  fallbacks para dev (FN_ASSETS_DIR, FN_CPP_ROOT).
- .claude/commands/compile.md — el deploy a Desktop pone TTFs +
  enrichers/ + runtime/ + gx-cli en <DEST>/assets/. Solo .exe y
  DLLs nativas (duckdb.dll) quedan en el top. local_files/ se
  preserva si existe.

Layout final:
  Desktop/apps/<APP>/
  ├── <APP>.exe + *.dll          (binario + DLLs Windows)
  ├── assets/                    (read-only distribuible)
  │   ├── *.ttf, enrichers/, runtime/, gx-cli, ...
  └── local_files/               (per-PC, creado al primer arranque)

Esto cierra la separacion conceptual de la convencion: la carpeta
es trivial de zippear (solo .exe + assets/), el reset/sync es
trivial (local_files/), y todas las apps del registry adoptan el
mismo layout via fn_framework.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 00:50:33 +02:00
parent 157bc093f4
commit c5b2ec8ad6
5 changed files with 92 additions and 31 deletions
+17 -8
View File
@@ -154,8 +154,11 @@ add_library(fn_framework STATIC
functions/core/panel_menu.cpp
functions/core/layouts_menu.cpp
functions/core/app_menubar.cpp
functions/core/logger.cpp
functions/core/log_window.cpp
functions/gfx/gl_loader.cpp
functions/core/layout_storage.cpp
functions/core/selectable_text.cpp
)
target_include_directories(fn_framework PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/framework
@@ -194,25 +197,31 @@ function(add_imgui_app target)
target_include_directories(${target} PRIVATE
${FN_CPP_ROOT_DIR}/functions
)
# Copia las fuentes junto al ejecutable para deploys autonomos (sin
# FN_CPP_ROOT en runtime). 4 TTFs vectoriales para el menu Settings + Tabler
# para los iconos TI_*.
# Convencion de layout (cpp_apps.md §7):
# <exe_dir>/<app>.exe + <app>.dll (binario + DLLs Windows convention)
# <exe_dir>/assets/ (read-only: ttfs, enrichers, runtime, etc.)
# <exe_dir>/local_files/ (creado en runtime: ini, db, projects)
#
# add_imgui_app copia las TTFs a <exe_dir>/assets/. La app las
# encuentra en runtime via fn::asset_path() (icon_font.cpp).
set(_ASSETS_DIR $<TARGET_FILE_DIR:${target}>/assets)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${_ASSETS_DIR}
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Karla-Regular.ttf
$<TARGET_FILE_DIR:${target}>/Karla-Regular.ttf
${_ASSETS_DIR}/Karla-Regular.ttf
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Roboto-Medium.ttf
$<TARGET_FILE_DIR:${target}>/Roboto-Medium.ttf
${_ASSETS_DIR}/Roboto-Medium.ttf
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/DroidSans.ttf
$<TARGET_FILE_DIR:${target}>/DroidSans.ttf
${_ASSETS_DIR}/DroidSans.ttf
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${FN_CPP_ROOT_DIR}/vendor/imgui/misc/fonts/Cousine-Regular.ttf
$<TARGET_FILE_DIR:${target}>/Cousine-Regular.ttf
${_ASSETS_DIR}/Cousine-Regular.ttf
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${FN_CPP_ROOT_DIR}/vendor/tabler-icons/tabler-icons.ttf
$<TARGET_FILE_DIR:${target}>/tabler-icons.ttf
${_ASSETS_DIR}/tabler-icons.ttf
VERBATIM
)
endfunction()
+24
View File
@@ -106,6 +106,30 @@ const char* local_path(const char* name) {
return buf.c_str();
}
namespace {
const std::string& asset_dir_ref() {
static std::string cached;
static bool inited = false;
if (inited) return cached;
const std::string& edir = exe_dir_ref();
cached = edir.empty() ? std::string("assets") : edir + "/assets";
inited = true;
return cached;
}
} // namespace
const char* asset_dir() { return asset_dir_ref().c_str(); }
const char* asset_path(const char* name) {
static thread_local std::string buf;
buf = asset_dir_ref();
if (name && *name) {
if (!buf.empty() && buf.back() != '/' && buf.back() != '\\') buf += '/';
buf += name;
}
return buf.c_str();
}
void migrate_to_local_files(const char* const* names, std::size_t n) {
if (!names || n == 0) return;
const std::string& ldir = local_dir_ref();
+9
View File
@@ -68,6 +68,15 @@ const char* local_dir();
// proxima llamada — copia el valor si vas a guardarlo.
const char* local_path(const char* name);
// Devuelve `<exe_dir>/assets/` — read-only sidecar shipped con la
// app (ttfs, enrichers, runtime Python, etc.). NO crea la carpeta;
// si no existe, la app debe fallback a buscar los assets en
// FN_CPP_ROOT u otras rutas. Sin trailing slash.
const char* asset_dir();
// Construye `<asset_dir>/<name>`. Mismo lifetime que local_path.
const char* asset_path(const char* name);
// Mueve los archivos listados de cwd o exe_dir a local_files/ si
// existen ahi pero NO existen ya en local_files/. Idempotente. Las
// apps lo llaman al iniciar para migrar instalaciones viejas.
+9 -3
View File
@@ -1,6 +1,7 @@
#include "icon_font.h"
#include "app_settings.h"
#include "../../framework/app_base.h"
#include "imgui.h"
#include <cstdio>
@@ -25,13 +26,18 @@ bool file_exists(const char* path) {
return false;
}
// Busca un asset (TTF) en las rutas estandar del registry. Devuelve la primera
// Busca un asset (TTF) en las rutas estandar. Devuelve la primera
// ruta valida o vacio.
//
// Orden: ./<filename> → ./assets/<filename> → $FN_ASSETS_DIR/<filename>
// → ${FN_CPP_ROOT}/<repo_subpath>
// Orden:
// 1. fn::asset_path(filename) <exe_dir>/assets/<filename>
// 2. ./<filename> cwd directo (legacy)
// 3. ./assets/<filename> cwd subdir (legacy / dev)
// 4. $FN_ASSETS_DIR/<filename> override env
// 5. ${FN_CPP_ROOT}/<repo_subpath> fuente del repo (dev sin build)
std::string find_asset(const char* filename, const char* repo_subpath) {
std::string p;
p = fn::asset_path(filename); if (file_exists(p.c_str())) return p;
p = std::string("./") + filename; if (file_exists(p.c_str())) return p;
p = std::string("./assets/") + filename; if (file_exists(p.c_str())) return p;
if (const char* env = std::getenv("FN_ASSETS_DIR")) {