ui: clear scroll replay tracking on root ui consumption (#6146)

This commit is contained in:
batistalucasv committed 2026-11-11 13:57:51 -03:00
1 parent 1c3256ab65
commit d6c68cbfbb
4 files changed
+52 -2

No files matched your search

+1
View File
@@ -422,6 +422,7 @@ impl WordApp {
/// Lay out the windows: on the desktop each open document's in its own native window
/// ([`windows`]), on the web the one window.
pub fn ui(&mut self, ui: &mut egui::Ui) {
self.clear_raw_scroll_replay();
if self.windows.native() {
self.windows_ui(ui);
} else {
-2
View File
@@ -8,8 +8,6 @@ impl WordApp {
// a layout anyone will see. The shot's `issued` flag makes repeated final passes harmless.
if !ctx.will_discard() {
control::issue_screenshots(self, ctx);
self.raw_scroll_events_scaled = 0;
self.last_scaled_scroll_events.clear();
}
// The raw input was consumed by this host frame's passes; only a final UI pass proves
// the step is done. A discarded pass keeps the queue for the next UI frame.
+6
View File
@@ -69,6 +69,12 @@ impl WordApp {
self.last_scaled_scroll_events = raw.events.clone();
}
/// Clears recorded scroll replay tracking when the UI pass consumes the frame's input.
pub(crate) fn clear_raw_scroll_replay(&mut self) {
self.raw_scroll_events_scaled = 0;
self.last_scaled_scroll_events.clear();
}
/// Whether this pass's input came from the control channel (`ui.click`, `ui.key`, `ui.drag`,
/// `ui.text`…, which MCP's `click`, `key` and `type` tools send). Decisions that are only the
/// user's ignore it: letting agent messages through again, and answering an agent's question
+45
View File
@@ -8277,3 +8277,48 @@ fn touchpad_point_scrolling_is_scaled_once_across_hidden_logic_ticks() {
assert_eq!(delta.y, expected_fresh, "fresh input is scaled once");
}
}
/// Issue #6146 / PR #6167 review: replay tracking must be cleared when a warm-up UI pass
/// consumes the input (which returns before `flush_shot_and_synthetic`), so a fresh event
/// on the next frame whose delta happens to equal the previous scaled delta is not falsely
/// treated as a replay.
#[test]
fn fresh_scroll_event_matching_previous_scaled_delta_is_scaled_after_warmup_ui() {
let mut a = app();
let ctx = egui::Context::default();
let wheel_event = |y: f32| egui::Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
delta: egui::vec2(0.0, y),
phase: egui::TouchPhase::Move,
modifiers: egui::Modifiers::NONE,
source: egui::MouseWheelSource::Unknown,
};
// Ensure the app is in the warm-up phase (fonts_frames < 2).
a.fonts_frames = 0;
// 1. First frame: incoming Point delta is -1.0. At Normal speed on Wayland, it scales to -4.0.
let mut raw1 = egui::RawInput { events: vec![wheel_event(-1.0)], ..Default::default() };
a.raw_input_hook(&ctx, &mut raw1);
let factor = a.ui.touchpad_scroll_speed.touchpad_factor();
let scaled_1 = -1.0 * factor;
assert_eq!(raw1.events[0], wheel_event(scaled_1));
// 2. Warm-up UI pass runs: window_ui returns early because fonts_frames < 2.
// This UI pass consumes raw1, so replay tracking must be cleared.
ctx.run_ui(raw1, |ui| {
a.ui(ui);
})
.drop_without_applying_deltas();
assert_eq!(a.fonts_frames, 1, "first warm-up frame executed");
// 3. Second frame: fresh incoming event happens to have delta equal to scaled_1 (-4.0).
// Because the previous frame's input was consumed by the UI, this is fresh input,
// NOT a replay, so it must be scaled by factor (e.g. -4.0 * 4.0 = -16.0).
let mut raw2 = egui::RawInput { events: vec![wheel_event(scaled_1)], ..Default::default() };
a.raw_input_hook(&ctx, &mut raw2);
let expected_2 = scaled_1 * factor;
if let egui::Event::MouseWheel { delta, .. } = &raw2.events[0] {
assert_eq!(delta.y, expected_2, "fresh event matching previous scaled delta must be scaled, not skipped as replay");
}
}