feat(renaser): Fase 16 — la barra viva: botón lanzador + reloj

La barra de tareas era pasiva: nombraba pero no hacía. Ahora lleva
en sus extremos los dos adornos de toda barra digna.

- Botón «+» indigo a la izquierda (36 px). Un clic incrementa
  `PARTOS` — el mismo contador que `Alt+N` — y la tarea del
  compositor lo recoge para lanzar la siguiente app de la rotación.
  Teclado y ratón comparten ya la misma vía para crear ventanas.
- Reloj `mm:ss` a la derecha (80 px), leído de
  `reloj::milisegundos()`. Tinta blanca sobre slate.
- `compositor::tick_reloj()` lo invoca la tarea del compositor cada
  fotograma; recompone solo cuando el segundo del reloj monótono
  cambia respecto al último mostrado (`ULTIMO_SEGUNDO: AtomicU64`).
  Cero coste mientras no toca refrescar.
- `Taskbar` crece con `launcher`, `reloj` y `reloj_region`; el
  layout de las pestañas se ajusta entre ambas cuñas. La cruz del
  lanzador se dibuja en píxeles directos —dos rectángulos cruzados,
  independiente de la tipografía—.

Verificado en QEMU con dos capturas separadas: la barra muestra el
«+» indigo, las siete pestañas (con `glotona` ya legible) y el
reloj. En la primera marca `0:17`; diez segundos después, `0:29` —
la barra se refrescó doce veces sin intervención.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-23 03:36:46 +00:00
parent 0422780dd9
commit 71ebcea899
7 changed files with 208 additions and 27 deletions
+85 -18
View File
@@ -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<ArrayQueue<Mando>> = 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<CeldaTaskbar> = 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<CeldaTaskbar> = 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<usize> {
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<usize> {
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
+48 -7
View File
@@ -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
+3
View File
@@ -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.