From 7a105a18436eb93675bb3a649f481e75090c38eb Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Fri, 24 Apr 2026 11:52:11 +0200 Subject: [PATCH] Remove last_accesskit_update and add on_accesskit_update hook --- crates/egui_kittest/src/lib.rs | 12 ++---------- crates/egui_kittest/src/plugin.rs | 14 +++++++++++++- crates/egui_kittest/tests/plugin.rs | 12 ++++++------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 064db525b..fd755dafc 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -89,7 +89,6 @@ pub struct Harness<'a, State: 'static = ()> { plugins: Vec>>, entry_location: Option<&'static std::panic::Location<'static>>, consumed_event_locations: Vec<&'static std::panic::Location<'static>>, - last_accesskit_update: Option, #[cfg(feature = "snapshot")] default_snapshot_options: SnapshotOptions, @@ -168,7 +167,7 @@ impl<'a, State: 'static> Harness<'a, State> { app, ctx, input, - kittest: kittest::State::new(initial_accesskit.clone()), + kittest: kittest::State::new(initial_accesskit), output, response, state, @@ -181,7 +180,6 @@ impl<'a, State: 'static> Harness<'a, State> { plugins, entry_location: None, consumed_event_locations: Vec::new(), - last_accesskit_update: Some(initial_accesskit), #[cfg(feature = "snapshot")] default_snapshot_options, @@ -247,12 +245,6 @@ impl<'a, State: 'static> Harness<'a, State> { self._step_inner(false); } - /// The most recent AccessKit tree update, if any. Useful for plugins that mirror - /// the accessibility tree to an external debugger. - pub fn accesskit_tree_update(&self) -> Option<&egui::accesskit::TreeUpdate> { - self.last_accesskit_update.as_ref() - } - /// [`std::panic::Location`] of the most recent public `#[track_caller]` entry point /// (e.g. the caller of `step()` / `run()`), or `None` if no such call has been made yet. pub fn entry_location(&self) -> Option<&'static std::panic::Location<'static>> { @@ -378,7 +370,7 @@ impl<'a, State: 'static> Harness<'a, State> { .accesskit_update .take() .expect("AccessKit was disabled"); - self.last_accesskit_update = Some(accesskit_update.clone()); + self.dispatch(|p, h| p.on_accesskit_update(h, &accesskit_update)); self.kittest.update(accesskit_update); self.renderer.handle_delta(&output.textures_delta); self.output = output; diff --git a/crates/egui_kittest/src/plugin.rs b/crates/egui_kittest/src/plugin.rs index de335d051..9647d2291 100644 --- a/crates/egui_kittest/src/plugin.rs +++ b/crates/egui_kittest/src/plugin.rs @@ -36,7 +36,7 @@ use crate::{ExceededMaxStepsError, Harness}; /// Plugin hooks receive `&mut Harness`. Calling [`Harness::step`] / [`Harness::run`] / /// etc. from inside a hook will recurse infinitely through your own `after_step`. If /// a plugin needs to advance the harness from inside a hook — e.g. an inspector that -/// blocks on user input — use [`Harness::advance_frame`] instead. +/// blocks on user input — use [`Harness::step_no_side_effects`] instead. #[expect(unused_variables, reason = "default no-op impls")] pub trait Plugin: Send + Any { /// Called once at the start of every `run()` / `try_run()` / `try_run_realtime()` / @@ -58,6 +58,18 @@ pub trait Plugin: Send + Any { /// Called immediately after each single-frame step. fn after_step(&mut self, harness: &mut Harness<'_, State>) {} + /// Called after each single-frame step with the AccessKit tree update egui produced + /// for that frame, before it's applied to the internal kittest state. + /// + /// Plugins that need the tree (e.g. to stream it to an external debugger) should + /// clone it here — the harness no longer retains it after this hook returns. + fn on_accesskit_update( + &mut self, + harness: &mut Harness<'_, State>, + tree: &egui::accesskit::TreeUpdate, + ) { + } + /// Called after a queued event has been pushed into the harness input, before the /// frame runs that consumes it. fn on_event(&mut self, harness: &mut Harness<'_, State>, event: &egui::Event) {} diff --git a/crates/egui_kittest/tests/plugin.rs b/crates/egui_kittest/tests/plugin.rs index f81b1547e..0a4b406ca 100644 --- a/crates/egui_kittest/tests/plugin.rs +++ b/crates/egui_kittest/tests/plugin.rs @@ -119,20 +119,20 @@ fn on_event_fires_per_event() { assert_eq!(events, 2, "expected 2 on_event calls, got log: {log:?}"); } -/// `advance_frame` does NOT fire `before_step`/`after_step`. +/// `step_no_side_effects` does NOT fire `before_step`/`after_step`. #[test] -fn advance_frame_skips_hooks() { +fn step_no_side_effects_skips_hooks() { struct DrivingPlugin { log: Log, drove: bool, } - impl Plugin for DrivingPlugin { + impl Plugin for DrivingPlugin { fn after_step(&mut self, h: &mut Harness<'_, S>) { self.log.lock().unwrap().push("after_step".into()); if !self.drove { self.drove = true; - // Call advance_frame from inside a hook — must not recurse. - h.advance_frame(); + // Call step_no_side_effects from inside a hook — must not recurse. + h.step_no_side_effects(); } } } @@ -152,7 +152,7 @@ fn advance_frame_skips_hooks() { let log = log.lock().unwrap(); // Exactly one after_step from the user's step(), plus any from construction-time run_ok - // (cleared above). advance_frame must NOT have produced another after_step. + // (cleared above). step_no_side_effects must NOT have produced another after_step. assert_eq!(log.iter().filter(|s| s == &"after_step").count(), 1); }