Commit Graph

56 Commits

Author SHA1 Message Date
egutierrez 958189227d chore(registry): notes en huerfanas usadas por framework/apps
Auditoria del issue 0044: anota en notes: el contexto de consumo de
huerfanos que no pueden registrarse en uses_functions porque sus
consumidores no son funciones del registry:
- consumido por cpp/framework/app_base.cpp (framework no indexado)
- consumido por cpp/apps/{shaders_lab,chart_demo,text_editor_smoke}/main.cpp
- scaffolding/demo en primitives_gallery

31 huerfanas anotadas. Las que quedan en uses_functions=[] tras esto
son hojas legitimas (no llaman a nada) o realmente sin uso (lista
DEAD reportada en el issue 0044).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:40:51 +02:00
egutierrez 96fcd05511 chore(registry): añadir uses_functions a consumidores reales (viz)
Auditoria del issue 0044: 9 archivos .md de cpp/functions/viz/ con
uses_functions actualizado. Resuelve dependencias detectadas via
#include: plot_static (consumido por bar_chart, histogram, line_plot,
pie_chart, scatter_plot), gl_loader, gl_framebuffer, gl_shader,
graph_force_layout, graph_renderer, graph_spatial_hash, orbit_camera,
sparkline y tokens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:40:37 +02:00
egutierrez 08cc179ca8 chore(registry): añadir uses_functions a consumidores reales (gfx)
Auditoria del issue 0044: 14 archivos .md de cpp/functions/gfx/ con
uses_functions actualizado. Resuelve dependencias detectadas via
#include: gl_loader (consumido por casi todo el dominio gfx),
dag_catalog (consumido por la familia dag_*), fullscreen_quad,
gl_framebuffer, gl_shader, mesh_obj_load, uniform_parser y
dag_node_previews.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:40:31 +02:00
egutierrez e356b7ac42 chore(registry): añadir uses_functions a consumidores reales (core)
Auditoria del issue 0044: 17 archivos .md de cpp/functions/core/ con
uses_functions actualizado para reflejar las llamadas reales detectadas
mediante #include en sus .cpp/.h. Los huerfanos referenciados (tokens,
app_about, app_settings, layouts_menu, panel_menu, table_view,
text_editor, tween_curves, app_settings) ahora aparecen en el grafo de
dependencias del registry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:40:22 +02:00
egutierrez 836ff02578 docs: ADR 0002 + CHANGELOG + reglas para dataforge/<name>+master
- docs/adr/0002-apps-analyses-as-dataforge-master.md: decision arquitectural
  con contexto, alternativas descartadas y cambios concretos del 2026-04-28.
- CHANGELOG.md: entrada 2026-04-28 con Added/Changed/Fixed.
- .claude/CLAUDE.md: nota sobre /full-git-push y dataforge/<name>+master.
- .claude/rules/apps_tbd.md: tronco unico master + init.defaultBranch.
- cpp/functions/core/app_menubar.md: notas del submenu Settings con About.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:41:55 +02:00
egutierrez edcf029c6d feat(cpp,bash): app_about + Settings submenu, ensure_repo_synced pipeline
cpp/core: nuevo modulo app_about — ventana About con project/version/desc,
componible via about_window_set_info() en el init de la app y rendererizada
automaticamente por fn::run_app al final de cada frame.

app_menubar: el item top-level "Settings..." pasa a ser un BeginMenu
"Settings" con dos subitems: "Settings..." (existente) y "About..." (nuevo).

bash/infra: nueva pipeline ensure_repo_synced que compone gitea_create_repo
y gitea_push_directory para garantizar repo Gitea existente + sync de un
directorio local en una sola llamada idempotente.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:05:31 +02:00
egutierrez 63031c26e0 merge: issue/0034 — scientific viz (treemap, sankey, chord, contour, voronoi)
# Conflicts:
#	cpp/apps/primitives_gallery/CMakeLists.txt
#	cpp/apps/primitives_gallery/demos.h
#	cpp/apps/primitives_gallery/main.cpp
2026-04-25 21:55:49 +02:00
egutierrez 1078b2d2e1 merge: issue/0032 — sql_workbench
# Conflicts:
#	cpp/apps/primitives_gallery/CMakeLists.txt
#	cpp/apps/primitives_gallery/demos.h
#	cpp/apps/primitives_gallery/main.cpp
2026-04-25 21:55:17 +02:00
egutierrez 7a96f01a20 merge: issue/0031 — animation curves (timeline + bezier_editor + tween_curves)
# Conflicts:
#	cpp/apps/primitives_gallery/CMakeLists.txt
2026-04-25 21:54:48 +02:00
egutierrez bcdb51e1b8 merge: issue/0029 — mesh_viewer + obj loader + orbit_camera
# Conflicts:
#	cpp/apps/primitives_gallery/demos.h
#	cpp/apps/primitives_gallery/main.cpp
2026-04-25 21:54:27 +02:00
egutierrez 4efbd61603 feat(viz): voronoi diagram via raster brute-force (MVP)
Para cada tile 4x4 px del rect de render: encontrar seed mas cercano
(distancia Euclidea) y rellenar con su color. Suficiente para N<=200
seeds en region <=600x400.

voronoi_layout deja polygon vacio en MVP — solo rellena seed/color.
Para extraer poligonos analiticos seria necesario half-plane
intersections (Fortune) — diferido a otro issue.
2026-04-25 21:52:52 +02:00
egutierrez 0b6b984dd3 feat(viz): contour plot via marching squares — layout puro + render
contour_compute implementa marching squares clasico (16 casos, casos
ambiguos 5 y 10 partidos en 2 segmentos). Para cada level devuelve un
ContourLine{pts, level} con segmentos en coords [0..nx-1]x[0..ny-1].

Verificado con gaussiana 32x32 + 4 niveles: todos los endpoints aparecen
>=2 veces (curvas cerradas, ningun endpoint huerfano).
2026-04-25 21:52:48 +02:00
egutierrez 071aa71a04 feat(viz): chord diagram — arcos circulares + cuerdas bezier
Para una matriz NxN: cada nodo ocupa un arco proporcional a sum(row).
Las cuerdas matrix[i,j] son bandas bezier cubico hacia el centro
conectando los arcos de i y j.

Limitacion: las cuerdas se dibujan con AddConvexPolyFilled aunque la
forma no sea estrictamente convexa — visualmente queda razonable.
2026-04-25 21:52:43 +02:00
egutierrez 64330944e1 feat(viz): sankey diagram — BFS topologico + bandas curvas (bezier cubico)
compute_levels asigna columnas via BFS, los nodos se apilan verticalmente
proporcional a max(in_total, out_total). Los links se renderizan como
bandas con bezier cubico, color del nodo origen + alpha bajo.

Asume DAG (sin ciclos). Si hay ciclos, los nodos del ciclo quedan en su
nivel parcial — no rompe pero puede solapar visualmente.
2026-04-25 21:52:37 +02:00
egutierrez 643d3a2abf feat(viz): treemap squarified (Bruls et al.) — layout puro + render DrawList
treemap_layout devuelve TreemapRect{min, max, item} con coords absolutas
dentro de la region. La suma de areas == area total (verificado via test
standalone, ratio=1.000000). El render usa AddRectFilled + AddText cuando
labels y valores caben dentro de la cell.

Limitaciones MVP: jerarquia plana (no recursivo), sin interaccion.
2026-04-25 21:52:33 +02:00
egutierrez 281502ac92 feat(viz): mesh_viewer — componente 3D con FBO + Lambert headlight
Compila/cachea por id un programa GLSL (vertex+fragment) con
iluminacion Lambert (luz=camara), gestiona Framebuffer cacheado por
id, dibuja MeshGpu con orbit camera, muestra via ImGui::Image y
maneja drag (mouse) + wheel (zoom). Wireframe opcional via
glPolygonMode.

gl_loader: añade glUniformMatrix4fv (proc requerido en Windows para
subir las matrices view/proj del mesh_viewer).

issue 0029
2026-04-25 21:51:22 +02:00
egutierrez 3b662ac4c3 feat(core): orbit_camera — camara orbital pura (matrices view/proj + drag)
State minimal (azimuth, elevation, distance, fov, aspect, near/far).
orbit_camera_matrices: lookAt+perspective row-major float[16] (subir a
GL con transpose=GL_TRUE). orbit_camera_handle_drag: dx→azimuth,
dy→elevation (clamp ±π/2-eps), wheel→distance (clamp >0.1). Sin glm,
solo <cmath>.

issue 0029
2026-04-25 21:51:15 +02:00
egutierrez 44e189c5cc feat(gfx): mesh_gpu — VAO/VBO/EBO upload para Mesh
mesh_gpu_upload sube positions+normals interleaved (stride 6 floats,
location 0=a_pos, location 1=a_normal) + EBO uint32. mesh_gpu_destroy
libera todo. GL_STATIC_DRAW (mesh inmutable post-upload).

issue 0029
2026-04-25 21:51:10 +02:00
egutierrez 580e4ba1fd feat(gfx): mesh_obj_load — minimal Wavefront .obj parser
mesh_obj_parse (pure) + mesh_obj_load (impure file helper).
Soporta v / vn / f (tris y quads). Genera normales per-face si
faltan (flat shading). Quads se parten en 2 tris; n-gons (>4) se
descartan silenciosamente. Indices 1-based positivos y negativos.

issue 0029
2026-04-25 21:51:05 +02:00
egutierrez 66f5ca1a4f feat(core): add timeline — DAW-style keyframe widget
Timeline widget with:
- Header: play/pause + reset + duration drag + loop checkbox
- Ruler: 0.5s ticks, scrub via click+drag
- Tracks: horizontal rows with diamond-shaped draggable keyframes
- Playhead: vertical primary_light line + ruler triangle marker

State and types:
- Keyframe { time, value, ease }
- Track { name, vector<Keyframe> }
- TimelineState { tracks, current_time, duration, playing, loop }

Pure functions:
- track_value_at(track, t): interp between keys, ease applied via the
  destination keyframe (Maya/AfterEffects convention)
- timeline_update(state, dt): advance current_time, wrap or saturate

Render with fn_tokens for visual coherence with the rest of the design
system. Keys are sorted by time on every changed frame to keep order
consistent during drag.
2026-04-25 21:50:35 +02:00
egutierrez b9810a88d4 feat(core): add bezier_editor — visual cubic Bezier curve editor
ImGui canvas with 4 draggable control points (p0/p3 locked at (0,0)/(1,1)
by default for use as easing curves). Pure evaluation via De Casteljau
(bezier_point) plus sampling-based y(x) lookup (bezier_eval).

Render uses fn_tokens for visual coherence: bg, border, primary curve,
text_dim diagonal reference, text_muted handle tangents.

p1.x and p2.x clamped to [0,1] to keep the curve usable as easing; Y
values are free to allow deliberate overshoot.
2026-04-25 21:50:29 +02:00
egutierrez 76215765a7 feat(core): add tween_curves — Penner easing functions
16 easing curves (linear, quad, cubic, expo, elastic, bounce in/out/inOut)
header-mostly so the compiler inlines on hot paths. Pure, no I/O, no state.

Includes:
- tween::apply(Ease, t) dispatcher for data-driven uses (timeline keyframe.ease,
  dropdown selection)
- tween::name(Ease) for UI labels
- tween::ease_count for iteration

Tested values documented in tween_curves.md.
2026-04-25 21:50:21 +02:00
egutierrez d854bcbae9 feat(core): sql_workbench component (issue 0032)
ImGui SQL workbench: editor (text_editor + CodeLang::SQL), schema sidebar
desde sqlite_master, tabla resultado (table_view), historial. Backend
sql_workbench_run_query separado de UI; readonly opt-in (rechaza non-SELECT/
PRAGMA/EXPLAIN/WITH); cap de filas (truncated_at, default 10000); last_ms
medido con steady_clock.

API: sql_workbench(id, sqlite3*, SqlWorkbenchState&, size). DB caller-owned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:48:50 +02:00
egutierrez 4268b6f187 feat(viz): surface_plot_3d real (ImPlot3D) + scatter_3d nuevo
surface_plot_3d (v2.0.0): quita el STUB. API basada en
SurfacePlot3DConfig (z[nx*ny] row-major + ranges X/Y) que delega en
ImPlot3D::PlotSurface. Las coordenadas X/Y por vertice se generan
internamente desde [x_min, x_max] x [y_min, y_max].

scatter_3d (v1.0.0): nuevo primitivo. Scatter 3D con tamano y color
opcionales por punto via ImPlot3DSpec::MarkerSizes / MarkerFillColors.
Util para PCA / clustering / nubes de puntos sinteticas.

Ambos namespace fn::, kind component, purity pure. Orbit / zoom / pan
los aporta ImPlot3D nativo.

Issue 0028.
2026-04-25 21:48:43 +02:00
egutierrez 24efee80e2 feat(cpp/gfx): code_to_generator + shaderlab_db
Dos primitivas del pipeline de shaders_lab que ya estaban en uso pero sin
indexar al registry:

- code_to_generator_cpp_gfx (function, pure)
  Traduce un fragment shader GLSL escrito a mano (modo Code, con void main()
  + fragColor = ...) en un body de DAG Gen + DagControl[]. Cada uniform
  anotado se convierte en un control; el body usa el parametro uv y reemplaza
  fragColor= por return. Empaqueta uniforms en vec4 (4 x n_uniforms).

- shaderlab_db_cpp_gfx (function, impure)
  CRUD persistente para generators custom de shaders_lab via sqlite3.
  Guarda el GLSL original, el body traducido para el DAG, los DagControl y
  los param_defaults en una BD local (shaders_lab.db). Soporta open(:memory:)
  para tests.

Ambas se indexan ahora en registry.db y son reusables fuera de shaders_lab
si en el futuro hay otra app que componga DAGs de shaders.
2026-04-25 21:26:03 +02:00
egutierrez d317900eea feat(cpp/core): app framework UI (settings, menubar, layouts)
5 primitivas que componen el chrome de una app fn_ui completa:

- app_settings_cpp_core         (function, impure)  ventana flotante de
  settings globales (Display + Typography + secciones extra registrables) con
  persistencia automatica en app_settings.ini junto al ejecutable.
- app_menubar_cpp_core          (component, pure)   MainMenuBar unificada
  con menu View (toggles de paneles) y Layouts. Punto de entrada de la
  menubar de cualquier app fn_ui.
- layouts_menu_cpp_core         (component, pure)   menu para guardar/aplicar/
  borrar/reset layouts de ImGui via callbacks (no I/O propio).
- panel_menu_cpp_core           (component, pure)   menu checkable para
  abrir/cerrar paneles, composable dentro de la MainMenuBar.
- layout_storage_sqlite_cpp_core (function, impure) primitivas CRUD de bajo
  nivel para persistir blobs INI de ImGui en sqlite (ui_layouts table).

Permiten que apps como shaders_lab y registry_dashboard ofrezcan: panel
toggles, layouts persistentes en BD, ventana de settings con preview en
vivo. Todas usan tokens_cpp_core para el styling.
2026-04-25 21:25:51 +02:00
egutierrez da6a8b5e59 feat(cpp/core): primitivas UI estilo Mantine
Anade 9 primitivas reutilizables al registry C++ que replican el comportamiento
de los componentes correspondientes de @fn_library / Mantine v9, todas
estilizadas con tokens_cpp_core (colores Mantine dark + indigo):

- button_cpp_core         (component, pure)  variantes primary/secondary/subtle/danger + sm/md/lg
- icon_button_cpp_core    (component, pure)  cuadrado 28x28 con glyph centrado + tooltip
- toolbar_cpp_core        (component, pure)  grupo horizontal de acciones con separadores
- modal_dialog_cpp_core   (component, pure)  popup modal centrada + close con Escape
- text_input_cpp_core     (component, impure) InputText con label muted + placeholder
- select_cpp_core         (component, impure) dropdown con label + opcion '(none)' opcional
- toast_cpp_core          (component, impure) notificaciones efimeras + inbox con badge
- tree_view_cpp_core      (component, impure) jerarquia low-level con tree_node_clicked helper
- process_runner_cpp_core (component, impure) tarea en std::thread + spinner inline

Cada primitiva tiene su .md con frontmatter completo (params/output) y se
indexa via fn index. Son la base del primitives_gallery y de cualquier
app fn_ui futura.
2026-04-25 21:25:39 +02:00
egutierrez 5d5b1d3fea feat(cpp/core): icon_font + icons_tabler
icon_font_cpp_core (impure): carga Karla-Regular como texto vectorial y
mergea Tabler Icons al mismo tamano en el atlas de ImGui. Tras el call,
los TI_* renderizan inline con el texto.

icons_tabler.h: header con macros TI_<NAME> que apuntan a los codepoints
del set Tabler (UTF-8 escapado). Generado a partir del CSS del vendor con
cpp/vendor/tabler-icons/gen_header.py — re-ejecutable si se actualiza el
set sin tocar a mano los ~5500 codepoints.

Justifica la regla cpp_icons.md: todas las apps C++ usan TI_* en lugar de
emoji o hex inline.
2026-04-25 21:25:25 +02:00
egutierrez b093c898a8 docs(issues): marcar 0025 y 0026 como completados + WIP master
Wave 1 de parallel-fix-issues integrada a master:
- 0025: text_editor_cpp_core + file_watcher_cpp_core
- 0026: gl_texture_load_cpp_gfx (vendor: stb_image v2.30)

Ademas se commitea WIP previo de master que estaba sin commitear (cambios
en shaders_lab, dag_*, framework, tokens, kpi_card, gl_loader.md, etc.)
para dejar HEAD buildable.

Notas:
- Algunos deps del gallery (button.cpp, toolbar.cpp, modal_dialog.cpp...)
  siguen UNTRACKED — gating con FN_BUILD_GALLERY=ON (default OFF) para
  que master build (sin flag) no los necesite.
- Build OK con y sin flag. fn index registra 904 functions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:14:15 +02:00
egutierrez 0c7a8393ab merge: issue/0026 — gl_texture_load (stb_image)
# Conflicts:
#	cpp/CMakeLists.txt
2026-04-25 21:05:11 +02:00
egutierrez 07a653d97d feat(cpp/core): add file_watcher_cpp_core (inotify Linux / RDCW Win)
Watcher de archivos no bloqueante con backend nativo por plataforma:

- Linux: inotify_init1(IN_NONBLOCK | IN_CLOEXEC), inotify_add_watch con
  mascara MODIFY|CREATE|DELETE|CLOSE_WRITE|MOVED_*. Drain en cada poll().
- Windows: ReadDirectoryChangesW overlapped + GetOverlappedResult no
  bloqueante. Para vigilar un archivo, registra el directorio padre y filtra
  por nombre exacto en el poll().
- Otros: stub — poll() devuelve vacio y last_error() reporta no soportado.

API en namespace fn:: con tipos opacos (FileWatcher PIMPL). Errores via
last_error() en vez de excepciones. Documentadas las limitaciones (limite de
inotify watches en Linux, granularidad directorio-level en Windows, no
recursividad).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:00:38 +02:00
egutierrez 24905eebc7 feat(cpp/core): add text_editor_cpp_core component (PIMPL)
Wrapper en namespace fn:: sobre ImGuiColorTextEdit. La API publica solo expone
TextEditorState como tipo opaco; el TextEditor del vendor vive dentro del .cpp.

Soporta CodeLang::{Generic, GLSL, SQL, Cpp} (highlighting via las
LanguageDefinition del vendor). text_editor_render() devuelve true en el frame
en que el contenido cambia; flag dirty manejado independientemente para casos
"editado pero aun no guardado".

text_editor_get_text() cachea el resultado en un std::string del state para
mantener el const char* valido entre llamadas (el GetText() del vendor devuelve
std::string por valor).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:00:31 +02:00
egutierrez d1d20bdc04 feat(gfx): añadir gl_texture_load_cpp_gfx
Funcion impura del registry que carga PNG/JPG/BMP/TGA/HDR a una textura
OpenGL lista para sampler2D. Composable con gl_loader, gl_shader,
shader_canvas. Genera mipmaps, soporta sRGB y HDR (RGBA16F).

API:
  GlTexture gl_texture_load(path, flip_y=true, srgb=false)
  GlTexture gl_texture_load_from_memory(data, size, ...)
  void      gl_texture_destroy(tex)
  const char* gl_texture_last_error()  // thread-local
  void      gl_texture_bind_uniform(prog, name, tex, unit)

Errores via thread_local string accesible por gl_texture_last_error().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:58:14 +02:00
egutierrez 83f64aa3e1 feat(gl_loader): añadir glActiveTexture y glGenerateMipmap
Necesarios para que gl_texture_load (cpp/functions/gfx/) funcione en
Windows tras wglGetProcAddress. En Linux son simbolos directos via
GL_GLEXT_PROTOTYPES, no afecta.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:58:07 +02:00
egutierrez ac65791663 feat(shaders_lab): per-node preview thumbnails + double-rclick delete node
- DagStep: preview_open flag (default false).
- dag_compile: emit `uniform int u_preview_target` and a series of
  early-return branches at the start of fragColor selection. -1 (default)
  falls through to the real Output-driven fragColor.
- dag_node_previews (new fn): per-node FBO keyed by editor_uid, lazy
  created. Renders each node with preview_open=true to its FBO by
  setting u_preview_target = step index. Texture exposed via
  dag_preview_texture(uid) for ImGui::Image.
- dag_node_editor: small toggle button "[+] preview"/"[-] preview" in
  each non-Output node; when open, ImGui::Image(96x64, V-flipped).
- dag_node_editor: double right-click on hovered node deletes it
  (Output is protected).
- main.cpp: dag_previews_render after Canvas DAG; dag_previews_destroy
  on shutdown.

Single GL program drives both the canvas and all thumbnails — moving
sliders never recompiles, only the topology change does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:06:50 +02:00
egutierrez a209afa46b feat(shaders_lab): pins straddle node edges (half outside, half inside)
draw_pin_circle takes a PinSide and centers the circle exactly on the
left or right edge of the node. The reserved Dummy is half-width
(PIN_RADIUS instead of PIN_DIAMETER) so the inside layout stays
compact, and ed::PinRect is set to the full circle so the protruding
half is still grabbable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:59:01 +02:00
egutierrez 426a842e2b feat(shaders_lab): pins on node edges, neutral color, right-click clears
- Pin radius bumped to 9px (18px diameter). Easier to grab.
- Single neutral pin/cable color across all kinds (data is uniformly
  vec4 — coloring by node kind was misleading).
- ed::StyleVar_NodePadding(0,8,0,8): zero left/right padding so the pin
  circles sit flush with the node's edges, separated from the title
  and controls by 8px gaps.
- Right-click on a pin clears all connections on that pin:
  - input pin → clear that single slot's source_id
  - output pin → clear every source_id across the pipeline pointing
    to this node (fan-out).
- Right-click on a link still deletes that single link (existing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:52:09 +02:00
egutierrez 075088b7aa feat(shaders_lab): big colored pin circles, side layout, right-click delete
- Three-column node layout: input pins on the left edge, controls in
  the middle, output pin on the right edge.
- Pins rendered as 14px filled circles with darker outline. Color is
  the node's kind (gen=blue, op=violet, blend=amber, output=red).
- ed::PinPivotAlignment(0,0.5)/(1,0.5) so the cable starts/ends at
  the circle center.
- Empty columns (gen with no inputs, output with no output) get a
  pin-sized Dummy so column widths stay consistent.
- ed::Link now passes color (= source node kind) and 2.5px thickness.
- Right-click on a link deletes it immediately via
  ed::ShowLinkContextMenu (no popup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:46:17 +02:00
egutierrez 144b15f0ce fix(shaders_lab): cycle check uses real reachability, not vector index
Previously the cycle validator rejected any link whose source had a
vector index >= target's, which silently killed legitimate connections
between nodes added in the wrong drop order.

Switch to a DFS over source_ids: an edge from->to creates a cycle iff
`from` already (transitively) depends on `to`. topo_sort runs after
each topology change so the vector ends up in a consistent order
regardless of how nodes were inserted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:37:50 +02:00
egutierrez c2e4f6a9e1 fix(shaders_lab): Output connectable after drop + working color picker
Two bugs:

1. Dropped nodes were pushed to the end of the pipeline, but the Output
   node already sat there from startup. The cycle validator and the
   compiler only look for sources at indices strictly lower than the
   target, so new nodes were invisible to the Output. Fix: insert
   dropped nodes before the first Output; topo_sort also stable-moves
   Output nodes to the back.

2. ColorEdit3 with default flags rendered RGB text inputs alongside the
   swatch; clicking them dragged the node instead of opening the picker.
   Fix: NoInputs + NoLabel leaves only the swatch (a single item), and
   ed::Suspend/Resume wraps the call so the popup isn't clipped to the
   node or captured by the canvas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:37:56 +02:00
egutierrez e3cfab3dc7 fix(shaders_lab): drop zone no longer eats node/slider input + inline tests
The previous InvisibleButton captured mouse events, so you could drag
from the Functions palette into the canvas, but node dragging and
slider interaction inside the canvas stopped working.

Fix: watch the global drag-drop payload without an explicit target. When
the mouse releases LMB over the DAG window with a "DAG_NODE_TYPE"
payload active, queue an add at that canvas position. No button, no
capture.

Tests (compiled standalone with preprocessor defines):
- dag_compile: 6/6 asserts (empty, single gen, op chain, multi-source
  blend, Output-driven fragColor, unconnected-Output fallback).
- dag_catalog: 8/8 asserts (uniqueness, per-kind input invariants,
  exactly one Output, body_glsl present & returns, control param
  indices valid).
Build with:
  g++ -std=c++17 -Icpp/functions -DDAG_COMPILE_TEST cpp/functions/gfx/dag_compile.cpp cpp/functions/gfx/dag_catalog.cpp -o /tmp/dag_compile_test
  g++ -std=c++17 -Icpp/functions -DDAG_CATALOG_TEST cpp/functions/gfx/dag_catalog.cpp -o /tmp/dag_catalog_test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:24:39 +02:00
egutierrez e828af3ac1 feat(shaders_lab): Output node + Functions palette with drag-drop
- DagKind::Output (new enum): terminal sink; compiler wires fragColor to its source_ids[0]
- dag_catalog: "output" node (1 input, red)
- dag_compile: skips Output in node_<i> emission; final fragColor resolves from Output's connection
- dag_node_editor: no more Add button; drops "DAG_NODE_TYPE" payloads at mouse canvas position; Output cannot be deleted; Output has no output pin
- dag_palette (new fn): Functions window with grouped, draggable node cards
- main.cpp: "Functions" window added; ensure_dag_default seeds plasma + connected Output
2026-04-24 22:16:47 +02:00
egutierrez 42957d10f6 fix(shaders_lab): node drag was overwritten by SetNodePosition each frame
Previously SetNodePosition was called after EndNode every frame, which
reset any drag the user had done. Now the initial position is set once
per editor_uid (tracked in a static unordered_set), and the editor owns
the position afterwards. GetNodePosition at end-of-frame keeps step
state in sync for future persistence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:04:07 +02:00
egutierrez 2b55a4823d feat(shaders_lab): visual node editor (imgui-node-editor) + multi-source
- cpp/vendor/imgui-node-editor: vendorized thedmd/imgui-node-editor v0.9.4
- cpp/functions/gfx/dag_node_editor: new visual pipeline editor replacing dag_panel
- DagStep: source_ids[4] + editor_pos + editor_uid (multi-input support)
- DagNodeDef: num_inputs explicit; circle reclassified as Op
- dag_compile: N inputs per node, topological ordering preserved
- main: use node editor; destroy on shutdown
- patch imgui_extra_math.inl: guard operator*(float, ImVec2) for imgui >= 18955

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:56:48 +02:00
egutierrez 3f622561ce feat(cpp/viz): static-plot primitive + tooltips + rotated labels + card compacta
Nuevo primitivo compartido:
- cpp/functions/viz/plot_static.h: header-only con flags ImPlotFlags /
  ImPlotAxisFlags agrupados (NoFrame|NoMenus|NoBoxSelect|NoMouseText +
  Lock|NoInitialFit|NoHighlight) para visualizacion estatica en
  dashboards. Lo usan todos los charts de viz/.

Charts refactorizados a v1.1 con parametro `height` explicito (rompe el
feedback loop con contenedores AutoResizeY que producia vibracion al
redimensionar) y ejes pineados con ImPlotCond_Always:
- bar_chart v1.2: tooltip al hover (label + valor) + auto-rotacion de
  labels a 45 cuando no caben horizontalmente (medidos con CalcTextSize
  vs ancho del plot). Los labels rotados se dibujan manualmente con
  ImDrawList::PrimQuadUV + ImFontBaked::FindGlyph (API ImGui 1.92+).
- pie_chart v1.1: tooltip por slice (detecta cual via atan2 desde centro
  en sentido CCW matematico, que es como ImPlot dibuja los slices desde
  angle0=90) con label + valor + porcentaje. Aspect 1:1 mantenido.
- line_plot, scatter_plot, histogram v1.1: ejes pineados con limites
  calculados de min/max + 5% headroom (histogram usa AutoFit por los
  bins dinamicos, con Lock para bloquear pan/zoom).

kpi_card v1.2: card mas compacta — altura 78px (antes 108), font scale
1.4x (antes 1.8x), padding sm (antes md). Apto para densidades altas
de KPIs en dashboards.

fullscreen_window v0.2: NoScrollbar|NoScrollWithMouse para eliminar el
scrollbar fugaz que aparecia cuando el contenido excedia por 1-2px la
ventana, reflow de ancho y vibracion visible al redimensionar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 21:31:00 +02:00
egutierrez bdce314199 feat(shaders_lab): drag-and-drop to reorder DAG steps
ImGui native BeginDragDropSource/Target over each CollapsingHeader.
Drag source adjusted if ahead of target to keep final position correct.
Move Up/Down buttons kept for keyboard/touchpad fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 21:21:58 +02:00
egutierrez e115c2e3fd feat(shaders_lab): DAG pipeline mode with node catalog
- cpp/functions/gfx/dag_types: DagStep, DagNodeDef, DagControl (header-only)
- cpp/functions/gfx/dag_catalog: 10 hardcoded nodes (4 gen, 3 op, 3 blend) ported from shader-dag-blends.jsx
- cpp/functions/gfx/dag_compile: pipeline → GLSL 330 core with fan-in via source_id
- cpp/functions/gfx/dag_uniforms: upload u_params[16] via glUniform4fv
- cpp/functions/gfx/dag_panel: ImGui pipeline editor (add/remove/reorder/controls)
- main.cpp: Code/DAG mode toggle, per-mode compile path and uniforms
- gl_loader: +glUniform4fv
- rebuild Windows .exe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:15:21 +02:00
egutierrez 4610bb4a99 feat(shaders_lab): uniform annotations → auto-generated ImGui controls
- cpp/functions/gfx/uniform_parser: regex-based parser of @slider/@color/@toggle/@xy annotations (+ inline tests)
- cpp/functions/gfx/uniform_panel: ImGui widgets + value store + glUniform* apply
- shader_canvas: optional uniforms callback invoked per-frame
- gl_loader: +glUniform1i/3f/4f
- seed plasma: demo uniforms u_speed + u_color
- rebuild Windows .exe

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:02:35 +02:00
egutierrez b828fd6acc feat(cpp/viz): kpi_card v1.1 + bar_chart v1.1 — contenedor y altura fijas
kpi_card:
- v1.1: envuelve el contenido en BeginChild con surface bg + border +
  radius::md + padding::md (tokens). Replica Mantine Paper withBorder
  radius="md" p="md" usado en @fn_library/kpi_card.tsx.
- Ancho adaptativo via GetContentRegionAvail — requiere contenedor que
  propague ancho constrained (ImGui::BeginTable). dashboard_grid / BeginGroup
  no funcionan porque no constrainen ancho y la card desborda la celda.
- Linea de trend SIEMPRE visible: delta, sparkline, o em dash (text_dim)
  como placeholder, para que un grid de KPIs quede alineado vertical.
- Colores del delta via tokens (success/error) en vez de hardcoded ImVec4.

bar_chart:
- v1.1: altura explicita como parametro (default 200px). Sin esto, ImPlot
  con ImVec2(-1, 0) entra en feedback loop cuando esta dentro de un
  dashboard_panel (BeginChild con AutoResizeY): plot pide espacio -> padre
  se redimensiona -> plot recalcula. Efecto visual: las barras se deslizan
  los primeros frames.
- Ejes blindados: Lock + NoInitialFit + Cond_Always ademas de los flags
  previos. Y max pre-calculado con 15% de headroom.
- Sin inputs (NoInputs|NoFrame|NoBoxSelect|NoMouseText): estos charts son
  de resumen, no de exploracion.

Actualizados los .md correspondientes con el contrato visual + requisitos
de contenedor, para que cualquier dashboard que componga estos primitivos
obtenga el mismo look.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:59:51 +02:00
egutierrez 9b1ca41c4d feat(shaders_lab): add gl_loader + Windows cross-compile
- cpp/functions/gfx/gl_loader.{h,cpp,md}: mini loader para OpenGL 2.0+
  (Linux no-op via GL_GLEXT_PROTOTYPES, Windows wglGetProcAddress)
- Portar gl_shader/gl_framebuffer/fullscreen_quad/shader_canvas al loader
- CMakeLists: WIN32_EXECUTABLE para lanzar sin consola en Windows
- apps/shaders_lab/shaders_lab.exe: binario PE32+ precompilado

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:52:37 +02:00