sequoia_view/
thumbnail.rs1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::hash::Hash;
4use std::rc::Rc;
5
6use sequoia_core::{ImageSource, Pixels};
7
8#[derive(Clone, Default)]
9pub struct ThumbnailCache {
10 inner: Rc<RefCell<HashMap<(String, u32, u32), ImageSource>>>,
11}
12
13impl ThumbnailCache {
14 pub fn new() -> Self {
15 Self::default()
16 }
17
18 pub fn get_or_insert<K>(
19 &self,
20 key: K,
21 width: u32,
22 height: u32,
23 build: impl FnOnce(Pixels) -> Pixels,
24 ) -> ImageSource
25 where
26 K: Hash + Eq + ToString,
27 {
28 let w = width.max(1);
29 let h = height.max(1);
30 let k = (key.to_string(), w, h);
31 let mut map = self.inner.borrow_mut();
32 if let Some(src) = map.get(&k) {
33 return src.clone();
34 }
35 let buf = build(Pixels::new(w, h));
36 let src = buf.into_source();
37 map.insert(k, src.clone());
38 src
39 }
40
41 pub fn len(&self) -> usize {
42 self.inner.borrow().len()
43 }
44
45 pub fn is_empty(&self) -> bool {
46 self.inner.borrow().is_empty()
47 }
48
49 pub fn clear(&self) {
50 self.inner.borrow_mut().clear();
51 }
52}
53
54pub fn shared_cache() -> ThumbnailCache {
55 thread_local! {
56 static SHARED: ThumbnailCache = ThumbnailCache::new();
57 }
58 SHARED.with(|c| c.clone())
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use sequoia_core::Color;
65
66 #[test]
67 fn build_runs_once_per_key_and_size() {
68 let cache = ThumbnailCache::new();
69 let calls = Rc::new(std::cell::Cell::new(0));
70 let build = {
71 let calls = calls.clone();
72 move |mut px: Pixels| {
73 calls.set(calls.get() + 1);
74 px.set(0, 0, Color::WHITE);
75 px
76 }
77 };
78 let a = cache.get_or_insert("n", 4, 4, build);
79 let b = cache.get_or_insert("n", 4, 4, |px| px);
80 assert_eq!(calls.get(), 1, "drugi raz to hit, nie build");
81 assert_eq!(a.cache_key(), b.cache_key());
82 let _ = cache.get_or_insert("n", 8, 8, |px| px);
83 assert_eq!(cache.len(), 2, "inny rozmiar = osobny wpis");
84 }
85
86 #[test]
87 fn degenerate_size_clamps_to_one() {
88 let cache = ThumbnailCache::new();
89 let src = cache.get_or_insert("z", 0, 0, |px| px);
90 let _ = src;
91 assert_eq!(cache.len(), 1);
92 }
93}