dumo 0.1.1

An embedded-graphics backend for Ratatui with an adjustable font size
Documentation
use core::fmt::Debug;
use core::mem;
use core::slice;

use alloc::vec::Vec;
use ratatui_core::buffer::Cell;
use ratatui_core::layout::Position;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CacheKey {
    pub x: u16,
    pub y: u16,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CacheItem {
    pub key: CacheKey,
    #[cfg_attr(feature = "defmt", defmt(Debug2Format))]
    pub cell: Cell,
    pub tickstamp: usize,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Cache(Vec<CacheItem>);

impl CacheKey {
    pub const fn new(x: u16, y: u16) -> Self {
        Self { x, y }
    }
}

impl CacheItem {
    pub const fn new(key: CacheKey, cell: Cell, tickstamp: usize) -> Self {
        Self {
            key,
            cell,
            tickstamp,
        }
    }
}

impl Cache {
    pub const fn new() -> Self {
        Self(Vec::new())
    }

    pub fn find(&self, Position { x, y }: Position) -> Option<(u16, u16, &Cell)> {
        use unicode_width::UnicodeWidthStr;

        const MAX_END: usize = 2;

        for left in (0..MAX_END)
            .filter_map(|x_offset| x_offset.try_into().ok())
            .filter_map(|x_offset| x.checked_sub(x_offset))
        {
            let key = CacheKey::new(left, y);
            let item = self.get(&key);
            if let Some(cell) = item.map(|item| &item.cell)
                && let width = cell.symbol().width()
                && let Some(x_offset) = width.try_into().ok()
                && let Some(sum) = left.checked_add(x_offset)
                && sum > x
            {
                return Some((left, y, cell));
            }
        }

        None
    }

    pub fn get(&self, key: &CacheKey) -> Option<&CacheItem> {
        let Self(vec) = self;
        if let Ok(index) = vec.binary_search_by_key(key, |e| e.key) {
            let item = &vec[index];

            Some(item)
        } else {
            None
        }
    }

    pub fn insert_or_replace(
        &mut self,
        key: CacheKey,
        cell: Cell,
        tickstamp: usize,
    ) -> Option<CacheItem> {
        use unicode_width::UnicodeWidthStr;

        let Self(vec) = self;
        let CacheKey { x, y } = key;
        let end = cell.symbol().width();
        let item = match vec.binary_search_by_key(&key, |e| e.key) {
            Ok(index) => {
                let item = CacheItem::new(key, cell, tickstamp);
                let item = mem::replace(&mut vec[index], item);

                Some(item)
            }
            Err(index) => {
                let item = CacheItem::new(key, cell, tickstamp);
                vec.insert(index, item);

                None
            }
        };

        let start = item
            .as_ref()
            .map(|item| &item.cell)
            .map(|cell| cell.symbol().width())
            .unwrap_or(1);

        for right in (start..end)
            .filter_map(|x_offset| x_offset.try_into().ok())
            .filter_map(|x_offset| x.checked_add(x_offset))
        {
            let key = CacheKey::new(right, y);
            let _ = self.remove(&key);
        }

        item
    }

    pub fn remove(&mut self, key: &CacheKey) -> Option<CacheItem> {
        let Self(vec) = self;
        if let Ok(index) = vec.binary_search_by_key(key, |e| e.key) {
            let item = vec.remove(index);

            Some(item)
        } else {
            None
        }
    }

    pub fn retain(&mut self, f: impl Fn(CacheKey) -> bool) {
        self.0.retain(|item| f(item.key))
    }

    pub fn clear(&mut self) {
        self.0.clear();
    }

    pub fn iter(&self) -> slice::Iter<'_, CacheItem> {
        self.0.iter()
    }
}

impl Default for Cache {
    fn default() -> Self {
        Self::new()
    }
}