Add `ViewportCommand::RequestCut`, `RequestCopy` and `RequestPaste` to trigger Clipboard actions (#4035)
### Motivation We want to offer our users a context menu with `Cut`, `Copy` and `Paste` actions. `Paste` is not possible out of the box. ### Changes This PR adds `ViewportCommand::RequestCut`, `ViewportCommand::RequestCopy` and `ViewportCommand::RequestPaste`. They are routed and handled after the example of `ViewportCommand::Screenshot` and result in the same code being executed as when the user uses `CTRL+V` style keyboard commands. ### Reasoning In our last release we used an instance of `egui_winit:📋:Clipboard` in order to get the `Paste` functionality. However Linux users on Wayland complained about broken clipboard interaction (https://github.com/mikedilger/gossip/issues/617). After a while of digging I could not find the issue although I have found references to problems with multiple clipboards per handle before (https://gitlab.gnome.org/GNOME/mutter/-/issues/1250) but I compared mutter with weston and the problem occured on both. So to solve this I set out to extend egui to access the clipboard instance already present in egui_winit. Since there was no trivial way to reach the instance of `egui_winit::State` I felt the best approach was to follow the logic of the new `ViewportCommand::Screenshot`. ### Variations It could make sense to make the introduced `enum ActionRequested` a part of crates/egui/src/viewport.rs and to then wrap them into one single `ViewportCommand::ActionRequest(ActionRequested)`. ### Example ```Rust let mut text = String::new(); let response = ui.text_edit_singleline(&mut text); if ui.button("Paste").clicked() { response.request_focus(); ui.ctx().send_viewport_cmd(ViewportCommand::RequestPaste); } ``` --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
87b294534e
commit
a2f1ca31a0
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant};
|
||||
|
||||
use egui_winit::ActionRequested;
|
||||
use glutin::{
|
||||
config::GlConfig,
|
||||
context::NotCurrentGlContext,
|
||||
|
|
@ -22,8 +23,9 @@ use winit::{
|
|||
};
|
||||
|
||||
use egui::{
|
||||
epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder,
|
||||
ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, ViewportOutput,
|
||||
ahash::HashSet, epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport,
|
||||
ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo,
|
||||
ViewportOutput,
|
||||
};
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
|
|
@ -104,7 +106,7 @@ struct Viewport {
|
|||
builder: ViewportBuilder,
|
||||
deferred_commands: Vec<egui::viewport::ViewportCommand>,
|
||||
info: ViewportInfo,
|
||||
screenshot_requested: bool,
|
||||
actions_requested: HashSet<egui_winit::ActionRequested>,
|
||||
|
||||
/// The user-callback that shows the ui.
|
||||
/// None for immediate viewports.
|
||||
|
|
@ -682,17 +684,38 @@ impl GlowWinitRunning {
|
|||
);
|
||||
|
||||
{
|
||||
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
|
||||
if screenshot_requested {
|
||||
let screenshot = painter.read_screen_rgba(screen_size_in_pixels);
|
||||
egui_winit
|
||||
.egui_input_mut()
|
||||
.events
|
||||
.push(egui::Event::Screenshot {
|
||||
viewport_id,
|
||||
image: screenshot.into(),
|
||||
});
|
||||
for action in viewport.actions_requested.drain() {
|
||||
match action {
|
||||
ActionRequested::Screenshot => {
|
||||
let screenshot = painter.read_screen_rgba(screen_size_in_pixels);
|
||||
egui_winit
|
||||
.egui_input_mut()
|
||||
.events
|
||||
.push(egui::Event::Screenshot {
|
||||
viewport_id,
|
||||
image: screenshot.into(),
|
||||
});
|
||||
}
|
||||
ActionRequested::Cut => {
|
||||
egui_winit.egui_input_mut().events.push(egui::Event::Cut);
|
||||
}
|
||||
ActionRequested::Copy => {
|
||||
egui_winit.egui_input_mut().events.push(egui::Event::Copy);
|
||||
}
|
||||
ActionRequested::Paste => {
|
||||
if let Some(contents) = egui_winit.clipboard_text() {
|
||||
let contents = contents.replace("\r\n", "\n");
|
||||
if !contents.is_empty() {
|
||||
egui_winit
|
||||
.egui_input_mut()
|
||||
.events
|
||||
.push(egui::Event::Paste(contents));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
integration.post_rendering(&window);
|
||||
}
|
||||
|
||||
|
|
@ -1020,7 +1043,7 @@ impl GlutinWindowContext {
|
|||
builder: viewport_builder,
|
||||
deferred_commands: vec![],
|
||||
info,
|
||||
screenshot_requested: false,
|
||||
actions_requested: Default::default(),
|
||||
viewport_ui_cb: None,
|
||||
gl_surface: None,
|
||||
window: window.map(Arc::new),
|
||||
|
|
@ -1277,7 +1300,7 @@ impl GlutinWindowContext {
|
|||
std::mem::take(&mut viewport.deferred_commands),
|
||||
window,
|
||||
is_viewport_focused,
|
||||
&mut viewport.screenshot_requested,
|
||||
&mut viewport.actions_requested,
|
||||
);
|
||||
|
||||
// For Wayland : https://github.com/emilk/egui/issues/4196
|
||||
|
|
@ -1323,7 +1346,7 @@ fn initialize_or_update_viewport(
|
|||
builder,
|
||||
deferred_commands: vec![],
|
||||
info: Default::default(),
|
||||
screenshot_requested: false,
|
||||
actions_requested: Default::default(),
|
||||
viewport_ui_cb,
|
||||
window: None,
|
||||
egui_winit: None,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant};
|
||||
|
||||
use egui_winit::ActionRequested;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
|
||||
use winit::{
|
||||
|
|
@ -15,9 +16,9 @@ use winit::{
|
|||
};
|
||||
|
||||
use egui::{
|
||||
ahash::HashMap, DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder,
|
||||
ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo,
|
||||
ViewportOutput,
|
||||
ahash::{HashMap, HashSet, HashSetExt},
|
||||
DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, ViewportClass,
|
||||
ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput,
|
||||
};
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
|
|
@ -78,7 +79,7 @@ pub struct Viewport {
|
|||
builder: ViewportBuilder,
|
||||
deferred_commands: Vec<egui::viewport::ViewportCommand>,
|
||||
info: ViewportInfo,
|
||||
screenshot_requested: bool,
|
||||
actions_requested: HashSet<ActionRequested>,
|
||||
|
||||
/// `None` for sync viewports.
|
||||
viewport_ui_cb: Option<Arc<DeferredViewportUiCallback>>,
|
||||
|
|
@ -289,7 +290,7 @@ impl WgpuWinitApp {
|
|||
builder,
|
||||
deferred_commands: vec![],
|
||||
info,
|
||||
screenshot_requested: false,
|
||||
actions_requested: Default::default(),
|
||||
viewport_ui_cb: None,
|
||||
window: Some(window),
|
||||
egui_winit: Some(egui_winit),
|
||||
|
|
@ -676,7 +677,10 @@ impl WgpuWinitRunning {
|
|||
|
||||
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
|
||||
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
|
||||
let screenshot_requested = viewport
|
||||
.actions_requested
|
||||
.take(&ActionRequested::Screenshot)
|
||||
.is_some();
|
||||
let (vsync_secs, screenshot) = painter.paint_and_update_textures(
|
||||
viewport_id,
|
||||
pixels_per_point,
|
||||
|
|
@ -695,6 +699,31 @@ impl WgpuWinitRunning {
|
|||
});
|
||||
}
|
||||
|
||||
for action in viewport.actions_requested.drain() {
|
||||
match action {
|
||||
ActionRequested::Screenshot => {
|
||||
// already handled above
|
||||
}
|
||||
ActionRequested::Cut => {
|
||||
egui_winit.egui_input_mut().events.push(egui::Event::Cut);
|
||||
}
|
||||
ActionRequested::Copy => {
|
||||
egui_winit.egui_input_mut().events.push(egui::Event::Copy);
|
||||
}
|
||||
ActionRequested::Paste => {
|
||||
if let Some(contents) = egui_winit.clipboard_text() {
|
||||
let contents = contents.replace("\r\n", "\n");
|
||||
if !contents.is_empty() {
|
||||
egui_winit
|
||||
.egui_input_mut()
|
||||
.events
|
||||
.push(egui::Event::Paste(contents));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
integration.post_rendering(window);
|
||||
|
||||
let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect();
|
||||
|
|
@ -1073,7 +1102,7 @@ fn handle_viewport_output(
|
|||
std::mem::take(&mut viewport.deferred_commands),
|
||||
window,
|
||||
is_viewport_focused,
|
||||
&mut viewport.screenshot_requested,
|
||||
&mut viewport.actions_requested,
|
||||
);
|
||||
|
||||
// For Wayland : https://github.com/emilk/egui/issues/4196
|
||||
|
|
@ -1120,7 +1149,7 @@ fn initialize_or_update_viewport(
|
|||
builder,
|
||||
deferred_commands: vec![],
|
||||
info: Default::default(),
|
||||
screenshot_requested: false,
|
||||
actions_requested: HashSet::new(),
|
||||
viewport_ui_cb,
|
||||
window: None,
|
||||
egui_winit: None,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ pub use accesskit_winit;
|
|||
pub use egui;
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui::accesskit;
|
||||
use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo};
|
||||
use egui::{
|
||||
ahash::HashSet, Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo,
|
||||
};
|
||||
pub use winit;
|
||||
|
||||
pub mod clipboard;
|
||||
|
|
@ -1254,6 +1256,13 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::Curs
|
|||
|
||||
// Helpers for egui Viewports
|
||||
// ---------------------------------------------------------------------------
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
pub enum ActionRequested {
|
||||
Screenshot,
|
||||
Cut,
|
||||
Copy,
|
||||
Paste,
|
||||
}
|
||||
|
||||
pub fn process_viewport_commands(
|
||||
egui_ctx: &egui::Context,
|
||||
|
|
@ -1261,7 +1270,7 @@ pub fn process_viewport_commands(
|
|||
commands: impl IntoIterator<Item = ViewportCommand>,
|
||||
window: &Window,
|
||||
is_viewport_focused: bool,
|
||||
screenshot_requested: &mut bool,
|
||||
actions_requested: &mut HashSet<ActionRequested>,
|
||||
) {
|
||||
for command in commands {
|
||||
process_viewport_command(
|
||||
|
|
@ -1270,7 +1279,7 @@ pub fn process_viewport_commands(
|
|||
command,
|
||||
info,
|
||||
is_viewport_focused,
|
||||
screenshot_requested,
|
||||
actions_requested,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1281,7 +1290,7 @@ fn process_viewport_command(
|
|||
command: ViewportCommand,
|
||||
info: &mut ViewportInfo,
|
||||
is_viewport_focused: bool,
|
||||
screenshot_requested: &mut bool,
|
||||
actions_requested: &mut HashSet<ActionRequested>,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
|
|
@ -1478,7 +1487,16 @@ fn process_viewport_command(
|
|||
}
|
||||
}
|
||||
ViewportCommand::Screenshot => {
|
||||
*screenshot_requested = true;
|
||||
actions_requested.insert(ActionRequested::Screenshot);
|
||||
}
|
||||
ViewportCommand::RequestCut => {
|
||||
actions_requested.insert(ActionRequested::Cut);
|
||||
}
|
||||
ViewportCommand::RequestCopy => {
|
||||
actions_requested.insert(ActionRequested::Copy);
|
||||
}
|
||||
ViewportCommand::RequestPaste => {
|
||||
actions_requested.insert(ActionRequested::Paste);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1038,6 +1038,21 @@ pub enum ViewportCommand {
|
|||
///
|
||||
/// The results are returned in `crate::Event::Screenshot`.
|
||||
Screenshot,
|
||||
|
||||
/// Request cut of the current selection
|
||||
///
|
||||
/// This is equivalent to the system keyboard shortcut for cut (e.g. CTRL + X).
|
||||
RequestCut,
|
||||
|
||||
/// Request a copy of the current selection.
|
||||
///
|
||||
/// This is equivalent to the system keyboard shortcut for copy (e.g. CTRL + C).
|
||||
RequestCopy,
|
||||
|
||||
/// Request a paste from the clipboard to the current focused TextEdit if any.
|
||||
///
|
||||
/// This is equivalent to the system keyboard shortcut for paste (e.g. CTRL + V).
|
||||
RequestPaste,
|
||||
}
|
||||
|
||||
impl ViewportCommand {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
pub use egui_winit;
|
||||
pub use egui_winit::EventResponse;
|
||||
|
||||
use egui::{ViewportId, ViewportOutput};
|
||||
use egui::{ahash::HashSet, ViewportId, ViewportOutput};
|
||||
use egui_winit::winit;
|
||||
|
||||
use crate::shader_version::ShaderVersion;
|
||||
|
|
@ -79,17 +79,17 @@ impl EguiGlow {
|
|||
log::warn!("Multiple viewports not yet supported by EguiGlow");
|
||||
}
|
||||
for (_, ViewportOutput { commands, .. }) in viewport_output {
|
||||
let mut screenshot_requested = false;
|
||||
let mut actions_requested: HashSet<egui_winit::ActionRequested> = Default::default();
|
||||
egui_winit::process_viewport_commands(
|
||||
&self.egui_ctx,
|
||||
&mut self.viewport_info,
|
||||
commands,
|
||||
window,
|
||||
true,
|
||||
&mut screenshot_requested,
|
||||
&mut actions_requested,
|
||||
);
|
||||
if screenshot_requested {
|
||||
log::warn!("Screenshot not yet supported by EguiGlow");
|
||||
for action in actions_requested {
|
||||
log::warn!("{:?} not yet supported by EguiGlow", action);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue