tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198629 Definiciones

feat(renaser): Fase 8d — manipulación de ventanas

El escritorio se podía recorrer con el foco, pero no reordenar. La 8d lo
hace manipulable: el orden de teselado se separa de la identidad.

- Escritorio gana `orden: Vec<usize>` — una permutacion que dice que
  ventana ocupa cada celda. Mover una ventana cambia su celda, no su
  indice_app: conserva su canal de teclado y su ranura de estado.
- aplicar_teselado reparte los marcos segun el orden.
- Alt+Enter promueve la ventana enfocada a la celda maestra; Alt+H/Alt+L
  la reordenan. mover_foco recorre ahora el orden, no los indices crudos.

Verificado en QEMU (sendkey): con memoriosa enfocada, Alt+Enter la
promueve a maestra y hola baja a la pila; Alt+L la devuelve a la pila. El
foco —el borde indigo— viaja siempre con la ventana, no con la celda.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

dacfbad1249db14522037f8bcb6792560e1c3047 · por sergio · (hace 5 meses) · padre 3454b8ba1 · ver el árbol en este commit

Delta semántico · 13

renaser/kernel/src/async_system/teclado.rs

renaser/kernel/src/compositor.rs

7 archivos · +188 −37

diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md
index e547907..ab2d57c 100644
--- a/renaser/CHANGELOG.md
+++ b/renaser/CHANGELOG.md
@@ -731,3 +731,32 @@ asume la persistencia visual con una caché de fotogramas.
     mueve con él.
   - **Enrutamiento** — con `memoriosa` enfocada, cuatro pulsaciones llegan sólo
     a ella —cuatro celdas violetas—; las demás apps, intactas.
+
+## Fase 8d — Manipulación de ventanas — 2026-05-22
+
+El escritorio de la 8c se podía recorrer con la vista —el foco— pero no
+reordenar. La 8d lo hace MANIPULABLE: el orden de teselado se separa de la
+identidad de las ventanas, y el teclado promueve y reordena.
+
+### Añadido
+- `Escritorio` gana `orden: Vec<usize>` — una permutación que dice qué ventana
+  ocupa cada celda del teselado. Separar el orden de la identidad
+  (`indice_app`) permite mover una ventana sin tocar su canal de teclado ni su
+  ranura de estado: cambia de pared, no de identidad.
+- `aplicar_teselado` — recalcula los marcos y los reparte según el orden.
+- Mandos `Promover`, `MoverAdelante`, `MoverAtras`:
+  - `Alt+Enter` — promueve la ventana enfocada a la celda maestra; las demás
+    se desplazan una posición.
+  - `Alt+L` / `Alt+H` — mueven la ventana enfocada adelante / atrás en el
+    orden, intercambiándola con su vecina.
+
+### Cambiado
+- `mover_foco` recorre ahora el ORDEN de teselado —no los índices crudos—: el
+  foco salta entre ventanas visualmente contiguas.
+- `ciclar_layout` y `fundar` delegan en `aplicar_teselado`.
+
+### Verificado
+- QEMU (`sendkey`): con `memoriosa` enfocada, `Alt+Enter` la promueve a la
+  ventana maestra y `hola` baja a la pila; `Alt+L` la devuelve a la pila y
+  `hola` recupera la maestra. El foco —el borde índigo— viaja siempre con la
+  ventana, no con la celda.
diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md
index 9ef95d8..ea499f5 100644
--- a/renaser/CLAUDE.md
+++ b/renaser/CLAUDE.md
@@ -75,9 +75,9 @@ QEMU 11, OVMF en `/usr/share/edk2/x64/OVMF.4m.fd` (sin módulo KVM → TCG).
 
 Fases 1 a 5, 6.0, 6.1, 6.2, la Fase 7 COMPLETA —el userspace nace del grafo de
 objetos— y la Fase 8 COMPLETA —el compositor teselante e interactivo: teselado
-con `mirada-layout` (8a), `Alt+Espacio` cicla el layout (8b), foco con
-`Alt+J`/`Alt+K` y enrutamiento selectivo del teclado (8c)—. Todo verificado en
-QEMU. Ver `ROADMAP.md`.
+con `mirada-layout` (8a), ciclado de layout (8b), foco y enrutamiento selectivo
+del teclado (8c), promoción y reordenación de ventanas (8d)—. Todo verificado
+en QEMU. Ver `ROADMAP.md`.
 
 ## Flujo de trabajo
 
diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md
index 7f763a6..c943df2 100644
--- a/renaser/DIARIO.md
+++ b/renaser/DIARIO.md
@@ -372,6 +372,24 @@ los libros de la casa. Sólo deja una nota breve en un casillero a prueba de
 prisas, y sigue su camino. Es el arquitecto quien, más tarde y con calma, lee
 la nota y mueve los tabiques. Nadie se pisa; nada se traba.
 
+## El cuarto se muda, el inquilino se queda
+
+Hasta ayer, la casa sabía mirar a un inquilino —darle el foco—, pero no
+moverlo. Su sitio era el que el arquitecto le había dado, y allí se quedaba.
+
+Hoy la casa aprendió a reacomodar. Con una tecla, quien la habita puede decir
+«este inquilino merece el cuarto grande», y el arquitecto lo asciende a la
+pieza maestra; los demás se corren un sitio. Con otras dos, puede adelantar o
+atrasar un cuarto en la fila, como quien reordena los libros de un estante.
+
+Y aquí hubo una distinción fina, casi filosófica. Una cosa es el INQUILINO —su
+nombre, su cajón de recuerdos, su buzón de cartas— y otra es el CUARTO que
+ocupa. La casa aprendió a no confundirlos. Cuando un inquilino cambia de
+habitación no cambia de identidad: sigue recibiendo su correo, sigue siendo el
+dueño de su cajón; sólo se mudó de pared. Por eso, cuando la mirada de la casa
+estaba puesta en él, lo sigue al cuarto nuevo — el foco viaja con la persona,
+nunca se queda mirando una pared vacía.
+
 ---
 
 *El diario continúa. La próxima página la escribirá la próxima jornada.*
diff --git a/renaser/FASE8.md b/renaser/FASE8.md
index f58829e..37262db 100644
--- a/renaser/FASE8.md
+++ b/renaser/FASE8.md
@@ -48,10 +48,17 @@ bare-metal.
   `mirada-layout` —ventanas, marcos, foco— sin adoptar su tipo: el kernel
   necesita además la caché de respaldo, que el `Workspace` no contempla.
 
+### 8d — Manipulación de ventanas — ✅ HECHA
+
+- `Escritorio` separa el ORDEN de teselado de la IDENTIDAD de las ventanas
+  (`orden: Vec<usize>`): una ventana puede cambiar de celda sin perder su canal
+  de teclado ni su ranura de estado.
+- `Alt+Enter` promueve la ventana enfocada a la celda maestra; `Alt+H` /
+  `Alt+L` la mueven atrás / adelante en el orden. El foco viaja con la ventana.
+
 ### Pendiente
 
 - Orden-Z y solapamiento (ventanas flotantes); alta y baja de apps en vivo.
-- Promover la app enfocada al área maestra (`Alt+Enter`).
 
 ## Estructura de archivos
 
diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md
index 5b82a51..140939e 100644
--- a/renaser/ROADMAP.md
+++ b/renaser/ROADMAP.md
@@ -139,6 +139,10 @@ en `FASE8.md`.
 - **8c — foco y enrutamiento selectivo (completada).** Una ventana enfocada,
   con borde índigo; `Alt+J` / `Alt+K` mueven el foco entre las ventanas vivas.
   El teclado deja de difundir: entrega cada tecla sólo a la app enfocada.
+- **8d — manipulación de ventanas (completada).** El orden de teselado se
+  separa de la identidad de las ventanas. `Alt+Enter` promueve la ventana
+  enfocada a la celda maestra; `Alt+H` / `Alt+L` la reordenan. El foco viaja
+  con la ventana.
 
 Líneas abiertas posteriores: orden-Z y ventanas flotantes; más capacidades del
 host (temporización, audio).
diff --git a/renaser/kernel/src/async_system/teclado.rs b/renaser/kernel/src/async_system/teclado.rs
index b531950..8a6cdb8 100644
--- a/renaser/kernel/src/async_system/teclado.rs
+++ b/renaser/kernel/src/async_system/teclado.rs
@@ -11,8 +11,8 @@
 //  FASE 8c :: el teclado deja de DIFUNDIR a ciegas. Ahora discrimina:
 //
 //    * La tecla Alt es el MODIFICADOR del sistema. Con Alt pulsada, los make
-//      codes son MANDOS del compositor (ciclar el teselado, mover el foco): se
-//      consumen aqui, jamas llegan a una app.
+//      codes son MANDOS del compositor (ciclar el teselado, mover el foco,
+//      promover y reordenar ventanas): se consumen aqui, jamas llegan a una app.
 //    * Una tecla ordinaria se entrega SOLO a la app ENFOCADA — la que el
 //      compositor senala. El censo de canales se indexa por el `indice_app`,
 //      de modo que el foco —un atomico— elija el canal exacto.
@@ -45,6 +45,12 @@ const ESPACIO: u8 = 0x39;
 const TECLA_J: u8 = 0x24;
 /// Tecla K — `Alt + K` mueve el foco a la ventana anterior.
 const TECLA_K: u8 = 0x25;
+/// Tecla H — `Alt + H` mueve la ventana enfocada atras en el orden.
+const TECLA_H: u8 = 0x23;
+/// Tecla L — `Alt + L` mueve la ventana enfocada adelante en el orden.
+const TECLA_L: u8 = 0x26;
+/// Tecla Enter — `Alt + Enter` promueve la ventana enfocada a maestra.
+const ENTER: u8 = 0x1C;
 
 /// Un canal de teclado: la cola lock-free de scancodes de UNA aplicacion.
 pub type CanalTeclado = Arc<ArrayQueue<u8>>;
@@ -127,6 +133,9 @@ pub fn recibir_scancode(scancode: u8) {
             ESPACIO => compositor::solicitar(Mando::CiclarLayout),
             TECLA_J => compositor::solicitar(Mando::FocoSiguiente),
             TECLA_K => compositor::solicitar(Mando::FocoAnterior),
+            ENTER => compositor::solicitar(Mando::Promover),
+            TECLA_L => compositor::solicitar(Mando::MoverAdelante),
+            TECLA_H => compositor::solicitar(Mando::MoverAtras),
             _ => {}
         }
         return;
diff --git a/renaser/kernel/src/compositor.rs b/renaser/kernel/src/compositor.rs
index 8921ab9..8469809 100644
--- a/renaser/kernel/src/compositor.rs
+++ b/renaser/kernel/src/compositor.rs
@@ -55,6 +55,12 @@ pub enum Mando {
     FocoSiguiente,
     /// Mover el foco a la ventana viva anterior.
     FocoAnterior,
+    /// Promover la ventana enfocada a la posicion maestra del teselado.
+    Promover,
+    /// Mover la ventana enfocada una posicion adelante en el orden de teselado.
+    MoverAdelante,
+    /// Mover la ventana enfocada una posicion atras en el orden de teselado.
+    MoverAtras,
 }
 
 /// Una ventana del escritorio: una app, su geometria y su ultimo fotograma.
@@ -84,7 +90,13 @@ struct Escritorio {
     modo: LayoutMode,
     ancho: usize,
     alto: usize,
+    /// Las ventanas, indexadas por `indice_app` — su IDENTIDAD, inmutable.
     ventanas: Vec<Ventana>,
+    /// El ORDEN de teselado: `orden[slot]` es el `indice_app` de la ventana que
+    /// ocupa esa celda del teselado. Una permutacion de `0..ventanas.len()`.
+    /// Separar el orden de la identidad permite promover y reordenar ventanas
+    /// sin tocar su `indice_app` —su canal de teclado, su ranura de estado—.
+    orden: Vec<usize>,
 }
 
 /// El escritorio global. Se funda una sola vez, en el arranque.
@@ -110,13 +122,18 @@ static MANDOS: Once<ArrayQueue<Mando>> = Once::new();
 pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize)]) {
     MANDOS.call_once(|| ArrayQueue::new(CAPACIDAD_MANDOS));
 
-    let marcos = teselar(naturales.len(), ancho, alto, MODO_INICIAL);
     let mut ventanas = Vec::with_capacity(naturales.len());
-    for (i, &(nat_ancho, nat_alto)) in naturales.iter().enumerate() {
+    for &(nat_ancho, nat_alto) in naturales {
         ventanas.push(Ventana {
             natural_ancho: nat_ancho,
             natural_alto: nat_alto,
-            marco: marcos[i],
+            // Marco provisional; `aplicar_teselado` lo fija enseguida.
+            marco: RegionPantalla {
+                x: 0,
+                y: 0,
+                ancho: 0,
+                alto: 0,
+            },
             // La cache: reservada UNA vez, acotada al lienzo natural.
             cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
             pintada: false,
@@ -124,14 +141,34 @@ pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize)]) {
         });
     }
 
-    ESCRITORIO.call_once(|| {
-        Mutex::new(Escritorio {
-            modo: MODO_INICIAL,
-            ancho,
-            alto,
-            ventanas,
-        })
-    });
+    // El orden de teselado arranca como la identidad: la ventana `i` ocupa la
+    // celda `i`. Los mandos del teclado lo permutaran.
+    let orden = (0..ventanas.len()).collect();
+    let mut escritorio = Escritorio {
+        modo: MODO_INICIAL,
+        ancho,
+        alto,
+        ventanas,
+        orden,
+    };
+    aplicar_teselado(&mut escritorio);
+
+    ESCRITORIO.call_once(|| Mutex::new(escritorio));
+}
+
+/// Recalcula el teselado y asigna a cada ventana su marco. La celda `slot` del
+/// teselado va a la ventana `orden[slot]`: manda el orden, no la identidad.
+fn aplicar_teselado(escritorio: &mut Escritorio) {
+    let marcos = teselar(
+        escritorio.orden.len(),
+        escritorio.ancho,
+        escritorio.alto,
+        escritorio.modo,
+    );
+    for (slot, marco) in marcos.into_iter().enumerate() {
+        let ventana = escritorio.orden[slot];
+        escritorio.ventanas[ventana].marco = marco;
+    }
 }
 
 /// Pinta el escenario inicial del compositor: el area de apps y sus marcos
@@ -223,6 +260,9 @@ pub fn atender_mandos() {
             Mando::CiclarLayout => ciclar_layout(),
             Mando::FocoSiguiente => mover_foco(true),
             Mando::FocoAnterior => mover_foco(false),
+            Mando::Promover => promover(),
+            Mando::MoverAdelante => mover_ventana(true),
+            Mando::MoverAtras => mover_ventana(false),
         }
     }
 }
@@ -235,52 +275,96 @@ fn ciclar_layout() {
     };
     let mut escritorio = escritorio.lock();
     escritorio.modo = escritorio.modo.next();
-
-    let marcos = teselar(
-        escritorio.ventanas.len(),
-        escritorio.ancho,
-        escritorio.alto,
-        escritorio.modo,
-    );
-    for (ventana, marco) in escritorio.ventanas.iter_mut().zip(marcos) {
-        ventana.marco = marco;
-    }
+    aplicar_teselado(&mut escritorio);
     redibujar_todo(&escritorio);
 }
 
-/// Mueve el foco a la siguiente ventana VIVA —saltando las desalojadas—; tras
-/// el salto, redibuja la ventana que pierde el foco y la que lo gana, para que
-/// el borde de cada una cambie de color.
+/// Mueve el foco a la siguiente ventana VIVA, recorriendo el ORDEN de teselado
+/// —de modo que el foco salte entre ventanas visualmente contiguas— y saltando
+/// las desalojadas. Redibuja la ventana que pierde el foco y la que lo gana.
 fn mover_foco(adelante: bool) {
     let Some(escritorio) = ESCRITORIO.get() else {
         return;
     };
     let escritorio = escritorio.lock();
-    let n = escritorio.ventanas.len();
+    let n = escritorio.orden.len();
     if n == 0 {
         return;
     }
-    let anterior = FOCO.load(Ordering::Relaxed).min(n - 1);
+    let anterior = FOCO.load(Ordering::Relaxed);
+    // La posicion del foco en el orden de teselado — el punto de partida.
+    let pos = escritorio
+        .orden
+        .iter()
+        .position(|&v| v == anterior)
+        .unwrap_or(0);
 
     // Avanzar saltando las ventanas desalojadas. Si no hay ninguna viva, tras
     // `n` pasos se vuelve al punto de partida y el foco no cambia.
+    let mut nueva_pos = pos;
     let mut nuevo = anterior;
     for _ in 0..n {
-        nuevo = if adelante {
-            (nuevo + 1) % n
+        nueva_pos = if adelante {
+            (nueva_pos + 1) % n
         } else {
-            (nuevo + n - 1) % n
+            (nueva_pos + n - 1) % n
         };
-        if escritorio.ventanas[nuevo].baliza.is_none() {
+        let candidata = escritorio.orden[nueva_pos];
+        if escritorio.ventanas[candidata].baliza.is_none() {
+            nuevo = candidata;
             break;
         }
     }
     FOCO.store(nuevo, Ordering::Relaxed);
 
-    redibujar_ventana(&escritorio.ventanas[anterior], false);
+    if let Some(ventana) = escritorio.ventanas.get(anterior) {
+        redibujar_ventana(ventana, false);
+    }
     redibujar_ventana(&escritorio.ventanas[nuevo], true);
 }
 
+/// Promueve la ventana enfocada a la posicion maestra —la celda 0— del
+/// teselado: la saca de su lugar en el orden y la inserta al frente. Las demas
+/// se desplazan una posicion. El escritorio entero se recompone.
+fn promover() {
+    let Some(escritorio) = ESCRITORIO.get() else {
+        return;
+    };
+    let mut escritorio = escritorio.lock();
+    let foco = FOCO.load(Ordering::Relaxed);
+    if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
+        let ventana = escritorio.orden.remove(pos);
+        escritorio.orden.insert(0, ventana);
+        aplicar_teselado(&mut escritorio);
+        redibujar_todo(&escritorio);
+    }
+}
+
+/// Mueve la ventana enfocada una posicion en el orden de teselado,
+/// intercambiandola con su vecina. El foco viaja con la ventana — el indice no
+/// cambia, solo su lugar en el orden.
+fn mover_ventana(adelante: bool) {
+    let Some(escritorio) = ESCRITORIO.get() else {
+        return;
+    };
+    let mut escritorio = escritorio.lock();
+    let n = escritorio.orden.len();
+    if n < 2 {
+        return;
+    }
+    let foco = FOCO.load(Ordering::Relaxed);
+    if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
+        let destino = if adelante {
+            (pos + 1) % n
+        } else {
+            (pos + n - 1) % n
+        };
+        escritorio.orden.swap(pos, destino);
+        aplicar_teselado(&mut escritorio);
+        redibujar_todo(&escritorio);
+    }
+}
+
 /// Recompone el escritorio entero: repinta el escenario —area y paneles— con
 /// los marcos nuevos y, sobre el, cada ventana desde su cache de respaldo.
 fn redibujar_todo(escritorio: &Escritorio) {