Skip to content

Commit c9bd736

Browse files
ryanbreenclaude
andcommitted
feat(graphics): add graphics demo showing all primitives
Add a visual demonstration of the graphics stack that runs during kernel boot in interactive mode: - demo.rs: Draws colorful shapes and text showcasing: - Filled and outlined rectangles in various colors - Circles (filled and outlined, concentric) - Radiating lines in a color gradient - Diagonal line patterns - Text in multiple colors (red, green, blue, yellow) - Highlighted text with background color - Multi-line text rendering - Hooks into kernel_main() after double buffer upgrade - Displays for 3 seconds before clearing and continuing boot - Only runs with --features interactive Also fixes Glyph::pixels() to use rasterized.raster() directly and adds double_buffer_mut() accessor to ShellFrameBuffer. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fd4fda9 commit c9bd736

File tree

5 files changed

+239
-8
lines changed

5 files changed

+239
-8
lines changed

kernel/src/graphics/demo.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//! Graphics demonstration module.
2+
//!
3+
//! Provides a visual demo of the graphics stack capabilities
4+
//! that runs during kernel boot.
5+
6+
use super::font::Font;
7+
use super::primitives::{
8+
draw_circle, draw_line, draw_rect, draw_text, fill_circle, fill_rect, Canvas, Color, Rect,
9+
TextStyle,
10+
};
11+
12+
/// Run a graphics demonstration on the given canvas.
13+
///
14+
/// This draws various shapes and text to showcase the graphics stack.
15+
pub fn run_demo(canvas: &mut impl Canvas) {
16+
let width = canvas.width() as i32;
17+
let height = canvas.height() as i32;
18+
19+
// Clear to a dark blue background
20+
fill_rect(
21+
canvas,
22+
Rect {
23+
x: 0,
24+
y: 0,
25+
width: width as u32,
26+
height: height as u32,
27+
},
28+
Color::rgb(20, 30, 50),
29+
);
30+
31+
// Draw a header banner
32+
fill_rect(
33+
canvas,
34+
Rect {
35+
x: 0,
36+
y: 0,
37+
width: width as u32,
38+
height: 60,
39+
},
40+
Color::rgb(40, 80, 120),
41+
);
42+
43+
// Title text
44+
let title_style = TextStyle::new()
45+
.with_color(Color::WHITE)
46+
.with_font(Font::default_font());
47+
48+
draw_text(canvas, 20, 20, "Breenix Graphics Stack Demo", &title_style);
49+
50+
// Draw colorful rectangles
51+
let colors = [
52+
Color::RED,
53+
Color::GREEN,
54+
Color::BLUE,
55+
Color::rgb(255, 255, 0), // Yellow
56+
Color::rgb(255, 0, 255), // Magenta
57+
Color::rgb(0, 255, 255), // Cyan
58+
];
59+
60+
let box_width = 80;
61+
let box_height = 60;
62+
let start_x = 50;
63+
let start_y = 100;
64+
65+
for (i, &color) in colors.iter().enumerate() {
66+
let x = start_x + (i as i32 % 3) * (box_width + 20);
67+
let y = start_y + (i as i32 / 3) * (box_height + 20);
68+
69+
// Filled rectangle
70+
fill_rect(
71+
canvas,
72+
Rect {
73+
x,
74+
y,
75+
width: box_width as u32,
76+
height: box_height as u32,
77+
},
78+
color,
79+
);
80+
81+
// White border
82+
draw_rect(
83+
canvas,
84+
Rect {
85+
x: x - 2,
86+
y: y - 2,
87+
width: (box_width + 4) as u32,
88+
height: (box_height + 4) as u32,
89+
},
90+
Color::WHITE,
91+
);
92+
}
93+
94+
// Draw circles section
95+
let circle_y = start_y + 180;
96+
let circle_text_style = TextStyle::new().with_color(Color::rgb(200, 200, 200));
97+
98+
draw_text(canvas, 50, circle_y, "Circles:", &circle_text_style);
99+
100+
// Filled circles
101+
fill_circle(canvas, 100, circle_y + 60, 30, Color::rgb(255, 100, 100));
102+
fill_circle(canvas, 180, circle_y + 60, 25, Color::rgb(100, 255, 100));
103+
fill_circle(canvas, 250, circle_y + 60, 20, Color::rgb(100, 100, 255));
104+
105+
// Circle outlines
106+
draw_circle(canvas, 340, circle_y + 60, 35, Color::WHITE);
107+
draw_circle(canvas, 340, circle_y + 60, 25, Color::rgb(255, 200, 0));
108+
draw_circle(canvas, 340, circle_y + 60, 15, Color::rgb(255, 100, 0));
109+
110+
// Draw lines section
111+
let lines_y = circle_y + 130;
112+
draw_text(canvas, 50, lines_y, "Lines:", &circle_text_style);
113+
114+
// Draw radiating lines (pre-computed approximate directions)
115+
// Using 12 directions at 30-degree increments
116+
let center_x = 150;
117+
let center_y = lines_y + 60;
118+
let radius = 50i32;
119+
120+
// Pre-computed (cos, sin) * 100 for angles 0, 30, 60, ..., 330 degrees
121+
let directions: [(i32, i32); 12] = [
122+
(100, 0), // 0°
123+
(87, 50), // 30°
124+
(50, 87), // 60°
125+
(0, 100), // 90°
126+
(-50, 87), // 120°
127+
(-87, 50), // 150°
128+
(-100, 0), // 180°
129+
(-87, -50), // 210°
130+
(-50, -87), // 240°
131+
(0, -100), // 270°
132+
(50, -87), // 300°
133+
(87, -50), // 330°
134+
];
135+
136+
for (i, (dx, dy)) in directions.iter().enumerate() {
137+
let end_x = center_x + (radius * dx) / 100;
138+
let end_y = center_y + (radius * dy) / 100;
139+
let intensity = ((i as u32 * 255) / 12) as u8;
140+
let color = Color::rgb(255, intensity, 255 - intensity);
141+
draw_line(canvas, center_x, center_y, end_x, end_y, color);
142+
}
143+
144+
// Draw diagonal lines
145+
for i in 0..10 {
146+
let x1 = 280 + i * 8;
147+
let color = Color::rgb(50 + i as u8 * 20, 100 + i as u8 * 15, 200);
148+
draw_line(canvas, x1, lines_y + 20, x1 + 60, lines_y + 100, color);
149+
}
150+
151+
// Text rendering showcase
152+
let text_y = lines_y + 140;
153+
draw_text(canvas, 50, text_y, "Text Rendering:", &circle_text_style);
154+
155+
// Different colored text
156+
let red_style = TextStyle::new().with_color(Color::RED);
157+
let green_style = TextStyle::new().with_color(Color::GREEN);
158+
let blue_style = TextStyle::new().with_color(Color::BLUE);
159+
let yellow_style = TextStyle::new().with_color(Color::rgb(255, 255, 0));
160+
161+
draw_text(canvas, 50, text_y + 30, "Red Text", &red_style);
162+
draw_text(canvas, 150, text_y + 30, "Green Text", &green_style);
163+
draw_text(canvas, 270, text_y + 30, "Blue Text", &blue_style);
164+
165+
// Text with background
166+
let bg_style = TextStyle::new()
167+
.with_color(Color::BLACK)
168+
.with_background(Color::rgb(255, 255, 0));
169+
170+
draw_text(canvas, 50, text_y + 60, " Highlighted Text ", &bg_style);
171+
172+
// Multi-line text
173+
let multiline_style = TextStyle::new().with_color(Color::rgb(180, 180, 255));
174+
draw_text(
175+
canvas,
176+
50,
177+
text_y + 95,
178+
"Multi-line text:\n Line 1\n Line 2\n Line 3",
179+
&multiline_style,
180+
);
181+
182+
// Footer
183+
let footer_style = TextStyle::new().with_color(Color::rgb(100, 100, 100));
184+
draw_text(
185+
canvas,
186+
50,
187+
height - 40,
188+
"Phase 4: Text Rendering Complete!",
189+
&footer_style,
190+
);
191+
192+
// Draw a decorative border
193+
draw_rect(
194+
canvas,
195+
Rect {
196+
x: 10,
197+
y: 70,
198+
width: (width - 20) as u32,
199+
height: (height - 120) as u32,
200+
},
201+
Color::rgb(60, 100, 140),
202+
);
203+
}

kernel/src/graphics/font.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,11 @@ impl Glyph {
155155
self.rasterized.height()
156156
}
157157

158-
/// Get the raster data as rows of intensity values (0-255).
159-
/// Each row is a slice of bytes, one per pixel column.
160-
pub fn raster(&self) -> &[[u8; 8]] {
161-
// noto-sans-mono-bitmap returns fixed-width arrays
162-
self.rasterized.raster()
163-
}
164-
165158
/// Iterate over the glyph pixels with coordinates and intensity.
166159
/// Yields (x, y, intensity) for each pixel.
167160
pub fn pixels(&self) -> impl Iterator<Item = (usize, usize, u8)> + '_ {
168161
let width = self.width();
169-
self.raster().iter().enumerate().flat_map(move |(y, row)| {
162+
self.rasterized.raster().iter().enumerate().flat_map(move |(y, row)| {
170163
row.iter()
171164
.take(width)
172165
.enumerate()

kernel/src/graphics/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! Provides framebuffer abstractions used by the kernel graphics stack.
44
5+
pub mod demo;
56
pub mod double_buffer;
67
pub mod font;
78
pub mod primitives;

kernel/src/logger.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ impl ShellFrameBuffer {
141141
}
142142
}
143143

144+
/// Get mutable access to the double buffer (if available).
145+
///
146+
/// Returns None if double buffering has not been enabled yet.
147+
pub fn double_buffer_mut(&mut self) -> Option<&mut DoubleBufferedFrameBuffer> {
148+
self.double_buffer.as_mut()
149+
}
150+
144151
/// Clear the framebuffer (fill with black)
145152
pub fn clear(&mut self) {
146153
self.x_pos = BORDER_PADDING;

kernel/src/main.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,33 @@ fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
180180
#[cfg(feature = "interactive")]
181181
logger::upgrade_to_double_buffer();
182182

183+
// Run graphics demo to showcase the graphics stack
184+
#[cfg(feature = "interactive")]
185+
{
186+
log::info!("Running graphics demo...");
187+
if let Some(fb) = logger::SHELL_FRAMEBUFFER.get() {
188+
let mut fb_guard = fb.lock();
189+
graphics::demo::run_demo(&mut *fb_guard);
190+
// Flush the demo to screen (access double buffer directly)
191+
if let Some(db) = fb_guard.double_buffer_mut() {
192+
db.flush_full();
193+
}
194+
log::info!("Graphics demo complete - display showing for 3 seconds");
195+
}
196+
// Wait 3 seconds so user can see the demo
197+
for _ in 0..30 {
198+
// Simple delay loop (approximately 100ms each iteration)
199+
for _ in 0..10_000_000 {
200+
core::hint::spin_loop();
201+
}
202+
}
203+
// Clear and continue to normal boot
204+
if let Some(fb) = logger::SHELL_FRAMEBUFFER.get() {
205+
let mut fb_guard = fb.lock();
206+
fb_guard.clear();
207+
}
208+
}
209+
183210
// Phase 0: Log kernel layout inventory
184211
memory::layout::log_kernel_layout();
185212

0 commit comments

Comments
 (0)