mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
Better test
This commit is contained in:
@@ -1738,7 +1738,7 @@ impl Context {
|
|||||||
///
|
///
|
||||||
/// This is `true` when the integration runs a pass only to keep app logic ticking,
|
/// This is `true` when the integration runs a pass only to keep app logic ticking,
|
||||||
/// e.g. because the window is minimized or occluded.
|
/// e.g. because the window is minimized or occluded.
|
||||||
/// Skip any ui work if this is `true`.
|
/// Don't show any ui while this is `true`.
|
||||||
///
|
///
|
||||||
/// See [`RawInput::uiless_pass`].
|
/// See [`RawInput::uiless_pass`].
|
||||||
pub fn is_uiless_pass(&self) -> bool {
|
pub fn is_uiless_pass(&self) -> bool {
|
||||||
@@ -2642,7 +2642,6 @@ impl ContextImpl {
|
|||||||
let pixels_per_point = viewport.input.pixels_per_point;
|
let pixels_per_point = viewport.input.pixels_per_point;
|
||||||
|
|
||||||
// If no ui was shown this pass we skip all book-keeping that assumes ui was shown.
|
// If no ui was shown this pass we skip all book-keeping that assumes ui was shown.
|
||||||
// See `RawInput::uiless_pass`.
|
|
||||||
let uiless_pass = viewport.input.raw.uiless_pass;
|
let uiless_pass = viewport.input.raw.uiless_pass;
|
||||||
|
|
||||||
if !uiless_pass {
|
if !uiless_pass {
|
||||||
@@ -4371,106 +4370,6 @@ fn warn_if_rect_changes_id(
|
|||||||
mod test {
|
mod test {
|
||||||
use super::Context;
|
use super::Context;
|
||||||
|
|
||||||
/// A pass with [`RawInput::uiless_pass`] set must leave all ui state alone,
|
|
||||||
/// so that nothing thinks it was hidden. See <https://github.com/emilk/egui/issues/8266>.
|
|
||||||
#[test]
|
|
||||||
fn test_uiless_pass_preserves_ui_state() {
|
|
||||||
use crate::{Id, LayerId, RawInput, Window};
|
|
||||||
|
|
||||||
let ctx = Context::default();
|
|
||||||
let window_layer = std::cell::Cell::new(LayerId::background());
|
|
||||||
|
|
||||||
let run = |uiless_pass: bool| {
|
|
||||||
let input = RawInput {
|
|
||||||
uiless_pass,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
ctx.run_ui(input, |ui| {
|
|
||||||
if uiless_pass {
|
|
||||||
return; // The integration shows no ui during a uiless pass.
|
|
||||||
}
|
|
||||||
let response = Window::new("My window")
|
|
||||||
.show(ui.ctx(), |ui| {
|
|
||||||
ui.button("Click me").request_focus();
|
|
||||||
})
|
|
||||||
.expect("The window should be open");
|
|
||||||
window_layer.set(response.response.layer_id);
|
|
||||||
})
|
|
||||||
.drop_without_applying_deltas();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Two normal passes, so that all the "previous pass" state has settled:
|
|
||||||
run(false);
|
|
||||||
run(false);
|
|
||||||
|
|
||||||
let focused = ctx.memory(|m| m.focused());
|
|
||||||
assert!(focused.is_some(), "The button should have focus");
|
|
||||||
assert!(ctx.memory(|m| m.areas().visible_last_frame(&window_layer.get())));
|
|
||||||
|
|
||||||
let popup_id = Id::new("My popup");
|
|
||||||
ctx.memory_mut(|m| m.open_popup(popup_id));
|
|
||||||
|
|
||||||
let pass_nr = ctx.cumulative_pass_nr();
|
|
||||||
let frame_nr = ctx.cumulative_frame_nr();
|
|
||||||
|
|
||||||
// A uiless pass must disturb none of it:
|
|
||||||
run(true);
|
|
||||||
|
|
||||||
assert_eq!(ctx.cumulative_pass_nr(), pass_nr, "Counted as a pass");
|
|
||||||
assert_eq!(ctx.cumulative_frame_nr(), frame_nr, "Counted as a frame");
|
|
||||||
assert_eq!(ctx.memory(|m| m.focused()), focused, "Lost focus");
|
|
||||||
assert!(
|
|
||||||
ctx.memory(|m| m.areas().visible_last_frame(&window_layer.get())),
|
|
||||||
"The window would replay its appear-animation"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
ctx.memory(|m| m.is_popup_open(popup_id)),
|
|
||||||
"Closed the popup"
|
|
||||||
);
|
|
||||||
|
|
||||||
// …and the window is still there on the next normal pass:
|
|
||||||
run(false);
|
|
||||||
assert!(ctx.memory(|m| m.areas().visible_last_frame(&window_layer.get())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A uiless pass never calls [`Context::show_viewport_deferred`], so it must not
|
|
||||||
/// garbage-collect child viewports — they would pop back up on the next normal pass.
|
|
||||||
/// See <https://github.com/emilk/egui/issues/8266>.
|
|
||||||
#[test]
|
|
||||||
fn test_uiless_pass_keeps_deferred_viewports() {
|
|
||||||
use crate::{RawInput, ViewportBuilder, ViewportId};
|
|
||||||
|
|
||||||
let ctx = Context::default();
|
|
||||||
ctx.set_embed_viewports(false);
|
|
||||||
|
|
||||||
let child_id = ViewportId::from_hash_of("My child viewport");
|
|
||||||
|
|
||||||
// Runs one pass and returns the viewports the backend is told to keep.
|
|
||||||
let run = |uiless_pass: bool| {
|
|
||||||
let input = RawInput {
|
|
||||||
uiless_pass,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let output = ctx.run_ui(input, |ui| {
|
|
||||||
if uiless_pass {
|
|
||||||
return; // The integration shows no ui during a uiless pass.
|
|
||||||
}
|
|
||||||
ui.ctx().show_viewport_deferred(
|
|
||||||
child_id,
|
|
||||||
ViewportBuilder::default(),
|
|
||||||
|_ui, _class| {},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
let has_child = output.viewport_output.contains_key(&child_id);
|
|
||||||
output.drop_without_applying_deltas();
|
|
||||||
has_child
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(run(false), "The child viewport should have been created");
|
|
||||||
assert!(run(true), "The child viewport was closed");
|
|
||||||
assert!(run(false), "The child viewport should still be there");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_pass() {
|
fn test_single_pass() {
|
||||||
let ctx = Context::default();
|
let ctx = Context::default();
|
||||||
|
|||||||
@@ -797,10 +797,13 @@ impl Memory {
|
|||||||
|
|
||||||
self.options.begin_pass(new_raw_input);
|
self.options.begin_pass(new_raw_input);
|
||||||
|
|
||||||
let focus = self.focus.entry(self.viewport_id).or_default();
|
|
||||||
if !new_raw_input.uiless_pass {
|
if !new_raw_input.uiless_pass {
|
||||||
// No widget will ask for focus this pass, so leave the focus state alone.
|
// No widget will ask for focus during a uiless pass,
|
||||||
focus.begin_pass(new_raw_input);
|
// so leave the focus state alone.
|
||||||
|
self.focus
|
||||||
|
.entry(self.viewport_id)
|
||||||
|
.or_default()
|
||||||
|
.begin_pass(new_raw_input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -259,16 +259,18 @@ impl<'a, State> Harness<'a, State> {
|
|||||||
fn _step(&mut self, sizing_pass: bool) {
|
fn _step(&mut self, sizing_pass: bool) {
|
||||||
self.input.predicted_dt = self.step_dt;
|
self.input.predicted_dt = self.step_dt;
|
||||||
|
|
||||||
|
let uiless_pass = self.input.uiless_pass;
|
||||||
|
|
||||||
let mut output = self.ctx.run_ui(self.input.take(), |ui| {
|
let mut output = self.ctx.run_ui(self.input.take(), |ui| {
|
||||||
self.response = self.app.run(ui, &mut self.state, sizing_pass);
|
self.response = self.app.run(ui, &mut self.state, sizing_pass);
|
||||||
});
|
});
|
||||||
self.kittest.update(
|
if let Some(accesskit_update) = output.platform_output.accesskit_update.take() {
|
||||||
output
|
self.kittest.update(accesskit_update);
|
||||||
.platform_output
|
} else {
|
||||||
.accesskit_update
|
// A uiless pass shows no ui, so there is no accessibility tree to update.
|
||||||
.take()
|
// Keep the tree from the last pass that did show ui.
|
||||||
.expect("AccessKit was disabled"),
|
assert!(uiless_pass, "AccessKit was disabled");
|
||||||
);
|
}
|
||||||
self.renderer.handle_delta(&mut output.textures_delta);
|
self.renderer.handle_delta(&mut output.textures_delta);
|
||||||
self.output = output;
|
self.output = output;
|
||||||
|
|
||||||
|
|||||||
@@ -559,3 +559,92 @@ fn tooltip_should_hand_over_to_neighboring_widget() {
|
|||||||
"Tooltip A should be hidden when hovering Button B"
|
"Tooltip A should be hidden when hovering Button B"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An integration runs a pass with [`egui::RawInput::uiless_pass`] set when its window is
|
||||||
|
/// hidden, minimized, or occluded, so that app logic keeps ticking without showing any ui.
|
||||||
|
///
|
||||||
|
/// Such a pass must leave all ui state alone. Otherwise areas think they were hidden and
|
||||||
|
/// replay their fade-in, popups close, focus is lost, and child viewports pop back up.
|
||||||
|
/// See <https://github.com/emilk/egui/issues/8266>.
|
||||||
|
#[test]
|
||||||
|
fn uiless_pass_should_not_disturb_ui_state() {
|
||||||
|
const MENU: &str = "My menu";
|
||||||
|
const MENU_ITEM: &str = "Button in my menu";
|
||||||
|
const FOCUSED_BUTTON: &str = "Click me";
|
||||||
|
|
||||||
|
let child_viewport = egui::ViewportId::from_hash_of("My child viewport");
|
||||||
|
let area_id = egui::Id::new("My area");
|
||||||
|
let area_layer = egui::LayerId::new(egui::Order::Middle, area_id);
|
||||||
|
|
||||||
|
let mut harness = Harness::builder()
|
||||||
|
.with_size(Vec2::new(400.0, 300.0))
|
||||||
|
.build_ui(move |ui| {
|
||||||
|
// A backend that can open real windows, like eframe:
|
||||||
|
ui.ctx().set_embed_viewports(false);
|
||||||
|
|
||||||
|
if ui.ctx().is_uiless_pass() {
|
||||||
|
// The integration shows no ui and viewports during a uiless pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.ctx()
|
||||||
|
.show_viewport_deferred(child_viewport, Default::default(), |_ui, _class| {});
|
||||||
|
|
||||||
|
ui.menu_button(MENU, |ui| {
|
||||||
|
_ = ui.button(MENU_ITEM);
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::Area::new(area_id)
|
||||||
|
.fixed_pos((150.0, 120.0))
|
||||||
|
.show(ui.ctx(), |ui| {
|
||||||
|
_ = ui.button(FOCUSED_BUTTON);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
harness.get_by_label(MENU).click();
|
||||||
|
harness.run();
|
||||||
|
// Nothing asks for focus again, so the test fails if egui ever loses it:
|
||||||
|
harness.get_by_label(FOCUSED_BUTTON).focus();
|
||||||
|
harness.run();
|
||||||
|
|
||||||
|
let assert_state = |harness: &Harness<'_>| {
|
||||||
|
assert!(
|
||||||
|
harness
|
||||||
|
.get_by_label(FOCUSED_BUTTON)
|
||||||
|
.accesskit_node()
|
||||||
|
.is_focused(),
|
||||||
|
"The button lost focus"
|
||||||
|
);
|
||||||
|
harness.get_by_label(MENU_ITEM); // Panics if the menu closed
|
||||||
|
assert!(
|
||||||
|
harness
|
||||||
|
.output()
|
||||||
|
.viewport_output
|
||||||
|
.contains_key(&child_viewport),
|
||||||
|
"The child viewport closed"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
harness
|
||||||
|
.ctx
|
||||||
|
.memory(|m| m.areas().visible_last_frame(&area_layer)),
|
||||||
|
"Area state reset"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_state(&harness);
|
||||||
|
|
||||||
|
// The window is now occluded, so the integration runs passes without any ui.
|
||||||
|
// egui keeps the accessibility tree from the last pass that did show ui,
|
||||||
|
// so we can still query it.
|
||||||
|
harness.input_mut().uiless_pass = true;
|
||||||
|
harness.step();
|
||||||
|
harness.step();
|
||||||
|
|
||||||
|
assert_state(&harness);
|
||||||
|
|
||||||
|
// The window is visible again, and everything should be where we left it:
|
||||||
|
harness.input_mut().uiless_pass = false;
|
||||||
|
harness.run();
|
||||||
|
|
||||||
|
assert_state(&harness);
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ impl eframe::App for App {
|
|||||||
|
|
||||||
fn viewport_info(ctx: &egui::Context) -> String {
|
fn viewport_info(ctx: &egui::Context) -> String {
|
||||||
ctx.input(|i| {
|
ctx.input(|i| {
|
||||||
|
use std::fmt::Write as _;
|
||||||
let ViewportInfo {
|
let ViewportInfo {
|
||||||
minimized,
|
minimized,
|
||||||
focused,
|
focused,
|
||||||
@@ -56,12 +57,10 @@ fn viewport_info(ctx: &egui::Context) -> String {
|
|||||||
];
|
];
|
||||||
for (name, value) in flags {
|
for (name, value) in flags {
|
||||||
if let Some(value) = value {
|
if let Some(value) = value {
|
||||||
use std::fmt::Write as _;
|
|
||||||
write!(s, " {name}={value}").ok();
|
write!(s, " {name}={value}").ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::fmt::Write as _;
|
|
||||||
write!(s, " uiless_pass={}", i.raw.uiless_pass).ok();
|
write!(s, " uiless_pass={}", i.raw.uiless_pass).ok();
|
||||||
|
|
||||||
s
|
s
|
||||||
|
|||||||
Reference in New Issue
Block a user