feat(types): Type Editor panel — CRUD de tipos en vivo (issue 0007)
- views_type_editor: panel "Types" con tabs Entities/Relations. Entities: name, color picker, shape combo, icon (ti-* + cp preview), principal_field combo, tabla de Fields (string/int/float/bool/date/url/enum) con required y enum values CSV; up/down/X por fila. Relations: name, color, style. Footer Save / Reload from disk + indicador dirty + error inline. - views_type_editor_delete_modal: confirm con conteo de entidades en uso. - types_registry: shape_name() + shape: emit en types_save_yaml para round-trip estable de la cosmetica editada en UI. - main.cpp: panel "Types" en g_panels; init types_draft tras load_input; want_types_save -> save + apply_types_yaml + rebuild atlas + bind + refresh inspector caches; want_types_reload simetrico; conteo de uso desde operations.db cuando se abre el modal de delete.
This commit is contained in:
@@ -26,6 +26,8 @@
|
||||
#include "entity_ops.h"
|
||||
#include "project_manager.h"
|
||||
|
||||
#include "../../../../cpp/vendor/sqlite3/sqlite3.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -206,6 +208,10 @@ static bool load_input() {
|
||||
g_app.parsed_types = std::move(pt);
|
||||
}
|
||||
}
|
||||
// Inicializar el draft del Type Editor con copia de parsed_types (0007).
|
||||
g_app.types_draft = g_app.parsed_types;
|
||||
g_app.types_dirty = false;
|
||||
g_app.types_save_error.clear();
|
||||
|
||||
// Restablecer viewport state (preserva camara user-visible)
|
||||
g_viewport.selection.clear();
|
||||
@@ -444,6 +450,7 @@ static fn_ui::PanelToggle g_panels[] = {
|
||||
{"Inspector", nullptr, &g_app.panel_inspector},
|
||||
{"Stats", nullptr, &g_app.panel_stats},
|
||||
{"Note", nullptr, &g_app.panel_note},
|
||||
{"Types", nullptr, &g_app.panel_type_editor},
|
||||
};
|
||||
|
||||
static void render() {
|
||||
@@ -573,6 +580,86 @@ static void render() {
|
||||
load_input();
|
||||
}
|
||||
|
||||
// ---- Type Editor (issue 0007) ----
|
||||
if (g_app.want_types_save) {
|
||||
g_app.want_types_save = false;
|
||||
g_app.types_save_error.clear();
|
||||
if (g_types_path.empty()) {
|
||||
g_app.types_save_error =
|
||||
"No hay types.yaml asignado (abre un proyecto o usa --types).";
|
||||
} else {
|
||||
std::string err;
|
||||
if (!ge::types_save_yaml(g_types_path.c_str(),
|
||||
g_app.types_draft, &err)) {
|
||||
g_app.types_save_error = "Save failed: " + err;
|
||||
} else {
|
||||
std::fprintf(stdout,
|
||||
"[graph_explorer] types.yaml saved -> %s\n",
|
||||
g_types_path.c_str());
|
||||
g_app.parsed_types = g_app.types_draft;
|
||||
std::vector<uint16_t> cps =
|
||||
ge::apply_types_yaml(g_graph, g_app.parsed_types);
|
||||
if (g_atlas) { graph_icons_destroy(g_atlas); g_atlas = nullptr; }
|
||||
g_atlas = ge::build_icon_atlas(cps);
|
||||
g_atlas_bound = false;
|
||||
g_gpu_dirty = true;
|
||||
g_app.types_dirty = false;
|
||||
ge::views_inspector_refresh_caches(g_app);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_app.want_types_reload) {
|
||||
g_app.want_types_reload = false;
|
||||
g_app.types_save_error.clear();
|
||||
if (g_types_path.empty()) {
|
||||
// Sin types.yaml en disco: descarta el draft a parsed_types actual.
|
||||
g_app.types_draft = g_app.parsed_types;
|
||||
g_app.types_dirty = false;
|
||||
} else {
|
||||
ge::ParsedTypes pt;
|
||||
std::string err;
|
||||
if (!ge::types_load_yaml(g_types_path.c_str(), &pt, &err)) {
|
||||
g_app.types_save_error = "Reload failed: " + err;
|
||||
} else {
|
||||
std::vector<uint16_t> cps = ge::apply_types_yaml(g_graph, pt);
|
||||
if (g_atlas) { graph_icons_destroy(g_atlas); g_atlas = nullptr; }
|
||||
g_atlas = ge::build_icon_atlas(cps);
|
||||
g_atlas_bound = false;
|
||||
g_gpu_dirty = true;
|
||||
g_app.parsed_types = pt;
|
||||
g_app.types_draft = std::move(pt);
|
||||
g_app.types_dirty = false;
|
||||
ge::views_inspector_refresh_caches(g_app);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Conteo de uso para el modal de borrado (entidades activas en BD).
|
||||
if (g_app.show_te_delete_modal && g_app.te_delete_use_count == 0
|
||||
&& !g_app.input_db_path.empty()) {
|
||||
const char* tname = nullptr;
|
||||
if (g_app.te_pending_delete_e >= 0
|
||||
&& g_app.te_pending_delete_e < (int)g_app.types_draft.entities.size()) {
|
||||
tname = g_app.types_draft.entities[g_app.te_pending_delete_e].name.c_str();
|
||||
}
|
||||
if (tname && *tname) {
|
||||
sqlite3* db = nullptr;
|
||||
if (sqlite3_open_v2(g_app.input_db_path.c_str(), &db,
|
||||
SQLITE_OPEN_READONLY, nullptr) == SQLITE_OK) {
|
||||
sqlite3_stmt* st = nullptr;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT COUNT(*) FROM entities WHERE type_ref = ?",
|
||||
-1, &st, nullptr) == SQLITE_OK) {
|
||||
sqlite3_bind_text(st, 1, tname, -1, SQLITE_TRANSIENT);
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
g_app.te_delete_use_count = sqlite3_column_int(st, 0);
|
||||
}
|
||||
sqlite3_finalize(st);
|
||||
}
|
||||
sqlite3_close(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Mutaciones (add/delete/duplicate/change_type) ----
|
||||
auto reload_after_mutation = [&]() {
|
||||
graph::GraphLoadStats stats{};
|
||||
@@ -845,6 +932,13 @@ static void render() {
|
||||
ImGui::SetNextWindowSize(ImVec2(700.0f, 480.0f), ImGuiCond_FirstUseEver);
|
||||
ge::views_note(g_app);
|
||||
|
||||
// Type Editor (issue 0007) — flotante, dockeable.
|
||||
ImGui::SetNextWindowPos (ImVec2(vp->WorkPos.x + W * 0.20f, top + 40.0f),
|
||||
ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(720.0f, 500.0f), ImGuiCond_FirstUseEver);
|
||||
ge::views_type_editor(g_app);
|
||||
ge::views_type_editor_delete_modal(g_app);
|
||||
|
||||
g_first_render = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user