ElyPDF: only content edits keep old tiles and known sizes; old tiles paint and free with their tab (R2.6)
This commit is contained in:
11 files changed
+108
-26
No files matched your search
@@ -72,7 +72,7 @@ impl Viewer {
|
||||
let turned = unturn(margins, view.read(cx).rotate);
|
||||
let points = turned.map(|side| side * MM);
|
||||
view.update(cx, |view, cx| {
|
||||
view.edit("cropping pages", move |worker, doc| worker.crop_pages(doc, &pages, points), cx)
|
||||
view.edit_pages("cropping pages", move |worker, doc| worker.crop_pages(doc, &pages, points), cx)
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
+31
-10
@@ -17,6 +17,8 @@ pub struct Queued {
|
||||
op: Op,
|
||||
pick: Option<BTreeSet<usize>>,
|
||||
doc: Doc,
|
||||
/// The edit may add, drop, move or reshape pages.
|
||||
pages: bool,
|
||||
}
|
||||
|
||||
/// The document as an edit left it.
|
||||
@@ -36,20 +38,31 @@ impl PdfView {
|
||||
op: impl FnOnce(&mut Worker, Doc) -> io::Result<String> + Send + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.edit_picking(what, None, op, cx);
|
||||
self.queue_edit(what, None, false, op, cx);
|
||||
}
|
||||
|
||||
/// Queues an edit to the pages themselves.
|
||||
pub fn edit_pages(
|
||||
&mut self,
|
||||
what: &'static str,
|
||||
op: impl FnOnce(&mut Worker, Doc) -> io::Result<String> + Send + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.queue_edit(what, None, true, op, cx);
|
||||
}
|
||||
|
||||
/// Queues `op`; once it lands, `pick` is picked.
|
||||
pub fn edit_picking(
|
||||
pub fn queue_edit(
|
||||
&mut self,
|
||||
what: &'static str,
|
||||
pick: Option<BTreeSet<usize>>,
|
||||
pages: bool,
|
||||
op: impl FnOnce(&mut Worker, Doc) -> io::Result<String> + Send + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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 });
|
||||
self.queue.push_back(Queued { what, op: Box::new(op), pick, doc: self.doc, pages });
|
||||
self.next_edit(cx);
|
||||
}
|
||||
|
||||
@@ -57,7 +70,7 @@ impl PdfView {
|
||||
if self.editing {
|
||||
return;
|
||||
}
|
||||
let Some(Queued { what, op, pick, doc }) = self.queue.pop_front() else {
|
||||
let Some(Queued { what, op, pick, doc, pages }) = self.queue.pop_front() else {
|
||||
return;
|
||||
};
|
||||
self.editing = true;
|
||||
@@ -73,7 +86,7 @@ impl PdfView {
|
||||
view.editing = false;
|
||||
match fresh {
|
||||
Ok(fresh) => {
|
||||
view.refresh(fresh, cx);
|
||||
view.refresh(fresh, pages, cx);
|
||||
if let Some(pick) = pick {
|
||||
view.anchor = pick.first().copied();
|
||||
view.picked = pick;
|
||||
@@ -93,13 +106,21 @@ impl PdfView {
|
||||
}
|
||||
|
||||
/// Everything read before the edit goes.
|
||||
fn refresh(&mut self, fresh: Fresh, cx: &mut Context<Self>) {
|
||||
fn refresh(&mut self, fresh: Fresh, pages: bool, cx: &mut Context<Self>) {
|
||||
self.version += 1;
|
||||
self.edited += 1;
|
||||
(self.doc, self.mark) = (fresh.doc, fresh.mark);
|
||||
// Old tiles stay under unchanged pages; nothing flashes.
|
||||
self.keep_all();
|
||||
self.sizes = sizes(fresh.pages, &fresh.first);
|
||||
let measured = sizes(fresh.pages, &fresh.first);
|
||||
if pages || fresh.pages != self.sizes.len() {
|
||||
// A page may now stand where another stood.
|
||||
self.retire_tiles();
|
||||
self.sizes = measured;
|
||||
} else {
|
||||
// Same pages: old tiles stay under them until repainted.
|
||||
self.keep_all();
|
||||
let read = fresh.first.len();
|
||||
self.sizes[..read].copy_from_slice(&measured[..read]);
|
||||
}
|
||||
self.marks = fresh.marks;
|
||||
self.failed.clear();
|
||||
self.text.clear();
|
||||
@@ -145,6 +166,6 @@ impl PdfView {
|
||||
|
||||
pub fn undo(&mut self, redo: bool, cx: &mut Context<Self>) {
|
||||
let what = if redo { "redo" } else { "undo" };
|
||||
self.edit(what, move |worker, doc| worker.undo(doc, redo), cx);
|
||||
self.edit_pages(what, move |worker, doc| worker.undo(doc, redo), cx);
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ impl PdfView {
|
||||
let pages = self.chosen_pages();
|
||||
let before = pages.iter().filter(|page| **page < to).count();
|
||||
let landed = (to - before..to - before + pages.len()).collect();
|
||||
self.edit_picking("moving pages", Some(landed), move |worker, doc| worker.move_pages(doc, &pages, to), cx);
|
||||
self.queue_edit("moving pages", Some(landed), true, move |worker, doc| worker.move_pages(doc, &pages, to), cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,14 +105,14 @@ impl Viewer {
|
||||
}
|
||||
view.update(cx, |view, cx| {
|
||||
view.picked.clear();
|
||||
view.edit("deleting pages", move |worker, doc| worker.delete_pages(doc, &pages), cx);
|
||||
view.edit_pages("deleting pages", move |worker, doc| worker.delete_pages(doc, &pages), cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn turn_pages(&mut self, by: i32, cx: &mut Context<Self>) {
|
||||
self.on_pages(cx, |view, cx| {
|
||||
let pages = view.chosen_pages();
|
||||
view.edit("turning pages", move |worker, doc| worker.rotate_pages(doc, &pages, by), cx);
|
||||
view.edit_pages("turning pages", move |worker, doc| worker.rotate_pages(doc, &pages, by), cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ impl Viewer {
|
||||
self.on_pages(cx, |view, cx| {
|
||||
let at = view.chosen_pages().last().copied().expect("one page at least");
|
||||
let size = view.sizes[at];
|
||||
view.edit("inserting a page", move |worker, doc| worker.insert_blank(doc, at + 1, size), cx);
|
||||
view.edit_pages("inserting a page", move |worker, doc| worker.insert_blank(doc, at + 1, size), cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Viewer {
|
||||
self.pick(false, cx, |viewer, path: PathBuf, cx| {
|
||||
viewer.on_pages(cx, |view, cx| {
|
||||
let at = view.chosen_pages().last().copied().expect("one page at least") + 1;
|
||||
view.edit(
|
||||
view.edit_pages(
|
||||
"inserting pages",
|
||||
move |worker, doc| worker.insert_file(doc, at, &path).map(|(_, mark)| mark),
|
||||
cx,
|
||||
|
||||
@@ -69,11 +69,12 @@ fn drawn_tile(image: Arc<RenderImage>, page: usize, view: gpui::Entity<PdfView>)
|
||||
canvas(
|
||||
|_, _, _| {},
|
||||
move |bounds, _, window, cx| match window.paint_image(bounds, bounds, Default::default(), image, 0, false) {
|
||||
Ok(()) if !view.read(cx).drawn => {
|
||||
view.update(cx, |view, _| view.drawn = true);
|
||||
log::info!("pdf: first tile drawn, page {page}");
|
||||
}
|
||||
Ok(()) => {}
|
||||
Ok(()) => view.update(cx, |view, _| {
|
||||
view.painted += 1;
|
||||
if view.painted == 1 {
|
||||
log::info!("pdf: first tile drawn, page {page}");
|
||||
}
|
||||
}),
|
||||
Err(error) => log::error!("pdf: drawing a page failed: {error}"),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -272,3 +272,46 @@ fn an_edit_past_the_first_hundred_sizes_keeps_the_page_drawn() {
|
||||
settle(cx, |app| view.read(app).edited == 1 && repainted(view.read(app)));
|
||||
assert!(!bare.get(), "the page never goes blank");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_edit_on_page_101_keeps_its_measured_size_and_its_tiles() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut pages: Vec<(f32, f32, &[&str])> = vec![(612.0, 792.0, &["Page"]); 100];
|
||||
pages.push((612.0, 900.0, &["Taller"]));
|
||||
let mut cx = TestAppContext::single();
|
||||
let (viewer, cx) = windowed(&mut cx, dir.path(), &[file(dir.path(), "mixed.pdf", &pages)], true);
|
||||
let view = shown(cx, &viewer);
|
||||
settle(cx, |app| view.read(app).sizes[100] == (612.0, 900.0));
|
||||
cx.update(|_, app| view.update(app, |view, cx| view.show(100, 0.0, cx)));
|
||||
settle(cx, |app| view.read(app).current == 100 && repainted(view.read(app)));
|
||||
let (_watch, bare) = never_bare(cx, &view);
|
||||
let late = ely_pdf_engine::NewAnnot { page: 100, ..note() };
|
||||
cx.update(|_, app| view.update(app, |view, cx| view.annotate("adding a note", vec![late], cx)));
|
||||
settle(cx, |app| view.read(app).edited == 1 && repainted(view.read(app)));
|
||||
assert!(!bare.get(), "page 101 never goes blank");
|
||||
assert_eq!(cx.update(|_, app| view.read(app).sizes[100]), (612.0, 900.0), "its measured size holds");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_tiles_are_painted_and_freed_with_their_tab() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let mut cx = TestAppContext::single();
|
||||
let (viewer, cx) = windowed(&mut cx, dir.path(), &[file(dir.path(), "one.pdf", &[(612.0, 792.0, &["Hello"])])], true);
|
||||
let view = shown(cx, &viewer);
|
||||
settle(cx, |app| repainted(view.read(app)));
|
||||
// Nothing new is asked; only old tiles can paint.
|
||||
let before = cx.update(|_, app| {
|
||||
view.update(app, |view, cx| {
|
||||
let keys: Vec<_> = view.tiles.keys().copied().collect();
|
||||
view.failed.extend(keys);
|
||||
view.keep_all();
|
||||
cx.notify();
|
||||
view.painted
|
||||
})
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let (painted, kept) = cx.update(|_, app| (view.read(app).painted, view.read(app).backdrop.len()));
|
||||
assert!(kept > 0 && painted >= before + kept as u64, "{kept} old tiles, {} paints", painted - before);
|
||||
cx.update(|_, app| view.update(app, |view, _| view.retire_all()));
|
||||
assert!(cx.update(|_, app| view.read(app).backdrop.is_empty()), "closing hands every old tile over");
|
||||
}
|
||||
@@ -37,8 +37,8 @@ pub struct PdfView {
|
||||
pub tiles: HashMap<TileKey, Arc<RenderImage>>,
|
||||
/// Images no longer drawn, freed at the next render.
|
||||
pub retired: ely_gpui_component::primitives::Retired,
|
||||
/// A tile reached the screen; logged once (PERF-05).
|
||||
pub drawn: bool,
|
||||
/// Tile images painted so far; the first is logged (PERF-05).
|
||||
pub painted: u64,
|
||||
/// Tiles the worker could not draw; not asked again.
|
||||
pub failed: HashSet<TileKey>,
|
||||
/// Old tiles shown under their page until covered.
|
||||
@@ -145,7 +145,8 @@ impl PdfView {
|
||||
pub fn retire_all(&mut self) -> ely_gpui_component::primitives::Retired {
|
||||
let mut images = std::mem::take(&mut self.retired);
|
||||
let tiles = self.tiles.drain().map(|(_, image)| image);
|
||||
for image in tiles.chain(self.thumbs.drain().map(|(_, image)| image)) {
|
||||
let old = self.backdrop.drain(..).map(|old| old.image);
|
||||
for image in tiles.chain(old).chain(self.thumbs.drain().map(|(_, image)| image)) {
|
||||
images.push(image);
|
||||
}
|
||||
images
|
||||
@@ -166,7 +167,7 @@ impl PdfView {
|
||||
scroll: ScrollHandle::new(),
|
||||
tiles: HashMap::new(),
|
||||
retired: Default::default(),
|
||||
drawn: false,
|
||||
painted: 0,
|
||||
failed: HashSet::new(),
|
||||
backdrop: Vec::new(),
|
||||
text: HashMap::new(),
|
||||
|
||||
Reference in new issue
Block a user