1use 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
31pub fn cmd_gfx(args: &[String]) -> Result<(), ShellError> {
33 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
151pub 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
228pub 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 const R1: f32 = 2.00; const R2: f32 = 0.80; const THETA_N: usize = 50; const PHI_N: usize = 100; const DOT_SIZE: usize = 2; let theta_step = 2.0 * PI / THETA_N as f32;
257 let phi_step = 2.0 * PI / PHI_N as f32;
258
259 const PALETTE: [RgbColor; 16] = [
261 RgbColor::new(0x10, 0x48, 0xA0), RgbColor::new(0x18, 0x60, 0xB0),
263 RgbColor::new(0x20, 0x7A, 0xC0),
264 RgbColor::new(0x30, 0x94, 0xD0),
265 RgbColor::new(0x40, 0xAE, 0xD0), RgbColor::new(0x50, 0xC8, 0xC0),
267 RgbColor::new(0x60, 0xD8, 0xA0), RgbColor::new(0x80, 0xE0, 0x80), RgbColor::new(0xA0, 0xE0, 0x60),
270 RgbColor::new(0xC0, 0xD0, 0x50), RgbColor::new(0xD0, 0xB0, 0x40), RgbColor::new(0xE0, 0x90, 0x30), RgbColor::new(0xE0, 0x70, 0x30),
274 RgbColor::new(0xE0, 0x50, 0x30), RgbColor::new(0xC0, 0x30, 0x30), RgbColor::new(0x90, 0x20, 0x30), ];
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; let rot_b = frame as f32 * 0.015; 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 let x = (R1 + R2 * ct) * cp;
312 let y = (R1 + R2 * ct) * sp;
313 let z = R2 * st;
314
315 let x1 = x;
317 let y1 = y * ca - z * sa;
318 let z1 = y * sa + z * ca;
319
320 let x2 = x1 * cb - y1 * sb;
322 let y2 = x1 * sb + y1 * cb;
323 let z2 = z1;
324
325 let nx = ct * cp;
327 let ny = ct * sp;
328 let nz = st;
329 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 let lum = nx2 * 0.3 + ny2 * 0.5 + nz2 * 0.8;
339 if lum > 0.0 {
341 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
377pub 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}