1
0
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:
bu5hm4nn
2024-04-22 01:06:33 -06:00
committed by GitHub
parent 87b294534e
commit a2f1ca31a0
5 changed files with 119 additions and 34 deletions

View File

@@ -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,

View File

@@ -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,