1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 22:30:03 -04:00

Allow controlling the tested app

This commit is contained in:
lucasmerlin
2026-04-20 13:10:48 +02:00
parent 06a82eff65
commit b7da254b16
7 changed files with 210 additions and 74 deletions

View File

@@ -92,15 +92,15 @@ impl Inspector {
}
/// Send the current frame + accesskit tree and block until the inspector replies.
/// Returns silently on send/receive failure (e.g. the inspector window was closed).
/// Returns any user events captured by the inspector (empty on failure / no control input).
pub fn send_step(
&mut self,
image: &image::RgbaImage,
pixels_per_point: f32,
accesskit: Option<accesskit::TreeUpdate>,
) {
) -> Vec<egui::Event> {
if self.broken {
return;
return Vec::new();
}
self.step = self.step.saturating_add(1);
let frame = Frame {
@@ -118,16 +118,17 @@ impl Inspector {
eprintln!("egui_kittest inspector: send failed: {err}");
}
self.broken = true;
return;
return Vec::new();
}
match read_message::<_, InspectorReply>(&mut self.reader) {
Ok(InspectorReply::Continue) => {}
Ok(InspectorReply::Continue { events }) => events,
Err(err) => {
#[expect(clippy::print_stderr)]
{
eprintln!("egui_kittest inspector: read failed: {err}");
}
self.broken = true;
Vec::new()
}
}
}

View File

@@ -316,6 +316,22 @@ impl<'a, State> Harness<'a, State> {
/// Run a single step. This will not process any events.
fn _step(&mut self, sizing_pass: bool) {
self._step_inner(sizing_pass);
#[cfg(feature = "recording")]
self.capture_frame_if_recording(false);
// Inspector Control mode: each event the user triggers in the inspector drives one
// extra `_step_inner` so the UI re-renders, but the outer test caller stays parked
// inside this method until the inspector replies with an empty event list
// (i.e. user clicked Next/Play, or has Control off and we're just forwarding).
#[cfg(feature = "inspector")]
self.drive_inspector();
}
/// The core of `_step`: run egui once and update internal state. Does not touch the
/// inspector or the recording hook, so it can be called in a loop from those.
fn _step_inner(&mut self, sizing_pass: bool) {
self.input.predicted_dt = self.step_dt;
let mut output = self.ctx.run_ui(self.input.take(), |ui| {
@@ -333,12 +349,6 @@ impl<'a, State> Harness<'a, State> {
self.kittest.update(accesskit_update);
self.renderer.handle_delta(&output.textures_delta);
self.output = output;
#[cfg(feature = "recording")]
self.capture_frame_if_recording(false);
#[cfg(feature = "inspector")]
self.send_to_inspector_if_attached();
}
/// Calculate the rect that includes all popups and tooltips.
@@ -812,25 +822,47 @@ impl<'a, State> Harness<'a, State> {
self.inspector = None;
}
/// Block at the inspector until it tells us to resume, re-rendering after each batch of
/// events it sends. Events drive an internal `_step_inner` (and recording capture), but
/// do NOT return control to the outer test — the test advances only when the inspector
/// replies with no events *and* egui has no pending repaint (i.e. user hit Next/Play with
/// a settled UI, or Control mode is off).
///
/// While paused in Control mode, any `request_repaint` from the app (animations, async
/// image loads, etc.) also drives another internal step so the user sees the UI tick.
#[cfg(feature = "inspector")]
fn send_to_inspector_if_attached(&mut self) {
fn drive_inspector(&mut self) {
if self.inspector.is_none() {
return;
}
let image = match self.render() {
Ok(img) => img,
Err(err) => {
#[expect(clippy::print_stderr)]
{
eprintln!("egui_kittest inspector: render failed, skipping frame: {err}");
loop {
let image = match self.render() {
Ok(img) => img,
Err(err) => {
#[expect(clippy::print_stderr)]
{
eprintln!("egui_kittest inspector: render failed: {err}");
}
return;
}
};
let tree = self.last_accesskit_update.clone();
let ppp = self.ctx.pixels_per_point();
let events = if let Some(inspector) = self.inspector.as_mut() {
inspector.send_step(&image, ppp, tree)
} else {
return;
};
let wants_repaint = self.root_viewport_output().repaint_delay == Duration::ZERO;
if events.is_empty() && !wants_repaint {
return;
}
};
let tree = self.last_accesskit_update.clone();
let ppp = self.ctx.pixels_per_point();
if let Some(inspector) = self.inspector.as_mut() {
inspector.send_step(&image, ppp, tree);
for event in events {
self.input.events.push(event);
}
self._step_inner(false);
#[cfg(feature = "recording")]
self.capture_frame_if_recording(false);
}
}