⎇ 2523652e2250835d1521b0c3f3fc8374f6f1ae15
renaser/kernel/src/compositor.rs
Historia
Quién tocó qué
crudo
feat(renaser): Fase 9 — orden-Z y ventanas flotantes
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// EXCLUSION DE INTERRUPCIONES. El `ESCRITORIO` lo tocan SOLO tareas
25// cooperativas (el `tick` de una app, la tarea del compositor): el manejador
26// de IRQ1 jamas lo bloquea. La IRQ se comunica con el mundo cooperativo por
27// un canal estrecho y a prueba de interbloqueos: dos atomicos —el foco y el
28// estado de Alt— y una cola lock-free de mandos. Ningun cerrojo que la IRQ
29// pudiera disputar a una tarea cooperativa.
30// =============================================================================
31
32use core::sync::atomic::{AtomicUsize, Ordering};
33
34use alloc::vec;
35use alloc::vec::Vec;
36
37use crossbeam_queue::ArrayQueue;
38use mirada_layout::{tile, LayoutMode, LayoutParams, Rect};
39use spin::{Mutex, Once};
40
41use crate::consola::{self, Capa, Contenido};
42use crate::grafico::{Color, RegionPantalla};
43
44/// Altura del strip superior reservado a la consola; las apps teselan debajo.
45/// La consola conserva ahi su registro de arranque completo —seis lineas,
46/// hasta la sonda asincrona de disco— legible sobre el teselado.
47const FRANJA_CONSOLA: usize = 296;
48
49/// El modo de teselado con que arranca el escritorio. El teclado lo cicla.
50const MODO_INICIAL: LayoutMode = LayoutMode::MasterStack;
51
52/// Margen entre ventanas teseladas, en pixeles.
53const MARGEN: i32 = 14;
54
55/// Capacidad de la cola de mandos del compositor — holgada: nadie pulsa tanto.
56const CAPACIDAD_MANDOS: usize = 32;
57
58/// Reborde de cromo de una ventana flotante: el panel que rodea su lienzo
59/// natural, donde se asienta el borde de foco sin tapar el dibujo de la app.
60const CROMO_FLOTANTE: usize = 8;
61
62/// Paso de la cascada con que se colocan las ventanas flotantes nuevas, en
63/// pixeles. Cada flotante se desplaza un paso respecto a la anterior, de modo
64/// que varias no se tapen por completo.
65const PASO_CASCADA: usize = 44;
66
67/// Un mando del compositor — lo emite el teclado desde el contexto de IRQ, lo
68/// atiende la tarea del compositor desde el reactor cooperativo.
69#[derive(Clone, Copy)]
70pub enum Mando {
71 /// Ciclar al siguiente modo de teselado de `mirada-layout`.
72 CiclarLayout,
73 /// Mover el foco a la siguiente ventana viva.
74 FocoSiguiente,
75 /// Mover el foco a la ventana viva anterior.
76 FocoAnterior,
77 /// Promover la ventana enfocada a la posicion maestra del teselado.
78 Promover,
79 /// Mover la ventana enfocada una posicion adelante en el orden de teselado.
80 MoverAdelante,
81 /// Mover la ventana enfocada una posicion atras en el orden de teselado.
82 MoverAtras,
83 /// Alternar la ventana enfocada entre teselada y flotante (Fase 9).
84 Flotar,
85}
86
87/// Una ventana del escritorio: una app, su geometria y su ultimo fotograma.
88struct Ventana {
89 /// Tamaño natural del lienzo de la app — lo que sabe pintar, fijo.
90 natural_ancho: usize,
91 natural_alto: usize,
92 /// El marco actual — donde la app vive en pantalla. Si la ventana esta
93 /// teselada, lo fija el teselado; si flota, es un marco propio y libre.
94 marco: RegionPantalla,
95 /// CACHE de respaldo: el ultimo fotograma exitoso que la app envio. Su
96 /// tamaño esta acotado al lienzo natural —`natural_ancho × natural_alto ×
97 /// 4`— y se reserva UNA sola vez, al fundar el escritorio: jamas crece. El
98 /// re-teselado recompone la ventana desde aqui, sin molestar a la app.
99 cache: Vec<u8>,
100 /// ¿Ha enviado la app al menos un fotograma? Hasta entonces, su cache es
101 /// solo ceros y no se recompone.
102 pintada: bool,
103 /// Si el kernel desalojo la app, el color de su baliza. `None` mientras
104 /// vive; `Some(color)` la marca como muerta y la excluye del foco.
105 baliza: Option<Color>,
106}
107
108/// El escritorio: el registro de todas las ventanas y el modo de teselado.
109/// Lo tocan SOLO tareas cooperativas — nunca el manejador de IRQ1.
110struct Escritorio {
111 modo: LayoutMode,
112 ancho: usize,
113 alto: usize,
114 /// Las ventanas, indexadas por `indice_app` — su IDENTIDAD, inmutable.
115 ventanas: Vec<Ventana>,
116 /// El ORDEN de teselado: `orden[slot]` es el `indice_app` de la ventana que
117 /// ocupa esa celda del teselado. Contiene SOLO las ventanas teseladas —las
118 /// flotantes salen de aqui—. Separar el orden de la identidad permite
119 /// promover y reordenar ventanas sin tocar su `indice_app`.
120 orden: Vec<usize>,
121 /// Las ventanas FLOTANTES, en orden-Z (Fase 9): de atras hacia adelante.
122 /// `flotantes.last()` es la ventana frontal. Una ventana esta en `orden` o
123 /// en `flotantes`, jamas en ambos ni en ninguno: juntos son una particion
124 /// de `0..ventanas.len()`.
125 flotantes: Vec<usize>,
126}
127
128/// El escritorio global. Se funda una sola vez, en el arranque.
129static ESCRITORIO: Once<Mutex<Escritorio>> = Once::new();
130
131/// El indice de la ventana ENFOCADA. Atomico —no un campo del `Escritorio`—
132/// porque el manejador de IRQ1 lo LEE para enrutar el teclado, y un atomico no
133/// se puede disputar: jamas hay interbloqueo entre la IRQ y una tarea.
134static FOCO: AtomicUsize = AtomicUsize::new(0);
135
136/// La cola de mandos: el manejador de IRQ1 deposita aqui las ordenes del
137/// teclado (lock-free, segura en contexto de interrupcion); la tarea del
138/// compositor las drena desde el reactor cooperativo.
139static MANDOS: Once<ArrayQueue<Mando>> = Once::new();
140
141// =============================================================================
142// Fundacion y consulta — el arranque
143// =============================================================================
144
145/// Funda el escritorio: crea una ventana por app, con su marco teselado inicial
146/// y su cache de respaldo ya reservada al tamaño natural. `naturales` da el
147/// `(ancho, alto)` del lienzo de cada app, en el orden del manifiesto.
148pub fn fundar(ancho: usize, alto: usize, naturales: &[(usize, usize)]) {
149 MANDOS.call_once(|| ArrayQueue::new(CAPACIDAD_MANDOS));
150
151 let mut ventanas = Vec::with_capacity(naturales.len());
152 for &(nat_ancho, nat_alto) in naturales {
153 ventanas.push(Ventana {
154 natural_ancho: nat_ancho,
155 natural_alto: nat_alto,
156 // Marco provisional; `aplicar_teselado` lo fija enseguida.
157 marco: RegionPantalla {
158 x: 0,
159 y: 0,
160 ancho: 0,
161 alto: 0,
162 },
163 // La cache: reservada UNA vez, acotada al lienzo natural.
164 cache: vec![0u8; nat_ancho.saturating_mul(nat_alto).saturating_mul(4)],
165 pintada: false,
166 baliza: None,
167 });
168 }
169
170 // El orden de teselado arranca como la identidad: la ventana `i` ocupa la
171 // celda `i`. Ninguna ventana flota al nacer — el escritorio es puro
172 // teselado hasta que el teclado lo decida (`Alt+F`).
173 let orden = (0..ventanas.len()).collect();
174 let mut escritorio = Escritorio {
175 modo: MODO_INICIAL,
176 ancho,
177 alto,
178 ventanas,
179 orden,
180 flotantes: Vec::new(),
181 };
182 aplicar_teselado(&mut escritorio);
183
184 ESCRITORIO.call_once(|| Mutex::new(escritorio));
185}
186
187/// Recalcula el teselado y asigna a cada ventana TESELADA su marco. La celda
188/// `slot` del teselado va a la ventana `orden[slot]`: manda el orden, no la
189/// identidad. Las ventanas flotantes no estan en `orden` y conservan su marco.
190fn aplicar_teselado(escritorio: &mut Escritorio) {
191 let marcos = teselar(
192 escritorio.orden.len(),
193 escritorio.ancho,
194 escritorio.alto,
195 escritorio.modo,
196 );
197 for (slot, marco) in marcos.into_iter().enumerate() {
198 let ventana = escritorio.orden[slot];
199 escritorio.ventanas[ventana].marco = marco;
200 }
201}
202
203/// Pinta el escenario inicial del compositor. Se invoca una vez, tras `fundar`,
204/// antes de encender las apps: recompone el escritorio con todas las ventanas
205/// aun sin pintar — el teselado se ve como una rejilla de paneles.
206pub fn componer_escenario() {
207 let Some(escritorio) = ESCRITORIO.get() else {
208 return;
209 };
210 let escritorio = escritorio.lock();
211 recomponer(&escritorio);
212}
213
214/// El indice de la ventana enfocada. Lo LEE el manejador de IRQ1 para enrutar
215/// cada tecla — por eso es una simple lectura atomica, sin cerrojo alguno.
216pub fn foco() -> usize {
217 FOCO.load(Ordering::Relaxed)
218}
219
220/// Encola un mando del teclado. Lo invoca el manejador de IRQ1: empujar a una
221/// cola lock-free es seguro en contexto de interrupcion.
222pub fn solicitar(mando: Mando) {
223 if let Some(mandos) = MANDOS.get() {
224 // Si la cola se desborda, el mando se pierde en silencio: mas vale
225 // perder una pulsacion que arriesgar un panico dentro de una IRQ.
226 let _ = mandos.push(mando);
227 }
228}
229
230// =============================================================================
231// El fotograma de una app — cache y composicion
232// =============================================================================
233
234/// Recibe el fotograma de la app `indice`: lo copia a su CACHE de respaldo —el
235/// kernel asume la persistencia visual— y lo lleva a pantalla. Sin ventanas
236/// flotantes ninguna ventana solapa a otra: basta repintar la que cambio —el
237/// camino RAPIDO—. Con flotantes vivas el solapamiento obliga a RECOMPONER el
238/// escritorio entero, respetando el orden-Z. Lo invoca la capacidad
239/// `sys_render_frame` desde el `tick` cooperativo.
240pub fn presentar_fotograma(indice: usize, datos: &[u8]) {
241 let Some(escritorio) = ESCRITORIO.get() else {
242 return;
243 };
244 let mut escritorio = escritorio.lock();
245 {
246 let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
247 return;
248 };
249 // Cachear el fotograma. El destino esta acotado al lienzo natural; se
250 // copia el minimo de ambas longitudes — jamas se desborda la cache.
251 let n = ventana.cache.len().min(datos.len());
252 ventana.cache[..n].copy_from_slice(&datos[..n]);
253 ventana.pintada = true;
254 }
255
256 if escritorio.flotantes.is_empty() {
257 // Camino RAPIDO: sin flotantes el escritorio es puro teselado y la app
258 // pinta directamente en su marco, como en la Fase 8.
259 let ventana = &escritorio.ventanas[indice];
260 let marco = ventana.marco;
261 let nat_ancho = ventana.natural_ancho;
262 let nat_alto = ventana.natural_alto;
263 let enfocada = FOCO.load(Ordering::Relaxed) == indice;
264 drop(escritorio);
265 consola::volcar_marco(marco, nat_ancho, nat_alto, datos, enfocada);
266 } else {
267 // Hay ventanas flotantes: el solapamiento obliga a recomponer.
268 recomponer(&escritorio);
269 }
270}
271
272/// Marca la ventana `indice` como desalojada y tatua su marco con la baliza.
273/// Desde aqui queda excluida del foco — el teclado la salta.
274pub fn desalojar(indice: usize, color: Color) {
275 let Some(escritorio) = ESCRITORIO.get() else {
276 return;
277 };
278 let mut escritorio = escritorio.lock();
279 {
280 let Some(ventana) = escritorio.ventanas.get_mut(indice) else {
281 return;
282 };
283 ventana.baliza = Some(color);
284 }
285
286 if escritorio.flotantes.is_empty() {
287 let marco = escritorio.ventanas[indice].marco;
288 let enfocada = FOCO.load(Ordering::Relaxed) == indice;
289 drop(escritorio);
290 consola::pintar_desalojo(marco, color, enfocada);
291 } else {
292 recomponer(&escritorio);
293 }
294}
295
296// =============================================================================
297// Los mandos del teclado — el escritorio interactivo
298// =============================================================================
299
300/// Atiende los mandos pendientes del teclado. La invoca la tarea del compositor
301/// en cada fotograma, desde el reactor cooperativo — el unico contexto donde es
302/// seguro bloquear el `ESCRITORIO` y la consola.
303pub fn atender_mandos() {
304 let Some(mandos) = MANDOS.get() else {
305 return;
306 };
307 while let Some(mando) = mandos.pop() {
308 match mando {
309 Mando::CiclarLayout => ciclar_layout(),
310 Mando::FocoSiguiente => mover_foco(true),
311 Mando::FocoAnterior => mover_foco(false),
312 Mando::Promover => promover(),
313 Mando::MoverAdelante => mover_ventana(true),
314 Mando::MoverAtras => mover_ventana(false),
315 Mando::Flotar => flotar(),
316 }
317 }
318}
319
320/// Cicla al siguiente modo de teselado: recalcula los marcos de las ventanas
321/// teseladas y recompone el escritorio entero desde las caches de respaldo.
322fn ciclar_layout() {
323 let Some(escritorio) = ESCRITORIO.get() else {
324 return;
325 };
326 let mut escritorio = escritorio.lock();
327 escritorio.modo = escritorio.modo.next();
328 aplicar_teselado(&mut escritorio);
329 recomponer(&escritorio);
330}
331
332/// Mueve el foco a la siguiente ventana VIVA. El recorrido abarca TODAS las
333/// ventanas —las teseladas y, tras ellas, las flotantes— saltando las
334/// desalojadas. Si la ventana recien enfocada flota, sube al frente del
335/// orden-Z: la flotante con el foco esta SIEMPRE delante.
336fn mover_foco(adelante: bool) {
337 let Some(escritorio) = ESCRITORIO.get() else {
338 return;
339 };
340 let mut escritorio = escritorio.lock();
341 // El recorrido del foco: las teseladas, luego las flotantes — un orden
342 // estable y visualmente coherente.
343 let recorrido: Vec<usize> = escritorio
344 .orden
345 .iter()
346 .chain(escritorio.flotantes.iter())
347 .copied()
348 .collect();
349 let n = recorrido.len();
350 if n == 0 {
351 return;
352 }
353 let anterior = FOCO.load(Ordering::Relaxed);
354 let pos = recorrido.iter().position(|&v| v == anterior).unwrap_or(0);
355
356 // Avanzar saltando las ventanas desalojadas. Si no hay ninguna viva, tras
357 // `n` pasos se vuelve al punto de partida y el foco no cambia.
358 let mut nueva_pos = pos;
359 let mut nuevo = anterior;
360 for _ in 0..n {
361 nueva_pos = if adelante {
362 (nueva_pos + 1) % n
363 } else {
364 (nueva_pos + n - 1) % n
365 };
366 let candidata = recorrido[nueva_pos];
367 if escritorio.ventanas[candidata].baliza.is_none() {
368 nuevo = candidata;
369 break;
370 }
371 }
372 FOCO.store(nuevo, Ordering::Relaxed);
373 // La ventana recien enfocada, si flota, al frente del orden-Z.
374 alzar_si_flota(&mut escritorio, nuevo);
375 recomponer(&escritorio);
376}
377
378/// Promueve la ventana enfocada a la posicion maestra —la celda 0— del
379/// teselado. Si la ventana enfocada flota, no esta en el orden de teselado y
380/// el mando no hace nada — promover es una operacion del teselado.
381fn promover() {
382 let Some(escritorio) = ESCRITORIO.get() else {
383 return;
384 };
385 let mut escritorio = escritorio.lock();
386 let foco = FOCO.load(Ordering::Relaxed);
387 if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
388 let ventana = escritorio.orden.remove(pos);
389 escritorio.orden.insert(0, ventana);
390 aplicar_teselado(&mut escritorio);
391 recomponer(&escritorio);
392 }
393}
394
395/// Mueve la ventana enfocada una posicion en el orden de teselado,
396/// intercambiandola con su vecina. Una ventana flotante no esta en el orden:
397/// el mando no la afecta.
398fn mover_ventana(adelante: bool) {
399 let Some(escritorio) = ESCRITORIO.get() else {
400 return;
401 };
402 let mut escritorio = escritorio.lock();
403 let n = escritorio.orden.len();
404 if n < 2 {
405 return;
406 }
407 let foco = FOCO.load(Ordering::Relaxed);
408 if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
409 let destino = if adelante {
410 (pos + 1) % n
411 } else {
412 (pos + n - 1) % n
413 };
414 escritorio.orden.swap(pos, destino);
415 aplicar_teselado(&mut escritorio);
416 recomponer(&escritorio);
417 }
418}
419
420// =============================================================================
421// FASE 9 — orden-Z y ventanas flotantes
422// =============================================================================
423
424/// Alterna la ventana enfocada entre TESELADA y FLOTANTE. Al flotar, la ventana
425/// abandona el teselado —que se recalcula para las que quedan—, recibe un marco
426/// propio en cascada y sube al frente del orden-Z. Al volver al teselado, se
427/// reincorpora al final del orden. El foco no cambia: viaja con la ventana.
428fn flotar() {
429 let Some(escritorio) = ESCRITORIO.get() else {
430 return;
431 };
432 let mut escritorio = escritorio.lock();
433 let foco = FOCO.load(Ordering::Relaxed);
434
435 if let Some(pos) = escritorio.orden.iter().position(|&v| v == foco) {
436 // Teselada -> flotante: se desliga del teselado, recibe su marco
437 // propio en cascada y sube al frente del orden-Z.
438 escritorio.orden.remove(pos);
439 let marco = marco_flotante(&escritorio, foco);
440 escritorio.ventanas[foco].marco = marco;
441 escritorio.flotantes.push(foco);
442 aplicar_teselado(&mut escritorio);
443 recomponer(&escritorio);
444 } else if let Some(pos) = escritorio.flotantes.iter().position(|&v| v == foco) {
445 // Flotante -> teselada: vuelve a la rejilla, al final del orden.
446 escritorio.flotantes.remove(pos);
447 escritorio.orden.push(foco);
448 aplicar_teselado(&mut escritorio);
449 recomponer(&escritorio);
450 }
451}
452
453/// Si la ventana `indice` es flotante, la lleva al frente del orden-Z —al final
454/// de `flotantes`—. Si esta teselada, no hace nada.
455fn alzar_si_flota(escritorio: &mut Escritorio, indice: usize) {
456 if let Some(pos) = escritorio.flotantes.iter().position(|&v| v == indice) {
457 let ventana = escritorio.flotantes.remove(pos);
458 escritorio.flotantes.push(ventana);
459 }
460}
461
462/// El marco de una ventana recien hecha flotante: su lienzo natural mas un
463/// reborde de cromo, colocado en cascada —para que varias flotantes no se
464/// tapen del todo— y acotado al area de apps. Se invoca ANTES de inscribir la
465/// ventana en `flotantes`: su longitud da el escalon de la cascada.
466fn marco_flotante(escritorio: &Escritorio, indice: usize) -> RegionPantalla {
467 let area = area_apps(escritorio.ancho, escritorio.alto);
468 let ventana = &escritorio.ventanas[indice];
469 let ancho = (ventana.natural_ancho + 2 * CROMO_FLOTANTE).min(area.ancho);
470 let alto = (ventana.natural_alto + 2 * CROMO_FLOTANTE).min(area.alto);
471
472 // La cascada: un escalon por cada flotante ya existente.
473 let escalon = escritorio.flotantes.len().saturating_mul(PASO_CASCADA);
474 let mut x = area.x + 48 + escalon;
475 let mut y = area.y + 40 + escalon;
476 // Acotar: la ventana entera ha de caber dentro del area de apps.
477 if x + ancho > area.x + area.ancho {
478 x = area.x + area.ancho.saturating_sub(ancho);
479 }
480 if y + alto > area.y + area.alto {
481 y = area.y + area.alto.saturating_sub(alto);
482 }
483 RegionPantalla {
484 x,
485 y,
486 ancho,
487 alto,
488 }
489}
490
491/// Recompone el escritorio entero respetando el orden-Z. Arma la lista de capas
492/// —primero las ventanas TESELADAS, la capa de fondo; despues las FLOTANTES, de
493/// atras hacia adelante— y se la entrega a la consola, que las funde en ese
494/// orden de una sola pasada. El solapamiento se resuelve por el orden del
495/// pintado. La invocan los mandos del teclado y `presentar_fotograma` cuando
496/// hay flotantes vivas. El llamante sostiene ya el cerrojo del `ESCRITORIO`.
497fn recomponer(escritorio: &Escritorio) {
498 let area = area_apps(escritorio.ancho, escritorio.alto);
499 let foco = FOCO.load(Ordering::Relaxed);
500 let mut capas: Vec<Capa> = Vec::with_capacity(escritorio.ventanas.len());
501 for &indice in escritorio.orden.iter().chain(escritorio.flotantes.iter()) {
502 let ventana = &escritorio.ventanas[indice];
503 let contenido = match ventana.baliza {
504 Some(color) => Contenido::Baliza(color),
505 None if ventana.pintada => Contenido::Fotograma(&ventana.cache),
506 None => Contenido::Panel,
507 };
508 capas.push(Capa {
509 marco: ventana.marco,
510 nat_ancho: ventana.natural_ancho,
511 nat_alto: ventana.natural_alto,
512 contenido,
513 enfocada: indice == foco,
514 });
515 }
516 consola::recomponer(area, &capas);
517}
518
519// =============================================================================
520// Teselado — la geometria pura de `mirada-layout`
521// =============================================================================
522
523/// El area de pantalla que el compositor tesela: toda la pantalla menos la
524/// franja de la consola en la cima.
525pub fn area_apps(ancho_pantalla: usize, alto_pantalla: usize) -> RegionPantalla {
526 RegionPantalla {
527 x: 0,
528 y: FRANJA_CONSOLA.min(alto_pantalla),
529 ancho: ancho_pantalla,
530 alto: alto_pantalla.saturating_sub(FRANJA_CONSOLA),
531 }
532}
533
534/// Tesela el area de apps en `n` marcos con el modo dado. El vector resultante
535/// tiene exactamente `n` elementos, en el orden de las celdas del teselado.
536fn teselar(n: usize, ancho: usize, alto: usize, modo: LayoutMode) -> Vec<RegionPantalla> {
537 let area = area_apps(ancho, alto);
538 let pantalla = Rect::new(
539 area.x as i32,
540 area.y as i32,
541 area.ancho as i32,
542 area.alto as i32,
543 );
544 let params = LayoutParams {
545 mode: modo,
546 gap: MARGEN,
547 ..LayoutParams::default()
548 };
549 tile(pantalla, n, ¶ms)
550 .into_iter()
551 .map(rect_a_region)
552 .collect()
553}
554
555/// Traduce un `Rect` de `mirada-layout` (`i32`) a la `RegionPantalla` del
556/// kernel (`usize`). Un rectangulo degenerado queda en cero.
557fn rect_a_region(r: Rect) -> RegionPantalla {
558 RegionPantalla {
559 x: r.x.max(0) as usize,
560 y: r.y.max(0) as usize,
561 ancho: r.w.max(0) as usize,
562 alto: r.h.max(0) as usize,
563 }
564}