Skip to main content

strat9_kernel/shell/commands/gfx/
mod.rs

1//! Graphics console commands
2use crate::{
3    arch::x86_64::vga::{self, RgbColor, TextAlign, TextOptions, UiTheme},
4    shell::ShellError,
5    shell_println,
6};
7use alloc::{string::String, vec, vec::Vec};
8use core::f32::consts::PI;
9
10fn sin_approx(x: f32) -> f32 {
11    let x2 = x * x;
12    x * (1.0 + x2 * (-0.166_666_67 + x2 * (0.008_333_334 - x2 * 0.000_198_412_7)))
13}
14
15fn cos_approx(x: f32) -> f32 {
16    let x2 = x * x;
17    1.0 + x2 * (-0.5 + x2 * (0.041_666_668 - x2 * 0.001_388_888_9))
18}
19
20fn norm_angle(a: f32) -> f32 {
21    let two_pi = 2.0 * PI;
22    let mut r = a % two_pi;
23    if r > PI {
24        r -= two_pi;
25    } else if r < -PI {
26        r += two_pi;
27    }
28    r
29}
30
31/// Graphics console commands
32pub fn cmd_gfx(args: &[String]) -> Result<(), ShellError> {
33    /// Performs the print gfx help operation.
34    fn print_gfx_help() {
35        shell_println!("Usage: gfx <subcommand>");
36        shell_println!("  help                 Show this help");
37        shell_println!("  info                 Show framebuffer/text console info");
38        shell_println!("  mode on|off          Enable/disable double-buffer mode");
39        shell_println!("  ui compact|normal|large");
40        shell_println!("                       Set UI scaling preset");
41        shell_println!("  test                 Draw graphics validation screen");
42        shell_println!("  test3d               Spinning 3D torus (SIMD framebuffer test)");
43    }
44
45    if args.is_empty() {
46        print_gfx_help();
47        return Ok(());
48    }
49    match args[0].as_str() {
50        "help" => {
51            print_gfx_help();
52            Ok(())
53        }
54        "info" => {
55            let info = vga::framebuffer_info();
56            if !info.available {
57                shell_println!("Graphics console: unavailable");
58                return Ok(());
59            }
60
61            shell_println!("Graphics console:");
62            shell_println!(
63                "  Framebuffer: {}x{} {}bpp pitch={}",
64                info.width,
65                info.height,
66                info.bpp,
67                info.pitch
68            );
69            shell_println!(
70                "  RGB masks: R({}:{}) G({}:{}) B({}:{})",
71                info.red_size,
72                info.red_shift,
73                info.green_size,
74                info.green_shift,
75                info.blue_size,
76                info.blue_shift
77            );
78            shell_println!(
79                "  Text grid: {}x{} (glyph={}x{})",
80                info.text_cols,
81                info.text_rows,
82                info.glyph_w,
83                info.glyph_h
84            );
85            shell_println!(
86                "  Double buffer mode: {}",
87                if info.double_buffer_mode { "on" } else { "off" }
88            );
89            shell_println!(
90                "  Double buffer active: {}",
91                if info.double_buffer_enabled {
92                    "yes"
93                } else {
94                    "no"
95                }
96            );
97            let scale = match info.ui_scale {
98                vga::UiScale::Compact => "compact",
99                vga::UiScale::Normal => "normal",
100                vga::UiScale::Large => "large",
101            };
102            shell_println!("  UI scale: {}", scale);
103            Ok(())
104        }
105        "mode" => {
106            if args.len() < 2 {
107                print_gfx_help();
108                return Ok(());
109            }
110            match args[1].as_str() {
111                "on" => {
112                    vga::set_double_buffer_mode(true);
113                    shell_println!("gfx: double-buffer mode enabled");
114                }
115                "off" => {
116                    vga::set_double_buffer_mode(false);
117                    shell_println!("gfx: double-buffer mode disabled");
118                }
119                _ => print_gfx_help(),
120            }
121            Ok(())
122        }
123        "ui" => {
124            if args.len() < 2 {
125                print_gfx_help();
126                return Ok(());
127            }
128            let scale = match args[1].as_str() {
129                "compact" => vga::UiScale::Compact,
130                "normal" => vga::UiScale::Normal,
131                "large" => vga::UiScale::Large,
132                _ => {
133                    print_gfx_help();
134                    return Ok(());
135                }
136            };
137            vga::set_ui_scale(scale);
138            shell_println!("gfx: ui scale updated");
139            Ok(())
140        }
141        "test" => cmd_gfx_test(),
142        "test3d" => cmd_gfx_test3d(),
143        "demo" => cmd_gfx_demo(args),
144        _ => {
145            print_gfx_help();
146            Ok(())
147        }
148    }
149}
150
151/// Performs the cmd gfx test operation.
152pub fn cmd_gfx_test() -> Result<(), ShellError> {
153    if !vga::is_available() {
154        shell_println!("gfx-test: framebuffer console unavailable");
155        return Ok(());
156    }
157
158    let (w, h) = vga::screen_size();
159    let canvas = vga::Canvas::new(
160        RgbColor::new(0xE2, 0xE8, 0xF0),
161        RgbColor::new(0x12, 0x16, 0x1E),
162    );
163    canvas.begin_frame();
164    canvas.clear();
165
166    for y in (0..h).step_by(40) {
167        vga::draw_line(
168            0,
169            y as isize,
170            w.saturating_sub(1) as isize,
171            y as isize,
172            RgbColor::new(0x22, 0x2E, 0x3A),
173        );
174    }
175    for x in (0..w).step_by(40) {
176        vga::draw_line(
177            x as isize,
178            0,
179            x as isize,
180            h.saturating_sub(1) as isize,
181            RgbColor::new(0x22, 0x2E, 0x3A),
182        );
183    }
184
185    let bw = w.saturating_sub(120).min(560);
186    let bh = h.saturating_sub(220).min(240);
187    let bx = 60;
188    let by = 80;
189    vga::fill_rect(bx, by, bw, bh, RgbColor::new(0x1A, 0x22, 0x2C));
190    vga::draw_rect(bx, by, bw, bh, RgbColor::new(0x4F, 0xB3, 0xB3));
191    vga::fill_rect_alpha(
192        bx + 24,
193        by + 24,
194        bw.saturating_sub(48),
195        bh.saturating_sub(48),
196        RgbColor::new(0x7E, 0xC1, 0xFF),
197        96,
198    );
199    vga::set_clip_rect(
200        bx + 12,
201        by + 12,
202        bw.saturating_sub(24),
203        bh.saturating_sub(24),
204    );
205    vga::fill_rect(bx, by, bw, bh, RgbColor::new(0x1B, 0x4D, 0x8A));
206    vga::reset_clip_rect();
207
208    vga::draw_text(
209        bx + 18,
210        by + 16,
211        "GFX TEST: alpha / clip / text",
212        TextOptions {
213            fg: RgbColor::new(0xF5, 0xFA, 0xFF),
214            bg: RgbColor::new(0x1A, 0x22, 0x2C),
215            align: TextAlign::Left,
216            wrap: false,
217            max_width: Some(bw.saturating_sub(36)),
218        },
219    );
220
221    canvas.system_status_line(UiTheme::OCEAN_STATUS);
222    canvas.end_frame();
223    vga::set_text_cursor(0, vga::text_rows().saturating_sub(2));
224    shell_println!("gfx-test: rendered");
225    Ok(())
226}
227
228/// Spinning 3D torus (donut) :test SIMD framebuffer in real conditions
229pub fn cmd_gfx_test3d() -> Result<(), ShellError> {
230    if !vga::is_available() {
231        shell_println!("gfx-test3d: framebuffer console unavailable");
232        return Ok(());
233    }
234
235    use crate::shell::is_interrupted;
236    use core::f32::consts::PI;
237
238    let (w, h) = vga::screen_size();
239    let cx = w as f32 / 2.0;
240    let cy = h as f32 / 2.0;
241    let scale = (w.min(h) as f32) * 0.28;
242
243    let canvas = vga::Canvas::new(
244        RgbColor::new(0xE2, 0xE8, 0xF0),
245        RgbColor::new(0x0A, 0x0E, 0x14),
246    );
247    vga::set_double_buffer_mode(true);
248
249    // Paramètres du tore
250    const R1: f32 = 2.00; // radius of the central circle
251    const R2: f32 = 0.80; // radius of the tube
252    const THETA_N: usize = 50; // subdivisions around the tube
253    const PHI_N: usize = 100; // subdivisions around the torus
254    const DOT_SIZE: usize = 2; // size of a point on the screen
255
256    let theta_step = 2.0 * PI / THETA_N as f32;
257    let phi_step = 2.0 * PI / PHI_N as f32;
258
259    // Cyclic palette (blue => cyan => green => yellow => red)
260    const PALETTE: [RgbColor; 16] = [
261        RgbColor::new(0x10, 0x48, 0xA0), // deep blue
262        RgbColor::new(0x18, 0x60, 0xB0),
263        RgbColor::new(0x20, 0x7A, 0xC0),
264        RgbColor::new(0x30, 0x94, 0xD0),
265        RgbColor::new(0x40, 0xAE, 0xD0), // blue-cyan
266        RgbColor::new(0x50, 0xC8, 0xC0),
267        RgbColor::new(0x60, 0xD8, 0xA0), // green-cyan
268        RgbColor::new(0x80, 0xE0, 0x80), // light green
269        RgbColor::new(0xA0, 0xE0, 0x60),
270        RgbColor::new(0xC0, 0xD0, 0x50), // yellow-green
271        RgbColor::new(0xD0, 0xB0, 0x40), // yellow
272        RgbColor::new(0xE0, 0x90, 0x30), // orange
273        RgbColor::new(0xE0, 0x70, 0x30),
274        RgbColor::new(0xE0, 0x50, 0x30), // red-orange
275        RgbColor::new(0xC0, 0x30, 0x30), // red
276        RgbColor::new(0x90, 0x20, 0x30), // dark red
277    ];
278
279    shell_println!("gfx-test3d: spinning donut : press Ctrl+C to quit");
280
281    let mut frame: u64 = 0;
282    loop {
283        if is_interrupted() {
284            shell_println!("gfx-test3d: stopped");
285            break;
286        }
287
288        canvas.begin_frame();
289        canvas.clear();
290
291        let rot_a = frame as f32 * 0.020; // rotation around X axis
292        let rot_b = frame as f32 * 0.015; // rotation around Z axis
293        let sa = sin_approx(norm_angle(rot_a));
294        let ca = cos_approx(norm_angle(rot_a));
295        let sb = sin_approx(norm_angle(rot_b));
296        let cb = cos_approx(norm_angle(rot_b));
297
298        let mut ti = 0;
299        while ti < THETA_N {
300            let theta = ti as f32 * theta_step;
301            let st = sin_approx(norm_angle(theta));
302            let ct = cos_approx(norm_angle(theta));
303
304            let mut pi = 0;
305            while pi < PHI_N {
306                let phi = pi as f32 * phi_step;
307                let sp = sin_approx(norm_angle(phi));
308                let cp = cos_approx(norm_angle(phi));
309
310                // 3D position on the torus (local coordinate system)
311                let x = (R1 + R2 * ct) * cp;
312                let y = (R1 + R2 * ct) * sp;
313                let z = R2 * st;
314
315                // Rotation around X axis
316                let x1 = x;
317                let y1 = y * ca - z * sa;
318                let z1 = y * sa + z * ca;
319
320                // Rotation around Z axis
321                let x2 = x1 * cb - y1 * sb;
322                let y2 = x1 * sb + y1 * cb;
323                let z2 = z1;
324
325                // Surface normal (pointing outward from the tube)
326                let nx = ct * cp;
327                let ny = ct * sp;
328                let nz = st;
329                // Apply rotations to the normal
330                let nx1 = nx;
331                let ny1 = ny * ca - nz * sa;
332                let nz1 = ny * sa + nz * ca;
333                let nx2 = nx1 * cb - ny1 * sb;
334                let ny2 = nx1 * sb + ny1 * cb;
335                let nz2 = nz1;
336
337                // Luminosity (fixed light direction)
338                let lum = nx2 * 0.3 + ny2 * 0.5 + nz2 * 0.8;
339                // Project only points whose normal is facing the light
340                if lum > 0.0 {
341                    // Perspective
342                    let dist = 6.0;
343                    let inv_z = dist / (z2 + 3.0 + dist);
344                    let sx = (cx + x2 * scale * inv_z) as isize;
345                    let sy = (cy - y2 * scale * inv_z) as isize;
346
347                    let idx = (lum * 15.0) as usize;
348                    let color = PALETTE[idx.min(15)];
349
350                    for dy in 0..DOT_SIZE {
351                        for dx in 0..DOT_SIZE {
352                            let px = sx + dx as isize;
353                            let py = sy + dy as isize;
354                            if px >= 0 && py >= 0 && px < w as isize && py < h as isize {
355                                vga::draw_pixel(px as usize, py as usize, color);
356                            }
357                        }
358                    }
359                }
360
361                pi += 1;
362            }
363            ti += 1;
364        }
365
366        canvas.system_status_line(UiTheme::OCEAN_STATUS);
367        canvas.end_frame();
368
369        frame += 1;
370    }
371
372    vga::set_double_buffer_mode(false);
373    vga::set_text_cursor(0, vga::text_rows().saturating_sub(2));
374    Ok(())
375}
376
377/// Performs the cmd gfx demo operation.
378pub fn cmd_gfx_demo(_args: &[String]) -> Result<(), ShellError> {
379    use vga::{
380        DockEdge, TerminalWidget, UiDockLayout, UiLabel, UiPanel, UiProgressBar, UiRect, UiTable,
381    };
382
383    if !vga::is_available() {
384        shell_println!("gfx-demo: framebuffer console unavailable");
385        return Ok(());
386    }
387
388    let mut layout = UiDockLayout::from_screen();
389    let top = layout.dock(DockEdge::Top, vga::ui_scale_px(56));
390    let bottom = layout.dock(DockEdge::Bottom, vga::ui_scale_px(120));
391    let left = layout.dock(DockEdge::Left, vga::ui_scale_px(360));
392    let _center = layout.remaining();
393
394    let theme = UiTheme::SLATE;
395    let canvas = vga::Canvas::new(theme.text, theme.background);
396    canvas.begin_frame();
397    canvas.ui_clear(theme);
398
399    vga::ui_draw_panel_widget(&UiPanel {
400        rect: top,
401        title: "Strat9 Graphics Console",
402        body: "Dock layout + widgets + terminal demo",
403        theme,
404    });
405
406    canvas.ui_label(&UiLabel {
407        rect: UiRect::new(
408            top.x + vga::ui_scale_px(8),
409            top.y + vga::ui_scale_px(30),
410            top.w.saturating_sub(vga::ui_scale_px(16)),
411            vga::ui_scale_px(24),
412        ),
413        text: "layout: top + bottom + left + center",
414        fg: RgbColor::new(0xD0, 0xE4, 0xFF),
415        bg: theme.panel_bg,
416        align: TextAlign::Left,
417    });
418
419    canvas.ui_panel(
420        left.x,
421        left.y,
422        left.w,
423        left.h,
424        "System",
425        "Progress bars and data table",
426        theme,
427    );
428
429    canvas.ui_progress_bar(UiProgressBar {
430        rect: UiRect::new(
431            left.x + vga::ui_scale_px(12),
432            left.y + vga::ui_scale_px(46),
433            left.w.saturating_sub(vga::ui_scale_px(24)),
434            vga::ui_scale_px(16),
435        ),
436        value: 72,
437        fg: RgbColor::new(0x58, 0xD6, 0xA3),
438        bg: RgbColor::new(0x12, 0x16, 0x1E),
439        border: theme.panel_border,
440    });
441
442    let headers = vec![
443        String::from("Metric"),
444        String::from("Value"),
445        String::from("Status"),
446    ];
447    let rows: Vec<Vec<String>> = vec![
448        vec![String::from("CPU"), String::from("72%"), String::from("ok")],
449        vec![
450            String::from("Memory"),
451            String::from("43%"),
452            String::from("ok"),
453        ],
454    ];
455    canvas.ui_table(&UiTable {
456        rect: UiRect::new(
457            left.x + vga::ui_scale_px(12),
458            left.y + vga::ui_scale_px(96),
459            left.w.saturating_sub(vga::ui_scale_px(24)),
460            left.h.saturating_sub(vga::ui_scale_px(108)),
461        ),
462        headers,
463        rows,
464        theme,
465    });
466
467    let mut term = TerminalWidget::new(bottom, 64);
468    term.title = String::from("Kernel Terminal");
469    term.push_ansi_line("\u{1b}[36m[boot]\u{1b}[0m ui widgets initialized");
470    term.draw();
471
472    canvas.system_status_line(UiTheme::OCEAN_STATUS);
473    canvas.end_frame();
474    let row = vga::text_rows().saturating_sub(2);
475    vga::set_text_cursor(0, row);
476    Ok(())
477}