use std::cell::RefCell;
use std::rc::Rc;
use egui_kittest::Harness;
use egui_kittest::wgpu::{WgpuTestRenderer, create_render_state, default_wgpu_setup};
use siplot::egui;
use siplot::{PlotInteractionMode, PlotWidget, Roi, RoiDrawKind, RulerToolButton, YAxis};
fn harness() -> (Rc<RefCell<PlotWidget>>, Harness<'static>) {
let rs = create_render_state(default_wgpu_setup());
siplot::install(&rs);
let mut plot = PlotWidget::new(&rs, 0);
let x: Vec<f64> = (0..=10).map(|i| i as f64).collect();
plot.add_curve(&x, &x, egui::Color32::WHITE);
plot.set_limits(0.0, 10.0, 0.0, 10.0, None);
let app = Rc::new(RefCell::new(plot));
let app_ui = app.clone();
let renderer = WgpuTestRenderer::from_render_state(rs.clone());
let mut harness = Harness::builder()
.with_size(egui::vec2(400.0, 400.0))
.with_pixels_per_point(1.0)
.renderer(renderer)
.build_ui(move |ui| {
app_ui.borrow_mut().show(ui);
});
harness.step();
harness.step();
(app, harness)
}
fn drag(harness: &mut Harness<'static>, p0: egui::Pos2, p1: egui::Pos2) {
harness.drag_at(p0);
harness.step();
for t in [0.0f32, 0.25, 0.5, 0.75, 1.0] {
harness.hover_at(p0 + (p1 - p0) * t);
harness.step();
}
harness.drop_at(p1);
harness.step();
harness.step();
}
fn px(app: &Rc<RefCell<PlotWidget>>, x: f64, y: f64) -> egui::Pos2 {
app.borrow()
.data_to_pixel(x, y, YAxis::Left)
.expect("transform cached after a frame")
}
#[test]
fn arming_the_ruler_enters_a_line_draw() {
let (app, _harness) = harness();
assert!(!app.borrow().ruler_active());
app.borrow_mut().set_ruler_active(true);
assert!(app.borrow().ruler_active());
assert_eq!(
app.borrow().interaction_mode(),
PlotInteractionMode::RoiCreate(RoiDrawKind::Line),
"arming the ruler must enter a line-ROI draw"
);
assert_eq!(app.borrow().ruler_roi(), None);
}
#[test]
fn dragging_draws_a_line_roi_labeled_with_its_distance() {
let (app, mut harness) = harness();
app.borrow_mut().set_ruler_active(true);
let p0 = px(&app, 1.0, 1.0);
let p1 = px(&app, 4.0, 5.0);
drag(&mut harness, p0, p1);
assert_eq!(app.borrow().rois().len(), 1);
let index = app.borrow().ruler_roi().expect("a ruler line was drawn");
assert_eq!(index, 0);
let roi = app.borrow().rois()[index].clone();
let Roi::Line { start, end } = roi.roi else {
panic!("the ruler ROI must be a line, got {:?}", roi.roi);
};
let expected = RulerToolButton::distance_text([start.0, start.1], [end.0, end.1]);
assert_eq!(roi.name, expected, "ruler label must equal its own length");
assert!(
(1.0..2.5).contains(&start.0) && (1.0..2.5).contains(&start.1),
"ruler start should sit near the (1,1) press, got {start:?}"
);
assert!(
(3.5..4.5).contains(&end.0) && (4.5..5.5).contains(&end.1),
"ruler end should sit near the (4,5) drop, got {end:?}"
);
}
#[test]
fn a_second_drag_replaces_the_ruler_line() {
let (app, mut harness) = harness();
app.borrow_mut().set_ruler_active(true);
drag(&mut harness, px(&app, 1.0, 1.0), px(&app, 4.0, 5.0));
assert_eq!(app.borrow().rois().len(), 1);
let first_name = app.borrow().rois()[0].name.clone();
drag(&mut harness, px(&app, 0.0, 0.0), px(&app, 8.0, 0.0));
assert_eq!(
app.borrow().rois().len(),
1,
"a new measurement must replace the previous ruler line, not accumulate"
);
let index = app.borrow().ruler_roi().expect("ruler line present");
let roi = app.borrow().rois()[index].clone();
let Roi::Line { start, end } = roi.roi else {
panic!("ruler ROI must be a line");
};
let expected = RulerToolButton::distance_text([start.0, start.1], [end.0, end.1]);
assert_eq!(roi.name, expected);
assert_ne!(roi.name, first_name);
}
#[test]
fn disarming_removes_the_ruler_line_and_restores_the_mode() {
let (app, mut harness) = harness();
assert_eq!(app.borrow().interaction_mode(), PlotInteractionMode::Zoom);
app.borrow_mut().set_ruler_active(true);
drag(&mut harness, px(&app, 1.0, 1.0), px(&app, 4.0, 5.0));
assert_eq!(app.borrow().rois().len(), 1);
app.borrow_mut().set_ruler_active(false);
assert!(!app.borrow().ruler_active());
assert_eq!(app.borrow().ruler_roi(), None);
assert_eq!(
app.borrow().rois().len(),
0,
"disarming the ruler must remove the ruler line"
);
assert_eq!(
app.borrow().interaction_mode(),
PlotInteractionMode::Zoom,
"disarming must restore the pre-ruler interaction mode"
);
}