mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user