mirror of
https://github.com/emilk/egui.git
synced 2026-09-03 15:20:05 -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:
@@ -147,7 +147,9 @@ impl<'a, State> Harness<'a, State> {
|
||||
response = app.run(ui, &mut state, false);
|
||||
});
|
||||
|
||||
renderer.handle_delta(&mut output.textures_delta);
|
||||
if let Some(pass_output) = &mut output.pass_output {
|
||||
renderer.handle_delta(&mut pass_output.textures_delta);
|
||||
}
|
||||
|
||||
let mut harness = Self {
|
||||
app,
|
||||
@@ -269,7 +271,9 @@ impl<'a, State> Harness<'a, State> {
|
||||
.take()
|
||||
.expect("AccessKit was disabled"),
|
||||
);
|
||||
self.renderer.handle_delta(&mut output.textures_delta);
|
||||
if let Some(pass_output) = &mut output.pass_output {
|
||||
self.renderer.handle_delta(&mut pass_output.textures_delta);
|
||||
}
|
||||
self.output = output;
|
||||
|
||||
self.handle_viewport_commands();
|
||||
@@ -630,7 +634,7 @@ impl<'a, State> Harness<'a, State> {
|
||||
/// This will add a [`RectShape`] to the output shapes, for the current frame.
|
||||
/// Will be overwritten on the next call to [`Self::run`].
|
||||
pub fn mask(&mut self, rect: Rect) {
|
||||
self.output.shapes.push(ClippedShape {
|
||||
self.pass_output_mut().shapes.push(ClippedShape {
|
||||
clip_rect: Rect::EVERYTHING,
|
||||
shape: Shape::Rect(RectShape::filled(rect, 0.0, Color32::MAGENTA)),
|
||||
});
|
||||
@@ -652,15 +656,20 @@ impl<'a, State> Harness<'a, State> {
|
||||
mouse_pos + egui::vec2(8.0, 16.0),
|
||||
];
|
||||
|
||||
output.shapes.push(ClippedShape {
|
||||
clip_rect: self.ctx.content_rect(),
|
||||
shape: egui::epaint::PathShape::convex_polygon(
|
||||
triangle,
|
||||
Color32::WHITE,
|
||||
egui::Stroke::new(1.0, Color32::BLACK),
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
output
|
||||
.pass_output
|
||||
.as_mut()
|
||||
.expect("Harness always runs a pass")
|
||||
.shapes
|
||||
.push(ClippedShape {
|
||||
clip_rect: self.ctx.content_rect(),
|
||||
shape: egui::epaint::PathShape::convex_polygon(
|
||||
triangle,
|
||||
Color32::WHITE,
|
||||
egui::Stroke::new(1.0, Color32::BLACK),
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
|
||||
self.renderer.render(&self.ctx, &output)
|
||||
@@ -677,18 +686,20 @@ impl<'a, State> Harness<'a, State> {
|
||||
/// Resize the harness to the last [`egui::ViewportCommand::InnerSize`] requested by the app
|
||||
/// during the last frame, if any.
|
||||
fn handle_inner_size(&mut self) {
|
||||
let new_inner_size =
|
||||
self.root_viewport_output()
|
||||
.commands
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|command| {
|
||||
if let egui::ViewportCommand::InnerSize(size) = command {
|
||||
Some(*size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let new_inner_size = self
|
||||
.output
|
||||
.viewport_commands
|
||||
.get(&ViewportId::ROOT)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.rev()
|
||||
.find_map(|command| {
|
||||
if let egui::ViewportCommand::InnerSize(size) = command {
|
||||
Some(*size)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(size) = new_inner_size {
|
||||
self.set_size(size);
|
||||
@@ -702,13 +713,13 @@ impl<'a, State> Harness<'a, State> {
|
||||
/// If a screenshot was requested and no renderer is available, an error will be logged.
|
||||
#[cfg(any(feature = "wgpu", feature = "snapshot"))]
|
||||
fn handle_screenshots(&mut self) {
|
||||
// Collect all screenshot requests from this frame's viewport output.
|
||||
// Collect all screenshot requests from this frame's viewport commands.
|
||||
let requests: Vec<(ViewportId, egui::UserData)> = self
|
||||
.output
|
||||
.viewport_output
|
||||
.viewport_commands
|
||||
.iter()
|
||||
.flat_map(|(id, viewport)| {
|
||||
viewport.commands.iter().filter_map(move |command| {
|
||||
.flat_map(|(id, commands)| {
|
||||
commands.iter().filter_map(move |command| {
|
||||
if let egui::ViewportCommand::Screenshot(user_data) = command {
|
||||
Some((*id, user_data.clone()))
|
||||
} else {
|
||||
@@ -749,11 +760,22 @@ impl<'a, State> Harness<'a, State> {
|
||||
/// Get the root viewport output
|
||||
fn root_viewport_output(&self) -> &egui::ViewportOutput {
|
||||
self.output
|
||||
.expect_pass()
|
||||
.viewport_output
|
||||
.get(&ViewportId::ROOT)
|
||||
.expect("Missing root viewport")
|
||||
}
|
||||
|
||||
/// The output of the last pass.
|
||||
///
|
||||
/// The harness always runs a pass each step, so this always exists.
|
||||
fn pass_output_mut(&mut self) -> &mut egui::PassOutput {
|
||||
self.output
|
||||
.pass_output
|
||||
.as_mut()
|
||||
.expect("Harness always runs a pass")
|
||||
}
|
||||
|
||||
/// The root node of the test harness.
|
||||
pub fn root(&self) -> Node<'_> {
|
||||
Node::new(
|
||||
|
||||
@@ -178,7 +178,8 @@ impl crate::TestRenderer for WgpuTestRenderer {
|
||||
size_in_pixels: [size.x.round() as u32, size.y.round() as u32],
|
||||
};
|
||||
|
||||
let tessellated = ctx.tessellate(output.shapes.clone(), ctx.pixels_per_point());
|
||||
let tessellated =
|
||||
ctx.tessellate(output.expect_pass().shapes.clone(), ctx.pixels_per_point());
|
||||
|
||||
let user_buffers = renderer.update_buffers(
|
||||
&self.render_state.device,
|
||||
|
||||
@@ -145,7 +145,9 @@ fn accesskit_output_single_egui_frame(run_ui: impl FnMut(&mut Ui)) -> TreeUpdate
|
||||
ctx.enable_accesskit();
|
||||
|
||||
let mut output = ctx.run_ui(RawInput::default(), run_ui);
|
||||
output.textures_delta.clear(); // Don't panic on drop with unapplied deltas
|
||||
if let Some(pass_output) = &mut output.pass_output {
|
||||
pass_output.textures_delta.clear(); // Don't panic on drop with unapplied deltas
|
||||
}
|
||||
|
||||
output
|
||||
.platform_output
|
||||
|
||||
@@ -402,7 +402,7 @@ pub fn horizontal_wrapped_text_should_not_overlap() {
|
||||
);
|
||||
}
|
||||
|
||||
for clipped in &harness.output().shapes {
|
||||
for clipped in &harness.output().expect_pass().shapes {
|
||||
if let egui::epaint::Shape::Text(text_shape) = &clipped.shape {
|
||||
let shape_rect = text_shape.visual_bounding_rect();
|
||||
assert!(
|
||||
@@ -638,7 +638,7 @@ fn window_fixed_size_is_outer_size() {
|
||||
}
|
||||
|
||||
let mut sizes = Vec::new();
|
||||
for clipped in &harness.output().shapes {
|
||||
for clipped in &harness.output().expect_pass().shapes {
|
||||
collect_filled_rect_sizes(&clipped.shape, &mut sizes);
|
||||
}
|
||||
|
||||
@@ -795,6 +795,7 @@ pub fn textedit_hint_text_should_follow_text_alignment() {
|
||||
// Find the hint text shape (the only text shape while the input is empty).
|
||||
let hint_shape = harness
|
||||
.output()
|
||||
.expect_pass()
|
||||
.shapes
|
||||
.iter()
|
||||
.find_map(|clipped| {
|
||||
|
||||
Reference in New Issue
Block a user