mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-29 21:16:57 -06:00
This commit is contained in:
@@ -132,6 +132,17 @@ impl Default for UiState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Text put on the system clipboard when the copied objects (a rectangle, a picture, slides…)
|
||||
/// carry no text. egui-winit drops Ctrl+V when the system clipboard is empty, so without it the
|
||||
/// shortcut never reached the session and only the context menu could paste objects (#182). An
|
||||
/// invisible word joiner, so a paste into another app adds nothing visible.
|
||||
const OBJECTS_MARKER: &str = "\u{2060}";
|
||||
|
||||
/// What a copy/cut leaves on the system clipboard for the session's plain-text rendering `t`.
|
||||
fn system_clipboard_text(t: &str) -> String {
|
||||
if t.is_empty() { OBJECTS_MARKER.to_string() } else { t.to_string() }
|
||||
}
|
||||
|
||||
/// UI-only commands (id, label, shortcut, params).
|
||||
pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
|
||||
("view.normal", "Normal", None, "{}"),
|
||||
@@ -900,17 +911,19 @@ impl SlideApp {
|
||||
if let Ok(v) = self.session.execute("edit.copy", &json!({}))
|
||||
&& let Some(t) = v.get("text").and_then(Value::as_str)
|
||||
{
|
||||
ctx.copy_text(t.to_string());
|
||||
ctx.copy_text(system_clipboard_text(t));
|
||||
}
|
||||
}
|
||||
egui::Event::Cut => {
|
||||
if let Ok(v) = self.session.execute("edit.cut", &json!({}))
|
||||
&& let Some(t) = v.get("text").and_then(Value::as_str)
|
||||
{
|
||||
ctx.copy_text(t.to_string());
|
||||
ctx.copy_text(system_clipboard_text(t));
|
||||
}
|
||||
}
|
||||
egui::Event::Paste(text) => {
|
||||
// Our own marker for objects without text: paste what the session copied.
|
||||
let text = if text == OBJECTS_MARKER { String::new() } else { text };
|
||||
// An image on the clipboard (screenshots, copied pictures) becomes a picture.
|
||||
let img = if text.is_empty() { self.services.clipboard_image.as_mut().and_then(|f| f()) } else { None };
|
||||
let r = match img {
|
||||
@@ -1467,4 +1480,33 @@ mod tests {
|
||||
assert_eq!(copied(&harness).as_deref(), Some("Aurora"), "Cut does too");
|
||||
assert!(harness.state().session.active().unwrap().doc.slides[0].shapes.iter().all(|s| s.id.0 != title), "and removes the shape");
|
||||
}
|
||||
|
||||
/// #182: Ctrl+C then Ctrl+V did nothing for objects without text (a rectangle), because
|
||||
/// egui-winit drops Ctrl+V when the system clipboard is empty; the context menu worked.
|
||||
#[test]
|
||||
fn copy_then_paste_shortcut_duplicates_an_object_without_text() {
|
||||
let mut harness = egui_kittest::Harness::builder().with_size(egui::vec2(1200.0, 800.0)).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
SlideApp::new(Session::with_new(), Services::default()),
|
||||
);
|
||||
harness.run_steps(2);
|
||||
harness.state_mut().session.execute("shape.insert", &json!({"preset": "rect", "rect": [10, 10, 50, 50]})).unwrap();
|
||||
let count = |h: &egui_kittest::Harness<'_, SlideApp>| h.state().session.doc().unwrap().current_slide().unwrap().shapes.len();
|
||||
let before = count(&harness);
|
||||
|
||||
// What the copy leaves on the system clipboard is what egui-winit hands back on Ctrl+V.
|
||||
assert_eq!(system_clipboard_text(""), OBJECTS_MARKER);
|
||||
assert_eq!(system_clipboard_text("hello"), "hello");
|
||||
harness.event(egui::Event::Copy);
|
||||
harness.run_steps(2);
|
||||
harness.event(egui::Event::Paste(OBJECTS_MARKER.to_string()));
|
||||
harness.run_steps(2);
|
||||
assert_eq!(count(&harness), before + 1, "the shortcut pastes a copy of the rectangle");
|
||||
let d = harness.state().session.active().unwrap();
|
||||
let pasted = d.doc.slides[0].shapes.last().unwrap();
|
||||
assert!(pasted.text.as_ref().is_none_or(|t| t.text().is_empty()), "a copy of the rectangle, not a text box holding the marker");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user