cu-bevymon 0.15.0

A Bevy-backed Ratatui monitor for Copper.
Documentation
use std::ops::{Deref, DerefMut};

use bevy::prelude::{Resource, Result, Vec2};
use ratatui::Terminal;
use soft_ratatui::rusttype::Font;
use soft_ratatui::{EmbeddedTTF, SoftBackend};

const DEFAULT_TERMINAL_COLS: u16 = 100;
const DEFAULT_TERMINAL_ROWS: u16 = 50;
const DEFAULT_FONT_SIZE_PX: u32 = 24;

const FONT_REGULAR: &[u8] = include_bytes!("../assets/fonts/CopperMono-Light.ttf");
const FONT_BOLD: &[u8] = include_bytes!("../assets/fonts/CopperMono-SemiBold.ttf");
const FONT_ITALIC: &[u8] = include_bytes!("../assets/fonts/CopperMono-LightItalic.ttf");

#[derive(Resource, Clone, Debug)]
pub struct CuBevyMonFontOptions {
    pub size_px: u32,
}

impl CuBevyMonFontOptions {
    pub fn new(size_px: u32) -> Self {
        Self { size_px }
    }

    fn clamped_size_px(&self) -> u32 {
        self.size_px.max(8)
    }
}

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

#[derive(Resource)]
pub struct CuBevyMonTerminal(pub Terminal<SoftBackend<EmbeddedTTF>>);

impl CuBevyMonTerminal {
    pub fn from_options(options: &CuBevyMonFontOptions) -> Result<Self> {
        let font_size = options.clamped_size_px();
        let backend = SoftBackend::<EmbeddedTTF>::new(
            DEFAULT_TERMINAL_COLS,
            DEFAULT_TERMINAL_ROWS,
            font_size,
            load_font(FONT_REGULAR, "CopperMono-Light.ttf")?,
            Some(load_font(FONT_BOLD, "CopperMono-SemiBold.ttf")?),
            Some(load_font(FONT_ITALIC, "CopperMono-LightItalic.ttf")?),
        );
        Ok(Self(Terminal::new(backend)?))
    }
}

impl Deref for CuBevyMonTerminal {
    type Target = Terminal<SoftBackend<EmbeddedTTF>>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for CuBevyMonTerminal {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

pub fn sync_terminal_to_panel(terminal: &mut CuBevyMonTerminal, panel_size: Vec2) {
    if panel_size.x <= 1.0 || panel_size.y <= 1.0 {
        return;
    }

    let char_width = terminal.backend().char_width.max(1) as f32;
    let char_height = terminal.backend().char_height.max(1) as f32;
    let cols = (panel_size.x / char_width).floor().max(1.0) as u16;
    let rows = (panel_size.y / char_height).floor().max(1.0) as u16;

    let current_area = terminal.backend().buffer().area;
    if current_area.width == cols && current_area.height == rows {
        return;
    }

    terminal.backend_mut().resize(cols, rows);
}

fn load_font(bytes: &'static [u8], label: &str) -> Result<Font<'static>> {
    Font::try_from_bytes(bytes).ok_or_else(|| {
        std::io::Error::other(format!("failed to load bundled cu_bevymon font {label}")).into()
    })
}