mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 20:01:37 +00:00
Merge pull request #5696 from dotCooCoo/fix/external-plain-text-paste-style
Use destination style for external same-text paste on Windows
This commit is contained in:
@@ -176,8 +176,7 @@ mod win {
|
||||
use clipboard_win::{formats, options::NoClear, raw};
|
||||
|
||||
use super::{
|
||||
BITMAP, ClipboardRead, Cursor, Flavour, PNG, SVG, SystemClipboard, TEXT, copied_file, decode_png, dib_image, png_flavour, text_of,
|
||||
windows_formats,
|
||||
BITMAP, ClipboardRead, Cursor, Flavour, PNG, SVG, SystemClipboard, TEXT, copied_file, decode_png, dib_image, png_flavour, windows_formats,
|
||||
};
|
||||
|
||||
/// The device-independent bitmaps, the one that carries alpha first. Each is there whenever the
|
||||
@@ -211,8 +210,6 @@ mod win {
|
||||
pub struct Clipboard {
|
||||
/// The clipboard's sequence number right after our last write.
|
||||
seq: Option<NonZeroU32>,
|
||||
/// The text of our last write.
|
||||
text: Option<String>,
|
||||
}
|
||||
|
||||
impl SystemClipboard for Clipboard {
|
||||
@@ -243,18 +240,13 @@ mod win {
|
||||
}
|
||||
}
|
||||
self.seq = raw::seq_num();
|
||||
self.text = text_of(flavours);
|
||||
result
|
||||
}
|
||||
|
||||
fn holds_ours(&mut self) -> bool {
|
||||
if self.seq.is_some() && raw::seq_num() == self.seq {
|
||||
return true;
|
||||
}
|
||||
// A clipboard manager may have copied our data again as its own.
|
||||
let Some(ours) = self.text.as_deref() else { return false };
|
||||
let Ok(_open) = clipboard_win::Clipboard::new_attempts(10) else { return false };
|
||||
read_one(TEXT).and_then(ClipboardRead::flavour).is_some_and(|f| f.data == ours.as_bytes())
|
||||
// The same text copied later by another app is a different clipboard entry. A
|
||||
// clipboard manager that republishes our data is also a new entry.
|
||||
self.seq.is_some() && raw::seq_num() == self.seq
|
||||
}
|
||||
|
||||
fn read(&mut self, mimes: &[&'static str]) -> Option<Flavour> {
|
||||
|
||||
@@ -590,18 +590,28 @@ fn text_key(app: &mut VectorcraftApp, k: ToolKey, m: Mods) {
|
||||
}
|
||||
|
||||
fn text_paste(app: &mut VectorcraftApp, s: Option<String>, plain: bool) {
|
||||
let text_from_clip = CLIP.lock().unwrap_or_else(|e| e.into_inner()).clone();
|
||||
let s = s.unwrap_or(text_from_clip);
|
||||
let s = match s {
|
||||
Some(text) => text,
|
||||
None if app.services.system_clipboard.is_some() => app.written_clipboard_text().unwrap_or_default(),
|
||||
None => CLIP.lock().unwrap_or_else(|e| e.into_inner()).clone(),
|
||||
};
|
||||
if !s.is_empty() {
|
||||
if plain {
|
||||
app.session.set_tool_option("copy", &json!([]));
|
||||
} else if let Some(runs) = CLIP_RUNS.lock().unwrap_or_else(|e| e.into_inner()).clone() {
|
||||
let internal = app.type_paste_has_our_clipboard();
|
||||
let mut styled = false;
|
||||
if !plain
|
||||
&& internal
|
||||
&& let Some(runs) = CLIP_RUNS.lock().unwrap_or_else(|e| e.into_inner()).clone()
|
||||
{
|
||||
let clip_text: String =
|
||||
serde_json::from_value::<Vec<vectorcraft_doc::TextRun>>(runs.clone()).unwrap_or_default().into_iter().map(|r| r.text).collect();
|
||||
if clip_text == s {
|
||||
app.session.set_tool_option("copy", &runs);
|
||||
styled = true;
|
||||
}
|
||||
}
|
||||
if !styled {
|
||||
app.session.set_tool_option("copy", &json!([]));
|
||||
}
|
||||
let v = app.view_info();
|
||||
let target_before = crate::ime::target(&mut app.session, v);
|
||||
let before = crate::ime::stamp(&mut app.session, v);
|
||||
|
||||
@@ -113,6 +113,12 @@ pub(crate) fn import_command(f: &Flavour, center: Option<[f64; 2]>) -> (&'static
|
||||
}
|
||||
|
||||
impl VectorcraftApp {
|
||||
/// A Type paste may reuse its saved styled runs only while our desktop copy still owns the
|
||||
/// clipboard. Pending formats also belong to that copy; web hosts have no ownership service.
|
||||
pub(crate) fn type_paste_has_our_clipboard(&mut self) -> bool {
|
||||
self.clipboard_publishing.pending || self.services.system_clipboard.as_mut().is_none_or(|cb| cb.holds_ours())
|
||||
}
|
||||
|
||||
/// After Copy or Cut: offer the copied objects to other apps. With the system clipboard
|
||||
/// service, the background worker makes the formats from a copy of the clipboard
|
||||
/// ([`background::run`]) and they are written when they are ready. While the worker has a
|
||||
|
||||
@@ -232,6 +232,47 @@ fn paste_without_formatting_takes_surrounding_style() {
|
||||
assert!(t2.runs.iter().all(|r| r.style.size == 10.0), "all runs take 10 pt size: {:?}", t2.runs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_plain_text_matching_an_old_copy_uses_destination_style() {
|
||||
let _guard = TEST_CLIP_LOCK.lock().unwrap();
|
||||
let (mut app, board) = super::sysclip::app();
|
||||
let source = run(&mut app, "text.create", json!({"x": 20, "y": 40, "text": "Hello", "size": 36, "color": "#ff0000"}))["id"].as_u64().unwrap();
|
||||
let v = app.view_info();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": source, "start": 0, "end": 5}));
|
||||
crate::panels::character::intercept_text_command(&mut app, "edit.copy");
|
||||
crate::background::wait_all(&mut app);
|
||||
|
||||
let own_destination =
|
||||
run(&mut app, "text.create", json!({"x": 20, "y": 60, "text": "Own: ", "size": 12, "color": "#000000"}))["id"].as_u64().unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": own_destination, "start": 5, "end": 5}));
|
||||
crate::panels::character::intercept_text_command(&mut app, "edit.paste");
|
||||
let node = app.session.doc().unwrap().doc.node(NodeId(own_destination)).unwrap();
|
||||
let vectorcraft_doc::NodeKind::Text(t) = &node.kind else { panic!() };
|
||||
assert_eq!(t.plain_text(), "Own: Hello");
|
||||
assert_eq!(t.runs.last().unwrap().style.size, 36.0, "our copy keeps its style");
|
||||
|
||||
// Another app later copies the same characters as plain text. Their style is unrelated.
|
||||
super::sysclip::copy_elsewhere(&board, vec![(vectorcraft_engine::cmd::clipboard::TEXT, b"Hello".to_vec())]);
|
||||
let destination =
|
||||
run(&mut app, "text.create", json!({"x": 20, "y": 80, "text": "Before ", "size": 12, "color": "#000000"}))["id"].as_u64().unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": 7, "end": 7}));
|
||||
super::sysclip::frame(&mut app, vec![egui::Event::Paste("Hello".into())]);
|
||||
assert_eq!(app.system_clipboard_text().as_deref(), Some("Hello"));
|
||||
app.session.select_tool("selection", v).unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": 12, "end": 12}));
|
||||
assert!(app.session.tool_wants_text());
|
||||
assert!(crate::panels::character::intercept_text_command(&mut app, "edit.paste").is_some());
|
||||
|
||||
let node = app.session.doc().unwrap().doc.node(NodeId(destination)).unwrap();
|
||||
let vectorcraft_doc::NodeKind::Text(t) = &node.kind else { panic!() };
|
||||
assert_eq!(t.plain_text(), "Before HelloHello");
|
||||
assert!(t.runs.iter().all(|run| run.style.size == 12.0 && run.style.fill == Paint::solid(Color::rgb8(0, 0, 0))));
|
||||
}
|
||||
|
||||
/// https://helpx.adobe.com/illustrator/using/format-text.html
|
||||
#[test]
|
||||
fn copying_single_character_carries_formatting_across_documents() {
|
||||
|
||||
Reference in New Issue
Block a user