fix: docking gaps + hover radius + node spread + markdown notes

Bug fixes
- ImGui ID conflict en menu Change type: dedup tipos del grafo +
  defaults; PushID/PopID por entrada.
- Dockspace ya no tapa la toolbar: se posiciona 44 px por debajo, asi
  las ventanas dockeadas al borde superior quedan bajo la barra de
  filtros, no detras.
- Hover radius proporcional al tamaño visual del nodo: query espacial
  amplio (24/zoom) + filtro fino por (radio_visual + 2 px) / zoom. El
  tooltip solo se dispara si el raton esta efectivamente sobre el nodo.

Layout
- Default layout = grid (en vez de force) para que los grafos cargados
  se distribuyan ordenadamente al abrir.
- Boton "Reset layout" en la toolbar: limpia NF_PINNED en todos los
  nodos, resetea velocidades y reaplica el layout activo.
- Nodos recien creados (add_node, duplicate) caen en un anillo poisson
  alrededor del centro de la vista, no en el origen. Posicion
  determinista por user_data para que el mismo nodo no salte entre
  reloads.

Notes (markdown)
- Panel "Note" (dockeable) abierto con doble click sobre un nodo.
- entity_get_notes / entity_set_notes en entity_ops sobre la columna
  `notes` de operations.db (ya existente en el schema).
- Ctrl+S guarda. Cabecera muestra entity, type, id.
This commit is contained in:
2026-04-30 23:11:48 +02:00
parent 02eef6e339
commit adde3026ea
5 changed files with 273 additions and 27 deletions
+76
View File
@@ -138,6 +138,10 @@ void views_toolbar(AppState& app) {
if (button(TI_REFRESH " Reload", ButtonVariant::Subtle)) {
app.want_reload = true;
}
if (button(TI_LAYOUT_GRID " Reset layout", ButtonVariant::Subtle)) {
app.want_unpin_all = true;
++app.apply_layout_tick;
}
toolbar_separator();
ImGui::Checkbox("GPU layout", &app.use_gpu);
@@ -338,6 +342,78 @@ void views_stats(AppState& app) {
ImGui::End();
}
// ----------------------------------------------------------------------------
// Note editor (markdown)
// ----------------------------------------------------------------------------
void views_note(AppState& app) {
if (!app.panel_note) return;
if (!ImGui::Begin(TI_FILE_TEXT " Note", &app.panel_note,
ImGuiWindowFlags_MenuBar)) {
ImGui::End();
return;
}
if (ImGui::BeginMenuBar()) {
if (ImGui::MenuItem(TI_DEVICE_FLOPPY " Save", "Ctrl+S",
/*selected=*/false, app.note_dirty)) {
app.want_save_note = true;
}
if (app.note_dirty) {
ImGui::TextDisabled("(modified)");
}
ImGui::EndMenuBar();
}
if (app.note_node < 0) {
ImGui::TextDisabled("Doble click sobre un nodo para abrir su nota.");
ImGui::End();
return;
}
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImGui::TextUnformatted("entity:");
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::TextUnformatted(app.note_entity_label.empty()
? "(unnamed)" : app.note_entity_label.c_str());
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImGui::TextUnformatted("type:");
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::TextUnformatted(app.note_entity_type.empty()
? "(no-type)" : app.note_entity_type.c_str());
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
ImGui::TextUnformatted("id:");
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::TextUnformatted(app.note_entity_id.c_str());
ImGui::Separator();
if (app.note_buf.empty()) app.note_buf.resize(1024, 0);
// Crece el buffer si esta cerca del limite.
if (std::strlen(app.note_buf.data()) + 64 >= app.note_buf.size()) {
app.note_buf.resize(app.note_buf.size() * 2, 0);
}
ImVec2 size = ImGui::GetContentRegionAvail();
if (ImGui::InputTextMultiline("##note_md", app.note_buf.data(),
app.note_buf.size(), size,
ImGuiInputTextFlags_AllowTabInput)) {
app.note_dirty = true;
}
if (ImGui::IsItemHovered() &&
ImGui::IsKeyChordPressed(ImGuiMod_Ctrl | ImGuiKey_S)) {
app.want_save_note = true;
}
ImGui::End();
}
// ----------------------------------------------------------------------------
// Modals
// ----------------------------------------------------------------------------