Repository navigation
Most apps only need to recompile: the items marked BREAKING only affect
code that uses them. ImageStore is new and optional; see
Migrating to ImageStore below.
Added
render_image::ImageStore: one type that holds an app's inline images and
does everything after the download: layout (prepare), drawing and click
areas (render), loading and failed state (start/finish; failed images
stay failed untilretry_failed, sostartis safe to call every frame),
re-fitting images when the width changes, and freeing memory (retain/
clear).- Opt-in placeholders while images load or fail:
with_spinner()(or
with_spinner_frames()with your own frames),with_loading_line()for any
line you like,with_failed_line(). Without them, images look
exactly as before. keep_placeholders(): keep each image's placeholder line (alt text, link
hint labels fromimage_formatter) above the image once it is drawn.- BREAKING
ImagePlacement::row: the visual row an image starts on, after
wrapping. Code that builds anImagePlacementby hand must set it
(placements returned by limner already have it).
Changed
compute_image_signed_positionsworks out all positions in one pass over
the lines instead of re-wrapping the text above every image; long documents
with several images lay out much faster. Results are the same.prepare_inline_images()keeps its signature and results.ImageStoreis
the recommended way from now on; the old function will be deprecated in a
later release, with the guide below as the migration path.
Removed
- BREAKING Functions deprecated since 0.5:
make_clipped_protocol,
make_scrolled_protocol,compute_image_render_rectsandImageRenderRect.
Draw withImageStore::render, or withmake_sliced_protocol+
compute_image_signed_positions+SlicedImageas recommended since 0.5.
Fixed
- BREAKING
render_markdown_with_extra: extra images used to have no line
of their own and overwrote the first lines of text once loaded. Each now gets
a placeholder line at the top, so they show while loading and never cover
text. Lines, images and links of the markdown move down by one line per extra
image (indices in the result are already shifted).
Migrating to ImageStore
Optional, and it can be done in one go. Your two caches, your "downloading"
set and the picker move into the store; downloading and decoding stays in your
code.
| Before (0.8) | After (0.9) |
|---|---|
picker: Picker |
store: ImageStore::new(picker) |
image_cache: HashMap<String, DynamicImage> |
(inside the store) |
protocol_cache: HashMap<String, SlicedProtocol> |
(inside the store) |
| your own "downloading" set | (inside the store) |
if !cache.contains_key(url) && pending.insert(url) { download } |
if store.start(url) { download } |
image_cache.insert(url, image) |
store.finish(url, Some(image)) |
| download/decode failed | store.finish(url, None) (stays failed; store.retry_failed() to try again) |
prepare_inline_images(&mut lines, &images, &mut image_cache, &mut protocol_cache, &picker, &picker.font_size(), width, 10) |
store.prepare(&mut lines, &images, width, 10) |
compute_image_signed_positions + protocol_cache.get + SlicedImage loop |
store.render(frame.buffer_mut(), area, &placements, scroll) |
protocol_cache.get(url) |
store.get(url) |
&picker elsewhere |
store.picker() |
Before:
for img in &result.images {
if !image_cache.contains_key(&img.url) && pending.insert(img.url.clone()) {
spawn_download(img.url.clone()); // later: pending.remove(&url); image_cache.insert(url, image)
}
}
let placements = prepare_inline_images(
&mut lines, &result.images, &mut image_cache, &mut protocol_cache,
&picker, &picker.font_size(), area.width, 10,
);
// inside terminal.draw:
let viewport = ImageViewport { content: area, scroll };
for r in compute_image_signed_positions(&placements, &lines, &viewport) {
if let Some(sliced) = protocol_cache.get(&r.url) {
frame.render_widget(SlicedImage::new(sliced, r.position), area);
}
}After:
for img in &result.images {
if store.start(&img.url) {
spawn_download(img.url.clone()); // later: store.finish(&url, decoded)
}
}
let placements = store.prepare(&mut lines, &result.images, area.width, 10);
// inside terminal.draw:
let hits = store.render(frame.buffer_mut(), area, &placements, scroll);Notes:
prepare()uses the picker's own font size. If you passed a different
FontSizetoprepare_inline_images, keep using that function for now.widthis the width the lines are drawn at; images now re-fit when it
changes (before, they kept the size of their first layout).- If you cache layouts, add
store.version()and the width to the cache key:
the version changes wheneverprepare()would return something different
(an image loaded or failed, or a spinner frame while images are loading). - To animate
with_spinner(), callstore.tick()on a timer (about 10 times a
second) and redraw. - The store keeps loaded images until you call
retainorclear, e.g.
store.retain(|url| on_screen.contains(url))when the user leaves a page.