diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 2bd80cecbc3..51c0cadce2a 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -661,13 +661,14 @@ impl<'app> GlowWinitRunning<'app> { { for action in viewport.actions_requested.drain() { match action { - ActionRequested::Screenshot => { + ActionRequested::Screenshot(user_data) => { let screenshot = painter.read_screen_rgba(screen_size_in_pixels); egui_winit .egui_input_mut() .events .push(egui::Event::Screenshot { viewport_id, + user_data, image: screenshot.into(), }); } diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 997383f85c3..d13bed0bf41 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -643,10 +643,16 @@ impl<'app> WgpuWinitRunning<'app> { let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); - let screenshot_requested = viewport - .actions_requested - .take(&ActionRequested::Screenshot) - .is_some(); + let mut screenshot_commands = vec![]; + viewport.actions_requested.retain(|cmd| { + if let ActionRequested::Screenshot(info) = cmd { + screenshot_commands.push(info.clone()); + false + } else { + true + } + }); + let screenshot_requested = !screenshot_commands.is_empty(); let (vsync_secs, screenshot) = painter.paint_and_update_textures( viewport_id, pixels_per_point, @@ -655,19 +661,32 @@ impl<'app> WgpuWinitRunning<'app> { &textures_delta, screenshot_requested, ); - if let Some(screenshot) = screenshot { - egui_winit - .egui_input_mut() - .events - .push(egui::Event::Screenshot { - viewport_id, - image: screenshot.into(), - }); + match (screenshot_requested, screenshot) { + (false, None) => {} + (true, Some(screenshot)) => { + let screenshot = Arc::new(screenshot); + for user_data in screenshot_commands { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + user_data, + image: screenshot.clone(), + }); + } + } + (true, None) => { + log::error!("Bug in egui_wgpu: screenshot requested, but no screenshot was taken"); + } + (false, Some(_)) => { + log::warn!("Bug in egui_wgpu: Got screenshot without requesting it"); + } } for action in viewport.actions_requested.drain() { match action { - ActionRequested::Screenshot => { + ActionRequested::Screenshot { .. } => { // already handled above } ActionRequested::Cut => { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index a78339d01fe..1cb2d502c5d 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1301,7 +1301,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option { - actions_requested.insert(ActionRequested::Screenshot); + ViewportCommand::Screenshot(user_data) => { + actions_requested.insert(ActionRequested::Screenshot(user_data)); } ViewportCommand::RequestCut => { actions_requested.insert(ActionRequested::Cut); diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index a1b9783280e..7987ea61225 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -529,6 +529,10 @@ pub enum Event { /// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`]. Screenshot { viewport_id: crate::ViewportId, + + /// Whatever was passed to [`crate::ViewportCommand::Screenshot`]. + user_data: crate::UserData, + image: std::sync::Arc, }, } diff --git a/crates/egui/src/data/mod.rs b/crates/egui/src/data/mod.rs index bfe1e8a327d..f6f267dd06a 100644 --- a/crates/egui/src/data/mod.rs +++ b/crates/egui/src/data/mod.rs @@ -3,5 +3,7 @@ pub mod input; mod key; pub mod output; +mod user_data; pub use key::Key; +pub use user_data::UserData; diff --git a/crates/egui/src/data/user_data.rs b/crates/egui/src/data/user_data.rs new file mode 100644 index 00000000000..1d7a35cd95c --- /dev/null +++ b/crates/egui/src/data/user_data.rs @@ -0,0 +1,57 @@ +use std::{any::Any, sync::Arc}; + +/// A wrapper around `dyn Any`, used for passing custom user data +/// to [`crate::ViewportCommand::Screenshot`]. +#[derive(Clone, Debug, Default)] +pub struct UserData { + /// A user value given to the screenshot command, + /// that will be returned in [`crate::Event::Screenshot`]. + pub data: Option>, +} + +impl UserData { + /// You can also use [`Self::default`]. + pub fn new(user_info: impl Any + Send + Sync) -> Self { + Self { + data: Some(Arc::new(user_info)), + } + } +} + +impl PartialEq for UserData { + fn eq(&self, other: &Self) -> bool { + match (&self.data, &other.data) { + (Some(a), Some(b)) => Arc::ptr_eq(a, b), + (None, None) => true, + _ => false, + } + } +} + +impl Eq for UserData {} + +impl std::hash::Hash for UserData { + fn hash(&self, state: &mut H) { + self.data.as_ref().map(Arc::as_ptr).hash(state); + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for UserData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + ().serialize(serializer) // can't serialize an `Any` + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for UserData { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::default()) // can't serialize an `Any` + } +} diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 866d20fc64d..18b99c69a1a 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -471,7 +471,7 @@ pub use self::{ output::{ self, CursorIcon, FullOutput, OpenUrl, PlatformOutput, UserAttentionType, WidgetInfo, }, - Key, + Key, UserData, }, drag_and_drop::DragAndDrop, epaint::text::TextWrapMode, diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 31cbc623e39..d8b26429c77 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1058,8 +1058,8 @@ pub enum ViewportCommand { /// Take a screenshot. /// - /// The results are returned in `crate::Event::Screenshot`. - Screenshot, + /// The results are returned in [`crate::Event::Screenshot`]. + Screenshot(crate::UserData), /// Request cut of the current selection /// @@ -1100,6 +1100,8 @@ impl ViewportCommand { } } +// ---------------------------------------------------------------------------- + /// Describes a viewport, i.e. a native window. /// /// This is returned by [`crate::Context::run`] on each frame, and should be applied diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 88b57f20e7a..1dd0bbf5076 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -45,7 +45,7 @@ impl eframe::App for MyApp { if ui.button("save to 'top_left.png'").clicked() { self.save_to_file = true; - ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot(Default::default())); } ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| { @@ -58,9 +58,13 @@ impl eframe::App for MyApp { } else { ctx.set_theme(egui::Theme::Light); }; - ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd( + egui::ViewportCommand::Screenshot(Default::default()), + ); } else if ui.button("take screenshot!").clicked() { - ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd( + egui::ViewportCommand::Screenshot(Default::default()), + ); } }); });