use crate::font::AgbFont;
use agb::display::tiled::{DynamicTile16, RegularBackground, TileEffect};
use agb::fixnum::{Vector2D, vec2};
use alloc::vec::Vec;
pub struct TextRenderer {
tiles: Vec<(i32, i32, DynamicTile16)>,
}
impl Default for TextRenderer {
fn default() -> Self {
Self {
tiles: Vec::with_capacity(32),
}
}
}
impl TextRenderer {
pub fn draw_text<T: AgbFont>(
&mut self,
text: &[u8],
font: &T,
background: &mut RegularBackground,
pos: Vector2D<i32>,
wrap_at: Option<u8>,
palette_id: u8,
) -> (i32, i32, i32) {
let wrap_at = wrap_at.map(|n| n as i32);
let mut cursor_y = pos.y;
let mut cursor_x = pos.x;
let mut longest = 0;
for &c in text {
if c == b'\n' {
cursor_y += font.glyph_height() as i32;
if cursor_x > longest {
longest = cursor_x;
}
cursor_x = pos.x;
} else {
let char_w = font.char_width(c) as i32;
if let Some(wrap_at) = wrap_at
&& cursor_x - pos.x + char_w > wrap_at
{
cursor_y += font.glyph_height() as i32;
if cursor_x > longest {
longest = cursor_x;
}
cursor_x = pos.x;
}
self.draw_glyph(
font.glyph(c),
font,
background,
cursor_x,
cursor_y,
palette_id,
);
cursor_x += char_w;
}
}
if cursor_x > longest {
longest = cursor_x;
}
(cursor_x - pos.x, cursor_y - pos.y, longest - pos.x)
}
fn draw_glyph<T: AgbFont>(
&mut self,
glyph: &[u32],
font: &T,
background: &mut RegularBackground,
pixel_x: i32,
pixel_y: i32,
palette_id: u8,
) {
let row_u32s = font.row_u32s();
let height = font.glyph_height() as i32;
for chunk in 0..row_u32s {
let px_left = pixel_x + ((chunk as i32) << 3);
let tile_x = px_left >> 3;
let x_shift = (px_left & 7) as u32;
let shift_bits = x_shift << 2;
let mut last_tile_y = -1;
let mut left_idx = 0;
let mut right_idx = None;
for row in 0..height {
let abs_y = pixel_y + row;
let current_tile_y = abs_y >> 3;
let row_in_tile = (abs_y & 7) as usize;
if current_tile_y != last_tile_y {
left_idx = self.ensure_tile_idx(tile_x, current_tile_y, background, palette_id);
right_idx = if x_shift > 0 {
Some(self.ensure_tile_idx(
tile_x + 1,
current_tile_y,
background,
palette_id,
))
} else {
None
};
last_tile_y = current_tile_y;
}
let pixel_data = glyph[(row as usize * row_u32s) + chunk];
let left_row = &mut self.tiles[left_idx].2.data_mut()[row_in_tile];
blit_pixel_row_arm(left_row, pixel_data << shift_bits);
if let Some(r_idx) = right_idx {
let right_row = &mut self.tiles[r_idx].2.data_mut()[row_in_tile];
blit_pixel_row_arm(right_row, pixel_data >> (32 - shift_bits));
}
}
}
}
fn ensure_tile_idx(&mut self, tx: i32, ty: i32, bg: &mut RegularBackground, pal: u8) -> usize {
if let Some(pos) = self
.tiles
.iter()
.rposition(|(x, y, _)| *x == tx && *y == ty)
{
return pos;
}
let tile = DynamicTile16::new().fill_with(0);
bg.set_tile_dynamic16(vec2(tx, ty), &tile, TileEffect::default().palette(pal));
self.tiles.push((tx, ty, tile));
self.tiles.len() - 1
}
pub fn clear_pixel_rect(
&mut self,
background: &mut RegularBackground,
pos: Vector2D<i32>,
width: i32,
height: i32,
palette_id: u8,
) {
for row in 0..height {
let abs_y = pos.y + row;
let tile_y = abs_y >> 3;
let row_in_tile = (abs_y & 7) as usize;
for x_off in (0..width).step_by(8) {
let abs_x = pos.x + x_off;
let tile_x = abs_x >> 3;
let x_shift = (abs_x & 7) as u32;
let left_idx = self.ensure_tile_idx(tile_x, tile_y, background, palette_id);
self.tiles[left_idx].2.data_mut()[row_in_tile] = 0;
if x_shift > 0 {
let right_idx =
self.ensure_tile_idx(tile_x + 1, tile_y, background, palette_id);
self.tiles[right_idx].2.data_mut()[row_in_tile] = 0;
}
}
}
}
}
#[unsafe(link_section = ".iwram")]
#[instruction_set(arm::a32)]
fn blit_pixel_row_arm(target: &mut u32, src: u32) {
if src == 0 {
return;
}
let hi = src & 0x8888_8888;
let lo = src & 0x7777_7777;
let set_nybbles = (hi | ((lo + 0x7777_7777) & 0x8888_8888)) >> 3;
let mask = set_nybbles * 0xF;
*target = (*target & !mask) | src;
}