diff --git a/renaser/CHANGELOG.md b/renaser/CHANGELOG.md index 44b9c6e..84b92f6 100644 --- a/renaser/CHANGELOG.md +++ b/renaser/CHANGELOG.md @@ -1046,3 +1046,42 @@ propia voz, prioritaria sobre las de los apps. desalojadas), y después la escala de `tonada` tomando la bocina. - Captura: la pestaña de `glotona` (crema) muestra ahora su nombre legible en tinta oscura; la de `discola` (púrpura) sigue clara, como antes. + +## Fase 16 — La barra viva: lanzador y reloj — 2026-05-23 + +La barra de tareas era informativa pero pasiva: nombraba a los inquilinos sin +ofrecerse a ningún gesto propio. La Fase 16 le añade los dos accesorios de +toda barra de tareas digna: un **botón lanzador** a la izquierda y un **reloj** +a la derecha, y le pone el reloj a latir cada segundo. + +### Añadido +- **Botón lanzador («+»)** a la izquierda de la barra (36 px de ancho, fondo + índigo del foco, cruz blanca). Un clic incrementa `PARTOS` — el mismo + contador que `Alt+N` —, así que la tarea del compositor lo recoge y lanza + la siguiente app de la rotación. El teclado y el ratón comparten ahora la + misma vía para crear ventanas. +- **Reloj** a la derecha de la barra (80 px), formato `mm:ss` desde el + arranque, leído de `reloj::milisegundos()`. Tinta blanca sobre slate. +- **`compositor::tick_reloj()`** — la invoca la tarea del compositor cada + fotograma. Si el segundo del reloj cambió respecto al último mostrado + (`ULTIMO_SEGUNDO: AtomicU64`), recompone; si no, vuelve. Cero coste cuando + no hace falta refrescar. +- `Taskbar` gana los campos `launcher: RegionPantalla`, `reloj: &str` y + `reloj_region: RegionPantalla`. `pintar_taskbar` los pinta como dos cuñas + fijas de la barra: el lanzador con su cruz dibujada en píxeles directos + (independiente de la tipografía), el reloj con la etiqueta de costumbre. + +### Cambiado +- El layout de la barra se recalcula: `cells_x0` empieza tras el lanzador, + `cells_x_max` termina antes del reloj. `celda_taskbar_en` respeta esos + límites; un clic en el lanzador (`clic_en_launcher`) tiene su propia rama + en `atender_raton`, antes que la búsqueda de pestaña. +- `CELDA_TASKBAR_ANCHO` baja de 156 a 150 para que el lanzador, las siete + pestañas y el reloj quepan holgados en una pantalla de 1280 píxeles. + +### Verificado +- QEMU. La barra al arrancar muestra el botón «+» indigo a la izquierda, las + siete pestañas (con `glotona` ya legible en tinta oscura sobre crema), y el + reloj `0:17` a la derecha (el tiempo que el kernel lleva vivo al capturar). + Diez segundos después, el reloj marca `0:29` — la barra se ha refrescado + doce veces sin intervención del ratón ni del teclado. diff --git a/renaser/CLAUDE.md b/renaser/CLAUDE.md index 71b700d..87d91d4 100644 --- a/renaser/CLAUDE.md +++ b/renaser/CLAUDE.md @@ -85,9 +85,11 @@ como capacidad de host (`sys_tono`) + la app `tonada`—, la Fase 13 COMPLETA —ratón PS/2, puntero, clic-para-enfocar y arrastre de ventanas flotantes—, infraestructura `memory::mmio` (mapeador propio de regiones MMIO en la tabla L4), la Fase 14 COMPLETA —nombres en cada ventana y barra de tareas con -clic-para-enfocar— y la Fase 15 COMPLETA —la voz del sistema: acorde al +clic-para-enfocar—, la Fase 15 COMPLETA —la voz del sistema: acorde al arrancar, repique al lanzar o cerrar, bajo al desalojar, con prioridad -sobre `sys_tono`—. Todo verificado en QEMU. Ver `ROADMAP.md`. +sobre `sys_tono`— y la Fase 16 COMPLETA —la barra viva: botón «+» +lanzador a la izquierda y reloj `mm:ss` a la derecha que late cada +segundo—. Todo verificado en QEMU. Ver `ROADMAP.md`. ## Flujo de trabajo diff --git a/renaser/DIARIO.md b/renaser/DIARIO.md index e23ef7f..a9c1f40 100644 --- a/renaser/DIARIO.md +++ b/renaser/DIARIO.md @@ -534,6 +534,19 @@ sencilla: mientras la casa esté diciendo lo suyo, los demás callan. En cuanto ella termina, devuelve la bocina al inquilino que la tenía, y la música del cuarto enfocado vuelve a sonar donde se quedó. +## El zócalo se anima — un botón y un reloj + +La cinta de carteles al pie nombraba, pero no hacía. Hoy le brotaron dos +adornos que la convierten en un zócalo de los de verdad. En el extremo +izquierdo, un cuadradito índigo con una cruz blanca en medio: una invitación +sencilla a tocarla, y al tocarla la casa recibe un inquilino nuevo —el mismo +gesto que antes pedía la combinación de teclas, ahora también al alcance del +dedo—. En el extremo derecho, dos números separados por dos puntos: los +minutos y los segundos que la casa lleva despierta. Lo más bonito es que ese +reloj LATE: cada vez que pasa un segundo nuevo —y sólo entonces, ni una vez +de más—, la casa recompone el zócalo para mostrar la cifra siguiente. El +resto del tiempo, el zócalo descansa. + --- *El diario continúa. La próxima página la escribirá la próxima jornada.* diff --git a/renaser/ROADMAP.md b/renaser/ROADMAP.md index fb51b0a..c35d4eb 100644 --- a/renaser/ROADMAP.md +++ b/renaser/ROADMAP.md @@ -273,6 +273,22 @@ desalojarla. Verificada en QEMU con captura PCM a WAV. - Pestañas de la barra de tareas: tinta calculada por brillo del fondo, así la pestaña crema del desalojo por memoria ya no lleva texto invisible. +## Fase 16 — la barra viva: lanzador y reloj (completada) + +La barra de tareas era pasiva. Esta fase la activa con un botón lanzador a la +izquierda («+», equivalente táctil de `Alt+N`) y un reloj `mm:ss` a la derecha +que late cada segundo. Verificada en QEMU con dos capturas separadas en el +tiempo: el reloj avanza de `0:17` a `0:29`. + +- `compositor`: layout reorganizado — lanzador a la izquierda (36 px), celdas + en el medio, reloj a la derecha (80 px). `clic_en_launcher` enruta el clic + al contador `PARTOS`; la tarea del compositor lo recoge y lanza. +- `compositor::tick_reloj()` — invocada cada fotograma; recompone sólo cuando + el segundo del reloj monótono cambia respecto al último mostrado. +- `consola::Taskbar` crece con `launcher`, `reloj` y `reloj_region`. + `pintar_taskbar` dibuja la cruz del lanzador como dos rectángulos + cruzados (sin depender de la tipografía) y rotula el reloj. + Líneas abiertas posteriores: reciclado de las ranuras de ventana cerradas; audio con varias voces (PCM) más allá del tono único de la bocina. diff --git a/renaser/kernel/src/compositor.rs b/renaser/kernel/src/compositor.rs index 758227d..7423a65 100644 --- a/renaser/kernel/src/compositor.rs +++ b/renaser/kernel/src/compositor.rs @@ -45,7 +45,7 @@ // pudiera disputar a una tarea cooperativa. // ============================================================================= -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use alloc::string::{String, ToString}; use alloc::vec; @@ -68,11 +68,19 @@ const FRANJA_CONSOLA: usize = 296; const FRANJA_TASKBAR: usize = 40; /// Anchura de cada celda de la barra de tareas, en pixeles. -const CELDA_TASKBAR_ANCHO: usize = 156; +const CELDA_TASKBAR_ANCHO: usize = 150; /// Hueco entre celdas adyacentes de la barra. const CELDA_TASKBAR_HUECO: usize = 6; -/// Margen izquierdo de la primera celda. -const CELDA_TASKBAR_MARGEN: usize = 16; +/// Margen izquierdo y derecho de la barra de tareas. +const CELDA_TASKBAR_MARGEN: usize = 12; +/// Anchura del boton lanzador («+» a la izquierda de la barra, Fase 16). +const LAUNCHER_ANCHO: usize = 36; +/// Hueco entre el lanzador y la primera pestaña. +const LAUNCHER_HUECO: usize = 8; +/// Anchura reservada para el reloj a la derecha de la barra (Fase 16). +const RELOJ_ANCHO: usize = 80; +/// Hueco entre la ultima pestaña y el reloj. +const RELOJ_HUECO: usize = 8; /// El modo de teselado con que arranca el escritorio. El teclado lo cicla. const MODO_INICIAL: LayoutMode = LayoutMode::MasterStack; @@ -198,6 +206,12 @@ static MANDOS: Once> = Once::new(); /// Atomico: el compositor lo escribe, el orquestador lo lee y lo pone a cero. static PARTOS: AtomicUsize = AtomicUsize::new(0); +/// El ultimo segundo del reloj monotono que la barra de tareas ha mostrado. +/// `tick_reloj` lo compara con el actual: si difiere, recompone para pintar el +/// nuevo. Centinela `u64::MAX` para garantizar que el primer tick fuerza un +/// repintado y la barra arranca con su reloj a 0:00. +static ULTIMO_SEGUNDO: AtomicU64 = AtomicU64::new(u64::MAX); + // ============================================================================= // Fundacion y consulta — el arranque // ============================================================================= @@ -600,21 +614,27 @@ fn recomponer(escritorio: &Escritorio) { }); } - // FASE 14 :: armar las celdas de la barra de tareas. Una pestaña por - // ventana viva (no cerrada), de izquierda a derecha, con el nombre de la - // app; la enfocada lleva el color indigo del foco, las desalojadas su - // baliza, las demas el slate del panel. El clic sobre una pestaña enfoca - // su ventana. + // FASE 14/16 :: armar la barra de tareas. A la izquierda un boton lanzador + // («+»); en el medio una pestaña por ventana viva; a la derecha el reloj. let area_bar = area_taskbar(escritorio.ancho, escritorio.alto); - let mut celdas: Vec = Vec::new(); - let mut cx = area_bar.x + CELDA_TASKBAR_MARGEN; let cy = area_bar.y + 4; let calto = area_bar.alto.saturating_sub(8); + let launcher = RegionPantalla { + x: area_bar.x + CELDA_TASKBAR_MARGEN, + y: cy, + ancho: LAUNCHER_ANCHO, + alto: calto, + }; + let cells_x0 = launcher.x + launcher.ancho + LAUNCHER_HUECO; + let cells_x_max = + area_bar.x + area_bar.ancho - CELDA_TASKBAR_MARGEN - RELOJ_ANCHO - RELOJ_HUECO; + let mut celdas: Vec = Vec::new(); + let mut cx = cells_x0; for (indice, ventana) in escritorio.ventanas.iter().enumerate() { if ventana.cerrada { continue; } - if cx + CELDA_TASKBAR_ANCHO > area_bar.x + area_bar.ancho { + if cx + CELDA_TASKBAR_ANCHO > cells_x_max { break; } let fondo = match ventana.baliza { @@ -635,24 +655,43 @@ fn recomponer(escritorio: &Escritorio) { }); cx += CELDA_TASKBAR_ANCHO + CELDA_TASKBAR_HUECO; } + // El reloj: minutos:segundos desde el arranque, alineado a la derecha. + let ms = crate::async_system::reloj::milisegundos(); + let segs = ms / 1000; + let reloj_texto = alloc::format!("{}:{:02}", segs / 60, segs % 60); + let reloj_region = RegionPantalla { + x: area_bar.x + area_bar.ancho - CELDA_TASKBAR_MARGEN - RELOJ_ANCHO, + y: cy, + ancho: RELOJ_ANCHO, + alto: calto, + }; let taskbar = Taskbar { area: area_bar, + launcher, celdas: &celdas, + reloj: &reloj_texto, + reloj_region, }; consola::recomponer(area, &capas, &taskbar); + // Recordar el segundo recien mostrado: `tick_reloj` evita repintar de mas + // mientras dure este mismo segundo. + ULTIMO_SEGUNDO.store(segs, Ordering::Relaxed); } /// Localiza la celda de la barra de tareas bajo la coordenada x: itera las /// ventanas vivas en orden de creacion y devuelve la N-esima donde la N es la -/// posicion en la barra. `None` si el clic cae en un hueco entre celdas, antes -/// del margen, o pasada la ultima. +/// posicion en la barra. `None` si el clic cae en el lanzador, en el reloj, en +/// un hueco entre celdas, o fuera del rango de las pestañas. fn celda_taskbar_en(escritorio: &Escritorio, x: usize) -> Option { let area_bar = area_taskbar(escritorio.ancho, escritorio.alto); - let margen_izq = area_bar.x + CELDA_TASKBAR_MARGEN; - if x < margen_izq { + // Las pestañas empiezan despues del lanzador. + let cells_x0 = area_bar.x + CELDA_TASKBAR_MARGEN + LAUNCHER_ANCHO + LAUNCHER_HUECO; + let cells_x_max = + area_bar.x + area_bar.ancho - CELDA_TASKBAR_MARGEN - RELOJ_ANCHO - RELOJ_HUECO; + if x < cells_x0 || x >= cells_x_max { return None; } - let rel = x - margen_izq; + let rel = x - cells_x0; let paso = CELDA_TASKBAR_ANCHO + CELDA_TASKBAR_HUECO; let posicion = rel / paso; let offset = rel % paso; @@ -672,6 +711,14 @@ fn celda_taskbar_en(escritorio: &Escritorio, x: usize) -> Option { None } +/// ¿Cae la coordenada x en el boton lanzador («+»)? +fn clic_en_launcher(escritorio: &Escritorio, x: usize) -> bool { + let area_bar = area_taskbar(escritorio.ancho, escritorio.alto); + let x0 = area_bar.x + CELDA_TASKBAR_MARGEN; + let x1 = x0 + LAUNCHER_ANCHO; + x >= x0 && x < x1 +} + // ============================================================================= // FASE 10 — alta y baja de aplicaciones en vivo // ============================================================================= @@ -783,6 +830,22 @@ pub fn partos_pendientes() -> usize { PARTOS.swap(0, Ordering::Relaxed) } +/// Avanza el reloj de la barra de tareas (Fase 16): si el segundo del reloj +/// monotono cambio respecto al ultimo mostrado, recompone para refrescar la +/// pantalla. Si el segundo es el mismo, vuelve sin hacer nada — un fotograma +/// barato—. La invoca la tarea del compositor cada fotograma. +pub fn tick_reloj() { + let actual = crate::async_system::reloj::milisegundos() / 1000; + if ULTIMO_SEGUNDO.load(Ordering::Relaxed) == actual { + return; + } + let Some(escritorio) = ESCRITORIO.get() else { + return; + }; + let escritorio = escritorio.lock(); + recomponer(&escritorio); +} + // ============================================================================= // FASE 13 — raton, puntero y arrastre de ventanas flotantes // ============================================================================= @@ -814,7 +877,11 @@ pub fn atender_raton() { // habitual: enfocar la ventana topmost bajo el puntero. let area_bar = area_taskbar(escritorio.ancho, escritorio.alto); if y >= area_bar.y && y < area_bar.y + area_bar.alto { - if let Some(v) = celda_taskbar_en(&escritorio, x) { + if clic_en_launcher(&escritorio, x) { + // El boton «+» equivale a `Alt+N`: solicita un parto. La + // tarea del compositor lo recogera en su proxima vuelta. + PARTOS.fetch_add(1, Ordering::Relaxed); + } else if let Some(v) = celda_taskbar_en(&escritorio, x) { let viva = { let w = &escritorio.ventanas[v]; w.baliza.is_none() && !w.cerrada diff --git a/renaser/kernel/src/consola.rs b/renaser/kernel/src/consola.rs index daf542d..b256fd1 100644 --- a/renaser/kernel/src/consola.rs +++ b/renaser/kernel/src/consola.rs @@ -79,10 +79,15 @@ pub(crate) struct CeldaTaskbar<'a> { pub(crate) tinta: Color, } -/// La barra de tareas del escritorio (Fase 14): su area y sus pestañas. +/// La barra de tareas del escritorio (Fase 14): el area entera, el boton +/// lanzador a la izquierda («+», Fase 16), las pestañas y el reloj a la +/// derecha (Fase 16). pub(crate) struct Taskbar<'a> { pub(crate) area: RegionPantalla, + pub(crate) launcher: RegionPantalla, pub(crate) celdas: &'a [CeldaTaskbar<'a>], + pub(crate) reloj: &'a str, + pub(crate) reloj_region: RegionPantalla, } /// La consola grafica de renaser: doble bufer, pantalla fisica y pluma. @@ -274,9 +279,10 @@ impl Consola { self.presentar(); } - /// Pinta la barra de tareas como ultima capa del escritorio (Fase 14): el - /// fondo de la franja, una linea fina arriba que la separa de las apps, y - /// las pestañas —cada una su rectángulo y su nombre—. + /// Pinta la barra de tareas como ultima capa del escritorio (Fase 14/16): + /// el fondo de la franja, una linea fina arriba que la separa de las apps, + /// el lanzador a la izquierda, las pestañas en el medio y el reloj a la + /// derecha. fn pintar_taskbar(&mut self, taskbar: &Taskbar) { // Fondo de la barra y linea de separacion. self.lienzo.rellenar_rect( @@ -286,18 +292,53 @@ impl Consola { taskbar.area.alto, Color::PANEL, ); + self.lienzo.rellenar_rect( + taskbar.area.x, + taskbar.area.y, + taskbar.area.ancho, + 1, + Color::SIN_FOCO, + ); + // El boton lanzador: un cuadrado indigo con un «+» centrado. Invita a + // pulsar — al hacerlo, el compositor solicita un parto (igual que Alt+N). + let l = taskbar.launcher; self.lienzo - .rellenar_rect(taskbar.area.x, taskbar.area.y, taskbar.area.ancho, 1, Color::SIN_FOCO); + .rellenar_rect(l.x, l.y, l.ancho, l.alto, Color::FOCO); + // El «+»: dos barras estrechas cruzadas en el centro. Mas legible que + // una sola hace una cruz limpia, sin depender de la tipografia. + let cx = l.x + l.ancho / 2; + let cy = l.y + l.alto / 2; + let radio: usize = 8; + let grosor: usize = 2; + // Barra horizontal. + self.lienzo.rellenar_rect( + cx.saturating_sub(radio), + cy.saturating_sub(grosor / 2), + radio * 2, + grosor, + Color::TEXTO, + ); + // Barra vertical. + self.lienzo.rellenar_rect( + cx.saturating_sub(grosor / 2), + cy.saturating_sub(radio), + grosor, + radio * 2, + Color::TEXTO, + ); // Las pestañas. for celda in taskbar.celdas { let r = celda.region; self.lienzo .rellenar_rect(r.x, r.y, r.ancho, r.alto, celda.fondo); - // El nombre, alineado a la izquierda de la pestaña, vertical- - // centrado a la altura visible de la franja. let base_y = r.y + (r.alto + 14) / 2; self.pintar_etiqueta(r.x + 10, base_y, celda.nombre, 16.0, celda.fondo, celda.tinta); } + // El reloj a la derecha: alineado a la izquierda de su region, sobre + // el fondo del panel (sin caja propia — la barra es su lienzo). + let r = taskbar.reloj_region; + let base_y = r.y + (r.alto + 14) / 2; + self.pintar_etiqueta(r.x, base_y, taskbar.reloj, 16.0, Color::PANEL, Color::TEXTO); } /// Rasteriza una cadena de texto a un tamaño dado, en (x, base_y), sobre diff --git a/renaser/kernel/src/main.rs b/renaser/kernel/src/main.rs index 324f116..b7eb9e3 100644 --- a/renaser/kernel/src/main.rs +++ b/renaser/kernel/src/main.rs @@ -162,6 +162,9 @@ async fn tarea_compositor() { // FASE 15 :: atender la voz del kernel — pasar a la nota siguiente // de la secuencia agendada, o silenciar al acabar. drivers::altavoz::atender(); + // FASE 16 :: avanzar el reloj de la barra de tareas — recompone si el + // segundo cambio respecto al ultimo mostrado. Si no, vuelve enseguida. + compositor::tick_reloj(); // FASE 10 :: atender las altas en vivo. Por cada `Alt+N` pendiente, // dar a luz una aplicacion nueva — el compositor solo conto la // peticion; instanciar el WASM es trabajo del orquestador.