1use std::ops::{Deref, DerefMut};
2
3use bevy::prelude::{Resource, Result, Vec2};
4use ratatui::Terminal;
5use soft_ratatui::rusttype::Font;
6use soft_ratatui::{EmbeddedTTF, SoftBackend};
7
8const DEFAULT_TERMINAL_COLS: u16 = 100;
9const DEFAULT_TERMINAL_ROWS: u16 = 50;
10const DEFAULT_FONT_SIZE_PX: u32 = 24;
11
12const FONT_REGULAR: &[u8] = include_bytes!("../assets/fonts/CopperMono-Light.ttf");
13const FONT_BOLD: &[u8] = include_bytes!("../assets/fonts/CopperMono-SemiBold.ttf");
14const FONT_ITALIC: &[u8] = include_bytes!("../assets/fonts/CopperMono-LightItalic.ttf");
15
16#[derive(Resource, Clone, Debug)]
17pub struct CuBevyMonFontOptions {
18 pub size_px: u32,
19}
20
21impl CuBevyMonFontOptions {
22 pub fn new(size_px: u32) -> Self {
23 Self { size_px }
24 }
25
26 fn clamped_size_px(&self) -> u32 {
27 self.size_px.max(8)
28 }
29}
30
31impl Default for CuBevyMonFontOptions {
32 fn default() -> Self {
33 Self::new(DEFAULT_FONT_SIZE_PX)
34 }
35}
36
37#[derive(Resource)]
38pub struct CuBevyMonTerminal(pub Terminal<SoftBackend<EmbeddedTTF>>);
39
40impl CuBevyMonTerminal {
41 pub fn from_options(options: &CuBevyMonFontOptions) -> Result<Self> {
42 let font_size = options.clamped_size_px();
43 let backend = SoftBackend::<EmbeddedTTF>::new(
44 DEFAULT_TERMINAL_COLS,
45 DEFAULT_TERMINAL_ROWS,
46 font_size,
47 load_font(FONT_REGULAR, "CopperMono-Light.ttf")?,
48 Some(load_font(FONT_BOLD, "CopperMono-SemiBold.ttf")?),
49 Some(load_font(FONT_ITALIC, "CopperMono-LightItalic.ttf")?),
50 );
51 Ok(Self(Terminal::new(backend)?))
52 }
53}
54
55impl Deref for CuBevyMonTerminal {
56 type Target = Terminal<SoftBackend<EmbeddedTTF>>;
57
58 fn deref(&self) -> &Self::Target {
59 &self.0
60 }
61}
62
63impl DerefMut for CuBevyMonTerminal {
64 fn deref_mut(&mut self) -> &mut Self::Target {
65 &mut self.0
66 }
67}
68
69pub fn sync_terminal_to_panel(terminal: &mut CuBevyMonTerminal, panel_size: Vec2) {
70 if panel_size.x <= 1.0 || panel_size.y <= 1.0 {
71 return;
72 }
73
74 let char_width = terminal.backend().char_width.max(1) as f32;
75 let char_height = terminal.backend().char_height.max(1) as f32;
76 let cols = (panel_size.x / char_width).floor().max(1.0) as u16;
77 let rows = (panel_size.y / char_height).floor().max(1.0) as u16;
78
79 let current_area = terminal.backend().buffer().area;
80 if current_area.width == cols && current_area.height == rows {
81 return;
82 }
83
84 terminal.backend_mut().resize(cols, rows);
85}
86
87fn load_font(bytes: &'static [u8], label: &str) -> Result<Font<'static>> {
88 Font::try_from_bytes(bytes).ok_or_else(|| {
89 std::io::Error::other(format!("failed to load bundled cu_bevymon font {label}")).into()
90 })
91}