From dacfbad1249db14522037f8bcb6792560e1c3047 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 22 May 2026 19:25:32 +0000 Subject: [PATCH] =?UTF-8?q?feat(renaser):=20Fase=208d=20=E2=80=94=20manipu?= =?UTF-8?q?laci=C3=B3n=20de=20ventanas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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` — 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 --- renaser/CHANGELOG.md | 29 ++++ renaser/CLAUDE.md | 6 +- renaser/DIARIO.md | 18 +++ renaser/FASE8.md | 9 +- renaser/ROADMAP.md | 4 + renaser/kernel/src/async_system/teclado.rs | 13 +- renaser/kernel/src/compositor.rs | 146 ++++++++++++++++----- 7 files changed, 188 insertions(+), 37 deletions(-) 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` — 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`): 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>; @@ -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, + /// 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, } /// El escritorio global. Se funda una sola vez, en el arranque. @@ -110,13 +122,18 @@ static MANDOS: Once> = 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) {