diff --git a/Cargo.toml b/Cargo.toml index 0b64a82ed3..0a4eb6b3fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3813,6 +3813,15 @@ doc-scrape-examples = true [package.metadata.example.minimizing] hidden = true +[[example]] +name = "desktop_request_redraw" +path = "tests/window/desktop_request_redraw.rs" +doc-scrape-examples = true +required-features = ["bevy_dev_tools"] + +[package.metadata.example.desktop_request_redraw] +hidden = true + [[example]] name = "window_resizing" path = "examples/window/window_resizing.rs" diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 97c26db7d8..e066929852 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -552,16 +552,11 @@ impl ApplicationHandler for WinitAppRunnerState { impl WinitAppRunnerState { fn redraw_requested(&mut self, event_loop: &ActiveEventLoop) { let mut redraw_event_reader = EventCursor::::default(); + let mut close_event_reader = EventCursor::::default(); let mut focused_windows_state: SystemState<(Res, Query<(Entity, &Window)>)> = SystemState::new(self.world_mut()); - if let Some(app_redraw_events) = self.world().get_resource::>() { - if redraw_event_reader.read(app_redraw_events).last().is_some() { - self.redraw_requested = true; - } - } - let (config, windows) = focused_windows_state.get(self.world()); let focused = windows.iter().any(|(_, window)| window.focused); @@ -671,6 +666,26 @@ impl WinitAppRunnerState { self.redraw_requested = true; } + // Read RequestRedraw events that may have been sent during the update + if let Some(app_redraw_events) = self.world().get_resource::>() { + if redraw_event_reader.read(app_redraw_events).last().is_some() { + self.redraw_requested = true; + } + } + + // Running the app may have produced WindowCloseRequested events that should be processed + if let Some(close_request_events) = + self.world().get_resource::>() + { + if close_event_reader + .read(close_request_events) + .last() + .is_some() + { + self.redraw_requested = true; + } + } + // Running the app may have changed the WinitSettings resource, so we have to re-extract it. let (config, windows) = focused_windows_state.get(self.world()); let focused = windows.iter().any(|(_, window)| window.focused); diff --git a/tests/window/desktop_request_redraw.rs b/tests/window/desktop_request_redraw.rs new file mode 100644 index 0000000000..61c5ee5c66 --- /dev/null +++ b/tests/window/desktop_request_redraw.rs @@ -0,0 +1,109 @@ +//! Desktop request redraw +use bevy::{ + dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin}, + prelude::*, + window::RequestRedraw, + winit::WinitSettings, +}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugins(MeshPickingPlugin) + // Enable the FPS overlay with a high resolution refresh interval. This makes it + // easier to validate that UpdateMode is behaving correctly when desktop_app is used. + // The FPS counter should essentially pause when the cube is not rotating and should + // update rapidly when the cube is rotating or there is input (e.g. moving the mouse). + // + // Left and Right clicking the cube should roggle rotation on/off. + .add_plugins(FpsOverlayPlugin { + config: FpsOverlayConfig { + text_config: TextFont { + font_size: 12.0, + ..default() + }, + text_color: Color::srgb(0.0, 1.0, 0.0), + refresh_interval: core::time::Duration::from_millis(16), + ..default() + }, + }) + .insert_resource(WinitSettings::desktop_app()) + .add_systems(Startup, setup) + .add_systems(Update, (update, redraw.after(update))) + .run(); +} + +#[derive(Component)] +struct AnimationActive; + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(0.0, 5.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y), + )); + + commands.spawn(( + PointLight { + intensity: 1e6, + ..Default::default() + }, + Transform::from_xyz(-1.0, 5.0, 1.0), + )); + + let node = Node { + display: Display::Block, + padding: UiRect::all(Val::Px(10.0)), + row_gap: Val::Px(10.0), + ..Default::default() + }; + + commands.spawn(( + node.clone(), + children![ + ( + node.clone(), + children![Text::new("Right click cube to pause animation")] + ), + ( + node.clone(), + children![Text::new("Left click cube to start animation")] + ) + ], + )); + + commands + .spawn(( + Mesh3d(meshes.add(Cuboid::from_length(1.0))), + MeshMaterial3d(materials.add(Color::WHITE)), + AnimationActive, + )) + .observe( + |trigger: Trigger>, mut commands: Commands| match trigger.button { + PointerButton::Primary => { + commands.entity(trigger.target()).insert(AnimationActive); + } + PointerButton::Secondary => { + commands + .entity(trigger.target()) + .remove::(); + } + _ => {} + }, + ); +} + +fn update(time: Res