Skip to main content

sequoia_view/
thumbnail.rs

1use 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}