tawasuyu

tawasuyu/brahman

Rust 582TOML 222Markdown 73JSON 198635 Definitions

⎇ 6e30dc2d7288b691aec553b8261f818c6f9dbba9 renaser/kernel/src/consola.rs Rust · 273 lines · 10.8 KB History Who touched what raw

feat(renaser): Fases 8b y 8c — el escritorio interactivo 5c462e6d3 · 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/// La consola grafica de renaser: doble bufer, pantalla fisica y pluma.
38pub(crate) struct Consola {
39    lienzo: Lienzo,
40    pantalla: Pantalla,
41    /// Posicion horizontal de la pluma de escritura.
42    pluma_x: usize,
43    /// Linea base vertical de la pluma de escritura.
44    base_y: usize,
45}
46
47// SEGURIDAD: `Consola` encierra, via `Pantalla`, un puntero crudo al
48// framebuffer. Ese puntero es valido durante toda la vida del kernel y todo
49// acceso a la consola se serializa tras un `Mutex`. En un sistema de un solo
50// nucleo, esto la hace segura de compartir entre el hilo principal y las tareas.
51unsafe impl Send for Consola {}
52
53impl Consola {
54    /// Crea una consola con la pluma en la esquina superior izquierda.
55    pub(crate) fn nueva(lienzo: Lienzo, pantalla: Pantalla) -> Consola {
56        Consola {
57            lienzo,
58            pantalla,
59            pluma_x: MARGEN,
60            base_y: BASE_INICIAL,
61        }
62    }
63
64    /// Lleva la pluma al inicio de la siguiente linea. Al llegar al fondo,
65    /// limpia el lienzo: una pizarra nueva.
66    fn nueva_linea(&mut self) {
67        self.pluma_x = MARGEN;
68        self.base_y += ALTO_LINEA;
69        if self.base_y + ALTO_LINEA >= self.lienzo.alto {
70            self.lienzo.limpiar(Color::LIENZO_EN_REPOSO);
71            self.base_y = BASE_INICIAL;
72        }
73    }
74
75    /// Escribe un caracter: rasteriza su glifo y avanza la pluma.
76    fn escribir_char(&mut self, caracter: char) {
77        if caracter == '\n' {
78            self.nueva_linea();
79            return;
80        }
81        let (metricas, cobertura) = texto::rasterizar(caracter, TAM_FUENTE);
82        // Salto de linea automatico al alcanzar el margen derecho.
83        if self.pluma_x + metricas.advance_width as usize + MARGEN > self.lienzo.ancho {
84            self.nueva_linea();
85        }
86        self.dibujar_glifo(&metricas, &cobertura);
87        self.pluma_x += metricas.advance_width as usize;
88    }
89
90    /// Escribe una cadena completa, caracter a caracter.
91    pub(crate) fn escribir(&mut self, texto: &str) {
92        for caracter in texto.chars() {
93            self.escribir_char(caracter);
94        }
95    }
96
97    /// Funde un mapa de cobertura de `fontdue` sobre el lienzo, en la pluma.
98    fn dibujar_glifo(&mut self, metricas: &fontdue::Metrics, cobertura: &[u8]) {
99        // Origen del glifo: la pluma desplazada por las metricas. El mapa de
100        // `fontdue` se recorre de arriba a abajo desde la cima del glifo.
101        let inicio_x = self.pluma_x as isize + metricas.xmin as isize;
102        let inicio_y = self.base_y as isize - metricas.ymin as isize - metricas.height as isize;
103
104        for fila in 0..metricas.height {
105            for col in 0..metricas.width {
106                let opacidad = cobertura[fila * metricas.width + col];
107                if opacidad == 0 {
108                    continue; // pixel transparente: no toca el fondo
109                }
110                let x = inicio_x + col as isize;
111                let y = inicio_y + fila as isize;
112                if x < 0 || y < 0 {
113                    continue;
114                }
115                let color = mezclar(Color::LIENZO_EN_REPOSO, Color::TEXTO, opacidad);
116                self.lienzo.pintar_pixel(x as usize, y as usize, color);
117            }
118        }
119    }
120
121    /// Compone un fotograma crudo del userspace WASM —pixeles `0x00RRGGBB`, con
122    /// sus limites ya verificados por el host— dentro del MARCO que el
123    /// compositor (Fase 8) asigno a su aplicacion. El fotograma mide el tamaño
124    /// NATURAL de la app (`nat_ancho × nat_alto`); se CENTRA en el marco —que el
125    /// teselado pudo hacer mayor o menor que ese natural— y se recorta con
126    /// firmeza a sus bordes. Una app jamas pinta un pixel fuera de su marco.
127    fn volcar_marco(
128        &mut self,
129        marco: RegionPantalla,
130        nat_ancho: usize,
131        nat_alto: usize,
132        datos: &[u8],
133        enfocada: bool,
134    ) {
135        if nat_ancho == 0 || nat_alto == 0 {
136            return;
137        }
138        // Centrar el fotograma natural dentro del marco. Si el natural excede
139        // al marco, el desplazamiento queda en cero y el sobrante se recorta.
140        let off_x = marco.x + marco.ancho.saturating_sub(nat_ancho) / 2;
141        let off_y = marco.y + marco.alto.saturating_sub(nat_alto) / 2;
142        let marco_x_fin = marco.x + marco.ancho;
143        let marco_y_fin = marco.y + marco.alto;
144
145        for (indice, trozo) in datos.chunks_exact(4).enumerate() {
146            let columna = indice % nat_ancho;
147            let fila = indice / nat_ancho;
148            if fila >= nat_alto {
149                break; // el fotograma excede su alto natural: se ignora el resto
150            }
151            let x = off_x + columna;
152            let y = off_y + fila;
153            // Recorte firme: al marco —el confinamiento de la app— y al lienzo.
154            if x >= marco_x_fin || y >= marco_y_fin {
155                continue;
156            }
157            if x >= self.lienzo.ancho || y >= self.lienzo.alto {
158                continue;
159            }
160            let p = u32::from_le_bytes([trozo[0], trozo[1], trozo[2], trozo[3]]);
161            let color = Color {
162                r: (p >> 16) as u8,
163                g: (p >> 8) as u8,
164                b: p as u8,
165            };
166            self.lienzo.pixeles[y * self.lienzo.ancho + x] =
167                codificar(self.lienzo.formato, color);
168        }
169        // El borde del compositor: delata, de un vistazo, quien tiene el foco.
170        self.dibujar_borde(marco, enfocada);
171        self.presentar();
172    }
173
174    /// Inunda una region entera con un color plano —la baliza de desalojo: una
175    /// app que falla tatua su marco de purpura— y le traza su borde de foco.
176    fn pintar_region(&mut self, region: RegionPantalla, color: Color, enfocada: bool) {
177        self.lienzo
178            .rellenar_rect(region.x, region.y, region.ancho, region.alto, color);
179        self.dibujar_borde(region, enfocada);
180        self.presentar();
181    }
182
183    /// Traza un borde de 3 px alrededor de un marco: indigo brillante si la
184    /// ventana tiene el foco del compositor, gris mate si no (Fase 8c).
185    fn dibujar_borde(&mut self, marco: RegionPantalla, enfocada: bool) {
186        const GROSOR: usize = 3;
187        let color = if enfocada { Color::FOCO } else { Color::SIN_FOCO };
188        // Lados superior e inferior.
189        self.lienzo
190            .rellenar_rect(marco.x, marco.y, marco.ancho, GROSOR, color);
191        self.lienzo.rellenar_rect(
192            marco.x,
193            marco.y + marco.alto.saturating_sub(GROSOR),
194            marco.ancho,
195            GROSOR,
196            color,
197        );
198        // Lados izquierdo y derecho.
199        self.lienzo
200            .rellenar_rect(marco.x, marco.y, GROSOR, marco.alto, color);
201        self.lienzo.rellenar_rect(
202            marco.x + marco.ancho.saturating_sub(GROSOR),
203            marco.y,
204            GROSOR,
205            marco.alto,
206            color,
207        );
208    }
209
210    /// Pinta el escenario del compositor (Fase 8): inunda el area de apps con
211    /// el reposo del lienzo —borrando cuanto hubiera debajo— y, sobre ella,
212    /// tiñe cada marco teselado con el color de panel. Asi el teselado se ve
213    /// como una rejilla de paneles aun antes de que sus apps pinten nada.
214    fn pintar_escenario(&mut self, area: RegionPantalla, marcos: &[RegionPantalla]) {
215        self.lienzo.rellenar_rect(
216            area.x,
217            area.y,
218            area.ancho,
219            area.alto,
220            Color::LIENZO_EN_REPOSO,
221        );
222        for marco in marcos {
223            self.lienzo
224                .rellenar_rect(marco.x, marco.y, marco.ancho, marco.alto, Color::PANEL);
225        }
226        self.presentar();
227    }
228
229    /// Vuelca el lienzo sobre la pantalla fisica.
230    pub(crate) fn presentar(&mut self) {
231        self.pantalla.presentar(&self.lienzo);
232    }
233}
234
235/// La consola global de renaser. Se funde en el arranque; despues, las tareas
236/// asincronas y las capacidades del userspace escriben en ella tras su `Mutex`.
237pub(crate) static CONSOLA: Once<Mutex<Consola>> = Once::new();
238
239/// Compone un fotograma del userspace —ya cacheado por el compositor— centrado
240/// en su marco teselado, con su borde de foco. La invoca `compositor` al
241/// recibir un `sys_render_frame` y al recomponer el escritorio tras un mando.
242pub(crate) fn volcar_marco(
243    marco: RegionPantalla,
244    nat_ancho: usize,
245    nat_alto: usize,
246    datos: &[u8],
247    enfocada: bool,
248) {
249    if let Some(consola) = CONSOLA.get() {
250        consola
251            .lock()
252            .volcar_marco(marco, nat_ancho, nat_alto, datos, enfocada);
253    }
254}
255
256/// Pinta el escenario del compositor (Fase 8): el area de apps y, sobre ella,
257/// cada marco teselado. La invoca `compositor` al arrancar y al re-teselar.
258pub(crate) fn pintar_escenario(area: RegionPantalla, marcos: &[RegionPantalla]) {
259    if let Some(consola) = CONSOLA.get() {
260        consola.lock().pintar_escenario(area, marcos);
261    }
262}
263
264/// Tatua la baliza de desalojo sobre el marco de una aplicacion que el kernel
265/// ha dado por terminada, con su borde de foco. El color delata la causa
266/// —purpura para una falla de ejecucion o de combustible, amarillo palido para
267/// un desbordo de memoria—. Es una advertencia NO fatal: la app muere, el
268/// kernel y sus vecinas siguen vivos.
269pub(crate) fn pintar_desalojo(marco: RegionPantalla, color: Color, enfocada: bool) {
270    if let Some(consola) = CONSOLA.get() {
271        consola.lock().pintar_region(marco, color, enfocada);
272    }
273}