Skip to content

Commit 70e7488

Browse files
committed
docs: add grand showcase GIF demonstrating rich controls, 2D grid layout, and Bézier curves
1 parent ebef364 commit 70e7488

4 files changed

Lines changed: 94 additions & 21 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ gui.render(&mut display)?;
5050

5151
## Visual Showcase
5252

53+
### Grand Showcase: Rich Controls, 2D Grid Layout & Vector Bézier Curves
54+
![Grand showcase of rich controls, 2D GridLayout, tables, graduated scales, spinboxes, and Bézier curves](docs/screenshots/rich_controls_grid_showcase.gif)
55+
5356
### Accelerated Graphics Pipeline & Frosted Glass Blur
5457
![Frosted glass and graphics pipeline showcase](docs/screenshots/frosted_glass_pipeline.gif)
5558

56.4 KB
Loading

examples/basics/rich_controls_grid_showcase.rs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,19 @@ fn render_showcase<D: DrawTarget<Color = Rgb565> + embedded_gui::PixelRead>(
8181
// Left Column: Spinbox (Top) + Linear Scale (Bottom)
8282
let left_area = cells[1];
8383
let spinbox_rect = Rect::new(left_area.x, left_area.y, left_area.w, 40);
84-
spinbox.render(
84+
85+
let mut anim_spinbox = *spinbox;
86+
anim_spinbox.focused_digit = ((frame / 20) % 4) as u8;
87+
anim_spinbox.value = 2400 + ((frame as i32 * 7) % 600);
88+
anim_spinbox.render(
8589
&mut ctx,
8690
spinbox_rect,
8791
Style::panel().into(),
8892
embedded_gui::VisualState::Normal,
8993
)?;
9094

9195
let lin_scale_rect = Rect::new(left_area.x, left_area.y + 46, left_area.w, 54);
92-
let needle_val = 20.0 + ((frame as f32 * 0.5).sin() * 20.0);
96+
let needle_val = 25.0 + ((frame as f32 * 0.08).sin() * 20.0);
9397
let lin_scale = ScaleWidget::linear_horizontal(0.0, 50.0, needle_val)
9498
.with_ticks(4, 2)
9599
.with_needle(true, Rgb565::CSS_CYAN);
@@ -103,15 +107,20 @@ fn render_showcase<D: DrawTarget<Color = Rgb565> + embedded_gui::PixelRead>(
103107
// Right Column: Table (Top) + Radial Speedometer (Bottom)
104108
let right_area = cells[2];
105109
let table_rect = Rect::new(right_area.x, right_area.y, right_area.w, 60);
106-
table.render(
110+
111+
let mut anim_table = *table;
112+
let sel_row = ((frame / 25) % 2) as usize;
113+
let sel_col = ((frame / 15) % 3) as usize;
114+
anim_table.selected = Some((sel_row, sel_col));
115+
anim_table.render(
107116
&mut ctx,
108117
table_rect,
109118
Style::panel().into(),
110119
embedded_gui::VisualState::Normal,
111120
)?;
112121

113122
let radial_rect = Rect::new(right_area.x + 30, right_area.y + 64, 90, 44);
114-
let speed_val = 60.0 + ((frame as f32 * 0.1).sin() * 40.0);
123+
let speed_val = 60.0 + ((frame as f32 * 0.06).sin() * 45.0);
115124
let radial_scale = ScaleWidget::new(0.0, 120.0, speed_val)
116125
.with_ticks(6, 2)
117126
.with_angles(180, 0)
@@ -164,7 +173,53 @@ fn render_showcase<D: DrawTarget<Color = Rgb565> + embedded_gui::PixelRead>(
164173
Ok(())
165174
}
166175

176+
fn record_frames() {
177+
let out_dir = std::path::Path::new("target/controls_frames");
178+
let _ = std::fs::create_dir_all(out_dir);
179+
180+
let mut fb = Framebuffer::<FB_SIZE>::new(W, H);
181+
let data: &[&[&str]] = &[
182+
&["Sensor 1", "24.5 C", "OK"],
183+
&["Sensor 2", "58.2 %", "HIGH"],
184+
];
185+
let headers: &[&str] = &["Device", "Value", "State"];
186+
let table = TableWidget::new(data).with_headers(headers);
187+
let spinbox = SpinboxWidget::new(0, 9999, 2500)
188+
.with_digits(4)
189+
.with_decimals(2);
190+
191+
let total_frames = 90;
192+
println!(
193+
"Recording {} frames to target/controls_frames...",
194+
total_frames
195+
);
196+
197+
for f in 0..total_frames {
198+
render_showcase(&mut fb, f, &table, &spinbox).unwrap();
199+
200+
let mut rgb888 = Vec::with_capacity((W * H * 3) as usize);
201+
for p in fb.pixels() {
202+
let r = (p.r() << 3) | (p.r() >> 2);
203+
let g = (p.g() << 2) | (p.g() >> 4);
204+
let b = (p.b() << 3) | (p.b() >> 2);
205+
rgb888.push(r);
206+
rgb888.push(g);
207+
rgb888.push(b);
208+
}
209+
210+
let filename = out_dir.join(format!("frame_{:03}.raw", f));
211+
std::fs::write(filename, &rgb888).unwrap();
212+
}
213+
println!("Frame recording complete!");
214+
}
215+
167216
fn main() {
217+
let args: Vec<String> = std::env::args().collect();
218+
if args.iter().any(|a| a == "--record-gif") {
219+
record_frames();
220+
return;
221+
}
222+
168223
println!("=== embedded-gui: Rich Controls & Grid Layout Showcase ===");
169224

170225
let res = std::panic::catch_unwind(|| {

scripts/generate_screenshots.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,11 @@
1010

1111
W, H = 320, 240
1212
REPO_ROOT = Path(__file__).resolve().parent.parent
13-
FRAMES_DIR = REPO_ROOT / "target" / "pipeline_frames"
14-
OUTPUT_GIF = REPO_ROOT / "docs" / "screenshots" / "frosted_glass_pipeline.gif"
1513

16-
def main():
17-
print("1. Running showcase in record-frames mode...")
18-
subprocess.run(
19-
["cargo", "run", "--example", "graphics_pipeline_showcase", "--", "--record-gif"],
20-
cwd=REPO_ROOT,
21-
check=True,
22-
)
23-
24-
print("2. Compiling raw frames into animated GIF...")
25-
raw_files = sorted(FRAMES_DIR.glob("frame_*.raw"))
14+
def compile_gif(frames_dir: Path, output_gif: Path, duration_ms: int = 33):
15+
raw_files = sorted(frames_dir.glob("frame_*.raw"))
2616
if not raw_files:
27-
print("Error: No recorded frames found!")
17+
print(f"Error: No recorded frames found in {frames_dir}!")
2818
return
2919

3020
images = []
@@ -33,16 +23,41 @@ def main():
3323
img = Image.frombytes("RGB", (W, H), raw_bytes)
3424
images.append(img)
3525

36-
OUTPUT_GIF.parent.mkdir(parents=True, exist_ok=True)
26+
output_gif.parent.mkdir(parents=True, exist_ok=True)
3727
images[0].save(
38-
OUTPUT_GIF,
28+
output_gif,
3929
save_all=True,
4030
append_images=images[1:],
41-
duration=33, # ~30 fps
31+
duration=duration_ms,
4232
loop=0,
4333
optimize=True,
4434
)
45-
print(f"-> Successfully generated {OUTPUT_GIF} ({len(images)} frames, {os.path.getsize(OUTPUT_GIF)} bytes)")
35+
print(f"-> Successfully generated {output_gif} ({len(images)} frames, {os.path.getsize(output_gif)} bytes)")
36+
37+
def main():
38+
# 1. Pipeline & frosted glass showcase
39+
print("1. Generating frosted glass & pipeline showcase GIF...")
40+
subprocess.run(
41+
["cargo", "run", "--example", "graphics_pipeline_showcase", "--", "--record-gif"],
42+
cwd=REPO_ROOT,
43+
check=True,
44+
)
45+
compile_gif(
46+
REPO_ROOT / "target" / "pipeline_frames",
47+
REPO_ROOT / "docs" / "screenshots" / "frosted_glass_pipeline.gif",
48+
)
49+
50+
# 2. Rich controls & 2D GridLayout showcase
51+
print("\n2. Generating rich controls, 2D GridLayout & Bézier showcase GIF...")
52+
subprocess.run(
53+
["cargo", "run", "--example", "rich_controls_grid_showcase", "--", "--record-gif"],
54+
cwd=REPO_ROOT,
55+
check=True,
56+
)
57+
compile_gif(
58+
REPO_ROOT / "target" / "controls_frames",
59+
REPO_ROOT / "docs" / "screenshots" / "rich_controls_grid_showcase.gif",
60+
)
4661

4762
if __name__ == "__main__":
4863
main()

0 commit comments

Comments
 (0)