use core::fmt::Debug;
use ratatui_core::style::Color;
use crate::lru_cache::LruCache;
pub struct ColorCache<Context, const N: usize>
where
Context: Debug + PartialEq + Copy + Eq + Default,
{
fg_cache: LruCache<CacheKey<Context>, Color, N>,
bg_cache: LruCache<CacheKey<Context>, Color, N>,
}
impl<Context, const N: usize> ColorCache<Context, N>
where
Context: Debug + PartialEq + Copy + Eq + Default,
{
pub fn new() -> Self {
Self {
fg_cache: LruCache::new(),
bg_cache: LruCache::new(),
}
}
pub fn memoize_fg<F>(&mut self, from: Color, context: Context, f: F) -> Color
where
F: FnOnce(&Color) -> Color,
{
let from = if from == Color::Reset { Color::White } else { from };
let key = CacheKey::new(from, context);
self.fg_cache.memoize(&key, |key| f(&key.from))
}
pub fn memoize_bg<F>(&mut self, from: Color, context: Context, f: F) -> Color
where
F: FnOnce(&Color) -> Color,
{
let from = if from == Color::Reset { Color::Black } else { from };
let key = CacheKey::new(from, context);
self.bg_cache.memoize(&key, |key| f(&key.from))
}
pub fn fg_cache_hits(&self) -> u32 {
self.fg_cache.cache_hits()
}
pub fn fg_cache_misses(&self) -> u32 {
self.fg_cache.cache_misses()
}
pub fn bg_cache_hits(&self) -> u32 {
self.bg_cache.cache_hits()
}
pub fn bg_cache_misses(&self) -> u32 {
self.bg_cache.cache_misses()
}
}
impl<Context, const N: usize> Default for ColorCache<Context, N>
where
Context: Debug + Copy + PartialEq + Eq + Default,
{
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct CacheKey<T> {
from: Color,
context: T,
}
impl<T> CacheKey<T> {
fn new(from: Color, context: T) -> Self {
Self { from, context }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ColorSpace;
#[test]
fn test_fg_color_reset_mapping() {
let mut cache = ColorCache::<Color, 4>::new();
let target = Color::Cyan;
let result1 = cache.memoize_fg(Color::Reset, target, |c| {
assert_eq!(*c, Color::White);
ColorSpace::Rgb.lerp(c, &target, 0.5)
});
let result2 = cache.memoize_fg(Color::Reset, target, |_c| {
panic!("Should not be called - should hit cache");
});
assert_eq!(result1, result2);
assert_eq!(cache.fg_cache_hits(), 1);
assert_eq!(cache.fg_cache_misses(), 1);
}
#[test]
fn test_bg_color_reset_mapping() {
let mut cache = ColorCache::<Color, 4>::new();
let target = Color::Cyan;
let result1 = cache.memoize_bg(Color::Reset, target, |c| {
assert_eq!(*c, Color::Black);
ColorSpace::Rgb.lerp(c, &target, 0.5)
});
let result2 = cache.memoize_bg(Color::Reset, target, |_c| {
panic!("Should not be called - should hit cache");
});
assert_eq!(result1, result2);
assert_eq!(cache.bg_cache_hits(), 1);
assert_eq!(cache.bg_cache_misses(), 1);
}
#[test]
fn test_non_reset_colors_passthrough() {
let mut cache = ColorCache::<Color, 4>::new();
let source = Color::Red;
let target = Color::Blue;
let result = cache.memoize_fg(source, target, |c| {
assert_eq!(*c, source);
ColorSpace::Rgb.lerp(c, &target, 0.5)
});
assert_eq!(result, Color::Rgb(64, 0, 64));
}
#[test]
fn test_separate_fg_bg_caches() {
let mut cache = ColorCache::<Color, 4>::new();
let target = Color::White;
let fg_result = cache.memoize_fg(Color::Reset, target, |c| {
ColorSpace::Rgb.lerp(c, &target, 0.5)
});
let bg_result = cache.memoize_bg(Color::Reset, target, |c| {
ColorSpace::Rgb.lerp(c, &target, 0.5)
});
assert_ne!(fg_result, bg_result);
assert_eq!(cache.fg_cache_misses(), 1);
assert_eq!(cache.bg_cache_misses(), 1);
}
}