mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-25 11:07:01 -06:00
Fix up merged PRs: a textless shape still copies as the objects marker so Ctrl+V pastes it (#182), not as a picture (#702)
With only an image on the system clipboard egui-winit drops Ctrl+V, so #702's picture copy of a textless shape undid main's #182 fix. Text keeps #702's HTML, and empty plain text falls back to the marker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
38e95d8c15
commit
7dcfe0f989
+1
-1
@@ -210,7 +210,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| 2026-10-14 | Design ▸ Effects: named effect schemes (DeckCraft, Soft, Crisp, Lift, Glow, Plain) replace the theme's fills, lines and effects and leave colours and fonts. `design.effects` |
|
||||
| 2026-10-14 | Slide Master: Preserve on the open master or layout, and Title and Footers placeholder checkboxes. Two commands (`master.preserve`, `master.placeholders`); dialogs stay 27; breadth stays 79% |
|
||||
| 2026-10-14 | Trim Media: start, end and fades open from the selected clip, so OK no longer resets them to zero |
|
||||
| 2026-10-14 | Copy and paste with other apps: bold, italic and underline travel as HTML (Pages keeps them), and a shape with no text copies out as a picture |
|
||||
| 2026-10-14 | Copy and paste with other apps: bold, italic and underline travel as HTML (Pages keeps them); a shape with no text still copies as DeckCraft objects so Ctrl+V pastes it (#182) |
|
||||
| 2026-10-13 | Slide transitions like PowerPoint: every transition and option measured frame by frame against PowerPoint's video export and retuned (curves, directions, soft edges, 3-D perspective, contents moving over backgrounds); PowerPoint's own XML elements and defaults read and written (diagonal Wipe, Zoom, Shape In/Out, Glitter Hexagons, Shred In/Out, Page Curl Single); `deckcraft-cli render-transition` renders frames headless |
|
||||
| 2026-10-13 | Wild text cases measured against PowerPoint (102 synthetic slides): bidi by run language, East Asian vertical text upright, mongolianVert/wordArtVert/wordArtVertRtl, underline styles and strikethrough at the font's metrics, centre/right/decimal tabs, East Asian, Thai, Hindi, Arabic and Hebrew numbering, small caps, all-caps "ß", transparent text, soft hyphens, kinsoku, rtlCol and just/dist anchoring, emoji presentation, fallback faces that no longer move the baseline |
|
||||
| 2026-10-13 | Render matches PowerPoint more closely on wild shape, picture and table cases: even-odd custom paths, dashes and caps, arrowheads, radial and path gradients, tile rects, rotWithShape, gradient lines, shadows cast by what is drawn (group and perspective too), group fill, picture borders, tiles, reflection, recolour (biLevel, alphaInv, brightness/contrast), table borders and grid default, 20+ preset shapes by their ECMA formulas, default miter joins, and approximate 3-D bevels, cameras and face lighting |
|
||||
|
||||
+12
-46
@@ -1120,59 +1120,24 @@ impl SlideApp {
|
||||
|
||||
/// Offer the copy or cut on the system clipboard.
|
||||
///
|
||||
/// Slides go out as plain text. A textless shape goes out as a picture (Keynote and Pages
|
||||
/// paste that). Text goes out as HTML plus the same plain words, so a paste back into
|
||||
/// DeckCraft still uses the internal clipboard.
|
||||
/// Slides go out as plain text. Text goes out as HTML plus the same plain words, so a paste
|
||||
/// back into DeckCraft still uses the internal clipboard. A textless shape leaves the
|
||||
/// invisible [`OBJECTS_MARKER`] rather than a picture: with only an image on the clipboard
|
||||
/// egui-winit drops Ctrl+V, and the shortcut could no longer paste the shape here (#182).
|
||||
fn publish_clipboard(&mut self, ctx: &egui::Context, cut: bool) {
|
||||
// Cut deletes the shapes, so the picture is taken first.
|
||||
let picture = self.textless_selection_image();
|
||||
let cmd = if cut { "edit.cut" } else { "edit.copy" };
|
||||
let Ok(v) = self.session.execute(cmd, &json!({})) else { return };
|
||||
let text = v.get("text").and_then(Value::as_str).unwrap_or("").to_string();
|
||||
let html = v.get("html").and_then(Value::as_str).unwrap_or("").to_string();
|
||||
if !self.session.clipboard.slides.is_empty() {
|
||||
ctx.copy_text(system_clipboard_text(&text));
|
||||
return;
|
||||
}
|
||||
if let Some(img) = picture {
|
||||
ctx.copy_image(img);
|
||||
return;
|
||||
}
|
||||
if !html.is_empty() && self.services.clipboard_set_html.as_mut().is_some_and(|f| f(&html, &text)) {
|
||||
if self.session.clipboard.slides.is_empty()
|
||||
&& !html.is_empty()
|
||||
&& self.services.clipboard_set_html.as_mut().is_some_and(|f| f(&html, &text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ctx.copy_text(system_clipboard_text(&text));
|
||||
}
|
||||
|
||||
/// A picture of the selected shapes when none of them has text, on the slide (not a master).
|
||||
fn textless_selection_image(&self) -> Option<egui::ColorImage> {
|
||||
let st = self.session.active()?;
|
||||
if st.selection.target != deckcraft_engine::Target::Slides || st.selection.text.is_some() || st.selection.shapes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut plain = String::new();
|
||||
let mut any = false;
|
||||
for id in &st.selection.shapes {
|
||||
let Some(sh) = st.shape(*id) else { continue };
|
||||
any = true;
|
||||
if let Some(t) = &sh.text {
|
||||
if !plain.is_empty() {
|
||||
plain.push('\n');
|
||||
}
|
||||
plain.push_str(&t.text());
|
||||
}
|
||||
}
|
||||
if !any || !plain.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let slide = st.current_slide()?;
|
||||
let img = deckcraft_render::render_shapes(&st.doc, slide, &st.selection.shapes, 2.0)?;
|
||||
if img.width == 0 || img.height == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(textures::to_color_image(&img, false))
|
||||
}
|
||||
|
||||
/// Lay out the whole window.
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let ctx = ui.ctx().clone();
|
||||
@@ -1943,14 +1908,15 @@ mod tests {
|
||||
assert_eq!(body.paragraphs[0].runs[0].props.bold, Some(true), "{body:?}");
|
||||
}
|
||||
|
||||
/// A shape with no text copies out as a picture.
|
||||
/// A shape with no text copies out as the objects marker (not a picture), so Ctrl+V can
|
||||
/// still paste it here (#182).
|
||||
#[test]
|
||||
fn a_textless_shape_copies_as_a_picture() {
|
||||
fn a_textless_shape_copies_as_the_objects_marker() {
|
||||
let mut h = step_app(Services::default());
|
||||
h.state_mut().session.execute("shape.insert", &json!({"preset": "rect", "rect": [80.0, 80.0, 120.0, 80.0]})).unwrap();
|
||||
h.event(egui::Event::Copy);
|
||||
h.run_steps(1);
|
||||
let cmds = &h.output().platform_output.commands;
|
||||
assert!(cmds.iter().any(|c| matches!(c, egui::output::OutputCommand::CopyImage(img) if img.size[0] > 0 && img.size[1] > 0)), "{cmds:?}");
|
||||
assert!(cmds.iter().any(|c| matches!(c, egui::output::OutputCommand::CopyText(t) if t == OBJECTS_MARKER)), "{cmds:?}");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -133,7 +133,7 @@ menu colours taken from the theme tokens (menu `chrome`, page `card`).
|
||||
| Drag and drop of files and between thumbnails | yes | yes |
|
||||
| Slide Sorter sections: a bar per section, add, rename in place, collapse, drag the whole section (its slides move with it) | yes | yes |
|
||||
| Image paste from the clipboard | yes | macOS yes; Windows and Linux no (#53) |
|
||||
| Image paste from the clipboard | yes | macOS pastes a picture when the paste carries no text (TIFF), a textless shape copies out as a picture, and rich text copies and pastes as HTML (bold, italic, underline). Windows and Linux image paste stays #53 |
|
||||
| Image paste from the clipboard | yes | macOS pastes a picture when the paste carries no text (TIFF), and rich text copies and pastes as HTML (bold, italic, underline). Windows and Linux image paste stays #53 |
|
||||
| Font menu: every installed family with its faces | yes | families yes; only Regular/Bold/Italic faces (#69) |
|
||||
|
||||
## Keyboard shortcuts
|
||||
@@ -223,7 +223,7 @@ on the slide as shapes. No pixel comparison against PowerPoint screenshots has b
|
||||
| 2026-10-14 | minor | Design ▸ Effects applies a named effect scheme (fills, lines and effects). Not a new dialog; dialogs stay 27 |
|
||||
| 2026-10-14 | minor | Slide Master: Preserve, and Title and Footers placeholders, on the open master or layout |
|
||||
| 2026-10-14 | trivial | Trim Media dialog: start, end and fades follow the selected clip |
|
||||
| 2026-10-14 | minor | Copy and paste rich text (HTML: bold, italic, underline) and a picture of a textless shape with other apps |
|
||||
| 2026-10-14 | minor | Copy and paste rich text (HTML: bold, italic, underline) with other apps |
|
||||
| 2026-10-12 | minor | File tab (Backstage) observed in PowerPoint for Windows and matched (#76); the start screen is File › Home |
|
||||
| 2026-10-12 | minor | Off-slide objects drawn and editable around the slide in Normal view (#270) |
|
||||
| 2026-10-12 | minor | Selection Pane matched to PowerPoint (#133): drag and drop, arrows, groups, eye, Lock |
|
||||
|
||||
Reference in New Issue
Block a user