gioser

tawasuyu/brahman

Rust 334TOML 129Markdown 39JSON 174745 Definitions

⎇ 2523652e2250835d1521b0c3f3fc8374f6f1ae15 renaser/kernel/src/consola.rs Rust · 340 lines · 13.7 KB History Who touched what raw

feat(renaser): Fase 9 — orden-Z y ventanas flotantes 2523652e2 · sergio ·

1// =============================================================================
2//  renaser :: kernel/src/consola.rs — la superficie de texto e imagen
3// -----------------------------------------------------------------------------
4//  La consola une el lienzo intermedio, la pantalla fisica y una pluma de
5//  escritura. Rasteriza cada glifo con `fontdue` al vuelo —el texto convertido,
6//  por fin, en dibujo— y tambien sabe volcar fotogramas crudos del userspace
7//  WASM. Es global y serializada tras un `Mutex`: las tareas escriben en ella.
8// =============================================================================
9
10use spin::{Mutex, Once};
11
12use crate::grafico::{codificar, Color, Lienzo, Pantalla, RegionPantalla};
13use crate::texto;
14
15/// Margen del texto respecto al borde del lienzo, en pixeles.
16const MARGEN: usize = 40;
17/// Tamaño de la tipografia, en pixeles.
18const TAM_FUENTE: f32 = 30.0;
19/// Altura de avance de una linea de texto, en pixeles.
20const ALTO_LINEA: usize = 40;
21/// Posicion vertical de la primera linea base.
22const BASE_INICIAL: usize = MARGEN + 30;
23
24/// Interpola dos colores segun una cobertura: `0` => `fondo`, `255` => `tinta`.
25fn mezclar(fondo: Color, tinta: Color, cobertura: u8) -> Color {
26    let canal = |a: u8, b: u8| -> u8 {
27        let c = cobertura as u16;
28        ((a as u16 * (255 - c) + b as u16 * c) / 255) as u8
29    };
30    Color {
31        r: canal(fondo.r, tinta.r),
32        g: canal(fondo.g, tinta.g),
33        b: canal(fondo.b, tinta.b),
34    }
35}
36
37// =============================================================================
38//  CAPAS — la descripcion de una recomposicion del escritorio (Fase 9)
39// -----------------------------------------------------------------------------
40//  Cuando hay ventanas flotantes, el escritorio no se pinta ventana a ventana:
41//  el compositor entrega la lista de CAPAS —ordenada de atras hacia adelante— y
42//  la consola las funde en ese orden de una sola pasada. El solapamiento de las
43//  ventanas se resuelve solo, por el orden del pintado.
44// =============================================================================
45
46/// El contenido visible de una capa al recomponer el escritorio.
47pub(crate) enum Contenido<'a> {
48    /// La ventana aun no ha pintado: solo se ve su panel de reposo.
49    Panel,
50    /// El ultimo fotograma de la ventana — su lienzo natural crudo.
51    Fotograma(&'a [u8]),
52    /// La ventana fue desalojada: su baliza, un color plano.
53    Baliza(Color),
54}
55
56/// Una capa del escritorio: una ventana, su marco y lo que muestra. El
57/// compositor arma con ellas una lista ordenada de atras hacia adelante.
58pub(crate) struct Capa<'a> {
59    /// El marco donde la ventana vive en pantalla.
60    pub(crate) marco: RegionPantalla,
61    /// El tamaño natural del lienzo de la app — su fotograma mide esto.
62    pub(crate) nat_ancho: usize,
63    pub(crate) nat_alto: usize,
64    /// Lo que la capa muestra.
65    pub(crate) contenido: Contenido<'a>,
66    /// ¿Tiene esta ventana el foco del compositor?
67    pub(crate) enfocada: bool,
68}
69
70/// La consola grafica de renaser: doble bufer, pantalla fisica y pluma.
71pub(crate) struct Consola {
72    lienzo: Lienzo,
73    pantalla: Pantalla,
74    /// Posicion horizontal de la pluma de escritura.
75    pluma_x: usize,
76    /// Linea base vertical de la pluma de escritura.
77    base_y: usize,
78}
79
80// SEGURIDAD: `Consola` encierra, via `Pantalla`, un puntero crudo al
81// framebuffer. Ese puntero es valido durante toda la vida del kernel y todo
82// acceso a la consola se serializa tras un `Mutex`. En un sistema de un solo
83// nucleo, esto la hace segura de compartir entre el hilo principal y las tareas.
84unsafe impl Send for Consola {}
85
86impl Consola {
87    /// Crea una consola con la pluma en la esquina superior izquierda.
88    pub(crate) fn nueva(lienzo: Lienzo, pantalla: Pantalla) -> Consola {
89        Consola {
90            lienzo,
91            pantalla,
92            pluma_x: MARGEN,
93            base_y: BASE_INICIAL,
94        }
95    }
96
97    /// Lleva la pluma al inicio de la siguiente linea. Al llegar al fondo,
98    /// limpia el lienzo: una pizarra nueva.
99    fn nueva_linea(&mut self) {
100        self.pluma_x = MARGEN;
101        self.base_y += ALTO_LINEA;
102        if self.base_y + ALTO_LINEA >= self.lienzo.alto {
103            self.lienzo.limpiar(Color::LIENZO_EN_REPOSO);
104            self.base_y = BASE_INICIAL;
105        }
106    }
107
108    /// Escribe un caracter: rasteriza su glifo y avanza la pluma.
109    fn escribir_char(&mut self, caracter: char) {
110        if caracter == '\n' {
111            self.nueva_linea();
112            return;
113        }
114        let (metricas, cobertura) = texto::rasterizar(caracter, TAM_FUENTE);
115        // Salto de linea automatico al alcanzar el margen derecho.
116        if self.pluma_x + metricas.advance_width as usize + MARGEN > self.lienzo.ancho {
117            self.nueva_linea();
118        }
119        self.dibujar_glifo(&metricas, &cobertura);
120        self.pluma_x += metricas.advance_width as usize;
121    }
122
123    /// Escribe una cadena completa, caracter a caracter.
124    pub(crate) fn escribir(&mut self, texto: &str) {
125        for caracter in texto.chars() {
126            self.escribir_char(caracter);
127        }
128    }
129
130    /// Funde un mapa de cobertura de `fontdue` sobre el lienzo, en la pluma.
131    fn dibujar_glifo(&mut self, metricas: &fontdue::Metrics, cobertura: &[u8]) {
132        // Origen del glifo: la pluma desplazada por las metricas. El mapa de
133        // `fontdue` se recorre de arriba a abajo desde la cima del glifo.
134        let inicio_x = self.pluma_x as isize + metricas.xmin as isize;
135        let inicio_y = self.base_y as isize - metricas.ymin as isize - metricas.height as isize;
136
137        for fila in 0..metricas.height {
138            for col in 0..metricas.width {
139                let opacidad = cobertura[fila * metricas.width + col];
140                if opacidad == 0 {
141                    continue; // pixel transparente: no toca el fondo
142                }
143                let x = inicio_x + col as isize;
144                let y = inicio_y + fila as isize;
145                if x < 0 || y < 0 {
146                    continue;
147                }
148                let color = mezclar(Color::LIENZO_EN_REPOSO, Color::TEXTO, opacidad);
149                self.lienzo.pintar_pixel(x as usize, y as usize, color);
150            }
151        }
152    }
153
154    /// Compone un fotograma crudo del userspace WASM —pixeles `0x00RRGGBB`, con
155    /// sus limites ya verificados por el host— dentro del MARCO que el
156    /// compositor asigno a su aplicacion. El fotograma mide el tamaño NATURAL
157    /// de la app (`nat_ancho × nat_alto`); se CENTRA en el marco —que pudo
158    /// hacerse mayor o menor que ese natural— y se recorta con firmeza a sus
159    /// bordes. Una app jamas pinta un pixel fuera de su marco. NO traza borde
160    /// ni presenta: de eso se encargan `volcar_marco` y `recomponer`.
161    fn componer_fotograma(
162        &mut self,
163        marco: RegionPantalla,
164        nat_ancho: usize,
165        nat_alto: usize,
166        datos: &[u8],
167    ) {
168        if nat_ancho == 0 || nat_alto == 0 {
169            return;
170        }
171        // Centrar el fotograma natural dentro del marco. Si el natural excede
172        // al marco, el desplazamiento queda en cero y el sobrante se recorta.
173        let off_x = marco.x + marco.ancho.saturating_sub(nat_ancho) / 2;
174        let off_y = marco.y + marco.alto.saturating_sub(nat_alto) / 2;
175        let marco_x_fin = marco.x + marco.ancho;
176        let marco_y_fin = marco.y + marco.alto;
177
178        for (indice, trozo) in datos.chunks_exact(4).enumerate() {
179            let columna = indice % nat_ancho;
180            let fila = indice / nat_ancho;
181            if fila >= nat_alto {
182                break; // el fotograma excede su alto natural: se ignora el resto
183            }
184            let x = off_x + columna;
185            let y = off_y + fila;
186            // Recorte firme: al marco —el confinamiento de la app— y al lienzo.
187            if x >= marco_x_fin || y >= marco_y_fin {
188                continue;
189            }
190            if x >= self.lienzo.ancho || y >= self.lienzo.alto {
191                continue;
192            }
193            let p = u32::from_le_bytes([trozo[0], trozo[1], trozo[2], trozo[3]]);
194            let color = Color {
195                r: (p >> 16) as u8,
196                g: (p >> 8) as u8,
197                b: p as u8,
198            };
199            self.lienzo.pixeles[y * self.lienzo.ancho + x] =
200                codificar(self.lienzo.formato, color);
201        }
202    }
203
204    /// Compone el fotograma de una app en su marco, le traza el borde de foco y
205    /// presenta. El camino RAPIDO del compositor: sin ventanas flotantes
206    /// ninguna ventana solapa a otra y basta repintar la que cambia.
207    fn volcar_marco(
208        &mut self,
209        marco: RegionPantalla,
210        nat_ancho: usize,
211        nat_alto: usize,
212        datos: &[u8],
213        enfocada: bool,
214    ) {
215        self.componer_fotograma(marco, nat_ancho, nat_alto, datos);
216        // El borde del compositor: delata, de un vistazo, quien tiene el foco.
217        self.dibujar_borde(marco, enfocada);
218        self.presentar();
219    }
220
221    /// Recompone el escritorio entero de una sola pasada (Fase 9). Inunda el
222    /// area de apps con el reposo del lienzo y funde sobre ella cada capa, EN
223    /// ORDEN —de atras hacia adelante—: asi el solapamiento de las ventanas
224    /// flotantes se resuelve por si solo, sin recortes ni mascaras. Cada capa
225    /// pinta primero su panel —el cromo de la ventana— y, encima, su contenido;
226    /// una sola presentacion cierra la pasada.
227    fn recomponer(&mut self, area: RegionPantalla, capas: &[Capa]) {
228        self.lienzo.rellenar_rect(
229            area.x,
230            area.y,
231            area.ancho,
232            area.alto,
233            Color::LIENZO_EN_REPOSO,
234        );
235        for capa in capas {
236            let m = capa.marco;
237            match &capa.contenido {
238                Contenido::Panel => {
239                    self.lienzo
240                        .rellenar_rect(m.x, m.y, m.ancho, m.alto, Color::PANEL);
241                }
242                Contenido::Fotograma(datos) => {
243                    // El panel primero —el cromo que rodea el lienzo— y el
244                    // fotograma natural centrado encima.
245                    self.lienzo
246                        .rellenar_rect(m.x, m.y, m.ancho, m.alto, Color::PANEL);
247                    self.componer_fotograma(m, capa.nat_ancho, capa.nat_alto, datos);
248                }
249                Contenido::Baliza(color) => {
250                    self.lienzo.rellenar_rect(m.x, m.y, m.ancho, m.alto, *color);
251                }
252            }
253            self.dibujar_borde(m, capa.enfocada);
254        }
255        self.presentar();
256    }
257
258    /// Inunda una region entera con un color plano —la baliza de desalojo: una
259    /// app que falla tatua su marco de purpura— y le traza su borde de foco.
260    fn pintar_region(&mut self, region: RegionPantalla, color: Color, enfocada: bool) {
261        self.lienzo
262            .rellenar_rect(region.x, region.y, region.ancho, region.alto, color);
263        self.dibujar_borde(region, enfocada);
264        self.presentar();
265    }
266
267    /// Traza un borde de 3 px alrededor de un marco: indigo brillante si la
268    /// ventana tiene el foco del compositor, gris mate si no (Fase 8c).
269    fn dibujar_borde(&mut self, marco: RegionPantalla, enfocada: bool) {
270        const GROSOR: usize = 3;
271        let color = if enfocada { Color::FOCO } else { Color::SIN_FOCO };
272        // Lados superior e inferior.
273        self.lienzo
274            .rellenar_rect(marco.x, marco.y, marco.ancho, GROSOR, color);
275        self.lienzo.rellenar_rect(
276            marco.x,
277            marco.y + marco.alto.saturating_sub(GROSOR),
278            marco.ancho,
279            GROSOR,
280            color,
281        );
282        // Lados izquierdo y derecho.
283        self.lienzo
284            .rellenar_rect(marco.x, marco.y, GROSOR, marco.alto, color);
285        self.lienzo.rellenar_rect(
286            marco.x + marco.ancho.saturating_sub(GROSOR),
287            marco.y,
288            GROSOR,
289            marco.alto,
290            color,
291        );
292    }
293
294    /// Vuelca el lienzo sobre la pantalla fisica.
295    pub(crate) fn presentar(&mut self) {
296        self.pantalla.presentar(&self.lienzo);
297    }
298}
299
300/// La consola global de renaser. Se funde en el arranque; despues, las tareas
301/// asincronas y las capacidades del userspace escriben en ella tras su `Mutex`.
302pub(crate) static CONSOLA: Once<Mutex<Consola>> = Once::new();
303
304/// Compone un fotograma del userspace —ya cacheado por el compositor— centrado
305/// en su marco teselado, con su borde de foco. La invoca `compositor` al
306/// recibir un `sys_render_frame` y al recomponer el escritorio tras un mando.
307pub(crate) fn volcar_marco(
308    marco: RegionPantalla,
309    nat_ancho: usize,
310    nat_alto: usize,
311    datos: &[u8],
312    enfocada: bool,
313) {
314    if let Some(consola) = CONSOLA.get() {
315        consola
316            .lock()
317            .volcar_marco(marco, nat_ancho, nat_alto, datos, enfocada);
318    }
319}
320
321/// Recompone el escritorio entero respetando el orden-Z de sus capas (Fase 9).
322/// La invoca `compositor` al arrancar y siempre que hay ventanas flotantes: el
323/// solapamiento obliga a repintar el escritorio en bloque, no ventana a
324/// ventana. Las capas llegan ya ordenadas de atras hacia adelante.
325pub(crate) fn recomponer(area: RegionPantalla, capas: &[Capa]) {
326    if let Some(consola) = CONSOLA.get() {
327        consola.lock().recomponer(area, capas);
328    }
329}
330
331/// Tatua la baliza de desalojo sobre el marco de una aplicacion que el kernel
332/// ha dado por terminada, con su borde de foco. El color delata la causa
333/// —purpura para una falla de ejecucion o de combustible, amarillo palido para
334/// un desbordo de memoria—. Es una advertencia NO fatal: la app muere, el
335/// kernel y sus vecinas siguen vivos.
336pub(crate) fn pintar_desalojo(marco: RegionPantalla, color: Color, enfocada: bool) {
337    if let Some(consola) = CONSOLA.get() {
338        consola.lock().pintar_region(marco, color, enfocada);
339    }
340}