ElyPDF: quick strokes and clicks queue behind the last edit instead of being refused (P-05)
This commit is contained in:
7 files changed
+82
-10
No files matched your search
@@ -254,11 +254,11 @@ impl PdfView {
|
||||
return;
|
||||
}
|
||||
let author = ely_ui::settings::current(cx).author.clone();
|
||||
self.settled_edit(what, cx, move |worker, doc| {
|
||||
self.edit_at(what, move |worker, doc| {
|
||||
let name = ely_platform::user::named(&author)?;
|
||||
annots.iter_mut().for_each(|annot| annot.author = name.clone());
|
||||
worker.annotate(doc, &annots)
|
||||
});
|
||||
}, cx);
|
||||
}
|
||||
|
||||
/// A press with a drawing or form tool.
|
||||
|
||||
@@ -19,6 +19,8 @@ pub struct Queued {
|
||||
doc: Doc,
|
||||
/// The edit may add, drop, move or reshape pages.
|
||||
pages: bool,
|
||||
/// Aimed at a point: runs on the state it meets.
|
||||
follow: bool,
|
||||
}
|
||||
|
||||
/// The document as an edit left it.
|
||||
@@ -41,6 +43,23 @@ impl PdfView {
|
||||
self.queue_edit(what, None, false, op, cx);
|
||||
}
|
||||
|
||||
/// Queues a point-aimed edit; only moving pages refuse it.
|
||||
pub fn edit_at(
|
||||
&mut self,
|
||||
what: &'static str,
|
||||
op: impl FnOnce(&mut Worker, Doc) -> io::Result<String> + Send + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.moving || self.queue.iter().any(|queued| queued.pages) {
|
||||
let text = crate::viewer::Viewer::text(cx, "pdf.pages.changed", &[]);
|
||||
cx.emit(crate::view::Failed(text.to_string()));
|
||||
return cx.notify();
|
||||
}
|
||||
log::info!("pdf: queues {what}");
|
||||
self.queue.push_back(Queued { what, op: Box::new(op), pick: None, doc: self.doc, pages: false, follow: true });
|
||||
self.next_edit(cx);
|
||||
}
|
||||
|
||||
/// Queues an edit to the pages themselves.
|
||||
pub fn edit_pages(
|
||||
&mut self,
|
||||
@@ -62,7 +81,7 @@ impl PdfView {
|
||||
) {
|
||||
log::info!("pdf: queues {what}");
|
||||
// Aimed at what is shown now; later states refuse it.
|
||||
self.queue.push_back(Queued { what, op: Box::new(op), pick, doc: self.doc, pages });
|
||||
self.queue.push_back(Queued { what, op: Box::new(op), pick, doc: self.doc, pages, follow: false });
|
||||
self.next_edit(cx);
|
||||
}
|
||||
|
||||
@@ -70,10 +89,12 @@ impl PdfView {
|
||||
if self.editing {
|
||||
return;
|
||||
}
|
||||
let Some(Queued { what, op, pick, doc, pages }) = self.queue.pop_front() else {
|
||||
let Some(Queued { what, op, pick, doc, pages, follow }) = self.queue.pop_front() else {
|
||||
return;
|
||||
};
|
||||
self.editing = true;
|
||||
// Earlier edits have landed; a point still means the same.
|
||||
let doc = if follow { self.doc } else { doc };
|
||||
(self.editing, self.moving) = (true, pages);
|
||||
self.off(
|
||||
move |worker| {
|
||||
let mark = op(worker, doc)?;
|
||||
@@ -83,7 +104,7 @@ impl PdfView {
|
||||
Ok(Fresh { doc, mark, pages, first, marks: worker.outline(doc)? })
|
||||
},
|
||||
move |view, fresh, cx| {
|
||||
view.editing = false;
|
||||
(view.editing, view.moving) = (false, false);
|
||||
match fresh {
|
||||
Ok(fresh) => {
|
||||
view.refresh(fresh, pages, cx);
|
||||
|
||||
@@ -315,3 +315,48 @@ fn a_default_name_avoids_a_field_with_no_widget() {
|
||||
send(cx, &viewer, Change::Design(typed, Vec::new()));
|
||||
assert_eq!(notes(cx, &view).fields.len(), 1, "the field lands");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_strokes_queue_behind_each_other() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let path = dir.path().join("one.pdf");
|
||||
let page: (f32, f32, &[&str]) = (612.0, 792.0, &["Hello"]);
|
||||
std::fs::write(&path, pdf_with(&[page], &[], &Extras::default())).expect("writes");
|
||||
let (viewer, cx) = windowed(&mut cx, dir.path(), &[path], true);
|
||||
let view = shown(cx, &viewer);
|
||||
let note = |y: f32| ely_pdf_engine::NewAnnot { page: 0, shape: ely_pdf_engine::Shape::Note((100.0, y)), rgb: None, contents: "n".into(), author: String::new() };
|
||||
// The second lands while the first still runs.
|
||||
cx.update(|_, app| view.update(app, |view, cx| {
|
||||
view.annotate("a first note", vec![note(500.0)], cx);
|
||||
view.annotate("a second note", vec![note(400.0)], cx);
|
||||
}));
|
||||
settle(cx, |app| view.read(app).settled());
|
||||
cx.update(|_, app| viewer.update(app, |viewer, cx| viewer.side.update(cx, |side, cx| {
|
||||
side.pane = crate::side::Pane::Notes;
|
||||
cx.notify();
|
||||
})));
|
||||
let count = |cx: &mut gpui::VisualTestContext| {
|
||||
settle(cx, |app| view.read(app).settled() && view.read(app).notes.contains_key(&0));
|
||||
cx.update(|_, app| view.read(app).notes[&0].annots.len())
|
||||
};
|
||||
assert_eq!(count(cx), 2, "both notes landed");
|
||||
let rotate = |view: &mut PdfView, cx: &mut gpui::Context<PdfView>| {
|
||||
view.edit_pages("rotating", |worker, doc| worker.rotate_pages(doc, &[0], 90), cx)
|
||||
};
|
||||
// A running rotation would move the point: refused.
|
||||
cx.update(|_, app| view.update(app, |view, cx| {
|
||||
rotate(view, cx);
|
||||
view.annotate("a note while turning", vec![note(300.0)], cx);
|
||||
}));
|
||||
assert_eq!(count(cx), 2, "refused while pages move");
|
||||
// A rotation waiting behind another edit: refused too.
|
||||
cx.update(|_, app| view.update(app, |view, cx| {
|
||||
view.annotate("a third note", vec![note(250.0)], cx);
|
||||
rotate(view, cx);
|
||||
view.annotate("a note behind a turn", vec![note(200.0)], cx);
|
||||
}));
|
||||
assert_eq!(count(cx), 3, "only the note before the turn");
|
||||
cx.update(|_, app| view.update(app, |view, cx| view.annotate("a settled note", vec![note(150.0)], cx)));
|
||||
assert_eq!(count(cx), 4, "taken once pages are still");
|
||||
}
|
||||
@@ -65,6 +65,8 @@ pub struct PdfView {
|
||||
/// Edits waiting their turn, and whether one runs.
|
||||
pub queue: VecDeque<crate::edits::Queued>,
|
||||
pub editing: bool,
|
||||
/// The running edit may move pages.
|
||||
pub moving: bool,
|
||||
/// History marks: now, and at the last load or save.
|
||||
pub mark: String,
|
||||
pub saved: String,
|
||||
@@ -186,6 +188,7 @@ impl PdfView {
|
||||
edited: 0,
|
||||
queue: VecDeque::new(),
|
||||
editing: false,
|
||||
moving: false,
|
||||
saved: loaded.mark.clone(),
|
||||
mark: loaded.mark,
|
||||
thumbs: HashMap::new(),
|
||||
|
||||
@@ -92,15 +92,16 @@ impl PdfView {
|
||||
rgb: 0,
|
||||
bx: [at[0], at[1], self.sizes[page].0 - at[0]],
|
||||
};
|
||||
self.settled_edit("placing a typed signature", cx, move |worker, doc| match worker.put_text(doc, page, &writing, 0.0)? {
|
||||
let write = move |worker: &mut ely_pdf_engine::Worker, doc| match worker.put_text(doc, page, &writing, 0.0)? {
|
||||
Wrote::Done(mark) => Ok(mark),
|
||||
Wrote::Missing(c) => Err(io::Error::new(io::ErrorKind::InvalidInput, format!("the font has no “{c}”"))),
|
||||
Wrote::Preview(..) => Err(io::Error::other("a preview where a write was asked")),
|
||||
});
|
||||
};
|
||||
self.edit_at("placing a typed signature", write, cx);
|
||||
}
|
||||
Stamp::Picture { path, aspect } => {
|
||||
let at = placed(aspect, rect, click);
|
||||
self.settled_edit("placing a signature picture", cx, move |worker, doc| worker.add_image(doc, page, &path, at));
|
||||
self.edit_at("placing a signature picture", move |worker, doc| worker.add_image(doc, page, &path, at), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ pub(super) const EN: &[(&str, &str)] = &[
|
||||
"pdf.object.changed",
|
||||
"The document changed meanwhile; nothing was done.",
|
||||
),
|
||||
("pdf.pages.changed", "Pages are moving; nothing was done. Try again."),
|
||||
("pdf.tool.edit", "Edit Text and Images"),
|
||||
("pdf.tool.text", "Add Text"),
|
||||
("pdf.tool.image", "Add Image…"),
|
||||
@@ -290,6 +291,7 @@ pub(super) const ZH_CN: &[(&str, &str)] = &[
|
||||
("pdf.object.replace", "替换图片…"),
|
||||
("pdf.object.delete", "删除"),
|
||||
("pdf.object.changed", "文档已改动,未执行。"),
|
||||
("pdf.pages.changed", "页面正在变动,未执行,请再试。"),
|
||||
("pdf.tool.edit", "编辑文字和图片"),
|
||||
("pdf.tool.text", "添加文字"),
|
||||
("pdf.tool.image", "添加图片…"),
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@
|
||||
- [x] P-02 每次编辑落地清掉全部图块与缩略图,页面空白、刚画的墨迹消失 — 图块随 R2.6;缩略图在编辑后留到重画;R1、R2 测试未证实际绘制;R3 PASS
|
||||
- [x] P-03 “编辑文字和图片”先命中图片对象再命中文字;一点抖动就移动了背景图 — 包住文字块的对象让位于文字,移动须过 3 个窗口像素;R1 漏掉同点的小图;R2 PASS
|
||||
- [x] P-04 `TEXT_BLOCKS` 对每个文字块重跑一遍整页内容过滤,编辑工具长时间无反应 — 整页一遍干跑判定全部文字块(1000 块 1.5 s→0.1 s);R1 转向文字后错位、共用原点只计一块;R2 PASS
|
||||
- [ ] P-05 连续快画或快点被拒:“文档在此间已改动,未执行”
|
||||
- [x] P-05 连续快画或快点被拒:“文档在此间已改动,未执行” — 按坐标的编辑排队、跑在出队时的状态上,只在页面变动时拒绝;R2 PASS
|
||||
- [ ] P-06 默认字体“原字体”在 Type 1/Type 3 字体的文件上失败,只给引擎原话
|
||||
- [ ] P-07 含连字的文字块永远不可编辑
|
||||
- [ ] P-08 页面读取失败后每帧重试、每帧弹提示
|
||||
|
||||
Reference in new issue
Block a user