⎇ 07ab095d4202aa5ff30d9a109e610d517a434e74
renaser/kernel/src/compositor.rs
History
Who touched what
raw
feat(renaser): Fase 19 — voz del userspace hacia la red (pregon)
1// =============================================================================
2// renaser :: kernel/src/compositor.rs — el compositor: teselado y flotantes
3// -----------------------------------------------------------------------------
4// El kernel no coloca las ventanas a mano: las TESELA. El motor es
5// `mirada-layout` —el mismo nucleo `no_std` que ordena el compositor Wayland
6// de brahman—, enlazado por `path` cruzando la frontera de workspace.
7//
8// FASE 8b/8c :: el compositor cobra vida. Mantiene un ESCRITORIO —el registro
9// de todas las ventanas— y, por cada una, una CACHE de respaldo con su ultimo
10// fotograma. Gracias a esa cache, el teclado puede re-teselar el escritorio en
11// caliente —o mover el foco— y el kernel recompone cada ventana en su marco
12// nuevo SIN despertar a las apps: una app que solo pinto en su `init` conserva
13// su imagen intacta a traves de cualquier reordenacion.
14//
15// FASE 9 :: orden-Z y ventanas flotantes. Una ventana puede ABANDONAR el
16// teselado y FLOTAR —un marco propio, libre, que SOLAPA a las demas—. El
17// escritorio separa entonces dos capas: las TESELADAS, al fondo, sin
18// solapamiento entre si; y las FLOTANTES, encima, apiladas por un orden-Z
19// —`flotantes` ES esa pila, de atras hacia adelante; la ultima es la frontal—.
20// Con flotantes vivas el kernel deja de pintar cada ventana por separado:
21// RECOMPONE el escritorio entero, capa a capa, de modo que el solapamiento se
22// resuelva por el orden del pintado, sin recortes ni mascaras.
23//
24// FASE 10 :: el escritorio deja de ser un censo fijo. Una ventana puede
25// CERRARSE en vivo (`Alt+Q`): se la marca, su app concluye su tarea por su
26// voluntad y el teselado reclama su espacio. Y puede NACER una ventana nueva
27// (`Alt+N`): `nacer_ventana` la añade al censo y devuelve su indice al
28// orquestador, que instancia su WASM y engendra su tarea. El censo de
29// ventanas solo crece —los indices son la IDENTIDAD, jamas se reciclan—; una
30// ventana cerrada queda como una ranura inerte, fuera del orden y del foco.
31//
32// FASE 13 :: el raton entra en juego. Hay un PUNTERO en pantalla y el
33// compositor gana dos gestos: clic-para-enfocar (sobre cualquier ventana viva)
34// y ARRASTRAR una flotante con el boton izquierdo sostenido. Como el teclado
35// y la bocina, los eventos del raton vienen del manejador de IRQ12 por una
36// cola lock-free; `atender_raton` los drena cooperativamente y, al detectar
37// un boton que baja o un arrastre en curso, mueve el foco o el marco. Los
38// cuartos flotantes dejan, por fin, de estar clavados en su cascada.
39//
40// EXCLUSION DE INTERRUPCIONES. El `ESCRITORIO` lo tocan SOLO tareas
41// cooperativas (el `tick` de una app, la tarea del compositor): el manejador
42// de IRQ1 jamas lo bloquea. La IRQ se comunica con el mundo cooperativo por
43// un canal estrecho y a prueba de interbloqueos: dos atomicos —el foco y el
44// estado de Alt— y una cola lock-free de mandos. Ningun cerrojo que la IRQ
45// pudiera disputar a una tarea cooperativa.
46// =============================================================================
47
48use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
49
50use alloc::string::{String, ToString};
51use alloc::vec;
52use alloc::vec::Vec;
53
54use crossbeam_queue::ArrayQueue;
55use mirada_layout::{tile, LayoutMode, LayoutParams, Rect};
56use spin::{Mutex, Once};
57
58use crate::consola::{self, Capa, CeldaTaskbar, Contenido, Taskbar};
59use crate::grafico::{Color, RegionPantalla};
60
61/// Altura del strip superior reservado a la consola; las apps teselan debajo.
62/// La consola conserva ahi su registro de arranque completo —seis lineas,
63/// hasta la sonda asincrona de disco— legible sobre el teselado.
64const FRANJA_CONSOLA: usize = 296;
65
66/// Altura de la barra de tareas inferior (Fase 14): cada ventana viva tiene
67/// ahi una pestaña con su nombre, que el clic enfoca.
68const FRANJA_TASKBAR: usize = 40;
69
70/// Anchura de cada celda de la barra de tareas, en pixeles. Dimensionada para
71/// que las nueve apps de genesis (Fase 19 anexa `pregon`) + el lanzador + el
72/// reloj caben holgados en una pantalla de 1280 px.
73const CELDA_TASKBAR_ANCHO: usize = 116;
74/// Hueco entre celdas adyacentes de la barra.
75const CELDA_TASKBAR_HUECO: usize = 6;
76/// Margen izquierdo y derecho de la barra de tareas.
77const CELDA_TASKBAR_MARGEN: usize = 12;
78/// Anchura del boton lanzador («+» a la izquierda de la barra, Fase 16).
79const LAUNCHER_ANCHO: usize = 36;
80/// Hueco entre el lanzador y la primera pestaña.
81const LAUNCHER_HUECO: usize = 8;
82/// Anchura reservada para el reloj a la derecha de la barra (Fase 16).
83const RELOJ_ANCHO: usize = 80;
84/// Hueco entre la ultima pestaña y el reloj.
85const RELOJ_HUECO: usize = 8;
86
87/// El modo de teselado con que arranca el escritorio. El teclado lo cicla.
88const MODO_INICIAL: LayoutMode = LayoutMode::MasterStack;
89
90/// Margen entre ventanas teseladas, en pixeles.
91const MARGEN: i32 = 14;
92
93/// Capacidad de la cola de mandos del compositor — holgada: nadie pulsa tanto.
94const CAPACIDAD_MANDOS: usize = 32;
95
96/// Reborde de cromo de una ventana flotante: el panel que rodea su lienzo
97/// natural, donde se asienta el borde de foco sin tapar el dibujo de la app.
98const CROMO_FLOTANTE: usize = 8;
99
100/// Paso de la cascada con que se colocan las ventanas flotantes nuevas, en
101/// pixeles. Cada flotante se desplaza un paso respecto a la anterior, de modo
102/// que varias no se tapen por completo.
103const PASO_CASCADA: usize = 44;
104
105/// Un mando del compositor — lo emite el teclado desde el contexto de IRQ, lo
106/// atiende la tarea del compositor desde el reactor cooperativo.
107#[derive(Clone, Copy)]
108pub enum Mando {
109 /// Ciclar al siguiente modo de teselado de `mirada-layout`.
110 CiclarLayout,
111 /// Mover el foco a la siguiente ventana viva.
112 FocoSiguiente,
113 /// Mover el foco a la ventana viva anterior.
114 FocoAnterior,
115 /// Promover la ventana enfocada a la posicion maestra del teselado.
116 Promover,
117 /// Mover la ventana enfocada una posicion adelante en el orden de teselado.
118 MoverAdelante,
119 /// Mover la ventana enfocada una posicion atras en el orden de teselado.
120 MoverAtras,
121 /// Alternar la ventana enfocada entre teselada y flotante (Fase 9).
122 Flotar,
123 /// Cerrar la aplicacion enfocada — una baja limpia, en vivo (Fase 10).
124 Cerrar,
125 /// Lanzar una aplicacion nueva — un alta en vivo (Fase 10).
126 Lanzar,
127}
128
129/// Un arrastre EN CURSO (Fase 13): el indice de la ventana flotante asida con
130/// el raton y el desfase con que se asio —para que la ventana no salte al
131/// agarrarla, sino que siga al puntero como si lo llevara cogido por ahi—.
132#[derive(Clone, Copy)]
133struct Arrastre {
134 ventana: usize,
135 agarre_dx: usize,
136 agarre_dy: usize,
137}
138
139/// Una ventana del escritorio: una app, su geometria y su ultimo fotograma.
140struct Ventana {
141 /// Nombre legible de la app — el que dicta su `EntradaApp` del manifiesto.
142 /// Aparece en la pestaña de la barra de tareas (Fase 14).
143 nombre: String,
144 /// Tamaño natural del lienzo de la app — lo que sabe pintar, fijo.
145 natural_ancho: usize,
146 natural_alto: usize,
147 /// El marco actual — donde la app vive en pantalla. Si la ventana esta
148 /// teselada, lo fija el teselado; si flota, es un marco propio y libre.
149 marco: RegionPantalla,
150 /// CACHE de respaldo: el ultimo fotograma exitoso que la app envio. Su
151 /// tamaño esta acotado al lienzo natural —`natural_ancho × natural_alto ×
152 /// 4`— y se reserva UNA sola vez, al fundar el escritorio: jamas crece. El
153 /// re-teselado recompone la ventana desde aqui, sin molestar a la app.
154 cache: Vec<u8>,
155 /// ¿Ha enviado la app al menos un fotograma? Hasta entonces, su cache es
156 /// solo ceros y no se recompone.
157 pintada: bool,
158 /// Si el kernel desalojo la app, el color de su baliza. `None` mientras
159 /// vive; `Some(color)` la marca como muerta y la excluye del foco.
160 baliza: Option<Color>,
161 /// ¿Se ha pedido cerrar esta ventana en vivo (Fase 10)? Una vez `true`, su
162 /// app concluye su tarea, la ranura queda inerte —fuera del orden, del
163 /// orden-Z y del foco— y el teselado reclama su espacio.
164 cerrada: bool,
165}
166
167/// El escritorio: el registro de todas las ventanas y el modo de teselado.
168/// Lo tocan SOLO tareas cooperativas — nunca el manejador de IRQ1.
169struct Escritorio {
170 modo: LayoutMode,
171 ancho: usize,
172 alto: usize,
173 /// Las ventanas, indexadas por `indice_app` — su IDENTIDAD, inmutable.
174 ventanas: Vec<Ventana>,
175 /// El ORDEN de teselado: `orden[slot]` es el `indice_app` de la ventana que
176 /// ocupa esa celda del teselado. Contiene SOLO las ventanas teseladas —las
177 /// flotantes salen de aqui—. Separar el orden de la identidad permite
178 /// promover y reordenar ventanas sin tocar su `indice_app`.
179 orden: Vec<usize>,
180 /// Las ventanas FLOTANTES, en orden-Z (Fase 9): de atras hacia adelante.
181 /// `flotantes.last()` es la ventana frontal. Una ventana esta en `orden` o
182 /// en `flotantes`, jamas en ambos ni en ninguno: juntos son una particion
183 /// de `0..ventanas.len()`.
184 flotantes: Vec<usize>,
185 /// ¿Estaba el boton izquierdo del raton pulsado en el evento anterior?
186 /// Para detectar las transiciones —el momento exacto del clic o de soltar—.
187 raton_izq: bool,
188 /// Arrastre en curso, si lo hay (Fase 13).
189 arrastre: Option<Arrastre>,
190}
191
192/// El escritorio global. Se funda una sola vez, en el arranque.
193static ESCRITORIO: Once<Mutex<Escritorio>> = Once::new();
194
195/// El indice de la ventana ENFOCADA. Atomico —no un campo del `Escritorio`—
196/// porque el manejador de IRQ1 lo LEE para enrutar el teclado, y un atomico no
197/// se puede disputar: jamas hay interbloqueo entre la IRQ y una tarea.
198static FOCO: AtomicUsize = AtomicUsize::new(0);
199
200/// La cola de mandos: el manejador de IRQ1 deposita aqui las ordenes del
201/// teclado (lock-free, segura en contexto de interrupcion); la tarea del
202/// compositor las drena desde el reactor cooperativo.
203static MANDOS: Once<ArrayQueue<Mando>> = Once::new();
204
205/// Cuantos lanzamientos de aplicacion (Fase 10) aguardan. Lo incrementa
206/// `atender_mandos` al recibir un `Mando::Lanzar`; lo drena `partos_pendientes`,
207/// que lo lee el orquestador del kernel —el unico que sabe instanciar un WASM—.
208/// Atomico: el compositor lo escribe, el orquestador lo lee y lo pone a cero.
209static PARTOS: AtomicUsize = AtomicUsize::new(0);
210
211/// El ultimo segundo del reloj monotono que la barra de tareas ha mostrado.
212/// `tick_reloj` lo compara con el actual: si difiere, recompone para pintar el
213/// nuevo. Centinela `u64::MAX` para garantizar que el primer tick fuerza un
214/// repintado y la barra arranca con su reloj a 0:00.
215static ULTIMO_SEGUNDO: AtomicU64 = AtomicU64::new(u64::MAX);
216
217// =============================================================================
218// Fundacion y consulta — el arranque
219// =============================================================================
220
221/// Funda el escritorio: crea una ventana por app, con su marco teselado inicial
222/// y su cache de respaldo ya reservada al tamaño natural. `naturales` da el
223/// `(ancho, alto)` del lienzo de cada app, en el orden del manifiesto.
224pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize, &str)]) {
225 MANDOS.call_once(|| ArrayQueue::new(CAPACIDAD_MANDOS));
226
227 let mut ventanas = Vec::with_capacity(naturales.len());
228 for &(nat_ancho, nat_alto, nombre) in naturales {
229 ventanas.push(Ventana {
230 nombre: nombre.to_string(),
231 natural_ancho: nat_ancho,
232 natural_alto: nat_alto,
233 // Marco provisional; `aplicar_teselado` lo fija enseguida.
234 marco: RegionPantalla {
235 x: 0,
236 y: 0,
237 ancho: 0,
238 alto: 0,
239 },
240 // La cache: reservada UNA vez, acotada al lienzo natural.
241 cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
242 pintada: false,
243 baliza: None,
244 cerrada: false,
245 });
246 }
247
248 // El orden de teselado arranca como la identidad: la ventana `i` ocupa la
249 // celda `i`. Ninguna ventana flota al nacer — el escritorio es puro
250 // teselado hasta que el teclado lo decida (`Alt+F`).
251 let orden = (0..ventanas.len()).collect();
252 let mut escritorio = Escritorio {
253 modo: MODO_INICIAL,
254 ancho,
255 alto,
256 ventanas,
257 orden,
258 flotantes: Vec::new(),
259 raton_izq: false,
260 arrastre: None,
261 };
262 aplicar_teselado(&mut escritorio);
263
264 ESCRITORIO.call_once(|| Mutex::new(escritorio));
265}
266
267/// Recalcula el teselado y asigna a cada ventana TESELADA su marco. La celda
268/// `slot` del teselado va a la ventana `orden[slot]`: manda el orden, no la
269/// identidad. Las ventanas flotantes no estan en `orden` y conservan su marco.
270fn aplicar_teselado(escritorio: &mut Escritorio) {
271 let marcos = teselar(
272 escritorio.orden.len(),
273 escritorio.ancho,
274 escritorio.alto,
275 escritorio.modo,
276 );
277 for (slot, marco) in marcos.into_iter().enumerate() {
278 let ventana = escritorio.orden[slot];
279 escritorio.ventanas[ventana].marco = marco;
280 }
281}
282
283/// Pinta el escenario inicial del compositor. Se invoca una vez, tras `fundar`,
284/// antes de encender las apps: recompone el escritorio con todas las ventanas
285/// aun sin pintar — el teselado se ve como una rejilla de paneles.
286pub fn componer_escenario() {
287 let Some(escritorio) = ESCRITORIO.get() else {
288 return;
289 };
290 let escritorio = escritorio.lock();
291 recomponer(&escritorio);
292}
293
294/// El indice de la ventana enfocada. Lo LEE el manejador de IRQ1 para enrutar
295/// cada tecla — por eso es una simple lectura atomica, sin cerrojo alguno.
296pub fn foco() -> usize {
297 FOCO.load(Ordering::Relaxed)
298}
299
300/// Encola un mando del teclado. Lo invoca el manejador de IRQ1: empujar a una
301/// cola lock-free es seguro en contexto de interrupcion.
302pub fn solicitar(mando: Mando) {
303 if let Some(mandos) = MANDOS.get() {
304 // Si la cola se desborda, el mando se pierde en silencio: mas vale
305 // perder una pulsacion que arriesgar un panico dentro de una IRQ.
306 let _ = mandos.push(mando);
307 }
308}
309
310// =============================================================================
311// El fotograma de una app — cache y composicion
312// =============================================================================
313
314/// Recibe el fotograma de la app `indice`: lo copia a su CACHE de respaldo —el
315/// kernel asume la persistencia visual— y lo lleva a pantalla. Sin ventanas
316/// flotantes ninguna ventana solapa a otra: basta repintar la que cambio —el
317/// camino RAPIDO—. Con flotantes vivas el solapamiento obliga a RECOMPONER el
318/// escritorio entero, respetando el orden-Z. Lo invoca la capacidad
319/// `sys_render_frame` desde el `tick` cooperativo.
320pub fn presentar_fotograma(indice: usize, datos: &[u8]) {
321 let Some(escritorio) = ESCRITORIO.get() else {
322 return;
323 };
324 let mut escritorio = escritorio.lock();
325 {
326 let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
327 return;
328 };
329 // Una ventana cerrada (Fase 10) ya no se pinta: su app pudo emitir un
330 // ultimo fotograma antes de que su tarea advirtiera la baja.
331 if ventana.cerrada {
332 return;
333 }
334 // Cachear el fotograma. El destino esta acotado al lienzo natural; se
335 // copia el minimo de ambas longitudes — jamas se desborda la cache.
336 let n = ventana.cache.len().min(datos.len());
337 ventana.cache[..n].copy_from_slice(&datos[..n]);
338 ventana.pintada = true;
339 }
340
341 if escritorio.flotantes.is_empty() {
342 // Camino RAPIDO: sin flotantes el escritorio es puro teselado y la app
343 // pinta directamente en su marco, como en la Fase 8.
344 let ventana = &escritorio.ventanas[indice];
345 let marco = ventana.marco;
346 let nat_ancho = ventana.natural_ancho;
347 let nat_alto = ventana.natural_alto;
348 let enfocada = FOCO.load(Ordering::Relaxed) == indice;
349 drop(escritorio);
350 consola::volcar_marco(marco, nat_ancho, nat_alto, datos, enfocada);
351 } else {
352 // Hay ventanas flotantes: el solapamiento obliga a recomponer.
353 recomponer(&escritorio);
354 }
355}
356
357/// Marca la ventana `indice` como desalojada y tatua su marco con la baliza.
358/// Desde aqui queda excluida del foco — el teclado la salta.
359pub fn desalojar(indice: usize, color: Color) {
360 let Some(escritorio) = ESCRITORIO.get() else {
361 return;
362 };
363 let mut escritorio = escritorio.lock();
364 {
365 let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
366 return;
367 };
368 // Una ventana ya cerrada (Fase 10) no recibe baliza: la baja limpia
369 // gana a un desalojo que llegue tarde, en la misma vuelta.
370 if ventana.cerrada {
371 return;
372 }
373 ventana.baliza = Some(color);
374 }
375 // Fase 15: la voz del kernel anuncia el desalojo.
376 crate::drivers::altavoz::agendar(&crate::drivers::altavoz::VOZ_DESALOJO);
377
378 if escritorio.flotantes.is_empty() {
379 let marco = escritorio.ventanas[indice].marco;
380 let enfocada = FOCO.load(Ordering::Relaxed) == indice;
381 drop(escritorio);
382 consola::pintar_desalojo(marco, color, enfocada);
383 } else {
384 recomponer(&escritorio);
385 }
386}
387
388// =============================================================================
389// Los mandos del teclado — el escritorio interactivo
390// =============================================================================
391
392/// Atiende los mandos pendientes del teclado. La invoca la tarea del compositor
393/// en cada fotograma, desde el reactor cooperativo — el unico contexto donde es
394/// seguro bloquear el `ESCRITORIO` y la consola.
395pub fn atender_mandos() {
396 let Some(mandos) = MANDOS.get() else {
397 return;
398 };
399 while let Some(mando) = mandos.pop() {
400 match mando {
401 Mando::CiclarLayout => ciclar_layout(),
402 Mando::FocoSiguiente => mover_foco(true),
403 Mando::FocoAnterior => mover_foco(false),
404 Mando::Promover => promover(),
405 Mando::MoverAdelante => mover_ventana(true),
406 Mando::MoverAtras => mover_ventana(false),
407 Mando::Flotar => flotar(),
408 Mando::Cerrar => cerrar(),
409 // El alta de una app necesita instanciar un WASM — algo que el
410 // compositor no sabe hacer—. Solo se cuenta la peticion; el
411 // orquestador del kernel la atendera (ver `partos_pendientes`).
412 Mando::Lanzar => {
413 PARTOS.fetch_add(1, Ordering::Relaxed);
414 }
415 }
416 }
417}
418
419/// Cicla al siguiente modo de teselado: recalcula los marcos de las ventanas
420/// teseladas y recompone el escritorio entero desde las caches de respaldo.
421fn ciclar_layout() {
422 let Some(escritorio) = ESCRITORIO.get() else {
423 return;
424 };
425 let mut escritorio = escritorio.lock();
426 escritorio.modo = escritorio.modo.next();
427 aplicar_teselado(&mut escritorio);
428 recomponer(&escritorio);
429}
430
431/// Mueve el foco a la siguiente ventana VIVA. El recorrido abarca TODAS las
432/// ventanas —las teseladas y, tras ellas, las flotantes— saltando las
433/// desalojadas. Si la ventana recien enfocada flota, sube al frente del
434/// orden-Z: la flotante con el foco esta SIEMPRE delante.
435fn mover_foco(adelante: bool) {
436 let Some(escritorio) = ESCRITORIO.get() else {
437 return;
438 };
439 let mut escritorio = escritorio.lock();
440 // El recorrido del foco: las teseladas, luego las flotantes — un orden
441 // estable y visualmente coherente.
442 let recorrido: Vec<usize> = escritorio
443 .orden
444 .iter()
445 .chain(escritorio.flotantes.iter())
446 .copied()
447 .collect();
448 let n = recorrido.len();
449 if n == 0 {
450 return;
451 }
452 let anterior = FOCO.load(Ordering::Relaxed);
453 let pos = recorrido.iter().position(|&v| v == anterior).unwrap_or(0);
454
455 // Avanzar saltando las ventanas desalojadas. Si no hay ninguna viva, tras
456 // `n` pasos se vuelve al punto de partida y el foco no cambia.
457 let mut nueva_pos = pos;
458 let mut nuevo = anterior;
459 for _ in 0..n {
460 nueva_pos = if adelante {
461 (nueva_pos + 1) % n
462 } else {
463 (nueva_pos + n - 1) % n
464 };
465 let candidata = recorrido[nueva_pos];
466 if escritorio.ventanas[candidata].baliza.is_none() {
467 nuevo = candidata;
468 break;
469 }
470 }
471 FOCO.store(nuevo, Ordering::Relaxed);
472 // La bocina pertenece a la ventana enfocada (Fase 12): al cambiar el foco,
473 // callar — la nueva dueña la reclamara en su proximo fotograma si quiere.
474 crate::drivers::altavoz::tono(0);
475 // La ventana recien enfocada, si flota, al frente del orden-Z.
476 alzar_si_flota(&mut escritorio, nuevo);
477 recomponer(&escritorio);
478}
479
480/// Promueve la ventana enfocada a la posicion maestra —la celda 0— del
481/// teselado. Si la ventana enfocada flota, no esta en el orden de teselado y
482/// el mando no hace nada — promover es una operacion del teselado.
483fn promover() {
484 let Some(escritorio) = ESCRITORIO.get() else {
485 return;
486 };
487 let mut escritorio = escritorio.lock();
488 let foco = FOCO.load(Ordering::Relaxed);
489 if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
490 let ventana = escritorio.orden.remove(pos);
491 escritorio.orden.insert(0, ventana);
492 aplicar_teselado(&mut escritorio);
493 recomponer(&escritorio);
494 }
495}
496
497/// Mueve la ventana enfocada una posicion en el orden de teselado,
498/// intercambiandola con su vecina. Una ventana flotante no esta en el orden:
499/// el mando no la afecta.
500fn mover_ventana(adelante: bool) {
501 let Some(escritorio) = ESCRITORIO.get() else {
502 return;
503 };
504 let mut escritorio = escritorio.lock();
505 let n = escritorio.orden.len();
506 if n < 2 {
507 return;
508 }
509 let foco = FOCO.load(Ordering::Relaxed);
510 if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
511 let destino = if adelante {
512 (pos + 1) % n
513 } else {
514 (pos + n - 1) % n
515 };
516 escritorio.orden.swap(pos, destino);
517 aplicar_teselado(&mut escritorio);
518 recomponer(&escritorio);
519 }
520}
521
522// =============================================================================
523// FASE 9 — orden-Z y ventanas flotantes
524// =============================================================================
525
526/// Alterna la ventana enfocada entre TESELADA y FLOTANTE. Al flotar, la ventana
527/// abandona el teselado —que se recalcula para las que quedan—, recibe un marco
528/// propio en cascada y sube al frente del orden-Z. Al volver al teselado, se
529/// reincorpora al final del orden. El foco no cambia: viaja con la ventana.
530fn flotar() {
531 let Some(escritorio) = ESCRITORIO.get() else {
532 return;
533 };
534 let mut escritorio = escritorio.lock();
535 let foco = FOCO.load(Ordering::Relaxed);
536
537 if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
538 // Teselada -> flotante: se desliga del teselado, recibe su marco
539 // propio en cascada y sube al frente del orden-Z.
540 escritorio.orden.remove(pos);
541 let marco = marco_flotante(&escritorio, foco);
542 escritorio.ventanas[foco].marco = marco;
543 escritorio.flotantes.push(foco);
544 aplicar_teselado(&mut escritorio);
545 recomponer(&escritorio);
546 } else if let Some(pos) = escritorio.flotantes.iter().position(|&v| v == foco) {
547 // Flotante -> teselada: vuelve a la rejilla, al final del orden.
548 escritorio.flotantes.remove(pos);
549 escritorio.orden.push(foco);
550 aplicar_teselado(&mut escritorio);
551 recomponer(&escritorio);
552 }
553}
554
555/// Si la ventana `indice` es flotante, la lleva al frente del orden-Z —al final
556/// de `flotantes`—. Si esta teselada, no hace nada.
557fn alzar_si_flota(escritorio: &mut Escritorio, indice: usize) {
558 if let Some(pos) = escritorio.flotantes.iter().position(|&v| v == indice) {
559 let ventana = escritorio.flotantes.remove(pos);
560 escritorio.flotantes.push(ventana);
561 }
562}
563
564/// El marco de una ventana recien hecha flotante: su lienzo natural mas un
565/// reborde de cromo, colocado en cascada —para que varias flotantes no se
566/// tapen del todo— y acotado al area de apps. Se invoca ANTES de inscribir la
567/// ventana en `flotantes`: su longitud da el escalon de la cascada.
568fn marco_flotante(escritorio: &Escritorio, indice: usize) -> RegionPantalla {
569 let area = area_apps(escritorio.ancho, escritorio.alto);
570 let ventana = &escritorio.ventanas[indice];
571 let ancho = (ventana.natural_ancho + 2 * CROMO_FLOTANTE).min(area.ancho);
572 let alto = (ventana.natural_alto + 2 * CROMO_FLOTANTE).min(area.alto);
573
574 // La cascada: un escalon por cada flotante ya existente.
575 let escalon = escritorio.flotantes.len().saturating_mul(PASO_CASCADA);
576 let mut x = area.x + 48 + escalon;
577 let mut y = area.y + 40 + escalon;
578 // Acotar: la ventana entera ha de caber dentro del area de apps.
579 if x + ancho > area.x + area.ancho {
580 x = area.x + area.ancho.saturating_sub(ancho);
581 }
582 if y + alto > area.y + area.alto {
583 y = area.y + area.alto.saturating_sub(alto);
584 }
585 RegionPantalla {
586 x,
587 y,
588 ancho,
589 alto,
590 }
591}
592
593/// Recompone el escritorio entero respetando el orden-Z. Arma la lista de capas
594/// —primero las ventanas TESELADAS, la capa de fondo; despues las FLOTANTES, de
595/// atras hacia adelante— y se la entrega a la consola, que las funde en ese
596/// orden de una sola pasada. El solapamiento se resuelve por el orden del
597/// pintado. La invocan los mandos del teclado y `presentar_fotograma` cuando
598/// hay flotantes vivas. El llamante sostiene ya el cerrojo del `ESCRITORIO`.
599fn recomponer(escritorio: &Escritorio) {
600 let area = area_apps(escritorio.ancho, escritorio.alto);
601 let foco = FOCO.load(Ordering::Relaxed);
602 let mut capas: Vec<Capa> = Vec::with_capacity(escritorio.ventanas.len());
603 for &indice in escritorio.orden.iter().chain(escritorio.flotantes.iter()) {
604 let ventana = &escritorio.ventanas[indice];
605 let contenido = match ventana.baliza {
606 Some(color) => Contenido::Baliza(color),
607 None if ventana.pintada => Contenido::Fotograma(&ventana.cache),
608 None => Contenido::Panel,
609 };
610 capas.push(Capa {
611 marco: ventana.marco,
612 nat_ancho: ventana.natural_ancho,
613 nat_alto: ventana.natural_alto,
614 contenido,
615 enfocada: indice == foco,
616 });
617 }
618
619 // FASE 14/16 :: armar la barra de tareas. A la izquierda un boton lanzador
620 // («+»); en el medio una pestaña por ventana viva; a la derecha el reloj.
621 let area_bar = area_taskbar(escritorio.ancho, escritorio.alto);
622 let cy = area_bar.y + 4;
623 let calto = area_bar.alto.saturating_sub(8);
624 let launcher = RegionPantalla {
625 x: area_bar.x + CELDA_TASKBAR_MARGEN,
626 y: cy,
627 ancho: LAUNCHER_ANCHO,
628 alto: calto,
629 };
630 let cells_x0 = launcher.x + launcher.ancho + LAUNCHER_HUECO;
631 let cells_x_max =
632 area_bar.x + area_bar.ancho - CELDA_TASKBAR_MARGEN - RELOJ_ANCHO - RELOJ_HUECO;
633 let mut celdas: Vec<CeldaTaskbar> = Vec::new();
634 let mut cx = cells_x0;
635 for (indice, ventana) in escritorio.ventanas.iter().enumerate() {
636 if ventana.cerrada {
637 continue;
638 }
639 if cx + CELDA_TASKBAR_ANCHO > cells_x_max {
640 break;
641 }
642 let fondo = match ventana.baliza {
643 Some(color) => color,
644 None if indice == foco => Color::FOCO,
645 None => Color::PANEL,
646 };
647 celdas.push(CeldaTaskbar {
648 region: RegionPantalla {
649 x: cx,
650 y: cy,
651 ancho: CELDA_TASKBAR_ANCHO,
652 alto: calto,
653 },
654 nombre: &ventana.nombre,
655 fondo,
656 tinta: tinta_para(fondo),
657 });
658 cx += CELDA_TASKBAR_ANCHO + CELDA_TASKBAR_HUECO;
659 }
660 // El reloj: minutos:segundos desde el arranque, alineado a la derecha.
661 let ms = crate::async_system::reloj::milisegundos();
662 let segs = ms / 1000;
663 let reloj_texto = alloc::format!("{}:{:02}", segs / 60, segs % 60);
664 let reloj_region = RegionPantalla {
665 x: area_bar.x + area_bar.ancho - CELDA_TASKBAR_MARGEN - RELOJ_ANCHO,
666 y: cy,
667 ancho: RELOJ_ANCHO,
668 alto: calto,
669 };
670 let taskbar = Taskbar {
671 area: area_bar,
672 launcher,
673 celdas: &celdas,
674 reloj: &reloj_texto,
675 reloj_region,
676 };
677 consola::recomponer(area, &capas, &taskbar);
678 // Recordar el segundo recien mostrado: `tick_reloj` evita repintar de mas
679 // mientras dure este mismo segundo.
680 ULTIMO_SEGUNDO.store(segs, Ordering::Relaxed);
681}
682
683/// Localiza la celda de la barra de tareas bajo la coordenada x: itera las
684/// ventanas vivas en orden de creacion y devuelve la N-esima donde la N es la
685/// posicion en la barra. `None` si el clic cae en el lanzador, en el reloj, en
686/// un hueco entre celdas, o fuera del rango de las pestañas.
687fn celda_taskbar_en(escritorio: &Escritorio, x: usize) -> Option<usize> {
688 let area_bar = area_taskbar(escritorio.ancho, escritorio.alto);
689 // Las pestañas empiezan despues del lanzador.
690 let cells_x0 = area_bar.x + CELDA_TASKBAR_MARGEN + LAUNCHER_ANCHO + LAUNCHER_HUECO;
691 let cells_x_max =
692 area_bar.x + area_bar.ancho - CELDA_TASKBAR_MARGEN - RELOJ_ANCHO - RELOJ_HUECO;
693 if x < cells_x0 || x >= cells_x_max {
694 return None;
695 }
696 let rel = x - cells_x0;
697 let paso = CELDA_TASKBAR_ANCHO + CELDA_TASKBAR_HUECO;
698 let posicion = rel / paso;
699 let offset = rel % paso;
700 if offset >= CELDA_TASKBAR_ANCHO {
701 return None;
702 }
703 let mut k = 0;
704 for (indice, ventana) in escritorio.ventanas.iter().enumerate() {
705 if ventana.cerrada {
706 continue;
707 }
708 if k == posicion {
709 return Some(indice);
710 }
711 k += 1;
712 }
713 None
714}
715
716/// ¿Cae la coordenada x en el boton lanzador («+»)?
717fn clic_en_launcher(escritorio: &Escritorio, x: usize) -> bool {
718 let area_bar = area_taskbar(escritorio.ancho, escritorio.alto);
719 let x0 = area_bar.x + CELDA_TASKBAR_MARGEN;
720 let x1 = x0 + LAUNCHER_ANCHO;
721 x >= x0 && x < x1
722}
723
724// =============================================================================
725// FASE 10 — alta y baja de aplicaciones en vivo
726// =============================================================================
727
728/// Cierra la aplicacion enfocada (`Alt+Q`): una baja LIMPIA, distinta del
729/// desalojo por falla. Marca la ventana como cerrada, libera su cache de
730/// respaldo, la saca del teselado y del orden-Z, y traslada el foco a una
731/// ventana viva contigua. La app, en su tarea, advertira la baja y concluira.
732fn cerrar() {
733 let Some(escritorio) = ESCRITORIO.get() else {
734 return;
735 };
736 let mut escritorio = escritorio.lock();
737 let foco = FOCO.load(Ordering::Relaxed);
738 // Solo se cierra una ventana viva. El foco jamas se posa en una muerta o
739 // cerrada, pero la guarda lo deja explicito.
740 match escritorio.ventanas.get(foco) {
741 Some(v) if v.baliza.is_none() && !v.cerrada => {}
742 _ => return,
743 }
744 // Fase 15: el kernel se despide de la app con un repique descendente.
745 crate::drivers::altavoz::agendar(&crate::drivers::altavoz::VOZ_CERRAR);
746 // Marcar la baja y liberar el respaldo: la cache de un fotograma puede
747 // pesar un megabyte — no tiene sentido retenerla en una ranura inerte.
748 let ventana = &mut escritorio.ventanas[foco];
749 ventana.cerrada = true;
750 ventana.pintada = false;
751 ventana.cache = Vec::new();
752 // Sacarla del teselado y del orden-Z. El censo conserva la ranura —los
753 // indices son la identidad, jamas se reciclan—, pero ya nadie la dibuja.
754 escritorio.orden.retain(|&v| v != foco);
755 escritorio.flotantes.retain(|&v| v != foco);
756 // Si la estabamos arrastrando con el raton (Fase 13), soltarla.
757 if escritorio.arrastre.map(|a| a.ventana) == Some(foco) {
758 escritorio.arrastre = None;
759 }
760 // El foco salta a la primera ventana viva que quede; si no queda ninguna,
761 // se queda donde estaba —inofensivo: no hay a quien enrutar el teclado—.
762 let nuevo = escritorio
763 .orden
764 .iter()
765 .chain(escritorio.flotantes.iter())
766 .copied()
767 .find(|&v| {
768 let w = &escritorio.ventanas[v];
769 w.baliza.is_none() && !w.cerrada
770 })
771 .unwrap_or(foco);
772 FOCO.store(nuevo, Ordering::Relaxed);
773 // El foco cambia: callar la bocina (Fase 12) — ver `mover_foco`.
774 crate::drivers::altavoz::tono(0);
775 alzar_si_flota(&mut escritorio, nuevo);
776 aplicar_teselado(&mut escritorio);
777 recomponer(&escritorio);
778}
779
780/// Da de alta una ventana NUEVA y devuelve su indice —su identidad—. La crea
781/// con su cache de respaldo al tamaño natural, la añade al final del orden de
782/// teselado, recalcula el teselado y recompone. La invoca el orquestador del
783/// kernel justo antes de instanciar el WASM de la app, que necesita ese indice.
784pub fn nacer_ventana(nat_ancho: usize, nat_alto: usize, nombre: &str) -> usize {
785 let Some(escritorio) = ESCRITORIO.get() else {
786 return 0;
787 };
788 let mut escritorio = escritorio.lock();
789 let indice = escritorio.ventanas.len();
790 escritorio.ventanas.push(Ventana {
791 nombre: nombre.to_string(),
792 natural_ancho: nat_ancho,
793 natural_alto: nat_alto,
794 marco: RegionPantalla {
795 x: 0,
796 y: 0,
797 ancho: 0,
798 alto: 0,
799 },
800 cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
801 pintada: false,
802 baliza: None,
803 cerrada: false,
804 });
805 escritorio.orden.push(indice);
806 aplicar_teselado(&mut escritorio);
807 recomponer(&escritorio);
808 // Fase 15: el kernel saluda al nacimiento con un repique ascendente.
809 crate::drivers::altavoz::agendar(&crate::drivers::altavoz::VOZ_LANZAR);
810 indice
811}
812
813/// ¿Se ha pedido cerrar la ventana `indice`? Cada app la consulta en su tarea,
814/// fotograma a fotograma: cuando es `true`, concluye su tarea y se libera. Una
815/// ventana inexistente cuenta como cerrada.
816pub fn ventana_cerrada(indice: usize) -> bool {
817 let Some(escritorio) = ESCRITORIO.get() else {
818 return false;
819 };
820 escritorio
821 .lock()
822 .ventanas
823 .get(indice)
824 .map(|ventana| ventana.cerrada)
825 .unwrap_or(true)
826}
827
828/// Cuantas aplicaciones nuevas se han pedido lanzar desde la ultima consulta —y
829/// pone el contador a cero—. La invoca el orquestador del kernel —el unico que
830/// sabe instanciar un WASM— en cada fotograma de la tarea del compositor.
831pub fn partos_pendientes() -> usize {
832 PARTOS.swap(0, Ordering::Relaxed)
833}
834
835/// Avanza el reloj de la barra de tareas (Fase 16): si el segundo del reloj
836/// monotono cambio respecto al ultimo mostrado, recompone para refrescar la
837/// pantalla. Si el segundo es el mismo, vuelve sin hacer nada — un fotograma
838/// barato—. La invoca la tarea del compositor cada fotograma.
839pub fn tick_reloj() {
840 let actual = crate::async_system::reloj::milisegundos() / 1000;
841 if ULTIMO_SEGUNDO.load(Ordering::Relaxed) == actual {
842 return;
843 }
844 let Some(escritorio) = ESCRITORIO.get() else {
845 return;
846 };
847 let escritorio = escritorio.lock();
848 recomponer(&escritorio);
849}
850
851// =============================================================================
852// FASE 13 — raton, puntero y arrastre de ventanas flotantes
853// =============================================================================
854
855/// La ultima posicion del puntero que el compositor REFRESCO. Si la posicion
856/// actual del raton coincide con esta, no hay nada nuevo que estampar; si
857/// difiere, la consola debe volver a presentar. Empacada como `y * 65536 + x`,
858/// con `usize::MAX` como centinela de «aun no he visto al raton».
859static PUNTERO_REFRESCADO: AtomicUsize = AtomicUsize::new(usize::MAX);
860
861/// Drena los eventos del raton y los aplica: clic enfoca la ventana bajo el
862/// puntero (y, si flota, inicia un arrastre); el boton sostenido la arrastra;
863/// soltarlo termina el gesto. La invoca la tarea del compositor en cada
864/// fotograma, desde el reactor cooperativo.
865pub fn atender_raton() {
866 let Some(escritorio) = ESCRITORIO.get() else {
867 return;
868 };
869 let mut escritorio = escritorio.lock();
870 let mut cambio = false;
871 while let Some(evento) = crate::drivers::raton::siguiente_evento() {
872 let izq = evento.botones & 0b001 != 0;
873 let x = evento.x as usize;
874 let y = evento.y as usize;
875 let izq_antes = escritorio.raton_izq;
876 if izq && !izq_antes {
877 // Boton bajó: un CLIC. Si cae en la barra de tareas, enfocar la
878 // pestaña pulsada SIN iniciar arrastre. Si no, comportamiento
879 // habitual: enfocar la ventana topmost bajo el puntero.
880 let area_bar = area_taskbar(escritorio.ancho, escritorio.alto);
881 if y >= area_bar.y && y < area_bar.y + area_bar.alto {
882 if clic_en_launcher(&escritorio, x) {
883 // El boton «+» equivale a `Alt+N`: solicita un parto. La
884 // tarea del compositor lo recogera en su proxima vuelta.
885 PARTOS.fetch_add(1, Ordering::Relaxed);
886 } else if let Some(v) = celda_taskbar_en(&escritorio, x) {
887 let viva = {
888 let w = &escritorio.ventanas[v];
889 w.baliza.is_none() && !w.cerrada
890 };
891 if viva {
892 FOCO.store(v, Ordering::Relaxed);
893 crate::drivers::altavoz::tono(0);
894 alzar_si_flota(&mut escritorio, v);
895 cambio = true;
896 }
897 }
898 } else if let Some(v) = ventana_en(&escritorio, x, y) {
899 let viva = {
900 let w = &escritorio.ventanas[v];
901 w.baliza.is_none() && !w.cerrada
902 };
903 if viva {
904 // Enfocar como hace `mover_foco`: foco + bocina muda + alza
905 // si flota.
906 FOCO.store(v, Ordering::Relaxed);
907 crate::drivers::altavoz::tono(0);
908 alzar_si_flota(&mut escritorio, v);
909 // Si la ventana flota, empezar a arrastrarla.
910 if escritorio.flotantes.contains(&v) {
911 let marco = escritorio.ventanas[v].marco;
912 escritorio.arrastre = Some(Arrastre {
913 ventana: v,
914 agarre_dx: x.saturating_sub(marco.x),
915 agarre_dy: y.saturating_sub(marco.y),
916 });
917 }
918 cambio = true;
919 }
920 }
921 } else if izq && izq_antes {
922 // Boton sostenido: arrastrar la ventana asida, si la hay.
923 if let Some(arr) = escritorio.arrastre {
924 mover_arrastrada(&mut escritorio, arr, x, y);
925 cambio = true;
926 }
927 } else if !izq && izq_antes {
928 // Boton subió: fin del arrastre.
929 escritorio.arrastre = None;
930 }
931 escritorio.raton_izq = izq;
932 }
933 if cambio {
934 recomponer(&escritorio);
935 // El recomponer ya presento; sincronizar el centinela para no presentar
936 // dos veces en la misma vuelta.
937 PUNTERO_REFRESCADO.store(empacar_puntero(), Ordering::Relaxed);
938 }
939}
940
941/// La ventana topmost que contiene el punto (x, y), si la hay. Recorre el
942/// orden-Z de delante hacia atras: primero las flotantes (la ultima es la
943/// frontal), despues las teseladas.
944fn ventana_en(escritorio: &Escritorio, x: usize, y: usize) -> Option<usize> {
945 for &v in escritorio.flotantes.iter().rev() {
946 if contiene(escritorio.ventanas[v].marco, x, y) {
947 return Some(v);
948 }
949 }
950 for &v in escritorio.orden.iter().rev() {
951 if contiene(escritorio.ventanas[v].marco, x, y) {
952 return Some(v);
953 }
954 }
955 None
956}
957
958/// ¿Contiene el marco al punto (x, y)?
959fn contiene(marco: RegionPantalla, x: usize, y: usize) -> bool {
960 x >= marco.x && x < marco.x + marco.ancho && y >= marco.y && y < marco.y + marco.alto
961}
962
963/// Mueve la ventana arrastrada de modo que el punto del puntero —la asa— siga
964/// estando, dentro de la ventana, donde se asio. La ventana queda acotada al
965/// area de apps.
966fn mover_arrastrada(escritorio: &mut Escritorio, arr: Arrastre, x: usize, y: usize) {
967 let area = area_apps(escritorio.ancho, escritorio.alto);
968 let Some(ventana) = escritorio.ventanas.get_mut(arr.ventana) else {
969 return;
970 };
971 let ancho = ventana.marco.ancho;
972 let alto = ventana.marco.alto;
973 let mut nx = x.saturating_sub(arr.agarre_dx);
974 let mut ny = y.saturating_sub(arr.agarre_dy);
975 // Acotar al area de apps: la ventana entera ha de caber dentro.
976 if nx + ancho > area.x + area.ancho {
977 nx = (area.x + area.ancho).saturating_sub(ancho);
978 }
979 if ny + alto > area.y + area.alto {
980 ny = (area.y + area.alto).saturating_sub(alto);
981 }
982 nx = nx.max(area.x);
983 ny = ny.max(area.y);
984 ventana.marco.x = nx;
985 ventana.marco.y = ny;
986}
987
988/// Empaca la posicion actual del puntero en un solo `usize` —`y * 65536 + x`—
989/// para compararla atomicamente con la ultima refrescada. `usize::MAX` indica
990/// «el raton no esta vivo».
991fn empacar_puntero() -> usize {
992 match crate::drivers::raton::posicion() {
993 Some((x, y)) => (y << 16) | (x & 0xFFFF),
994 None => usize::MAX,
995 }
996}
997
998/// Si el puntero se ha movido desde la ultima presentacion del compositor, le
999/// pide a la consola un volcado fresco —para reestampar el puntero en su
1000/// nuevo lugar—. La invoca la tarea del compositor cada fotograma.
1001pub fn refrescar_puntero() {
1002 let actual = empacar_puntero();
1003 if actual == usize::MAX {
1004 return;
1005 }
1006 if PUNTERO_REFRESCADO.swap(actual, Ordering::Relaxed) != actual {
1007 crate::consola::refrescar();
1008 }
1009}
1010
1011// =============================================================================
1012// Teselado — la geometria pura de `mirada-layout`
1013// =============================================================================
1014
1015/// El area de pantalla que el compositor tesela: toda la pantalla menos la
1016/// franja de la consola en la cima y la barra de tareas al pie.
1017pub fn area_apps(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla {
1018 let cabeza = FRANJA_CONSOLA.min(alto_pantalla);
1019 let pie = FRANJA_TASKBAR.min(alto_pantalla.saturating_sub(cabeza));
1020 RegionPantalla {
1021 x: 0,
1022 y: cabeza,
1023 ancho: ancho_pantalla,
1024 alto: alto_pantalla.saturating_sub(cabeza).saturating_sub(pie),
1025 }
1026}
1027
1028/// El area de la barra de tareas: una franja al pie de la pantalla.
1029fn area_taskbar(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla {
1030 let pie = FRANJA_TASKBAR.min(alto_pantalla);
1031 RegionPantalla {
1032 x: 0,
1033 y: alto_pantalla.saturating_sub(pie),
1034 ancho: ancho_pantalla,
1035 alto: pie,
1036 }
1037}
1038
1039/// El color de tinta —oscuro o claro— que da contraste legible sobre `fondo`.
1040/// Sin esto, la pestaña amarilla palida del desalojo por memoria quedaba con
1041/// texto blanco sobre crema: ilegible. La regla de luminancia ITU-R BT.601 fija
1042/// el umbral: fondos claros llevan tinta oscura, fondos oscuros la clara.
1043fn tinta_para(fondo: Color) -> Color {
1044 let brillo =
1045 (fondo.r as u32 * 299 + fondo.g as u32 * 587 + fondo.b as u32 * 114) / 1000;
1046 if brillo > 160 {
1047 // Fondo claro: tinta del reposo del lienzo, casi negra.
1048 Color::LIENZO_EN_REPOSO
1049 } else {
1050 Color::TEXTO
1051 }
1052}
1053
1054/// Tesela el area de apps en `n` marcos con el modo dado. El vector resultante
1055/// tiene exactamente `n` elementos, en el orden de las celdas del teselado.
1056fn teselar(n: usize, ancho: usize, alto: usize, modo: LayoutMode) -> Vec<RegionPantalla> {
1057 let area = area_apps(ancho, alto);
1058 let pantalla = Rect::new(
1059 area.x as i32,
1060 area.y as i32,
1061 area.ancho as i32,
1062 area.alto as i32,
1063 );
1064 let params = LayoutParams {
1065 mode: modo,
1066 gap: MARGEN,
1067 ..LayoutParams::default()
1068 };
1069 tile(pantalla, n, ¶ms)
1070 .into_iter()
1071 .map(rect_a_region)
1072 .collect()
1073}
1074
1075/// Traduce un `Rect` de `mirada-layout` (`i32`) a la `RegionPantalla` del
1076/// kernel (`usize`). Un rectangulo degenerado queda en cero.
1077fn rect_a_region(r: Rect) -> RegionPantalla {
1078 RegionPantalla {
1079 x: r.x.max(0) as usize,
1080 y: r.y.max(0) as usize,
1081 ancho: r.w.max(0) as usize,
1082 alto: r.h.max(0) as usize,
1083 }
1084}