mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Untangle logic-only frames: one frame entry point, one output type
Same fixes as the previous commits (run no egui pass at all when
nothing will be shown), but the mechanisms are shared instead of
duplicated:
* `Context::run_frame(input, show_ui, f)` is the one entry point for
integrations: one `FramePhase::Logic` (always, outside any pass),
then one `FramePhase::Ui` per pass (none when `show_ui` is false).
`Context::run_logic` is now a thin wrapper around it, and the
logic-outside-of-pass sequencing lives in egui, not in each backend.
* egui itself buffers the input of pass-less frames
(`ViewportState::pending_raw_input`) and prepends it to the next
pass. This replaces `EpiIntegration::pending_raw_input` and the web
backend's `input.raw.append`, so an integration cannot lose input.
* `FullOutput` is now `{ platform_output, viewport_commands,
pass_output: Option<PassOutput> }`, and `LogicOutput` is gone.
One-shot viewport commands (imperative) are separated from
`ViewportOutput` (which viewports should exist - declarative), so
each backend has exactly one command-handling path, shared by frames
with and without a pass. `pass_output: None` encodes "no pass ran:
paint nothing, leave the viewports alone" in the type.
* The glow/wgpu `!show_ui` early-return blocks are gone: a hidden root
viewport flows through the same tail as a visible one, with the
paint and viewport-structure steps gated on `pass_output`.
Behavioral fixes that fall out:
* `App::logic` now sees the current window state in visible frames
too (it was one frame stale outside the hidden path).
* Auto-save keeps working while a window is minimized or occluded.
* Commands sent to a freshly created viewport apply in the same frame.
* The Wayland resize workaround now also covers commands from
pass-less frames.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -264,7 +264,7 @@ fn interact_on_ui_response_should_be_stable() {
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn has_red_warning_rect(output: &egui::FullOutput) -> bool {
|
||||
output.shapes.iter().any(|clipped| {
|
||||
output.expect_pass().shapes.iter().any(|clipped| {
|
||||
matches!(
|
||||
&clipped.shape,
|
||||
Shape::Rect(rect_shape)
|
||||
@@ -631,7 +631,7 @@ fn run_logic_should_not_disturb_ui_state() {
|
||||
.or_default()
|
||||
.occluded = Some(true);
|
||||
|
||||
let output = harness.ctx.run_logic(&raw_input, |ctx| {
|
||||
let output = harness.ctx.run_logic(raw_input, |ctx| {
|
||||
assert_eq!(
|
||||
ctx.input(|i| i.viewport().occluded),
|
||||
Some(true),
|
||||
@@ -642,6 +642,10 @@ fn run_logic_should_not_disturb_ui_state() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Focus);
|
||||
});
|
||||
|
||||
assert!(
|
||||
output.pass_output.is_none(),
|
||||
"No pass was run, so there should be no pass output"
|
||||
);
|
||||
assert_eq!(
|
||||
output
|
||||
.viewport_commands
|
||||
@@ -660,3 +664,55 @@ fn run_logic_should_not_disturb_ui_state() {
|
||||
|
||||
assert_state(&harness);
|
||||
}
|
||||
|
||||
/// Input given to [`egui::Context::run_logic`] is not consumed by any pass,
|
||||
/// so egui must keep it and feed it to the next pass.
|
||||
#[test]
|
||||
fn run_logic_should_buffer_input_for_the_next_pass() {
|
||||
let ctx = egui::Context::default();
|
||||
|
||||
let mut raw_input = egui::RawInput::default();
|
||||
raw_input.events.push(egui::Event::Key {
|
||||
key: egui::Key::A,
|
||||
physical_key: None,
|
||||
pressed: true,
|
||||
repeat: false,
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
});
|
||||
|
||||
let logic_output = ctx.run_logic(raw_input, |_ctx| {});
|
||||
logic_output.drop_without_applying_deltas();
|
||||
|
||||
let ui_output = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
assert!(
|
||||
ui.input(|i| i.key_pressed(egui::Key::A)),
|
||||
"The key press from the pass-less frame should reach the next pass"
|
||||
);
|
||||
});
|
||||
ui_output.drop_without_applying_deltas();
|
||||
}
|
||||
|
||||
/// The logic phase of [`egui::Context::run_frame`] should see the current window state,
|
||||
/// also in frames where ui is shown (and not the state of the previous frame).
|
||||
#[test]
|
||||
fn logic_phase_should_see_fresh_window_state() {
|
||||
let ctx = egui::Context::default();
|
||||
|
||||
let mut raw_input = egui::RawInput::default();
|
||||
raw_input
|
||||
.viewports
|
||||
.entry(egui::ViewportId::ROOT)
|
||||
.or_default()
|
||||
.focused = Some(true);
|
||||
|
||||
let output = ctx.run_frame(raw_input, true, |phase| {
|
||||
if let egui::FramePhase::Logic(ctx) = phase {
|
||||
assert_eq!(
|
||||
ctx.input(|i| i.viewport().focused),
|
||||
Some(true),
|
||||
"App logic should see the current window state, not last frame's"
|
||||
);
|
||||
}
|
||||
});
|
||||
output.drop_without_applying_deltas();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user