diff --git a/CLAUDE.md b/CLAUDE.md index 1dd8134..1eebea0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,4 @@ -# Telekino — Contexto para Claude Code +# QTorres — Contexto para Claude Code > Última actualización: 2026-04-20 @@ -22,10 +22,10 @@ Estas reglas son inviolables. No importa qué Issue estés implementando ni qué ## Qué es este proyecto -Telekino es una alternativa open source a LabVIEW construida íntegramente en Red-Lang. El usuario construye programas arrastrando bloques y conectándolos con wires, igual que en LabVIEW. Al guardar, Telekino genera un fichero `.qvi` con código Red/View completo que al ejecutarse muestra el Front Panel como una ventana, igual que LabVIEW. +QTorres es una alternativa open source a LabVIEW construida íntegramente en Red-Lang. El usuario construye programas arrastrando bloques y conectándolos con wires, igual que en LabVIEW. Al guardar, QTorres genera un fichero `.qvi` con código Red/View completo que al ejecutarse muestra el Front Panel como una ventana, igual que LabVIEW. -**Nombre:** Telekino (por Torres Quevedo) -**Repositorio:** https://github.com/anlaco/Telekino +**Nombre:** QTorres (por Torres Quevedo) +**Repositorio:** https://github.com/anlaco/QTorres **Backlog:** https://github.com/users/anlaco/projects/1 ## Stack tecnológico @@ -43,7 +43,7 @@ Sin dependencias externas. Un solo binario. Funciona en Linux, Windows y macOS. ## Estructura del proyecto ``` -Telekino/ +QTorres/ ├── CLAUDE.md # Este fichero — contexto principal para IA ├── README.md ├── docs/ @@ -53,11 +53,11 @@ Telekino/ │ ├── PLANNING.md # Decisiones pendientes críticas │ ├── retos.md # Riesgos y dificultades │ ├── visual-spec.md # Especificación visual (documento vivo) -│ ├── tipos-de-fichero.md # Mapeo LabVIEW → Telekino +│ ├── tipos-de-fichero.md # Mapeo LabVIEW → QTorres │ ├── labview-comportamiento.md # Arquitectura LabVIEW: renderizado, modos, estilos │ └── GTK_ISSUES.md # Bugs del backend GTK en Linux ├── src/ -│ ├── telekino.red # Punto de entrada + toolbar + ventana principal +│ ├── qtorres.red # Punto de entrada + toolbar + ventana principal │ ├── graph/ │ │ ├── model.red # Modelo: make-label, make-node, make-wire, make-fp-item, set-config, find-node-by-id (635 líneas) │ │ └── blocks.red # Registro de bloques + dialecto block-def — 40 bloques @@ -126,20 +126,17 @@ Telekino/ - ~~#64 FP como ventana maestra~~ ✅ (FP=blocking master, BD=no-wait slave, Ctrl+E toggle, títulos sincronizados, current-file en app-model) - ~~#65 Scroll en BD y FP~~ ✅ (ventanas fijas 900x600, scroll wheel + click scrollbar, límites por contenido real) -**Fase 4 — Hardware (en curso):** -- ~~#19 TCP/IP — bloques básicos cliente~~ ✅ (tcp-open/write/read/close estilo LabVIEW, session-through por connection refnum, verificado con socat) - **Fase 5 — UX y gestión de proyectos (planificado):** - Splash / Welcome screen (Create New VI, Open Existing, proyectos recientes) - Project Explorer con formato .qproj (árbol de ficheros, gestión de dependencias) - Depende de: .qlib (#18) ✅ y FP como ventana maestra (#64) ✅ - **Nota:** Prototipo temprano de `.qproj` existe en `examples/ejemplo.qproj` — sirve como referencia del formato, pero sin tooling de Project Explorer aún -**Próximo paso:** seguir Fase 4 (#20 USBTMC, #21 Serie, #22 Modbus/servidor TCP, #23 DAQ) → Fase 4.5 (integración red-sg) → Fase 5 (UX) +**Próximo paso:** Fase 4 (hardware) → Fase 4.5 (integración red-sg) → Fase 5 (UX) **Refactor 4B ✅ COMPLETADO (2026-04-17):** `compiler.red` (1255 → 18 líneas orquestador + 5 módulos) y `file-io.red` (939 → 17 líneas orquestador + 4 módulos). Todos los módulos <400 líneas excepto `file-io-serialize.red` (468) por `format-qvi` monolítica. 482/482 tests PASS. Ver `docs/refactor-4b-plan.md` para el plan original. -**Prioridad:** Fase 4 hardware antes que Fase 5 UX. Un Telekino que habla con instrumentos reales es más valioso que uno con undo/redo pulido. La Fase 4.5 (red-sg) se sitúa entre ambas como puente natural de la separación aplicación/toolkit (ver DT-030 y `docs/roadmap-9-10.md`). +**Prioridad:** Fase 4 hardware antes que Fase 5 UX. Un QTorres que habla con instrumentos reales es más valioso que uno con undo/redo pulido. La Fase 4.5 (red-sg) se sitúa entre ambas como puente natural de la separación aplicación/toolkit (ver DT-030 y `docs/roadmap-9-10.md`). **Nota sobre el fork `anlaco/red`:** Los binarios `red-cli` y `red-view` se compilan desde un fork propio del repositorio Red, mantenido en `/home/alaforga/Anlaco/01-PRODUCTOS/red/` con origen `https://github.com/anlaco/red.git`. Este fork aplica fixes GTK3 (GTK-014, GTK-003 A/B) que upstream no ha cerrado. Ver `docs/GTK_ISSUES.md` para estado de cada bug y sus commits resolutivos en el fork. El fork se sincroniza periódicamente con `red/red` upstream pero se mantiene como copia local para independencia de Red upstream. @@ -184,14 +181,14 @@ view layout [ 3. **emit** — define qué código Red genera cada bloque al compilar ### DT-005: El .qvi tiene dos secciones -1. `qvi-diagram: [...]` — cabecera gráfica (inerte para Red, usada por Telekino para reconstruir la vista) +1. `qvi-diagram: [...]` — cabecera gráfica (inerte para Red, usada por QTorres para reconstruir la vista) 2. Código Red/View generado — ejecutable directamente con `red mi-vi.qvi` ### DT-010: Runner en memoria (decisión clave) Run compila en memoria y ejecuta con `do`. Save escribe el `.qvi` al disco. Son independientes. ### DT-011: qvi-diagram es la fuente de verdad -El código generado es un artefacto. Telekino siempre recompila desde `qvi-diagram` al cargar. +El código generado es un artefacto. QTorres siempre recompila desde `qvi-diagram` al cargar. Un `.qvi` con solo `qvi-diagram` (sin código generado) es válido. ### DT-017: El tipo de VI lo determina el contexto de llamada @@ -211,7 +208,7 @@ Prototipo `base-element` + constructores `make-node`, `make-wire`. Patrón idiom Son independientes. El compilador usa `name`, la UI usa `label/text`. ### DT-027 — CRÍTICO: Concurrencia cooperativa (rate/on-time) -Red no tiene multihilo. Telekino simula concurrencia con timers de Red/View: +Red no tiene multihilo. QTorres simula concurrencia con timers de Red/View: - While Loop = timer (`face/rate` + `on-time`) que ejecuta una iteración por tick - Múltiples loops = múltiples timers independientes, Red despacha en round-robin - Event Structure = timer que comprueba cola de eventos @@ -223,7 +220,7 @@ Red no tiene multihilo. Telekino simula concurrencia con timers de Red/View: El `.qvi` generado **debe compilarse** con `red -c` a ejecutable nativo. - **PROHIBIDO** en código generado: `do` con bloques dinámicos, `load` de strings, `compose` en runtime - **PERMITIDO**: `view layout [...]` estático, funciones con nombre, `face/rate` + `on-time` -- `compose` se ejecuta en el compilador de Telekino (al generar), NO en el `.qvi` generado +- `compose` se ejecuta en el compilador de QTorres (al generar), NO en el `.qvi` generado ### DT-029: Error handling progresivo - **Nivel 0 (Fase 2)**: Error nativo de Red — programa se para. Sin cables de error. @@ -256,11 +253,11 @@ qvi-diagram: [ ## Flujo de trabajo ### Cómo trabajar un Issue -1. Leer el Issue en GitHub (`gh issue view N --repo anlaco/Telekino`) +1. Leer el Issue en GitHub (`gh issue view N --repo anlaco/QTorres`) 2. Implementar en el módulo correspondiente de `src/` 3. Verificar con los ejemplos de `examples/` 4. Ejecutar los tests (`red-cli tests/run-all.red`) y verificar que pasan -5. Cerrar el Issue cuando esté completo (`gh issue close N --repo anlaco/Telekino`) +5. Cerrar el Issue cuando esté completo (`gh issue close N --repo anlaco/QTorres`) ### Orden de los Issues (backlog) Trabajar siempre en orden de Fase. No empezar una fase sin completar la anterior. @@ -297,14 +294,14 @@ Spec visual: cada tipo implementa su aspecto según `docs/visual-spec.md`. - #64 FP como ventana maestra — BD bajo demanda (Ctrl+E) ✅ - ~~#65 Scroll en BD y FP~~ ✅ (ventanas fijas 900x600, scrollbars draw-based, límites por contenido) -**Fase 4 — Hardware (en curso):** -- ~~#19 TCP/IP — bloques básicos cliente~~ ✅ (tcp-open/write/read/close estilo LabVIEW, session-through por connection refnum) +**Fase 4 — Hardware:** +- #19 TCP/IP — bloques básicos cliente (tcp-connect/write/read/close) - #20 USBTMC — acceso genérico a instrumentos USB (/dev/usbtmc*) - #21 Puerto serie RS-232/RS-485 (Arduino, ESP32) - #22 Modbus TCP y servidor TCP/IP (depende de #19) - #23 DAQ analógico (comedi/libcomedi) -> **Nota:** NO se implementan bloques SCPI específicos. SCPI es un protocolo de comandos en texto que se envía como string vía `tcp-write` o `usbtmc-write`. Esto mantiene Telekino genérico y sirve también para Modbus, protocolos propios y cualquier otro protocolo sobre TCP/USB. +> **Nota:** NO se implementan bloques SCPI específicos. SCPI es un protocolo de comandos en texto que se envía como string vía `tcp-write` o `usbtmc-write`. Esto mantiene QTorres genérico y sirve también para Modbus, protocolos propios y cualquier otro protocolo sobre TCP/USB. **Fase 5 — UX y gestión de proyectos:** - Splash / Welcome screen (Create New VI, Open Existing, proyectos recientes) @@ -316,7 +313,7 @@ Los binarios `red-cli` y `red-view` se compilan desde el fork `https://github.co ## Ollama MCP — Delegación de tareas a modelo local -Telekino tiene un MCP server que conecta con Ollama (modelo local). Ollama tiene cargado automáticamente CLAUDE.md y el skill de Red-Lang como contexto del proyecto. +QTorres tiene un MCP server que conecta con Ollama (modelo local). Ollama tiene cargado automáticamente CLAUDE.md y el skill de Red-Lang como contexto del proyecto. ### Cuándo usar Ollama (herramienta `ollama_delegate`) @@ -356,7 +353,7 @@ El contexto se define en `.ollama-context.json` en la raíz del proyecto: ```json { "context_files": ["./CLAUDE.md", "./skills/red-lang/SKILL.md"], - "system_prompt": "You are a coding assistant for Telekino..." + "system_prompt": "You are a coding assistant for QTorres..." } ``` @@ -372,16 +369,16 @@ red examples/suma-basica.qvi red-cli tests/run-all.red # Ejecutar la aplicación completa -red-view src/telekino.red +red-view src/qtorres.red # Ver Issues pendientes -gh issue list --repo anlaco/Telekino --label "fase-2" +gh issue list --repo anlaco/QTorres --label "fase-2" # Ver un Issue concreto -gh issue view 14 --repo anlaco/Telekino +gh issue view 14 --repo anlaco/QTorres # Cerrar un Issue -gh issue close 14 --repo anlaco/Telekino --comment "Implementado en src/..." +gh issue close 14 --repo anlaco/QTorres --comment "Implementado en src/..." ``` ## Convenciones de código @@ -427,7 +424,7 @@ Cubre sintaxis core, View, Draw, VID, Parse, patrones idiomáticos y gotchas. | `make-fp-item`, `fp-cluster-fields`, `fp-default-label` | `model.red` | ✅ Movida | | `find-node-by-id` | `model.red` | ✅ Añadida | | `set-config` | `model.red` | ✅ Añadida | -| Lógica de `btn-run` (50+ líneas inline) | telekino.red | ⚠️ Pendiente Fase 3 | +| Lógica de `btn-run` (50+ líneas inline) | qtorres.red | ⚠️ Pendiente Fase 3 | ### Dependencia canvas.red <-> panel.red @@ -467,9 +464,9 @@ El acoplamiento es **por diseño del dominio** (FP↔BD son una unidad 1:1) y no ### Estado global compartido -`app-model` (definido en telekino.red) es el único modelo compartido. canvas.red, panel.red y telekino.red lo leen y mutan a través de `face/extra`. No hay mecanismo de notificación. +`app-model` (definido en qtorres.red) es el único modelo compartido. canvas.red, panel.red y qtorres.red lo leen y mutan a través de `face/extra`. No hay mecanismo de notificación. ### Plan de corrección (pendiente Fase 3) 1. Centralizar conocimiento de tipos en blocks.red (hints de renderizado) -2. Extraer lógica de `btn-run` a función nombrada en telekino.red +2. Extraer lógica de `btn-run` a función nombrada en qtorres.red diff --git a/docs/ai-reference.md b/docs/ai-reference.md index 7aefae7..287070f 100644 --- a/docs/ai-reference.md +++ b/docs/ai-reference.md @@ -1,6 +1,6 @@ -# Referencia de formatos Telekino para agentes de IA +# Referencia de formatos QTorres para agentes de IA -Este documento es una referencia de consumo para agentes de IA que necesiten generar ficheros del ecosistema Telekino. No es documentación interna del proyecto — es el contrato entre Telekino y cualquier modelo que genere ficheros para él. +Este documento es una referencia de consumo para agentes de IA que necesiten generar ficheros del ecosistema QTorres. No es documentación interna del proyecto — es el contrato entre QTorres y cualquier modelo que genere ficheros para él. **Versión:** 1.1 (solo `.qvi` con tipos numéricos) **Decisiones relacionadas:** DT-020, DT-021, DT-022, DT-023, DT-024 @@ -9,10 +9,10 @@ Este documento es una referencia de consumo para agentes de IA que necesiten gen ## Principio fundamental -Todo fichero Telekino tiene dos secciones: +Todo fichero QTorres tiene dos secciones: 1. **Fuente de verdad** — un dialecto Red que describe la estructura gráfica y funcional. Es la única sección que se genera o edita. -2. **Código generado** — código Red ejecutable generado automáticamente por Telekino al guardar. **No se genera por IA.** Telekino lo produce a partir de la sección 1. +2. **Código generado** — código Red ejecutable generado automáticamente por QTorres al guardar. **No se genera por IA.** QTorres lo produce a partir de la sección 1. Un agente de IA solo trabaja con la sección 1. Nunca genera la sección 2. @@ -380,7 +380,7 @@ qvi-diagram: [ ## Errores comunes a evitar -1. **No generar la sección 2 (código ejecutable).** Telekino la genera. El agente solo produce `qvi-diagram`. +1. **No generar la sección 2 (código ejecutable).** QTorres la genera. El agente solo produce `qvi-diagram`. 2. **No inventar tipos de nodo.** Usar solo los tipos listados en la tabla de nodos. 3. **Respetar los nombres de puerto exactos.** `'a` y `'b` para bloques math, `'out` para controles, `'in` para indicadores. 4. **Los IDs deben ser únicos** dentro de un mismo `block-diagram`. @@ -395,4 +395,4 @@ qvi-diagram: [ ## Nota sobre evolución -Este documento refleja el estado actual de Telekino (tipos numéricos). Conforme evolucione, se añadirán tipos de datos (`'boolean`, `'string`, `'array`), estructuras de control (loops, case), bloques genéricos de hardware (TCP/IP, USBTMC, serie, Modbus TCP), y nuevos bloques primitivos. +Este documento refleja el estado actual de QTorres (tipos numéricos). Conforme evolucione, se añadirán tipos de datos (`'boolean`, `'string`, `'array`), estructuras de control (loops, case), bloques genéricos de hardware (TCP/IP, USBTMC, serie, Modbus TCP), y nuevos bloques primitivos. diff --git a/docs/plan.md b/docs/plan.md index 89265ec..d7d3df5 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -1,8 +1,8 @@ -# Plan de desarrollo — Telekino +# Plan de desarrollo — QTorres ## Visión -Telekino es una alternativa open source a LabVIEW para el mismo público objetivo: ingenieros de instrumentación y automatización. El usuario construye programas visualmente con bloques y wires (igual que en LabVIEW), y Telekino genera código Red-Lang puro y legible. +QTorres es una alternativa open source a LabVIEW para el mismo público objetivo: ingenieros de instrumentación y automatización. El usuario construye programas visualmente con bloques y wires (igual que en LabVIEW), y QTorres genera código Red-Lang puro y legible. **Principios de diseño:** - Mismo modelo mental que LabVIEW: Front Panel + Block Diagram, dataflow, sub-VIs @@ -96,7 +96,7 @@ Ciclo completo: dibujar → compilar → ejecutar → ver resultado. ### Front Panel modular - [x] Panel con controles e indicadores arrastrables (#7) - [x] Botón Run visible en el panel -- [x] Conectar módulos en `telekino.red` (#8) +- [x] Conectar módulos en `qtorres.red` (#8) ### Qué genera el compilador (decisión DT-009) @@ -106,7 +106,7 @@ Estructura del `.qvi` generado: ``` [Cabecera Red] -[qvi-diagram: ... — para reconstruir la vista en Telekino] +[qvi-diagram: ... — para reconstruir la vista en QTorres] [view layout [ ... — ventana con controles, botón Run e indicadores ]] ``` @@ -185,7 +185,7 @@ Esta fase es esencial para el público objetivo (mismo que LabVIEW: ingeniería - [ ] USBTMC: acceso a `/dev/usbtmc*` con misma interfaz (#20) - [ ] Manejo de timeouts y errores de red/USB -> **Nota:** NO se implementan bloques SCPI específicos. SCPI es un protocolo de comandos en texto que el usuario envía como string a través de `tcp-write`/`usbtmc-write`. Esto mantiene Telekino genérico y sirve también para Modbus (#22), protocolos propios y cualquier otro protocolo sobre TCP/USB. +> **Nota:** NO se implementan bloques SCPI específicos. SCPI es un protocolo de comandos en texto que el usuario envía como string a través de `tcp-write`/`usbtmc-write`. Esto mantiene QTorres genérico y sirve también para Modbus (#22), protocolos propios y cualquier otro protocolo sobre TCP/USB. ### Comunicación serie - [ ] Puerto serie RS-232/RS-485: bloques open/write/read/close (#30) @@ -207,9 +207,9 @@ Esta fase es esencial para el público objetivo (mismo que LabVIEW: ingeniería ## Fase 4.5 — Integración red-sg (puente entre hardware y UX) -**Premisa:** red-sg es el toolkit hermano de Telekino. La separación aplicación/toolkit +**Premisa:** red-sg es el toolkit hermano de QTorres. La separación aplicación/toolkit (ver DT-030 y `docs/roadmap-9-10.md` sección "red-sg: separación de responsabilidades -por equipos") implica que, una vez red-sg esté estable, Telekino delega en él la capa +por equipos") implica que, una vez red-sg esté estable, QTorres delega en él la capa gráfica genérica (scene graph, transforms, hit-test, undo/redo, widgets). **Prerrequisitos:** @@ -219,7 +219,7 @@ gráfica genérica (scene graph, transforms, hit-test, undo/redo, widgets). **Entregables:** - [ ] Migrar hit-test manual a `sg-hit-test` -- [ ] Mapear nodos Telekino a `sg-node` con `draw-cmd` +- [ ] Mapear nodos QTorres a `sg-node` con `draw-cmd` - [ ] Reemplazar scroll manual por `scene/view-x`, `scene/view-y` - [ ] Activar undo/redo con `sg-undo` (DT-031) - [ ] Migrar panel.red al mismo patrón @@ -232,7 +232,7 @@ gráfica genérica (scene graph, transforms, hit-test, undo/redo, widgets). ## Fase 5 — Experiencia de usuario y gestión de proyectos ### Splash / Welcome screen -- [ ] Pantalla de bienvenida al lanzar Telekino (Create New VI, Open Existing, proyectos recientes) +- [ ] Pantalla de bienvenida al lanzar QTorres (Create New VI, Open Existing, proyectos recientes) - [ ] Depende de que exista el concepto de proyecto (.qproj) o al menos .qlib (#18) ### Project Explorer (.qproj) @@ -262,9 +262,9 @@ gráfica genérica (scene graph, transforms, hit-test, undo/redo, widgets). | Sub-VIs | VIs reutilizables como bloques con connector | 3 ✅ | | FP como master | FP ventana principal, BD bajo demanda | 3 | | Resize + scroll | Ventanas redimensionables con scrollbars | 3 | -| Primera medida real | Controlar un Keysight desde Telekino | 4 | +| Primera medida real | Controlar un Keysight desde QTorres | 4 | | DAQ completo | Adquisición continua con tarjeta o Arduino | 4 | -| Welcome screen | Splash con Create/Open al lanzar Telekino | 5 | +| Welcome screen | Splash con Create/Open al lanzar QTorres | 5 | | Project Explorer | Árbol de proyecto .qproj con gestión de VIs | 5 | --- @@ -283,6 +283,6 @@ Trabajar siempre Issues en orden de Fase. No empezar una fase sin completar la a Si en el futuro los agentes de IA muestran problemas recurrentes con la sintaxis Red (confusión con Rebol, funciones inventadas, mezcla de dialectos), se creará un documento o skill de referencia del lenguaje Red específicamente diseñado para consumo por LLMs. Por ahora no es necesario — no se han observado problemas que lo justifiquen. -### Generación de ficheros Telekino por IA (vibe coding → spec-driven design) +### Generación de ficheros QTorres por IA (vibe coding → spec-driven design) Ver DT-021 en `docs/decisiones.md`. La referencia de formatos para agentes de IA está en `docs/ai-reference.md`. Conforme el proyecto madure y se implementen más tipos de fichero, esta referencia crecerá y el nivel de rigor de la generación por IA aumentará progresivamente — desde generar un `.qvi` individual (vibe coding) hasta generar proyectos completos desde especificaciones técnicas (spec-driven design). diff --git a/docs/tcp-api.md b/docs/tcp-api.md index 1e44d7b..fb64d31 100644 --- a/docs/tcp-api.md +++ b/docs/tcp-api.md @@ -43,7 +43,7 @@ Recibe datos del servidor (bloqueante). ```red response: tcp/receive 1024 if response [ - print to string! response + print to-string! response ] ``` @@ -134,14 +134,14 @@ tcp/send "PING^/" ; Leer respuesta response: tcp/receive 256 -print ["Respuesta: " to string! response] +print ["Respuesta: " to-string! response] ; Cerrar tcp/close ``` > Para enviar comandos de instrumentación (texto plano como `*IDN?`, `MEAS:VOLT?`, -> cadenas Modbus, etc.) basta con poner el string adecuado en `tcp/send`. Telekino no +> cadenas Modbus, etc.) basta con poner el string adecuado en `tcp/send`. QTorres no > incluye bloques específicos por protocolo — el usuario elige qué cadena enviar. ### Lectura secuencial (con timeout) @@ -152,7 +152,7 @@ tcp/set-timeout 2000 loop 10 [ data: tcp/receive 64 if data [ - print ["Dato " index ": " to string! data] + print ["Dato " index ": " to-string! data] ] ] @@ -179,11 +179,11 @@ tcp/close - **Bloqueante por defecto:** `tcp/receive` bloquea hasta recibir datos o timeout - **Terminación de línea:** muchos protocolos de texto requieren `\n` o `\r\n` al final de cada mensaje — usar `rejoin [cmd newline]` -- **Binary vs String:** TCP transporta bytes. Convertir con `to string!` / `to binary!` cuando el protocolo sea texto +- **Binary vs String:** TCP transporta bytes. Convertir con `to-string!` / `to-binary!` cuando el protocolo sea texto - **Sin hilos:** Red no tiene multihilo. Para múltiples conexiones, usar polling no-bloqueante + `on-time` / timers (DT-027) - **Error handling:** revisar `tcp/last-error` si `connect` o `send` fallan -## Integración Telekino (Fase 4) +## Integración QTorres (Fase 4) Los bloques de hardware (#19, #22) usarán esta API de forma genérica: @@ -192,7 +192,7 @@ Los bloques de hardware (#19, #22) usarán esta API de forma genérica: - **Timeout configurable** → parámetro de bloque → `tcp/set-timeout` - **Modbus TCP** (#22) → syntactic sugar que construye la trama Modbus y la envía con `tcp/send` -> Telekino no incluye bloques específicos por protocolo (HTTP, SCPI, MQTT, …). Cada +> QTorres no incluye bloques específicos por protocolo (HTTP, SCPI, MQTT, …). Cada > protocolo de texto se usa pasando la cadena adecuada al bloque `tcp-write`. > Protocolos binarios (Modbus, custom) pueden construirse con `to-binary!`. diff --git a/skills/red-lang/SKILL.md b/skills/red-lang/SKILL.md index 83394f4..c4bb9cc 100644 --- a/skills/red-lang/SKILL.md +++ b/skills/red-lang/SKILL.md @@ -1,11 +1,11 @@ -# Red-Lang Skill para Telekino +# Red-Lang Skill para QTorres -> Referencia rápida de Red para codificar Telekino. Consultar antes de escribir código Red, especialmente Draw y View. +> Referencia rápida de Red para codificar QTorres. Consultar antes de escribir código Red, especialmente Draw y View. **Repositorio Red oficial:** https://www.red-lang.org **Documentación:** https://doc.red-lang.org **Versión:** 0.6.6+ -**Telekino:** Red 100% (DT-001) +**QTorres:** Red 100% (DT-001) --- @@ -360,7 +360,7 @@ if tcp/connect "192.168.1.100" 5000 [ response: tcp/receive 256 ; Procesar - print to string! response + print to-string! response ; Cerrar tcp/close @@ -398,19 +398,19 @@ tcp/send "HELLO^/" response: tcp/receive 256 if response [ - print ["Respuesta: " to string! response] + print ["Respuesta: " to-string! response] ] tcp/close ``` -> Telekino no incluye bloques específicos por protocolo. Para enviar comandos de texto +> QTorres no incluye bloques específicos por protocolo. Para enviar comandos de texto > de instrumentación, Modbus, HTTP, MQTT o similar, basta con poner el string adecuado > en `tcp/send`. Ver `docs/tcp-api.md` para referencia completa. --- -## Dialects propios de Telekino +## Dialects propios de QTorres ### block-def — Definición de bloques @@ -461,7 +461,7 @@ emit: [ - ❌ `do` con bloques dinámicos en `.qvi` generado (debe compilarse con `red -c`) - ❌ `load` de strings → use parse -- ❌ `compose` en runtime del VI generado (OK en compilador de Telekino) +- ❌ `compose` en runtime del VI generado (OK en compilador de QTorres) - ❌ Herencia profunda (A → B → C) → usar composición - ❌ Faces nativas en canvas del editor (usar Draw) - ❌ Strings intermedios en compilador (manipular bloques Red) @@ -492,8 +492,8 @@ emit: [ - **Red/View:** https://doc.red-lang.org/en/view.html - **Red/Draw:** https://doc.red-lang.org/en/view.html#_draw-dialect - **Red/Parse:** https://doc.red-lang.org/en/parse.html -- **TCP API:** Ver `docs/tcp-api.md` (específico de Telekino) -- **Skill Red en Telekino:** Este fichero +- **TCP API:** Ver `docs/tcp-api.md` (específico de QTorres) +- **Skill Red en QTorres:** Este fichero ## Cuándo consultar esta skill diff --git a/src/graph/model.red b/src/graph/model.red index 9301c53..488ae16 100644 --- a/src/graph/model.red +++ b/src/graph/model.red @@ -53,7 +53,7 @@ make-label: func [ make object! [ text: any [select spec 'text ""] visible: either none? select spec 'visible [true] [select spec 'visible] - offset: any [select spec 'offset 0x-15] + offset: any [select spec 'offset 0x0] ] ] diff --git a/src/io/file-io-serialize.red b/src/io/file-io-serialize.red index ed05f84..12a7411 100644 --- a/src/io/file-io-serialize.red +++ b/src/io/file-io-serialize.red @@ -21,9 +21,9 @@ serialize-nodes: func [ nodes-block: copy [] foreach n nodes [ lbl-block: either all [n/label object? n/label] [ - compose [text: (n/label/text) visible: (n/label/visible)] + compose [text: (n/label/text) visible: (n/label/visible) offset: (n/label/offset)] ][ - compose [text: (either string? n/label [n/label] [""]) visible: (true)] + compose [text: (either string? n/label [n/label] [""]) visible: (true) offset: 0x0] ] nx: either relative [n/x - rel-x] [n/x] ny: either relative [n/y - rel-y] [n/y] diff --git a/src/ui/diagram/canvas-render.red b/src/ui/diagram/canvas-render.red index b125e55..912908d 100644 --- a/src/ui/diagram/canvas-render.red +++ b/src/ui/diagram/canvas-render.red @@ -10,7 +10,7 @@ Red [ ; No contiene estado mutable ni side-effects de UI. ; ────────────────────────────────────────────────────────────────── -block-width: 120 block-height: 50 port-radius: 8 grid-size: 20 +block-width: 60 block-height: 60 port-radius: 8 grid-size: 20 col-canvas: 225.228.235 col-grid: 200.203.212 @@ -27,7 +27,7 @@ col-wire-sel: 0.160.200 col-port-in: 50.110.200 col-port-out: 195.80.25 col-sel: 0.175.210 -col-text: 240.245.250 +col-text: 255.255.255 col-black: 0.0.0 ; Colores de estructuras contenedoras (while-loop) @@ -42,9 +42,11 @@ sr-terminal-half: 6 ; semitamaño del triángulo SR (triángulo case-nav-height: 24 ; altura de la barra de navegación case-btn-size: 18 ; tamaño de botones ◀ ▶ [+][-] col-case-nav-bg: 160.185.215 ; fondo de barra de navegación +bd-label-above: 3 ; gap label→nodo (LabVIEW: 2-4px) +col-text-label: 0.0.0 ; negro — labels sobre fondo claro del canvas -; Compensación vertical de texto (8px en Linux por diferencia de baseline) -text-dy: either system/platform = 'Linux [8] [0] +; Compensación vertical de texto (0px en Linux por diferencia de baseline) +text-dy: either system/platform = 'Linux [0] [0] ; ══════════════════════════════════════════════════════════ ; GEOMETRÍA DE NODOS — funciones puras sin side-effects @@ -250,17 +252,49 @@ port-xy: func [node port-name direction /local ports port-index found] [ ; Devuelve la altura visual de un nodo. ; bundle/unbundle/cluster-control/cluster-indicator: variable según número de campos. ; Resto: block-height fijo. -node-height: func [node /local n-in n-out] [ +node-height: func [node /local n-in n-out h] [ case [ find [bundle unbundle cluster-control cluster-indicator] node/type [ n-in: length? in-ports node n-out: length? out-ports node - max block-height (12 + (max n-in n-out) * 20 + 10) + max block-height (12 + ((max n-in n-out) * 20)) ] true [block-height] ] ] +; ══════════════════════════════════════════════════════════ +; DASHED-BOX — dibuja rectángulo discontinuo (estilo LabVIEW) +; ══════════════════════════════════════════════════════════ +dashed-box: func [x1 y1 x2 y2 dash gap /local cmds pos lim step] [ + cmds: copy [] + pos: x1 lim: x2 + while [pos < lim] [ + step: min dash (lim - pos) + append cmds compose [line (as-pair pos y1) (as-pair (pos + step) y1)] + pos: pos + dash + gap + ] + pos: y1 lim: y2 + while [pos < lim] [ + step: min dash (lim - pos) + append cmds compose [line (as-pair x2 pos) (as-pair x2 (pos + step))] + pos: pos + dash + gap + ] + pos: x2 lim: x1 + while [pos > lim] [ + step: min dash (pos - lim) + append cmds compose [line (as-pair pos y2) (as-pair (pos - step) y2)] + pos: pos - dash - gap + ] + pos: y2 lim: y1 + while [pos > lim] [ + step: min dash (pos - lim) + append cmds compose [line (as-pair x1 pos) (as-pair x1 (pos - step))] + pos: pos - dash - gap + ] + cmds +] + ; ══════════════════════════════════════════════════════════ ; MODELO — todo el estado mutable vive aquí ; ══════════════════════════════════════════════════════════ @@ -338,7 +372,7 @@ render-wire-list: func [ render-node-list: func [ "Genera Draw cmds para una lista de nodos" nodes selected-node - /local cmds node node-color type-label ports in-port-y out-port-y port + /local cmds node node-color type-label ports in-port-y out-port-y port lbl-x lbl-y ][ cmds: copy [] foreach node nodes [ @@ -391,33 +425,27 @@ render-node-list: func [ array-size ["SIZE[]"] array-subset ["SUB[]"] ] [uppercase form node/type] - either all [node/label object? node/label node/label/visible] [ + ; ── Label (fuera del nodo, arriba) ── + if all [node/label object? node/label node/label/visible] [ + lbl-x: node/x + either pair? node/label/offset [node/label/offset/x] [0] + lbl-y: node/y - bd-label-above + either pair? node/label/offset [node/label/offset/y] [0] append cmds compose [ - fill-pen col-text - text (as-pair (node/x + 10) (node/y + 10 + text-dy)) (any [node/label/text ""]) - text (as-pair (node/x + 10) (node/y + 26 + text-dy)) (type-label) - ] - ][ - either all [node/label string? node/label] [ - append cmds compose [ - fill-pen col-text - text (as-pair (node/x + 10) (node/y + 10 + text-dy)) (node/label) - text (as-pair (node/x + 10) (node/y + 26 + text-dy)) (type-label) - ] - ][ - append cmds compose [ - fill-pen col-text - text (as-pair (node/x + 10) (node/y + 14 + text-dy)) (type-label) - ] + pen (col-text-label) + text (as-pair lbl-x (lbl-y + text-dy)) (any [node/label/text ""]) ] ] + ; ── Type-label (siempre fijo dentro del nodo) ── + append cmds compose [ + pen (col-text) + text (as-pair (node/x + 10) (node/y + 14 + text-dy)) (type-label) + ] ports: in-ports node in-port-y: node/y + 12 foreach port ports [ append cmds compose [ pen col-port-in fill-pen col-port-in circle (as-pair (node/x - port-radius) in-port-y) (port-radius) - fill-pen col-text + pen (col-text-label) text (as-pair (node/x - port-radius - 22) (in-port-y - 7 + text-dy)) (subvi-port-label node port) ] in-port-y: in-port-y + 20 @@ -428,17 +456,26 @@ render-node-list: func [ append cmds compose [ pen col-port-out fill-pen col-port-out circle (as-pair (node/x + block-width + port-radius) out-port-y) (port-radius) - fill-pen col-text + pen (col-text-label) text (as-pair (node/x + block-width + port-radius + 12) (out-port-y - 7 + text-dy)) (subvi-port-label node port) ] out-port-y: out-port-y + 20 ] + ; ── Selección: dashed-box body + dashed-box label ── if same? node selected-node [ - append cmds compose [ - pen col-sel line-width 2 fill-pen off - box (as-pair (node/x - 3) (node/y - 3)) (as-pair (node/x + block-width + 3) (node/y + block-height + 3)) 6 - line-width 1 + append cmds compose [pen col-sel line-width 2 fill-pen off] + append cmds dashed-box + (node/x - 3) (node/y - 3) + (node/x + block-width + 3) (node/y + block-height + 3) + 6 4 + if all [node/label object? node/label node/label/visible] [ + lbl-x: node/x + either pair? node/label/offset [node/label/offset/x] [0] + lbl-y: node/y - bd-label-above + either pair? node/label/offset [node/label/offset/y] [0] + lw: max 30 (7 * length? any [node/label/text ""]) + append cmds compose [pen col-sel line-width 1 fill-pen off] + append cmds dashed-box (lbl-x - 2) (lbl-y - 2) (lbl-x + lw + 2) (lbl-y + 15) 4 3 ] + append cmds [line-width 1] ] ] cmds @@ -451,7 +488,7 @@ render-node-list: func [ render-cluster-node: func [ "Genera Draw cmds para un nodo bundle/unbundle con altura variable y puertos coloreados" node selected-node - /local cmds node-color h type-label ports in-port-y out-port-y port port-col + /local cmds node-color h type-label ports in-port-y out-port-y port port-col lbl-x lbl-y ][ cmds: copy [] node-color: col-wire-cluster @@ -472,8 +509,18 @@ render-cluster-node: func [ node/type = 'cluster-control ["CLU-CTRL"] true ["CLU-IND"] ] + ; ── Label (fuera del nodo, arriba) ── + if all [node/label object? node/label node/label/visible] [ + lbl-x: node/x + either pair? node/label/offset [node/label/offset/x] [0] + lbl-y: node/y - bd-label-above + either pair? node/label/offset [node/label/offset/y] [0] + append cmds compose [ + pen (col-text-label) + text (as-pair lbl-x (lbl-y + text-dy)) (any [node/label/text ""]) + ] + ] + ; ── Type-label (siempre fijo dentro del nodo) ── append cmds compose [ - fill-pen col-text + pen (col-text) text (as-pair (node/x + 8) (node/y + 14 + text-dy)) (type-label) ] @@ -485,7 +532,7 @@ render-cluster-node: func [ append cmds compose [ pen (port-col) fill-pen (port-col) circle (as-pair (node/x - port-radius) in-port-y) (port-radius) - fill-pen col-text + pen (col-text-label) text (as-pair (node/x - port-radius - 22) (in-port-y - 7)) (form port) ] in-port-y: in-port-y + 20 @@ -499,19 +546,27 @@ render-cluster-node: func [ append cmds compose [ pen (port-col) fill-pen (port-col) circle (as-pair (node/x + block-width + port-radius) out-port-y) (port-radius) - fill-pen col-text + pen (col-text-label) text (as-pair (node/x + block-width + port-radius + 12) (out-port-y - 7)) (form port) ] out-port-y: out-port-y + 20 ] - ; Borde de selección + ; ── Selección: dashed-box body + dashed-box label ── if same? node selected-node [ - append cmds compose [ - pen col-sel line-width 2 fill-pen off - box (as-pair (node/x - 3) (node/y - 3)) (as-pair (node/x + block-width + 3) (node/y + h + 3)) 6 - line-width 1 + append cmds compose [pen col-sel line-width 2 fill-pen off] + append cmds dashed-box + (node/x - 3) (node/y - 3) + (node/x + block-width + 3) (node/y + h + 3) + 6 4 + if all [node/label object? node/label node/label/visible] [ + lbl-x: node/x + either pair? node/label/offset [node/label/offset/x] [0] + lbl-y: node/y - bd-label-above + either pair? node/label/offset [node/label/offset/y] [0] + lw: max 30 (7 * length? any [node/label/text ""]) + append cmds compose [pen col-sel line-width 1 fill-pen off] + append cmds dashed-box (lbl-x - 2) (lbl-y - 2) (lbl-x + lw + 2) (lbl-y + 15) 4 3 ] + append cmds [line-width 1] ] cmds ] @@ -684,7 +739,7 @@ render-structure: func [ pen (col-struct-border) line-width 1 fill-pen (col-struct-term-i) box (as-pair (bx + 8) (by2 - tx - 8)) (as-pair (bx + 8 + tx) (by2 - 8)) 2 - pen off fill-pen col-text + pen (col-text) fill-pen off text (as-pair (bx + 11) (by2 - tx - 5 + text-dy)) "i" ] @@ -703,7 +758,7 @@ render-structure: func [ pen (col-struct-border) line-width 1 fill-pen (col-wire) box (as-pair (bx + 8) (by + 8)) (as-pair (bx + 8 + tx) (by + 8 + tx)) 2 - pen off fill-pen col-text + pen (col-text) fill-pen off text (as-pair (bx + 11) (by + 11 + text-dy)) "N" ] ] diff --git a/src/ui/diagram/canvas.red b/src/ui/diagram/canvas.red index 3ff798b..5d278c3 100644 --- a/src/ui/diagram/canvas.red +++ b/src/ui/diagram/canvas.red @@ -299,6 +299,23 @@ hit-node: func [model mouse-x mouse-y /local found-node node h] [ found-node ] +hit-node-label: func [model mouse-x mouse-y /local node lx ly lw lbl-dx lbl-dy] [ + foreach node model/nodes [ + if all [node/label object? node/label node/label/visible] [ + lbl-dx: either pair? node/label/offset [node/label/offset/x] [0] + lbl-dy: either pair? node/label/offset [node/label/offset/y] [0] + lx: node/x + lbl-dx + ly: node/y - bd-label-above + lbl-dy + lw: max 30 (7 * length? any [node/label/text ""]) + if all [ + mouse-x >= (lx - 2) mouse-x <= (lx + lw + 2) + mouse-y >= (ly - 2) mouse-y <= (ly + 14) + ] [return reduce [node 'label]] + ] + ] + none +] + ; Comprueba si el punto (mx my) está sobre algún wire de la lista dada. hit-wire-in-list: func [wires nodes mouse-x mouse-y /local tolerance src-node dst-node out-xy in-xy mid-x wire node] [ tolerance: 8 @@ -887,7 +904,25 @@ render-diagram: func [model canvas-width canvas-height /local canvas-face] [ return none ] - ; 6) Nodo normal externo? — antes que interior de estructura + ; 6) Label de nodo? — antes que el body del nodo + hit-ref: hit-node-label model mouse-x mouse-y + if hit-ref [ + model/wire-src: none model/wire-port: none model/mouse-pos: none model/wire-src-struct: none model/wire-src-sr: none + model/broken-wire: none + model/selected-wire: none + model/selected-struct: none + model/selected-sr: none + model/selected-node: hit-ref/1 + model/drag-node: hit-ref/1 + model/drag-is-label: true + lbl-dx: either pair? hit-ref/1/label/offset [hit-ref/1/label/offset/x] [0] + lbl-dy: either pair? hit-ref/1/label/offset [hit-ref/1/label/offset/y] [0] + model/drag-off: as-pair (mouse-x - hit-ref/1/x - lbl-dx) (mouse-y - hit-ref/1/y + bd-label-above - lbl-dy) + face/draw: render-bd model + return none + ] + + ; 7) Nodo normal externo? — antes que interior de estructura ; (fix bug #7: nodo arrastrado dentro del while queda accesible) hit-ref: hit-node model mouse-x mouse-y if hit-ref [ @@ -906,7 +941,7 @@ render-diagram: func [model canvas-width canvas-height /local canvas-face] [ return none ] - ; 7) Interior de estructura (fondo → seleccionar estructura, paleta interna futura)? + ; 8) Interior de estructura (fondo → seleccionar estructura, paleta interna futura)? hit-result: point-in-structure? model mouse-x mouse-y if hit-result [ model/selected-struct: hit-result @@ -918,7 +953,7 @@ render-diagram: func [model canvas-width canvas-height /local canvas-face] [ return none ] - ; 8) Wire normal? + ; 9) Wire normal? hit-ref: hit-wire model mouse-x mouse-y if hit-ref [ model/selected-wire: hit-ref @@ -929,7 +964,7 @@ render-diagram: func [model canvas-width canvas-height /local canvas-face] [ return none ] - ; 9) Clic en vacío: cancelar todo + ; 10) Clic en vacío: cancelar todo model/wire-src: none model/wire-port: none model/mouse-pos: none model/wire-src-struct: none model/wire-src-sr: none model/broken-wire: none model/drag-node: none model/selected-wire: none @@ -942,21 +977,27 @@ render-diagram: func [model canvas-width canvas-height /local canvas-face] [ mouse-x: event/offset/x + model/scroll-x mouse-y: event/offset/y + model/scroll-y - ; Drag de nodo (normal o interno) + ; Drag de nodo (normal o interno) o de label if all [model/drag-node model/drag-off event/down?] [ - model/drag-node/x: mouse-x - model/drag-off/x - model/drag-node/y: mouse-y - model/drag-off/y - ; Clamp nodo interno dentro de la estructura (margen 20px) - if model/selected-struct [ - _st: model/selected-struct - ; Case Structure: clamp Considerar nav-height para Y - _nav-h: either _st/type = 'case-structure [case-nav-height + 4] [22] - model/drag-node/x: max (_st/x + 20) - min (_st/x + _st/w - block-width - 20) - model/drag-node/x - model/drag-node/y: max (_st/y + _nav-h) - min (_st/y + _st/h - block-height - 20) - model/drag-node/y + either model/drag-is-label [ + model/drag-node/label/offset: as-pair + (mouse-x - model/drag-off/x - model/drag-node/x) + (mouse-y - model/drag-off/y - model/drag-node/y + bd-label-above) + ][ + model/drag-node/x: mouse-x - model/drag-off/x + model/drag-node/y: mouse-y - model/drag-off/y + ; Clamp nodo interno dentro de la estructura (margen 20px) + if model/selected-struct [ + _st: model/selected-struct + ; Case Structure: clamp Considerar nav-height para Y + _nav-h: either _st/type = 'case-structure [case-nav-height + 4] [22] + model/drag-node/x: max (_st/x + 20) + min (_st/x + _st/w - block-width - 20) + model/drag-node/x + model/drag-node/y: max (_st/y + _nav-h) + min (_st/y + _st/h - block-height - 20) + model/drag-node/y + ] ] face/draw: render-bd model return none @@ -1057,6 +1098,7 @@ render-diagram: func [model canvas-width canvas-height /local canvas-face] [ model/drag-struct: none model/drag-struct-off: none model/resize-struct: none + model/drag-is-label: false ] on-key: func [face event /local model] [ diff --git a/src/ui/panel/panel-render.red b/src/ui/panel/panel-render.red index 7c88d62..717e174 100644 --- a/src/ui/panel/panel-render.red +++ b/src/ui/panel/panel-render.red @@ -30,7 +30,7 @@ fp-chart-height: 160 ; GTK-010: en Linux/GTK, Draw text usa baseline como Y en vez de top-left. ; Compensamos añadiendo fp-text-dy a todas las coordenadas Y de texto. -fp-text-dy: either system/platform = 'Linux [8] [0] +fp-text-dy: either system/platform = 'Linux [0] [0] fp-color?: func [item-type] [ either find [control bool-control str-control arr-control cluster-control] item-type [fp-control-color] [fp-indicator-color] @@ -44,7 +44,7 @@ fp-border-color?: func [item-type] [ fp-cluster-height: func [item /local n] [ n: (length? fp-cluster-fields item) / 2 - 20 + (max 1 n) * 20 + 20 + ((max 1 n) * 20) ] fp-type-label?: func [item-type] [