agb_eb_ext 0.25.0

AGB Extension methods
use agb::display::GraphicsFrame;
use agb::display::object::{Object, Sprite, Tag};
use agb::display::tile_data::TileData;
use agb::display::tiled::RegularBackground;
use agb::fixnum::{Vector2D, vec2};

pub trait ShowSprite {
    fn show(&self, at: Vector2D<i32>, frame: &mut GraphicsFrame);
}

pub trait ShowTag {
    fn show(&self, idx: usize, at: Vector2D<i32>, frame: &mut GraphicsFrame);
}

impl ShowSprite for &'static Sprite {
    #[inline]
    fn show(&self, at: Vector2D<i32>, frame: &mut GraphicsFrame) {
        Object::new(*self).set_pos(at).show(frame);
    }
}

impl ShowTag for Tag {
    #[inline]
    fn show(&self, idx: usize, at: Vector2D<i32>, frame: &mut GraphicsFrame) {
        Object::new(self.sprite(idx)).set_pos(at).show(frame);
    }
}

pub trait ShowAllTag {
    fn show_all(&self, at: Vector2D<i32>, frame: &mut GraphicsFrame);
}

impl ShowAllTag for Tag {
    #[inline]
    fn show_all(&self, at: Vector2D<i32>, frame: &mut GraphicsFrame) {
        let size = self.sprite(0).size().to_width_height();
        self.sprites().iter().enumerate().for_each(|(idx, sprite)| {
            let pos = at + vec2((idx * size.0) as i32, 0);
            sprite.show(pos, frame);
        });
    }
}

/// draw frame onto background
///
/// vert_tiles must be for top (will be vflipped for bottom)
/// horz_tiles must be for left (will be hflipped for right)
/// corner_tiles must be [top_left] or [top_left, top_right, bottom_left, bottom_right] (will be flipped if just top_left)
///
/// draws frame using tiles at `at` going to `at + size`
///
/// # Params
/// - `at` - tile to start drawing at
/// - `vert_tiles` - tile indices to draw on top and bottom (loops)
/// - `horz_tiles` - tile indices to draw on left and right (loops)
/// - `corner_tiles` - tile indices to draw on corners
/// - `center_tiles` - tile indices to fill interior with (loops per row)
/// - `size` - size in tiles of frame
#[allow(clippy::too_many_arguments)]
pub fn draw_bg_frame(
    bg: &mut RegularBackground,
    tile_data: &TileData,
    at: Vector2D<i32>,
    vert_tiles: &[u16],
    horz_tiles: &[u16],
    corner_tiles: &[u16],
    center_tiles: &[u16],
    size: (u8, u8),
) {
    debug_assert!(
        corner_tiles.len() == 1 || corner_tiles.len() == 4,
        "corner_tiles must have 1 or 4 entries"
    );
    debug_assert!(
        !vert_tiles.is_empty() && !horz_tiles.is_empty(),
        "vert_tiles and horz_tiles must not be empty"
    );
    debug_assert!(size.0 >= 2 && size.1 >= 2, "frame must be at least 2x2");

    let (tl, tr, bl, br) = if corner_tiles.len() == 1 {
        let s = tile_data.tile_settings[corner_tiles[0] as usize];
        (s, s.hflip(true), s.vflip(true), s.hflip(true).vflip(true))
    } else {
        (
            tile_data.tile_settings[corner_tiles[0] as usize],
            tile_data.tile_settings[corner_tiles[1] as usize],
            tile_data.tile_settings[corner_tiles[2] as usize],
            tile_data.tile_settings[corner_tiles[3] as usize],
        )
    };

    let w = size.0 as i32;
    let h = size.1 as i32;

    bg.set_tile(at, &tile_data.tiles, tl);
    bg.set_tile(vec2(at.x + w - 1, at.y), &tile_data.tiles, tr);
    bg.set_tile(vec2(at.x, at.y + h - 1), &tile_data.tiles, bl);
    bg.set_tile(vec2(at.x + w - 1, at.y + h - 1), &tile_data.tiles, br);

    for (i, &tile_idx) in (1..w - 1).zip(vert_tiles.iter().cycle()) {
        let s = tile_data.tile_settings[tile_idx as usize];
        bg.set_tile(vec2(at.x + i, at.y), &tile_data.tiles, s);
        bg.set_tile(
            vec2(at.x + i, at.y + h - 1),
            &tile_data.tiles,
            s.vflip(true),
        );
    }

    for (i, &tile_idx) in (1..h - 1).zip(horz_tiles.iter().cycle()) {
        let s = tile_data.tile_settings[tile_idx as usize];
        bg.set_tile(vec2(at.x, at.y + i), &tile_data.tiles, s);
        bg.set_tile(
            vec2(at.x + w - 1, at.y + i),
            &tile_data.tiles,
            s.hflip(true),
        );
    }

    if !center_tiles.is_empty() {
        for i in 1..h - 1 {
            for (j, &c_idx) in (1..w - 1).zip(center_tiles.iter().cycle()) {
                bg.set_tile(
                    vec2(at.x + j, at.y + i),
                    &tile_data.tiles,
                    tile_data.tile_settings[c_idx as usize],
                );
            }
        }
    }
}

/// draw repeated tiles onto background
///
/// Requires tiles in image be a rectangle
///
/// So, if `start_tile_idx` is 0 and `meta_tile_size` is (2,2), `count` is (4,2), `bg_image_width_tiles` is 10, `pos` is (0,0) then bg becomes
///
/// 0,   1,  0,  1,  0,  1,  0,  1
/// 10, 11, 10, 11, 10, 11, 10, 11
/// 0,   1,  0,  1  0,   1,  0,  1
/// 10, 11, 10, 11, 10, 11, 10, 11
///
/// # Params
/// - `start_tile_idx` - first tile index in bg image
/// - `pos` - tile to start drawing at
/// - `bg_image_width_tiles` - width of image in tiles (basically stride of image)
/// - `meta_tile_size` - size in tiles of meta tiles
/// - `count` - number of (horizontal, vertical) repeats of meta tiles
pub fn draw_bg_tiles(
    bg: &mut RegularBackground,
    tile_data: &TileData,
    pos: Vector2D<i32>,
    start_tile_idx: u16,
    bg_image_width_tiles: u16,
    meta_tile_size: (u16, u16),
    count: (u8, u8),
) {
    let count = (count.0 as i32, count.1 as i32);
    let meta_tile_size = (meta_tile_size.0 as i32, meta_tile_size.1 as i32);
    let start_tile_idx = start_tile_idx as i32;
    let bg_image_width_tiles = bg_image_width_tiles as i32;

    for meta_row in 0..meta_tile_size.1 {
        for meta_col in 0..meta_tile_size.0 {
            let tile_idx = start_tile_idx + meta_row * bg_image_width_tiles + meta_col;
            let setting = tile_data.tile_settings[tile_idx as usize];
            for vr in 0..count.1 {
                for hr in 0..count.0 {
                    bg.set_tile(
                        vec2(
                            pos.x + hr * meta_tile_size.0 + meta_col,
                            pos.y + vr * meta_tile_size.1 + meta_row,
                        ),
                        &tile_data.tiles,
                        setting,
                    );
                }
            }
        }
    }
}