ratatui-wgpu 0.1.0

A wgpu based backend for ratatui.
Documentation
use std::{
    collections::HashMap,
    ops::Deref,
};

use guillotiere::{
    AllocId,
    AtlasAllocator,
    Change,
    Rectangle,
    Size,
};
use priority_queue::PriorityQueue;
use ratatui::style::Modifier;
use swash::CacheKey;

#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub(crate) struct Key {
    pub(crate) style: Modifier,
    pub(crate) glyph: u16,
    pub(crate) font: CacheKey,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) struct CacheRect {
    pub(crate) x: u32,
    pub(crate) y: u32,
    pub(crate) width: u32,
    pub(crate) height: u32,
}

impl From<Rectangle> for CacheRect {
    fn from(value: Rectangle) -> Self {
        Self {
            x: value.min.x as u32,
            y: value.min.y as u32,
            width: value.width() as u32,
            height: value.height() as u32,
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum Entry {
    Cached(CacheRect),
    Uncached(CacheRect),
}

impl Entry {
    pub(crate) fn cached(&self) -> bool {
        matches!(self, Entry::Cached(_))
    }
}

impl Deref for Entry {
    type Target = CacheRect;

    fn deref(&self) -> &Self::Target {
        let (Entry::Cached(entry) | Entry::Uncached(entry)) = self;
        entry
    }
}

#[derive(Debug, PartialEq, Eq)]
struct CacheEntry {
    age: u64,
    id: AllocId,
    value: CacheRect,
}

impl PartialOrd for CacheEntry {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for CacheEntry {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.age.cmp(&other.age)
    }
}

pub(crate) struct Atlas {
    lru: PriorityQueue<Key, CacheEntry>,
    inner: AtlasAllocator,

    next_age: u64,
}

impl Atlas {
    pub(crate) fn new(width: u32, height: u32) -> Self {
        Atlas {
            lru: PriorityQueue::new(),
            inner: AtlasAllocator::new(Size::new(width as i32, height as i32)),
            next_age: u64::MAX,
        }
    }

    pub(crate) fn clear(&mut self) {
        self.lru.clear();
        self.inner.clear();
        self.next_age = u64::MAX;
    }

    pub(crate) fn try_get(&mut self, key: &Key) -> Option<Entry> {
        if self.next_age == 0 {
            self.clear();
            return None;
        }

        let mut existing = None;
        self.lru.change_priority_by(key, |entry| {
            entry.age = self.next_age;
            existing = Some(entry.value);
            self.next_age -= 1;
        });
        existing.map(Entry::Cached)
    }

    pub(crate) fn get(&mut self, key: &Key, width: u32, height: u32) -> Entry {
        self.try_get(key).unwrap_or_else(|| {
            let mut tries = 1;
            loop {
                if let Some(alloc) = self.inner.allocate(Size::new(width as i32, height as i32)) {
                    let rect = alloc.rectangle.into();
                    self.lru.push(
                        *key,
                        CacheEntry {
                            age: self.next_age,
                            id: alloc.id,
                            value: alloc.rectangle.into(),
                        },
                    );

                    self.next_age -= 1;

                    return Entry::Uncached(rect);
                }

                self.inner.deallocate(self.lru.pop().unwrap().1.id);
                if tries % 10 == 0 {
                    let changes = self
                        .inner
                        .rearrange()
                        .changes
                        .into_iter()
                        .map(|Change { old, new }| (old.id, new))
                        .collect::<HashMap<_, _>>();

                    for (_, rect) in self.lru.iter_mut() {
                        if let Some(new_alloc) = changes.get(&rect.id) {
                            rect.id = new_alloc.id;
                            rect.value = new_alloc.rectangle.into();
                        }
                    }
                }
                tries += 1;
            }
        })
    }
}