mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:19:56 -06:00
Type: Paste keeps a copy the system clipboard refused
When a Copy in the Type tool cannot be written to the system clipboard (another app holds it open, or a format fails after the clipboard was emptied), Paste into type or a text field now inserts that copy, with its style in type, instead of the text the clipboard kept. This covers Edit > Paste, the paste key, and a paste that arrives while the copy is still being written. When another app copies something, before or after the failed write, Paste takes that. The new SystemClipboard::change_count reports the clipboard's change count (Windows' sequence number). Where a platform has none, a change of the clipboard's text marks a new copy.
This commit is contained in:
@@ -249,6 +249,10 @@ mod win {
|
||||
self.seq.is_some() && raw::seq_num() == self.seq
|
||||
}
|
||||
|
||||
fn change_count(&mut self) -> Option<u64> {
|
||||
raw::seq_num().map(|n| u64::from(n.get()))
|
||||
}
|
||||
|
||||
fn read(&mut self, mimes: &[&'static str]) -> Option<Flavour> {
|
||||
self.read_deferred(mimes)?.flavour()
|
||||
}
|
||||
|
||||
@@ -34,6 +34,29 @@ pub trait SystemClipboard {
|
||||
fn read_deferred(&mut self, mimes: &[&'static str]) -> Option<ClipboardRead> {
|
||||
self.read(mimes).map(ClipboardRead::Now)
|
||||
}
|
||||
/// A number that changes each time any app copies something, read without opening the
|
||||
/// clipboard; `None` where the platform has none (the default).
|
||||
fn change_count(&mut self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// What the system clipboard held when the formats of a Copy or Cut could not be written.
|
||||
#[derive(PartialEq)]
|
||||
enum Held {
|
||||
/// Its [`SystemClipboard::change_count`].
|
||||
Count(u64),
|
||||
/// Its text, where the platform has no change count.
|
||||
Text(Option<Vec<u8>>),
|
||||
}
|
||||
|
||||
impl Held {
|
||||
fn now(cb: &mut (dyn SystemClipboard + 'static)) -> Self {
|
||||
match cb.change_count() {
|
||||
Some(n) => Self::Count(n),
|
||||
None => Self::Text(cb.read(&[TEXT]).map(|f| f.data)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What [`SystemClipboard::read_deferred`] read.
|
||||
@@ -71,6 +94,9 @@ pub(crate) struct Publishing {
|
||||
latest: Arc<AtomicU64>,
|
||||
/// The latest one's formats wait to be written.
|
||||
pending: bool,
|
||||
/// The latest one's formats could not be written: what the system clipboard held when the
|
||||
/// write failed, and the text of those formats.
|
||||
unwritten: Option<(Held, String)>,
|
||||
}
|
||||
|
||||
impl Publishing {
|
||||
@@ -116,7 +142,20 @@ 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())
|
||||
self.clipboard_publishing.pending
|
||||
|| self.unwritten_clipboard_text().is_some()
|
||||
|| self.services.system_clipboard.as_mut().is_none_or(|cb| cb.holds_ours())
|
||||
}
|
||||
|
||||
/// The text of the latest Copy or Cut whose formats could not be written to the system
|
||||
/// clipboard, while nothing has been copied since that Copy or Cut. A paste into type or a text
|
||||
/// field takes this text until another app copies something. Where the platform has no change count, a
|
||||
/// copy is noticed only when it changes the clipboard's text, and once a clipboard whose text
|
||||
/// could not be read when the write failed has text, Paste takes that text.
|
||||
pub(crate) fn unwritten_clipboard_text(&mut self) -> Option<String> {
|
||||
let cb = self.services.system_clipboard.as_deref_mut()?;
|
||||
let (held, text) = self.clipboard_publishing.unwritten.as_ref()?;
|
||||
(Held::now(cb) == *held).then(|| text.clone())
|
||||
}
|
||||
|
||||
/// After Copy or Cut: offer the copied objects to other apps. With the system clipboard
|
||||
@@ -148,6 +187,9 @@ impl VectorcraftApp {
|
||||
let latest = self.clipboard_publishing.latest.clone();
|
||||
let n = latest.fetch_add(1, Ordering::SeqCst).wrapping_add(1);
|
||||
self.clipboard_publishing.pending = true;
|
||||
self.clipboard_publishing.unwritten = None;
|
||||
// What the clipboard holds at the Copy or Cut: a copy made elsewhere before the write is newer.
|
||||
let before = self.services.system_clipboard.as_deref_mut().map(Held::now);
|
||||
let (tx, made) = channel();
|
||||
let work = move |_: Writer| {
|
||||
// These formats are not made when a later Copy or Cut, or a copy through egui, dropped them.
|
||||
@@ -165,7 +207,18 @@ impl VectorcraftApp {
|
||||
publishing.pending = false;
|
||||
let formats = r.and_then(|_| made.try_recv().map_err(|e| e.to_string()));
|
||||
let written = formats.and_then(|(f, warnings)| {
|
||||
app.services.system_clipboard.as_mut().map_or(Ok(()), |cb| cb.write(&f))?;
|
||||
let Some(cb) = app.services.system_clipboard.as_mut() else { return Ok(warnings) };
|
||||
// Nothing copied elsewhere since the Copy or Cut.
|
||||
let current = before.as_ref().is_some_and(|b| *b == Held::now(cb.as_mut()));
|
||||
if let Err(e) = cb.write(&f) {
|
||||
// Another app may hold the clipboard open. It keeps what it held (or what the
|
||||
// failed write left), and a paste into text takes this copy's text until
|
||||
// something else is copied.
|
||||
let held = Held::now(cb.as_mut());
|
||||
let text = f.iter().find(|t| t.mime == TEXT).map(|t| String::from_utf8_lossy(&t.data).into_owned());
|
||||
app.clipboard_publishing.unwritten = text.filter(|_| current).map(|text| (held, text));
|
||||
return Err(e);
|
||||
}
|
||||
Ok(warnings)
|
||||
});
|
||||
match written {
|
||||
@@ -196,6 +249,7 @@ impl VectorcraftApp {
|
||||
let publishing = &mut self.clipboard_publishing;
|
||||
publishing.latest.fetch_add(1, Ordering::SeqCst);
|
||||
publishing.pending = false;
|
||||
publishing.unwritten = None;
|
||||
match self.services.system_clipboard.as_mut() {
|
||||
Some(cb) => {
|
||||
if let Err(e) = cb.write(&[]) {
|
||||
@@ -217,6 +271,9 @@ impl VectorcraftApp {
|
||||
pub(crate) fn drop_formats_after_egui_copy(&mut self, ctx: &egui::Context) {
|
||||
let copied = ctx.output(|o| o.commands.iter().any(|c| matches!(c, egui::OutputCommand::CopyText(_) | egui::OutputCommand::CopyImage(_))));
|
||||
let publishing = &mut self.clipboard_publishing;
|
||||
if copied {
|
||||
publishing.unwritten = None;
|
||||
}
|
||||
if copied && publishing.pending {
|
||||
publishing.latest.fetch_add(1, Ordering::SeqCst);
|
||||
publishing.pending = false;
|
||||
@@ -241,6 +298,22 @@ impl VectorcraftApp {
|
||||
let waited = ctx.data(|d| d.get_temp::<bool>(egui::Id::unique("clipboard.pending-at-tick"))).unwrap_or(false);
|
||||
ctx.data_mut(|d| d.remove::<bool>(egui::Id::unique("clipboard.pending-at-tick")));
|
||||
if !(self.clipboard_publishing.pending || waited) {
|
||||
// A copy whose formats could not be written: a paste takes its text, not the text the
|
||||
// clipboard kept ([`Self::unwritten_clipboard_text`]).
|
||||
let pasting = textless || ctx.input(|i| i.events.iter().any(|e| matches!(e, egui::Event::Paste(_))));
|
||||
if pasting && let Some(text) = self.unwritten_clipboard_text() {
|
||||
ctx.input_mut(|i| {
|
||||
for e in &mut i.events {
|
||||
if let egui::Event::Paste(t) = e {
|
||||
t.clone_from(&text);
|
||||
}
|
||||
}
|
||||
if textless {
|
||||
i.events.insert(0, egui::Event::Paste(text));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
// The formats the clipboard still holds were written after the chord's key went down.
|
||||
if textless
|
||||
&& self.services.system_clipboard.as_mut().is_some_and(|cb| cb.holds_ours())
|
||||
@@ -368,11 +441,12 @@ impl VectorcraftApp {
|
||||
}
|
||||
|
||||
/// The text on the system clipboard once the formats a Copy or Cut is making are written, for a
|
||||
/// paste into a text field or type.
|
||||
/// paste into a text field or type. When they could not be written, their text
|
||||
/// ([`Self::unwritten_clipboard_text`]).
|
||||
pub(crate) fn written_clipboard_text(&mut self) -> Option<String> {
|
||||
// Only other copies' formats are ahead of them on the worker ([`Self::publish_clipboard`]).
|
||||
background::wait_until(self, |app| !app.clipboard_publishing.pending);
|
||||
self.system_clipboard_text()
|
||||
self.unwritten_clipboard_text().or_else(|| self.system_clipboard_text())
|
||||
}
|
||||
|
||||
/// The text on the system clipboard, if any.
|
||||
|
||||
@@ -273,6 +273,165 @@ fn external_plain_text_matching_an_old_copy_uses_destination_style() {
|
||||
assert!(t.runs.iter().all(|run| run.style.size == 12.0 && run.style.fill == Paint::solid(Color::rgb8(0, 0, 0))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_paste_keeps_a_copy_the_system_clipboard_refused_until_another_app_copies() {
|
||||
let _guard = TEST_CLIP_LOCK.lock().unwrap();
|
||||
let (mut app, board) = super::sysclip::app();
|
||||
super::sysclip::copy_elsewhere(&board, vec![(vectorcraft_engine::cmd::clipboard::TEXT, b"Old".to_vec())]);
|
||||
super::sysclip::lock(&board, true);
|
||||
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);
|
||||
super::sysclip::lock(&board, false);
|
||||
assert_eq!(app.system_clipboard_text().as_deref(), Some("Old"), "the write was refused");
|
||||
|
||||
let 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": destination, "start": 5, "end": 5}));
|
||||
crate::panels::character::intercept_text_command(&mut app, "edit.paste");
|
||||
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(), "Own: Hello");
|
||||
assert_eq!(t.runs.last().unwrap().style.size, 36.0, "our copy keeps its style");
|
||||
|
||||
// A paste event carries the text the clipboard kept.
|
||||
app.session.select_tool("selection", v).unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": 10, "end": 10}));
|
||||
super::sysclip::frame(&mut app, vec![egui::Event::Paste("Old".into())]);
|
||||
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(), "Own: HelloHello");
|
||||
|
||||
// Another app copies, even the same text: Paste takes it, in the destination's style.
|
||||
super::sysclip::copy_elsewhere(&board, vec![(vectorcraft_engine::cmd::clipboard::TEXT, b"Old".to_vec())]);
|
||||
app.session.select_tool("selection", v).unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": 5, "end": 5}));
|
||||
crate::panels::character::intercept_text_command(&mut app, "edit.paste");
|
||||
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(), "Own: OldHelloHello");
|
||||
let old = t.runs.iter().find(|run| run.text.contains("Old")).unwrap();
|
||||
assert_eq!(old.style.size, 12.0, "another app's text takes the destination's style");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_paste_without_a_change_count_keeps_a_refused_copy_while_the_text_is_unchanged() {
|
||||
let _guard = TEST_CLIP_LOCK.lock().unwrap();
|
||||
let (mut app, board) = super::sysclip::app();
|
||||
super::sysclip::without_change_count(&board);
|
||||
super::sysclip::copy_elsewhere(&board, vec![(vectorcraft_engine::cmd::clipboard::TEXT, b"Old".to_vec())]);
|
||||
super::sysclip::lock(&board, true);
|
||||
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 destination =
|
||||
run(&mut app, "text.create", json!({"x": 20, "y": 60, "text": "Own: ", "size": 12, "color": "#000000"}))["id"].as_u64().unwrap();
|
||||
let paste_at = |app: &mut VectorcraftApp, at: usize| {
|
||||
app.session.select_tool("selection", v).unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": at, "end": at}));
|
||||
crate::panels::character::intercept_text_command(app, "edit.paste");
|
||||
let node = app.session.doc().unwrap().doc.node(NodeId(destination)).unwrap();
|
||||
let vectorcraft_doc::NodeKind::Text(t) = &node.kind else { panic!() };
|
||||
t.plain_text()
|
||||
};
|
||||
// The clipboard still can't be read, as when the write failed.
|
||||
assert_eq!(paste_at(&mut app, 5), "Own: Hello");
|
||||
// Its text read later may be another app's copy: Paste takes it.
|
||||
super::sysclip::lock(&board, false);
|
||||
assert_eq!(paste_at(&mut app, 10), "Own: HelloOld");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_paste_event_waiting_on_a_refused_copy_pastes_that_copy() {
|
||||
let _guard = TEST_CLIP_LOCK.lock().unwrap();
|
||||
// Another copy's formats hold the worker, so this Copy's formats wait.
|
||||
let (mut app, board, hold) = super::sysclip::app_with_busy_worker("");
|
||||
super::sysclip::copy_elsewhere(&board, vec![(vectorcraft_engine::cmd::clipboard::TEXT, b"Old".to_vec())]);
|
||||
let source = run(&mut app, "text.create", json!({"x": 20, "y": 40, "text": "Hello", "size": 36, "color": "#ff0000"}))["id"].as_u64().unwrap();
|
||||
let destination =
|
||||
run(&mut app, "text.create", json!({"x": 20, "y": 60, "text": "Own: ", "size": 12, "color": "#000000"}))["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");
|
||||
assert!(app.clipboard_publishing.is_pending());
|
||||
|
||||
// The paste comes while the formats wait, and the write then fails with the clipboard open.
|
||||
super::sysclip::lock(&board, true);
|
||||
drop(hold);
|
||||
app.session.select_tool("selection", v).unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": 5, "end": 5}));
|
||||
super::sysclip::frame(&mut app, vec![egui::Event::Paste("Old".into())]);
|
||||
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(), "Own: Hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_copy_elsewhere_before_a_refused_write_wins_the_type_paste() {
|
||||
let _guard = TEST_CLIP_LOCK.lock().unwrap();
|
||||
let (mut app, board, hold) = super::sysclip::app_with_busy_worker("");
|
||||
let source = run(&mut app, "text.create", json!({"x": 20, "y": 40, "text": "Hello", "size": 36, "color": "#ff0000"}))["id"].as_u64().unwrap();
|
||||
let destination =
|
||||
run(&mut app, "text.create", json!({"x": 20, "y": 60, "text": "Own: ", "size": 12, "color": "#000000"}))["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");
|
||||
assert!(app.clipboard_publishing.is_pending());
|
||||
|
||||
// Another app copies while the formats wait, and keeps the clipboard open for the write.
|
||||
super::sysclip::copy_elsewhere(&board, vec![(vectorcraft_engine::cmd::clipboard::TEXT, b"New".to_vec())]);
|
||||
super::sysclip::lock(&board, true);
|
||||
drop(hold);
|
||||
crate::background::wait_all(&mut app);
|
||||
super::sysclip::lock(&board, false);
|
||||
|
||||
app.session.select_tool("selection", v).unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": 5, "end": 5}));
|
||||
crate::panels::character::intercept_text_command(&mut app, "edit.paste");
|
||||
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(), "Own: New");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_paste_keeps_a_copy_whose_write_failed_after_emptying_the_clipboard() {
|
||||
let _guard = TEST_CLIP_LOCK.lock().unwrap();
|
||||
let (mut app, board) = super::sysclip::app();
|
||||
super::sysclip::copy_elsewhere(&board, vec![(vectorcraft_engine::cmd::clipboard::TEXT, b"Old".to_vec())]);
|
||||
super::sysclip::fail_writes_after_emptying(&board);
|
||||
let source = run(&mut app, "text.create", json!({"x": 20, "y": 40, "text": "Hello", "size": 36, "color": "#ff0000"}))["id"].as_u64().unwrap();
|
||||
let destination =
|
||||
run(&mut app, "text.create", json!({"x": 20, "y": 60, "text": "Own: ", "size": 12, "color": "#000000"}))["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);
|
||||
|
||||
app.session.select_tool("selection", v).unwrap();
|
||||
app.session.select_tool("type", v).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"id": destination, "start": 5, "end": 5}));
|
||||
crate::panels::character::intercept_text_command(&mut app, "edit.paste");
|
||||
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(), "Own: Hello");
|
||||
}
|
||||
|
||||
/// https://helpx.adobe.com/illustrator/using/format-text.html
|
||||
#[test]
|
||||
fn copying_single_character_carries_formatting_across_documents() {
|
||||
|
||||
@@ -22,6 +22,15 @@ pub(super) struct Board {
|
||||
ours: bool,
|
||||
/// How many times the app wrote it.
|
||||
writes: usize,
|
||||
/// Another app has the clipboard open: the app's writes fail and change nothing, and reads
|
||||
/// find nothing.
|
||||
locked: bool,
|
||||
/// Raised by every write and every copy elsewhere.
|
||||
changes: u64,
|
||||
/// The platform has no change count.
|
||||
uncounted: bool,
|
||||
/// The app's writes empty the clipboard and then fail.
|
||||
partial: bool,
|
||||
}
|
||||
|
||||
struct Fake(Rc<RefCell<Board>>);
|
||||
@@ -29,6 +38,9 @@ struct Fake(Rc<RefCell<Board>>);
|
||||
impl Fake {
|
||||
fn find(&self, mimes: &[&'static str]) -> Option<Flavour> {
|
||||
let b = self.0.borrow();
|
||||
if b.locked {
|
||||
return None;
|
||||
}
|
||||
let matches = |m: &str, f: &Flavour| f.mime == m || (m == BITMAP && f.mime.starts_with("image/") && f.mime != SVG);
|
||||
mimes.iter().find_map(|m| b.flavours.iter().find(|f| matches(m, f)).cloned())
|
||||
}
|
||||
@@ -37,9 +49,21 @@ impl Fake {
|
||||
impl SystemClipboard for Fake {
|
||||
fn write(&mut self, flavours: &[Flavour]) -> Result<(), String> {
|
||||
let mut b = self.0.borrow_mut();
|
||||
*b = Board { flavours: flavours.to_vec(), ours: true, writes: b.writes + 1 };
|
||||
if b.locked {
|
||||
return Err("another app has the clipboard open".into());
|
||||
}
|
||||
if b.partial {
|
||||
b.flavours.clear();
|
||||
b.changes += 1;
|
||||
return Err("a format could not be written".into());
|
||||
}
|
||||
*b = Board { flavours: flavours.to_vec(), ours: true, writes: b.writes + 1, changes: b.changes + 1, ..std::mem::take(&mut *b) };
|
||||
Ok(())
|
||||
}
|
||||
fn change_count(&mut self) -> Option<u64> {
|
||||
let b = self.0.borrow();
|
||||
(!b.uncounted).then_some(b.changes)
|
||||
}
|
||||
fn holds_ours(&mut self) -> bool {
|
||||
self.0.borrow().ours
|
||||
}
|
||||
@@ -66,7 +90,22 @@ pub(super) fn app() -> (VectorcraftApp, Rc<RefCell<Board>>) {
|
||||
pub(super) fn copy_elsewhere(board: &Rc<RefCell<Board>>, flavours: Vec<(&'static str, Vec<u8>)>) {
|
||||
let flavours = flavours.into_iter().map(|(mime, data)| Flavour { mime, data }).collect();
|
||||
let mut b = board.borrow_mut();
|
||||
*b = Board { flavours, ours: false, writes: b.writes };
|
||||
*b = Board { flavours, ours: false, changes: b.changes + 1, ..std::mem::take(&mut *b) };
|
||||
}
|
||||
|
||||
/// Another app opens the clipboard (`true`) or closes it.
|
||||
pub(super) fn lock(board: &Rc<RefCell<Board>>, locked: bool) {
|
||||
board.borrow_mut().locked = locked;
|
||||
}
|
||||
|
||||
/// The fake clipboard reports no change count, as on platforms that have none.
|
||||
pub(super) fn without_change_count(board: &Rc<RefCell<Board>>) {
|
||||
board.borrow_mut().uncounted = true;
|
||||
}
|
||||
|
||||
/// The app's writes empty the clipboard and then fail, as when one format can't be written.
|
||||
pub(super) fn fail_writes_after_emptying(board: &Rc<RefCell<Board>>) {
|
||||
board.borrow_mut().partial = true;
|
||||
}
|
||||
|
||||
pub(super) fn run(app: &mut VectorcraftApp, id: &str, p: Value) -> Value {
|
||||
|
||||
Reference in New Issue
Block a user