feat(framework): convencion local_files/ — separacion distribuible vs estado
Toda app C++ basada en fn::run_app coloca sus archivos escribibles
bajo <exe_dir>/local_files/. Los distribuibles (.exe, dlls, ttfs,
enrichers/, runtime/) siguen junto al .exe. Esto deja la carpeta
distribuible limpia para zippear y separa con claridad lo que
viaja con la app de lo que el PC genera.
API publica en fn:: (cpp/framework/app_base.h):
- exe_dir() directorio del ejecutable
- local_dir() <exe_dir>/local_files/, creado on-demand
- local_path(name) <local_dir>/<name>
- migrate_to_local_files(...) mueve archivos viejos desde cwd/exe_dir
Cambios:
- run_app configura io.IniFilename = local_path("imgui.ini") y
llama migrate_to_local_files(["imgui.ini","app_settings.ini"])
antes de settings_load(). Migracion idempotente para PCs con
instalacion previa.
- app_settings.cpp usa local_path("app_settings.ini") en lugar de
hardcoded "app_settings.ini" relativo al cwd.
- cpp_apps.md §7 documenta la convencion como obligatoria. Las
apps deben usar fn::local_path() para cualquier archivo
escribible nuevo.
Beneficios:
- zip distribuible no se "ensucia" con .ini/.db generados al usar.
- reset trivial: borrar local_files/.
- backup/sync per-PC: solo local_files/ es propio del PC.
- elimina la mezcla de paths Linux/Windows que generaba bugs como
"projects\\default\\operations.db" en builds cross-platform.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -120,7 +120,52 @@ Cada app C++ es su propio repo en `dataforge/<name>` con branch `master`. Esto s
|
|||||||
- TBD obligatorio mientras se desarrolla la app: ver `apps_tbd.md`. Trabajar en `issue/<NNNN>-<slug>` o `quick/<slug>`, mergear a `master` con `--no-ff`.
|
- TBD obligatorio mientras se desarrolla la app: ver `apps_tbd.md`. Trabajar en `issue/<NNNN>-<slug>` o `quick/<slug>`, mergear a `master` con `--no-ff`.
|
||||||
- Sync entre PCs y push/pull se gestionan con `/full-git-push` y `/full-git-pull`.
|
- Sync entre PCs y push/pull se gestionan con `/full-git-push` y `/full-git-pull`.
|
||||||
|
|
||||||
### 7. Convenciones de runtime
|
### 7. Convencion `local_files/` — separacion de distribuible vs estado local
|
||||||
|
|
||||||
|
**OBLIGATORIO**: TODA app coloca sus archivos escribibles bajo
|
||||||
|
`<exe_dir>/local_files/`. Los archivos distribuibles (`.exe`, `.dll`,
|
||||||
|
`.ttf`, `enrichers/`, `runtime/`) viven directos en `<exe_dir>/`.
|
||||||
|
|
||||||
|
```
|
||||||
|
<exe_dir>/
|
||||||
|
├── <app>.exe
|
||||||
|
├── duckdb.dll, *.ttf, runtime/, enrichers/ ← read-only, ships con el zip
|
||||||
|
└── local_files/ ← writable, per-PC
|
||||||
|
├── imgui.ini ← gestionado por fn::run_app
|
||||||
|
├── app_settings.ini ← gestionado por fn_ui::settings_*
|
||||||
|
└── <lo que la app escriba> ← usar fn::local_path("nombre")
|
||||||
|
```
|
||||||
|
|
||||||
|
`fn::run_app` lo gestiona automaticamente para `imgui.ini` y
|
||||||
|
`app_settings.ini` y migra desde `<exe_dir>/` o `cwd` si vienen de
|
||||||
|
una version previa.
|
||||||
|
|
||||||
|
Apps que escriban archivos extra (DBs, caches, proyectos del
|
||||||
|
usuario) **DEBEN** usar `fn::local_path("nombre")` al construir
|
||||||
|
sus paths. Ejemplo:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// MAL
|
||||||
|
sqlite3_open("graph_explorer.db", &db);
|
||||||
|
fopen("graph_explorer.ini", "r");
|
||||||
|
|
||||||
|
// BIEN
|
||||||
|
sqlite3_open(fn::local_path("graph_explorer.db"), &db);
|
||||||
|
fopen(fn::local_path("graph_explorer.ini"), "r");
|
||||||
|
```
|
||||||
|
|
||||||
|
API en `cpp/framework/app_base.h`:
|
||||||
|
- `fn::exe_dir()` — directorio del ejecutable.
|
||||||
|
- `fn::local_dir()` — `<exe_dir>/local_files/`, creado on-demand.
|
||||||
|
- `fn::local_path(name)` — `<local_dir>/<name>`.
|
||||||
|
- `fn::migrate_to_local_files(names, n)` — mueve archivos viejos.
|
||||||
|
|
||||||
|
Beneficios:
|
||||||
|
- Carpeta del .exe limpia para distribuir (zip portable).
|
||||||
|
- Reset trivial (basta borrar `local_files/`).
|
||||||
|
- Separacion clara para backup/sync (solo `local_files/` es propio del PC).
|
||||||
|
|
||||||
|
### 8. Convenciones de runtime
|
||||||
|
|
||||||
Cumplir el checklist completo de `cpp/PATTERNS.md`. Resumen de lo que NUNCA debe aparecer en una app:
|
Cumplir el checklist completo de `cpp/PATTERNS.md`. Resumen de lo que NUNCA debe aparecer en una app:
|
||||||
|
|
||||||
@@ -134,6 +179,7 @@ Cumplir el checklist completo de `cpp/PATTERNS.md`. Resumen de lo que NUNCA debe
|
|||||||
| About hardcoded en un panel | `cfg.about = {...}` |
|
| About hardcoded en un panel | `cfg.about = {...}` |
|
||||||
| `gl*` directo sin loader | `cfg.init_gl_loader = true` |
|
| `gl*` directo sin loader | `cfg.init_gl_loader = true` |
|
||||||
| Tabla SQLite en la raiz del repo | `<app_dir>/<app>.db` (operations.db es solo para entities/relations/executions) |
|
| Tabla SQLite en la raiz del repo | `<app_dir>/<app>.db` (operations.db es solo para entities/relations/executions) |
|
||||||
|
| `fopen("foo.ini", ...)` con path relativo | `fopen(fn::local_path("foo.ini"), ...)` (ver §7) |
|
||||||
|
|
||||||
### 8. Tests visuales (recomendado, no obligatorio)
|
### 8. Tests visuales (recomendado, no obligatorio)
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,25 @@
|
|||||||
#include "core/app_about.h"
|
#include "core/app_about.h"
|
||||||
#include "core/app_menubar.h"
|
#include "core/app_menubar.h"
|
||||||
#include "core/fps_overlay.h"
|
#include "core/fps_overlay.h"
|
||||||
|
#include "core/logger.h"
|
||||||
|
#include "core/log_window.h"
|
||||||
#include "gfx/gl_loader.h"
|
#include "gfx/gl_loader.h"
|
||||||
|
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef TRACY_ENABLE
|
#ifdef TRACY_ENABLE
|
||||||
#include "tracy/Tracy.hpp"
|
#include "tracy/Tracy.hpp"
|
||||||
@@ -26,10 +41,121 @@ static void glfw_error_callback(int error, const char* description) {
|
|||||||
|
|
||||||
namespace fn {
|
namespace fn {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Local files
|
||||||
|
// ============================================================================
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string compute_exe_dir() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
wchar_t buf[MAX_PATH * 2];
|
||||||
|
DWORD n = GetModuleFileNameW(nullptr, buf,
|
||||||
|
(DWORD)(sizeof(buf) / sizeof(buf[0])));
|
||||||
|
if (n == 0 || n >= sizeof(buf)/sizeof(buf[0])) return "";
|
||||||
|
int u8n = WideCharToMultiByte(CP_UTF8, 0, buf, (int)n,
|
||||||
|
nullptr, 0, nullptr, nullptr);
|
||||||
|
std::string out(u8n, 0);
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, buf, (int)n, out.data(), u8n,
|
||||||
|
nullptr, nullptr);
|
||||||
|
size_t slash = out.find_last_of("/\\");
|
||||||
|
return (slash == std::string::npos) ? "" : out.substr(0, slash);
|
||||||
|
#else
|
||||||
|
char buf[4096];
|
||||||
|
ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
|
||||||
|
if (n <= 0) return "";
|
||||||
|
buf[n] = 0;
|
||||||
|
std::string out(buf);
|
||||||
|
size_t slash = out.find_last_of('/');
|
||||||
|
return (slash == std::string::npos) ? "" : out.substr(0, slash);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& exe_dir_ref() {
|
||||||
|
static std::string cached = compute_exe_dir();
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& local_dir_ref() {
|
||||||
|
static std::string cached;
|
||||||
|
static bool inited = false;
|
||||||
|
if (inited) return cached;
|
||||||
|
const std::string& edir = exe_dir_ref();
|
||||||
|
if (edir.empty()) {
|
||||||
|
cached = "local_files"; // fallback: relativo al cwd
|
||||||
|
} else {
|
||||||
|
cached = edir + "/local_files";
|
||||||
|
}
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directories(cached, ec);
|
||||||
|
inited = true;
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const char* exe_dir() { return exe_dir_ref().c_str(); }
|
||||||
|
const char* local_dir() { return local_dir_ref().c_str(); }
|
||||||
|
|
||||||
|
const char* local_path(const char* name) {
|
||||||
|
static thread_local std::string buf;
|
||||||
|
buf = local_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();
|
||||||
|
const std::string& edir = exe_dir_ref();
|
||||||
|
for (std::size_t i = 0; i < n; ++i) {
|
||||||
|
const char* name = names[i];
|
||||||
|
if (!name || !*name) continue;
|
||||||
|
std::string dst = ldir + "/" + name;
|
||||||
|
struct stat st{};
|
||||||
|
if (::stat(dst.c_str(), &st) == 0) continue; // ya existe en local
|
||||||
|
|
||||||
|
// Buscar en exe_dir y en cwd. Mover el primero que aparezca.
|
||||||
|
std::string cands[] = {
|
||||||
|
edir.empty() ? std::string() : (edir + "/" + name),
|
||||||
|
std::string(name),
|
||||||
|
};
|
||||||
|
for (const auto& src : cands) {
|
||||||
|
if (src.empty() || src == dst) continue;
|
||||||
|
if (::stat(src.c_str(), &st) != 0) continue;
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::rename(src, dst, ec);
|
||||||
|
if (ec) {
|
||||||
|
// Cross-device o permisos — fallback a copy + remove.
|
||||||
|
std::filesystem::copy(src, dst,
|
||||||
|
std::filesystem::copy_options::recursive |
|
||||||
|
std::filesystem::copy_options::overwrite_existing, ec);
|
||||||
|
if (!ec) std::filesystem::remove_all(src, ec);
|
||||||
|
}
|
||||||
|
std::fprintf(stdout,
|
||||||
|
"[local_files] migrado: %s -> %s\n",
|
||||||
|
src.c_str(), dst.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int run_app(AppConfig config, std::function<void()> render_fn) {
|
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) {
|
||||||
|
fn_log::logger_init(
|
||||||
|
config.log.file_path,
|
||||||
|
static_cast<fn_log::Level>(config.log.level));
|
||||||
|
fn_log::log_info("app start: %s", config.title ? config.title : "(no title)");
|
||||||
|
}
|
||||||
|
|
||||||
glfwSetErrorCallback(glfw_error_callback);
|
glfwSetErrorCallback(glfw_error_callback);
|
||||||
if (!glfwInit()) {
|
if (!glfwInit()) {
|
||||||
fprintf(stderr, "Failed to initialize GLFW\n");
|
fprintf(stderr, "Failed to initialize GLFW\n");
|
||||||
|
fn_log::log_error("GLFW init failed");
|
||||||
|
if (config.log.file_path != nullptr) fn_log::logger_close();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +170,8 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
|||||||
GLFWwindow* window = glfwCreateWindow(config.width, config.height, config.title, nullptr, nullptr);
|
GLFWwindow* window = glfwCreateWindow(config.width, config.height, config.title, nullptr, nullptr);
|
||||||
if (!window) {
|
if (!window) {
|
||||||
fprintf(stderr, "Failed to create GLFW window\n");
|
fprintf(stderr, "Failed to create GLFW window\n");
|
||||||
|
fn_log::log_error("GLFW createWindow failed (%dx%d)", config.width, config.height);
|
||||||
|
if (config.log.file_path != nullptr) fn_log::logger_close();
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -56,6 +184,8 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
|||||||
if (config.init_gl_loader) {
|
if (config.init_gl_loader) {
|
||||||
if (!fn::gfx::gl_loader_init()) {
|
if (!fn::gfx::gl_loader_init()) {
|
||||||
fprintf(stderr, "Failed to initialize GL function loader\n");
|
fprintf(stderr, "Failed to initialize GL function loader\n");
|
||||||
|
fn_log::log_error("gl_loader_init failed");
|
||||||
|
if (config.log.file_path != nullptr) fn_log::logger_close();
|
||||||
glfwDestroyWindow(window);
|
glfwDestroyWindow(window);
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -72,6 +202,17 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
|||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||||
|
|
||||||
|
// Convencion local_files: imgui.ini y app_settings.ini viven en
|
||||||
|
// <exe_dir>/local_files/. Migra automaticamente desde el cwd o
|
||||||
|
// exe_dir si vienen de una version previa.
|
||||||
|
{
|
||||||
|
static const char* legacy_names[] = {"imgui.ini", "app_settings.ini"};
|
||||||
|
migrate_to_local_files(legacy_names,
|
||||||
|
sizeof(legacy_names) / sizeof(legacy_names[0]));
|
||||||
|
}
|
||||||
|
static std::string s_imgui_ini = local_path("imgui.ini");
|
||||||
|
io.IniFilename = s_imgui_ini.c_str();
|
||||||
|
|
||||||
// Lee app_settings.ini (font_id, font_size_px, show_fps) antes de cargar
|
// Lee app_settings.ini (font_id, font_size_px, show_fps) antes de cargar
|
||||||
// fuentes. Si no existe el .ini, los defaults se aplican.
|
// fuentes. Si no existe el .ini, los defaults se aplican.
|
||||||
fn_ui::settings_load();
|
fn_ui::settings_load();
|
||||||
@@ -160,6 +301,9 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
|||||||
// Ventana de Settings (no-op si esta cerrada).
|
// Ventana de Settings (no-op si esta cerrada).
|
||||||
fn_ui::settings_window_render();
|
fn_ui::settings_window_render();
|
||||||
|
|
||||||
|
// Ventana de Logs (no-op si esta cerrada).
|
||||||
|
fn_ui::log_window_render();
|
||||||
|
|
||||||
// Ventana About (no-op si esta cerrada).
|
// Ventana About (no-op si esta cerrada).
|
||||||
fn_ui::about_window_render();
|
fn_ui::about_window_render();
|
||||||
|
|
||||||
@@ -194,6 +338,12 @@ int run_app(AppConfig config, std::function<void()> render_fn) {
|
|||||||
// Persiste settings al exit (idempotente con auto-saves del menu).
|
// Persiste settings al exit (idempotente con auto-saves del menu).
|
||||||
fn_ui::settings_save();
|
fn_ui::settings_save();
|
||||||
|
|
||||||
|
// Cierra el archivo de log (si la app lo abrio).
|
||||||
|
if (config.log.file_path != nullptr) {
|
||||||
|
fn_log::log_info("app exit");
|
||||||
|
fn_log::logger_close();
|
||||||
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
ImGui_ImplOpenGL3_Shutdown();
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
|
|||||||
@@ -17,10 +17,62 @@ namespace fn_ui {
|
|||||||
const char* version = nullptr;
|
const char* version = nullptr;
|
||||||
const char* description = nullptr;
|
const char* description = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Config de logging. Si file_path != nullptr, fn::run_app llama
|
||||||
|
// fn_log::logger_init(file_path, level) al inicio y fn_log::logger_close()
|
||||||
|
// al exit. file_path se interpreta relativo al cwd (junto al ejecutable,
|
||||||
|
// igual que app_settings.ini). Si file_path == nullptr, no se escribe a
|
||||||
|
// disco — la ventana Logs sigue funcionando contra el buffer in-memory.
|
||||||
|
//
|
||||||
|
// level: 0=Debug, 1=Info, 2=Warn, 3=Error. Default Info.
|
||||||
|
struct AppLogConfig {
|
||||||
|
const char* file_path = nullptr;
|
||||||
|
int level = 1; // fn_log::Level::Info
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace fn {
|
namespace fn {
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Local files — separacion de archivos distribuibles vs estado local.
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Convencion del registry: TODA app coloca sus archivos escribibles
|
||||||
|
// (settings, DBs, layouts ImGui, caches, proyectos del usuario) bajo
|
||||||
|
// `<exe_dir>/local_files/`. Los archivos distribuibles (.exe, .ttf,
|
||||||
|
// .dll, runtime/, enrichers/) viven directos en `<exe_dir>/`.
|
||||||
|
//
|
||||||
|
// Esto mantiene la carpeta del .exe limpia para distribuir, separa
|
||||||
|
// nitidamente "lo que vino con el zip" de "lo que el PC genero", y
|
||||||
|
// facilita el reset (basta con borrar local_files/).
|
||||||
|
//
|
||||||
|
// `fn::run_app` configura `io.IniFilename = local_path("imgui.ini")` y
|
||||||
|
// `app_settings.ini` se lee/escribe desde local_files/ automaticamente.
|
||||||
|
// Cualquier archivo escribible adicional de la app debe usar
|
||||||
|
// `fn::local_path("nombre")` al construir su path.
|
||||||
|
//
|
||||||
|
// La carpeta se crea on-demand en la primera llamada a `local_dir()`.
|
||||||
|
// Si existen archivos viejos en el cwd (compat con versiones previas
|
||||||
|
// del registry), `migrate_to_local_files()` los mueve.
|
||||||
|
|
||||||
|
// Devuelve el directorio del ejecutable actual (sin trailing slash).
|
||||||
|
// "" si no se puede resolver (raro — fallback al cwd).
|
||||||
|
const char* exe_dir();
|
||||||
|
|
||||||
|
// Devuelve el path absoluto a `<exe_dir>/local_files/`. Crea la
|
||||||
|
// carpeta si no existe. Sin trailing slash.
|
||||||
|
const char* local_dir();
|
||||||
|
|
||||||
|
// Construye `<local_dir>/<name>`. El puntero retornado apunta a un
|
||||||
|
// std::string interno por-thread que permanece valido hasta la
|
||||||
|
// proxima llamada — copia el valor si vas a guardarlo.
|
||||||
|
const char* local_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.
|
||||||
|
void migrate_to_local_files(const char* const* names, std::size_t n);
|
||||||
|
|
||||||
// Modos de tema para run_app.
|
// Modos de tema para run_app.
|
||||||
enum class ThemeMode {
|
enum class ThemeMode {
|
||||||
FnDark, // Identidad del registry (Mantine v9 dark + indigo). DEFAULT.
|
FnDark, // Identidad del registry (Mantine v9 dark + indigo). DEFAULT.
|
||||||
@@ -58,6 +110,12 @@ struct AppConfig {
|
|||||||
// GL y antes del primer frame. Necesario para apps que llaman gl* directo
|
// GL y antes del primer frame. Necesario para apps que llaman gl* directo
|
||||||
// en Windows (en Linux es no-op).
|
// en Windows (en Linux es no-op).
|
||||||
bool init_gl_loader = false;
|
bool init_gl_loader = false;
|
||||||
|
|
||||||
|
// Logging opcional. Si log.file_path != nullptr, run_app inicializa el
|
||||||
|
// logger global antes del primer frame y lo cierra al exit. La ventana
|
||||||
|
// "Logs..." en el menubar siempre esta disponible (lee del buffer
|
||||||
|
// in-memory aunque no haya archivo).
|
||||||
|
fn_ui::AppLogConfig log{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run an ImGui application. The render_fn is called every frame
|
// Run an ImGui application. The render_fn is called every frame
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "app_settings.h"
|
#include "app_settings.h"
|
||||||
|
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
#include "../../framework/app_base.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -16,7 +17,9 @@ AppSettings g_settings;
|
|||||||
bool g_font_dirty = false;
|
bool g_font_dirty = false;
|
||||||
bool g_window_open = false;
|
bool g_window_open = false;
|
||||||
|
|
||||||
constexpr const char* kSettingsPath = "app_settings.ini";
|
// app_settings.ini vive en <exe_dir>/local_files/ (convencion del
|
||||||
|
// registry — todos los archivos escribibles bajo local_files/).
|
||||||
|
const char* settings_path() { return fn::local_path("app_settings.ini"); }
|
||||||
|
|
||||||
const float k_sizes[] = {12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 18.0f, 20.0f};
|
const float k_sizes[] = {12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 18.0f, 20.0f};
|
||||||
constexpr int k_size_count = sizeof(k_sizes) / sizeof(k_sizes[0]);
|
constexpr int k_size_count = sizeof(k_sizes) / sizeof(k_sizes[0]);
|
||||||
@@ -59,7 +62,7 @@ void parse_line(const char* line) {
|
|||||||
AppSettings& settings() { return g_settings; }
|
AppSettings& settings() { return g_settings; }
|
||||||
|
|
||||||
void settings_load() {
|
void settings_load() {
|
||||||
FILE* f = std::fopen(kSettingsPath, "r");
|
FILE* f = std::fopen(settings_path(), "r");
|
||||||
if (!f) return;
|
if (!f) return;
|
||||||
char line[256];
|
char line[256];
|
||||||
while (std::fgets(line, sizeof(line), f)) parse_line(line);
|
while (std::fgets(line, sizeof(line), f)) parse_line(line);
|
||||||
@@ -67,9 +70,9 @@ void settings_load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void settings_save() {
|
void settings_save() {
|
||||||
FILE* f = std::fopen(kSettingsPath, "w");
|
FILE* f = std::fopen(settings_path(), "w");
|
||||||
if (!f) {
|
if (!f) {
|
||||||
std::fprintf(stderr, "[fn_ui] settings_save: no pude abrir %s\n", kSettingsPath);
|
std::fprintf(stderr, "[fn_ui] settings_save: no pude abrir %s\n", settings_path());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::fprintf(f, "# fn_registry app_settings.ini — autogenerado, editable\n");
|
std::fprintf(f, "# fn_registry app_settings.ini — autogenerado, editable\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user