use egui_kittest::Harness;
use egui_kittest::wgpu::{WgpuTestRenderer, create_render_state, default_wgpu_setup};
use siplot::egui;
use siplot::egui::Color32;
use siplot::egui_wgpu::RenderState;
use siplot::{Box3D, Scene3dGeometry, SceneWidget, Vec3};
use std::cell::RefCell;
use std::rc::Rc;
const WIN: f32 = 320.0;
struct App {
scene: SceneWidget,
last_rect: Option<egui::Rect>,
}
impl App {
fn new(rs: &RenderState) -> Self {
let mut scene = SceneWidget::new(rs, 0);
scene.set_bounds(rs, (Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0)));
Self {
scene,
last_rect: None,
}
}
fn ui(&mut self, ui: &mut egui::Ui) {
let resp = self.scene.show(ui);
self.last_rect = Some(resp.rect);
}
}
fn classify(raw: &[u8], iw: usize, ih: usize) -> (usize, usize, usize, usize) {
let (mut red, mut green, mut blue, mut bg) = (0usize, 0usize, 0usize, 0usize);
for px in 0..(iw * ih) {
let i = px * 4;
let (r, g, b, a) = (raw[i], raw[i + 1], raw[i + 2], raw[i + 3]);
if r > 120 && g < 80 && b < 80 {
red += 1;
} else if g > 120 && r < 80 && b < 80 {
green += 1;
} else if b > 120 && r < 80 && g < 80 {
blue += 1;
} else if a == 255 && r < 60 && g < 60 && b < 60 && r.abs_diff(g).max(g.abs_diff(b)) <= 12 {
bg += 1;
}
}
(red, green, blue, bg)
}
#[test]
fn scene_widget_renders_axes_and_orbits_on_drag() {
let rs = create_render_state(default_wgpu_setup());
let app = Rc::new(RefCell::new(App::new(&rs)));
let renderer = WgpuTestRenderer::from_render_state(rs);
let app_ui = app.clone();
let mut harness = Harness::builder()
.with_size(egui::vec2(WIN, WIN))
.with_pixels_per_point(1.0)
.renderer(renderer)
.build_ui(move |ui| app_ui.borrow_mut().ui(ui));
harness.step();
let rect = app.borrow().last_rect.expect("scene rect");
let image1 = harness.render().expect("headless wgpu render");
let (iw, ih) = (image1.width() as usize, image1.height() as usize);
let frame1 = image1.as_raw().clone();
let (red, green, blue, bg) = classify(&frame1, iw, ih);
assert!(red > 0, "X axis (red) not visible");
assert!(green > 0, "Y axis (green) not visible");
assert!(blue > 0, "Z axis (blue) not visible");
assert!(
bg > iw * ih / 2,
"dark background should dominate; got {bg}/{} px",
iw * ih
);
let a = rect.center();
let b = a + egui::vec2(80.0, 0.0);
harness.hover_at(a);
harness.drag_at(a);
harness.step(); for k in 1..=4 {
harness.hover_at(a + (b - a) * (k as f32 / 4.0));
harness.step();
}
harness.drop_at(b); harness.step();
let image2 = harness.render().expect("headless wgpu render");
let frame2 = image2.as_raw();
let changed = frame1
.iter()
.zip(frame2.iter())
.filter(|(x, y)| x.abs_diff(**y) > 30)
.count();
assert!(
changed > 200,
"left-drag orbit should change the rendered image; only {changed} byte diffs"
);
}
struct ContentApp {
scene: SceneWidget,
last_rect: Option<egui::Rect>,
}
impl ContentApp {
fn new(rs: &RenderState) -> Self {
let mut scene = SceneWidget::new(rs, 1);
scene.set_bounds(rs, (Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0)));
let mut bx = Box3D::new();
bx.set_data(
[1.0, 1.0, 1.0],
&[Color32::from_rgb(255, 0, 255)],
&[[0.5, 0.5, 0.5]],
(0.0, [0.0, 0.0, 1.0]),
);
let mut g = Scene3dGeometry::new();
bx.append_to(&mut g);
scene.set_geometry(rs, g);
Self {
scene,
last_rect: None,
}
}
fn ui(&mut self, ui: &mut egui::Ui) {
let resp = self.scene.show(ui);
self.last_rect = Some(resp.rect);
}
}
#[test]
fn scene_widget_forwards_and_renders_content_mesh() {
let rs = create_render_state(default_wgpu_setup());
let app = Rc::new(RefCell::new(ContentApp::new(&rs)));
let renderer = WgpuTestRenderer::from_render_state(rs);
let app_ui = app.clone();
let mut harness = Harness::builder()
.with_size(egui::vec2(WIN, WIN))
.with_pixels_per_point(1.0)
.renderer(renderer)
.build_ui(move |ui| app_ui.borrow_mut().ui(ui));
harness.step();
let image = harness.render().expect("headless wgpu render");
let (iw, ih) = (image.width() as usize, image.height() as usize);
let raw = image.as_raw();
let magenta = (0..iw * ih)
.filter(|&px| {
let i = px * 4;
raw[i] > 120 && raw[i + 2] > 120 && raw[i + 1] < 80
})
.count();
assert!(
magenta > 200,
"the content box (magenta) must render through the widget; only {magenta} px"
);
}