mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Merge pull request #1011 from Faabyy/fix/thumbs-visible-only-969
Thumbnails: only render the slides in view (#969)
This commit is contained in:
@@ -132,6 +132,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-18 | Native chart sidecar bytes move from JSON arrays into referenced ZIP entries, with shared media byte/item limits and bounded reads; older inline sidecars still load. Synthetic format regressions cover large workbooks and malformed members |
|
||||
| 2026-10-18 | Slide thumbnails and the Slide Sorter render only the slides in view: on a deck of more than 600 slides they used to evict their own thumbnails, so the pane flashed white and never finished (#969) |
|
||||
| 2026-10-17 | Opening a file caps absurd effect sizes (glow, soft edges, shadows, reflections, on shapes, text and table text) at what Format Shape allows: a glow of 10^18 pt in a .pptx made rendering ask for 34 GB and abort |
|
||||
| 2026-10-17 | Opening a .deckcraft file whose shapes repeat an id gives the repeats fresh ids (groups, layouts and masters too), so the deck validates and edits by id find one shape |
|
||||
| 2026-10-17 | Edit Points: turning a shape into a freeform is one undo step, so Undo brings the original shape back (it used to stay a freeform) |
|
||||
@@ -242,6 +243,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|---|---|---|
|
||||
| 2026-10-18 | minor | Native chart payloads use referenced ZIP members and share media byte/item limits; old inline sidecars remain readable. Synthetic tests cover multi-megabyte layout, all shape owners, shared budgets, missing/corrupt members and invalid references. Scores and milestone scope unchanged |
|
||||
| 2026-10-15 | trivial | Progress log: chart package parts survive a native save. Scores and milestone names unchanged |
|
||||
| 2026-10-18 | trivial | Progress log: thumbnails of huge decks render only the slides in view (#969) |
|
||||
| 2026-10-15 | trivial | Progress log: PPTX trigger click-step round trip. Feature scores and milestone names unchanged |
|
||||
| 2026-10-16 | trivial | Progress log: PNG export with a transparent background (#784) |
|
||||
| 2026-10-16 | trivial | Progress log: a `.ppsx` from the system starts the slide show (#862) |
|
||||
|
||||
@@ -37,3 +37,40 @@ fn the_outline_of_a_huge_deck_lays_out_only_what_is_in_view() {
|
||||
assert!(h.query_all_by_role(Role::TextInput).count() < 200);
|
||||
assert_eq!(h.state().session.doc().unwrap().selection.slide, 1500);
|
||||
}
|
||||
|
||||
/// The thumbnail pane of an 800-slide deck renders only the thumbnails in view. When every row
|
||||
/// asked for one, a deck bigger than the 600-entry thumbnail cache evicted its own thumbnails before
|
||||
/// the pane was done, so the pane kept rendering and flashing white forever (#969).
|
||||
#[test]
|
||||
fn the_thumbnail_pane_of_a_huge_deck_renders_only_the_slides_in_view() {
|
||||
let mut h = harness(800);
|
||||
h.run_steps(60);
|
||||
let settled = h.state().textures.thumb_count();
|
||||
assert!(settled > 3 && settled < 60, "{settled} thumbnails rendered");
|
||||
// The visible thumbnails are done: more frames render nothing new.
|
||||
h.run_steps(10);
|
||||
assert_eq!(h.state().textures.thumb_count(), settled);
|
||||
// Scrolled far down, the slides now in view get their thumbnails, and only those.
|
||||
h.hover_at(egui::pos2(100.0, 450.0));
|
||||
h.event(egui::Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Point,
|
||||
delta: egui::vec2(0.0, -40_000.0),
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
phase: egui::TouchPhase::Move,
|
||||
});
|
||||
h.run_steps(60);
|
||||
let scrolled = h.state().textures.thumb_count();
|
||||
assert!(scrolled > settled && scrolled < 2 * settled + 10, "{scrolled} thumbnails after scrolling (was {settled})");
|
||||
}
|
||||
|
||||
/// The same in the Slide Sorter: only the cells in view render a thumbnail (#969).
|
||||
#[test]
|
||||
fn the_sorter_of_a_huge_deck_renders_only_the_slides_in_view() {
|
||||
let mut h = harness(800);
|
||||
h.state_mut().ui.view = crate::ViewMode::Sorter;
|
||||
h.run_steps(60);
|
||||
let settled = h.state().textures.thumb_count();
|
||||
assert!(settled > 3 && settled < 80, "{settled} thumbnails rendered");
|
||||
h.run_steps(10);
|
||||
assert_eq!(h.state().textures.thumb_count(), settled);
|
||||
}
|
||||
|
||||
@@ -203,13 +203,17 @@ fn place_slides(app: &mut SlideApp, ui: &mut Ui, g: &Grid<'_>, budget: &mut u32,
|
||||
let img = crate::textures::device_pixel_rect(Rect::from_min_size(cell.min + vec2(18.0, 8.0), vec2(g.tw, g.th)), ppp);
|
||||
rects.push((i, cell));
|
||||
let p = ui.painter();
|
||||
match app.textures.thumb(ui.ctx(), g.doc, i, (img.width() * ppp).round() as u32, budget) {
|
||||
// Only cells in view (plus a little margin) ask for a thumbnail, so a deck bigger
|
||||
// than the thumbnail cache does not evict its own thumbnails forever (#969).
|
||||
let near = ui.clip_rect().expand2(vec2(0.0, 2.0 * cell.height())).intersects(cell);
|
||||
let tex = if near { app.textures.thumb(ui.ctx(), g.doc, i, (img.width() * ppp).round() as u32, budget) } else { None };
|
||||
match tex {
|
||||
Some(tex) => {
|
||||
p.image(tex.id(), img, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE);
|
||||
}
|
||||
None => {
|
||||
p.rect_filled(img, CornerRadius::ZERO, Color32::WHITE);
|
||||
need_more = true;
|
||||
need_more |= near;
|
||||
}
|
||||
}
|
||||
let id = g.doc.slides.get(i).map(|s| s.id);
|
||||
|
||||
@@ -115,7 +115,11 @@ fn list(app: &mut SlideApp, ui: &mut Ui) {
|
||||
p.text(pos2(row.min.x + 14.0, img.min.y + 20.0), Align2::CENTER_TOP, "★", theme::font(10.0), t.text_faint);
|
||||
}
|
||||
let wpx = (img.width() * ppp).round() as u32;
|
||||
match app.textures.thumb(ui.ctx(), &doc, i, wpx, &mut budget) {
|
||||
// Only rows in view (plus a little margin) ask for a thumbnail: with every row asking, a
|
||||
// deck bigger than the thumbnail cache evicts its own thumbnails and never settles (#969).
|
||||
let near = ui.clip_rect().expand2(vec2(0.0, 2.0 * row.height())).intersects(row);
|
||||
let tex = if near { app.textures.thumb(ui.ctx(), &doc, i, wpx, &mut budget) } else { None };
|
||||
match tex {
|
||||
Some(tex) => {
|
||||
p.image(
|
||||
tex.id(),
|
||||
@@ -126,7 +130,7 @@ fn list(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
None => {
|
||||
p.rect_filled(img, CornerRadius::ZERO, Color32::WHITE);
|
||||
need_more = true;
|
||||
need_more |= near;
|
||||
}
|
||||
}
|
||||
if selected {
|
||||
|
||||
Reference in New Issue
Block a user