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};
8
9/// Graphics console commands
10pub fn cmd_gfx(args: &[String]) -> Result<(), ShellError> {
11    /// Performs the print gfx help operation.
12    fn print_gfx_help() {
13        shell_println!("Usage: gfx <subcommand>");
14        shell_println!("  help                 Show this help");
15        shell_println!("  info                 Show framebuffer/text console info");
16        shell_println!("  mode on|off          Enable/disable double-buffer mode");
17        shell_println!("  ui compact|normal|large");
18        shell_println!("                       Set UI scaling preset");
19        shell_println!("  test                 Draw graphics validation screen");
20    }
21
22    if args.is_empty() {
23        print_gfx_help();
24        return Ok(());
25    }
26    match args[0].as_str() {
27        "help" => {
28            print_gfx_help();
29            Ok(())
30        }
31        "info" => {
32            let info = vga::framebuffer_info();
33            if !info.available {
34                shell_println!("Graphics console: unavailable");
35                return Ok(());
36            }
37
38            shell_println!("Graphics console:");
39            shell_println!(
40                "  Framebuffer: {}x{} {}bpp pitch={}",
41                info.width,
42                info.height,
43                info.bpp,
44                info.pitch
45            );
46            shell_println!(
47                "  RGB masks: R({}:{}) G({}:{}) B({}:{})",
48                info.red_size,
49                info.red_shift,
50                info.green_size,
51                info.green_shift,
52                info.blue_size,
53                info.blue_shift
54            );
55            shell_println!(
56                "  Text grid: {}x{} (glyph={}x{})",
57                info.text_cols,
58                info.text_rows,
59                info.glyph_w,
60                info.glyph_h
61            );
62            shell_println!(
63                "  Double buffer mode: {}",
64                if info.double_buffer_mode { "on" } else { "off" }
65            );
66            shell_println!(
67                "  Double buffer active: {}",
68                if info.double_buffer_enabled {
69                    "yes"
70                } else {
71                    "no"
72                }
73            );
74            let scale = match info.ui_scale {
75                vga::UiScale::Compact => "compact",
76                vga::UiScale::Normal => "normal",
77                vga::UiScale::Large => "large",
78            };
79            shell_println!("  UI scale: {}", scale);
80            Ok(())
81        }
82        "mode" => {
83            if args.len() < 2 {
84                print_gfx_help();
85                return Ok(());
86            }
87            match args[1].as_str() {
88                "on" => {
89                    vga::set_double_buffer_mode(true);
90                    shell_println!("gfx: double-buffer mode enabled");
91                }
92                "off" => {
93                    vga::set_double_buffer_mode(false);
94                    shell_println!("gfx: double-buffer mode disabled");
95                }
96                _ => print_gfx_help(),
97            }
98            Ok(())
99        }
100        "ui" => {
101            if args.len() < 2 {
102                print_gfx_help();
103                return Ok(());
104            }
105            let scale = match args[1].as_str() {
106                "compact" => vga::UiScale::Compact,
107                "normal" => vga::UiScale::Normal,
108                "large" => vga::UiScale::Large,
109                _ => {
110                    print_gfx_help();
111                    return Ok(());
112                }
113            };
114            vga::set_ui_scale(scale);
115            shell_println!("gfx: ui scale updated");
116            Ok(())
117        }
118        "test" => cmd_gfx_test(),
119        _ => {
120            print_gfx_help();
121            Ok(())
122        }
123    }
124}
125
126/// Performs the cmd gfx test operation.
127pub fn cmd_gfx_test() -> Result<(), ShellError> {
128    if !vga::is_available() {
129        shell_println!("gfx-test: framebuffer console unavailable");
130        return Ok(());
131    }
132
133    let (w, h) = vga::screen_size();
134    let canvas = vga::Canvas::new(
135        RgbColor::new(0xE2, 0xE8, 0xF0),
136        RgbColor::new(0x12, 0x16, 0x1E),
137    );
138    canvas.begin_frame();
139    canvas.clear();
140
141    for y in (0..h).step_by(40) {
142        vga::draw_line(
143            0,
144            y as isize,
145            w.saturating_sub(1) as isize,
146            y as isize,
147            RgbColor::new(0x22, 0x2E, 0x3A),
148        );
149    }
150    for x in (0..w).step_by(40) {
151        vga::draw_line(
152            x as isize,
153            0,
154            x as isize,
155            h.saturating_sub(1) as isize,
156            RgbColor::new(0x22, 0x2E, 0x3A),
157        );
158    }
159
160    let bw = w.saturating_sub(120).min(560);
161    let bh = h.saturating_sub(220).min(240);
162    let bx = 60;
163    let by = 80;
164    vga::fill_rect(bx, by, bw, bh, RgbColor::new(0x1A, 0x22, 0x2C));
165    vga::draw_rect(bx, by, bw, bh, RgbColor::new(0x4F, 0xB3, 0xB3));
166    vga::fill_rect_alpha(
167        bx + 24,
168        by + 24,
169        bw.saturating_sub(48),
170        bh.saturating_sub(48),
171        RgbColor::new(0x7E, 0xC1, 0xFF),
172        96,
173    );
174    vga::set_clip_rect(
175        bx + 12,
176        by + 12,
177        bw.saturating_sub(24),
178        bh.saturating_sub(24),
179    );
180    vga::fill_rect(bx, by, bw, bh, RgbColor::new(0x1B, 0x4D, 0x8A));
181    vga::reset_clip_rect();
182
183    vga::draw_text(
184        bx + 18,
185        by + 16,
186        "GFX TEST: alpha / clip / text",
187        TextOptions {
188            fg: RgbColor::new(0xF5, 0xFA, 0xFF),
189            bg: RgbColor::new(0x1A, 0x22, 0x2C),
190            align: TextAlign::Left,
191            wrap: false,
192            max_width: Some(bw.saturating_sub(36)),
193        },
194    );
195
196    canvas.system_status_line(UiTheme::OCEAN_STATUS);
197    canvas.end_frame();
198    vga::set_text_cursor(0, vga::text_rows().saturating_sub(2));
199    shell_println!("gfx-test: rendered");
200    Ok(())
201}
202
203/// Performs the cmd gfx demo operation.
204pub fn cmd_gfx_demo(_args: &[String]) -> Result<(), ShellError> {
205    use vga::{
206        DockEdge, TerminalWidget, UiDockLayout, UiLabel, UiPanel, UiProgressBar, UiRect, UiTable,
207    };
208
209    if !vga::is_available() {
210        shell_println!("gfx-demo: framebuffer console unavailable");
211        return Ok(());
212    }
213
214    let mut layout = UiDockLayout::from_screen();
215    let top = layout.dock(DockEdge::Top, vga::ui_scale_px(56));
216    let bottom = layout.dock(DockEdge::Bottom, vga::ui_scale_px(120));
217    let left = layout.dock(DockEdge::Left, vga::ui_scale_px(360));
218    let _center = layout.remaining();
219
220    let theme = UiTheme::SLATE;
221    let canvas = vga::Canvas::new(theme.text, theme.background);
222    canvas.begin_frame();
223    canvas.ui_clear(theme);
224
225    vga::ui_draw_panel_widget(&UiPanel {
226        rect: top,
227        title: "Strat9 Graphics Console",
228        body: "Dock layout + widgets + terminal demo",
229        theme,
230    });
231
232    canvas.ui_label(&UiLabel {
233        rect: UiRect::new(
234            top.x + vga::ui_scale_px(8),
235            top.y + vga::ui_scale_px(30),
236            top.w.saturating_sub(vga::ui_scale_px(16)),
237            vga::ui_scale_px(24),
238        ),
239        text: "layout: top + bottom + left + center",
240        fg: RgbColor::new(0xD0, 0xE4, 0xFF),
241        bg: theme.panel_bg,
242        align: TextAlign::Left,
243    });
244
245    canvas.ui_panel(
246        left.x,
247        left.y,
248        left.w,
249        left.h,
250        "System",
251        "Progress bars and data table",
252        theme,
253    );
254
255    canvas.ui_progress_bar(UiProgressBar {
256        rect: UiRect::new(
257            left.x + vga::ui_scale_px(12),
258            left.y + vga::ui_scale_px(46),
259            left.w.saturating_sub(vga::ui_scale_px(24)),
260            vga::ui_scale_px(16),
261        ),
262        value: 72,
263        fg: RgbColor::new(0x58, 0xD6, 0xA3),
264        bg: RgbColor::new(0x12, 0x16, 0x1E),
265        border: theme.panel_border,
266    });
267
268    let headers = vec![
269        String::from("Metric"),
270        String::from("Value"),
271        String::from("Status"),
272    ];
273    let rows: Vec<Vec<String>> = vec![
274        vec![String::from("CPU"), String::from("72%"), String::from("ok")],
275        vec![
276            String::from("Memory"),
277            String::from("43%"),
278            String::from("ok"),
279        ],
280    ];
281    canvas.ui_table(&UiTable {
282        rect: UiRect::new(
283            left.x + vga::ui_scale_px(12),
284            left.y + vga::ui_scale_px(96),
285            left.w.saturating_sub(vga::ui_scale_px(24)),
286            left.h.saturating_sub(vga::ui_scale_px(108)),
287        ),
288        headers,
289        rows,
290        theme,
291    });
292
293    let mut term = TerminalWidget::new(bottom, 64);
294    term.title = String::from("Kernel Terminal");
295    term.push_ansi_line("\u{1b}[36m[boot]\u{1b}[0m ui widgets initialized");
296    term.draw();
297
298    canvas.system_status_line(UiTheme::OCEAN_STATUS);
299    canvas.end_frame();
300    let row = vga::text_rows().saturating_sub(2);
301    vga::set_text_cursor(0, row);
302    Ok(())
303}