agb_eb_ext 0.25.3

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

/// Show a sprite in one call
pub trait ShowSprite {
    /// Show this sprite at `at`
    fn show(&self, at: Vector2D<i32>, frame: &mut GraphicsFrame);
}

/// Show one sprite from a tag in one call
pub trait ShowTag {
    /// Show sprite `idx` of this tag at `at`
    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);
    }
}

/// Show every sprite of a tag side by side
pub trait ShowAllTag {
    /// Show every sprite in this tag in a row starting at `at`, each offset by the sprite width
    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 (width, _) = self.sprite(0).size().to_width_height();
        let mut pos = at;
        for sprite in self.sprites() {
            sprite.show(pos, frame);
            pos.x += width as i32;
        }
    }
}

/// Corner tiles for [`draw_bg_frame`]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Corners {
    /// Top left tile, flipped for the other three corners
    Single(u16),
    /// `[top_left, top_right, bottom_left, bottom_right]`
    All([u16; 4]),
}

/// Tile indices used by [`draw_bg_frame`]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct FrameTiles<'a> {
    /// Tiles for the top edge, repeated; vflipped for the bottom edge. Must not be empty
    pub top: &'a [u16],
    /// Tiles for the left edge, repeated; hflipped for the right edge. Must not be empty
    pub left: &'a [u16],
    /// Corner tiles
    pub corners: Corners,
    /// Tiles to fill the interior with, repeated per row. Empty leaves the interior untouched
    pub center: &'a [u16],
}

/// draw frame onto background
///
/// Draws a frame using `tiles` from `at` (top left, in tiles) covering `size` tiles
///
/// Panics if `size` is smaller than 2x2 or an edge tile list is empty
pub fn draw_bg_frame(
    bg: &mut RegularBackground,
    tile_data: &TileData,
    at: impl Into<Vector2D<i32>>,
    size: impl Into<Vector2D<i32>>,
    tiles: &FrameTiles,
) {
    let at = at.into();
    let size = size.into();
    assert!(
        !tiles.top.is_empty() && !tiles.left.is_empty(),
        "frame top and left tiles must not be empty"
    );
    assert!(size.x >= 2 && size.y >= 2, "frame must be at least 2x2");

    let setting = |idx: u16| tile_data.tile_settings[idx as usize];

    let (tl, tr, bl, br) = match tiles.corners {
        Corners::Single(idx) => {
            let s = setting(idx);
            (s, s.hflip(true), s.vflip(true), s.hflip(true).vflip(true))
        }
        Corners::All([tl, tr, bl, br]) => (setting(tl), setting(tr), setting(bl), setting(br)),
    };

    let right = at.x + size.x - 1;
    let bottom = at.y + size.y - 1;

    bg.set_tile(at, &tile_data.tiles, tl);
    bg.set_tile(vec2(right, at.y), &tile_data.tiles, tr);
    bg.set_tile(vec2(at.x, bottom), &tile_data.tiles, bl);
    bg.set_tile(vec2(right, bottom), &tile_data.tiles, br);

    for (x, &idx) in (at.x + 1..right).zip(tiles.top.iter().cycle()) {
        let s = setting(idx);
        bg.set_tile(vec2(x, at.y), &tile_data.tiles, s);
        bg.set_tile(vec2(x, bottom), &tile_data.tiles, s.vflip(true));
    }

    for (y, &idx) in (at.y + 1..bottom).zip(tiles.left.iter().cycle()) {
        let s = setting(idx);
        bg.set_tile(vec2(at.x, y), &tile_data.tiles, s);
        bg.set_tile(vec2(right, y), &tile_data.tiles, s.hflip(true));
    }

    if !tiles.center.is_empty() {
        for y in at.y + 1..bottom {
            for (x, &idx) in (at.x + 1..right).zip(tiles.center.iter().cycle()) {
                bg.set_tile(vec2(x, y), &tile_data.tiles, setting(idx));
            }
        }
    }
}

/// draw repeated meta tiles onto background
///
/// The meta tile is the rectangle of `meta_tile_size` tiles whose top left is `start_tile_idx`
/// in the source image (`tile_data.width` is used as the stride).
///
/// So, if `start_tile_idx` is 0, `meta_tile_size` is (2,2), `count` is (4,2), the image is 10 tiles wide and `pos` is (0,0) then bg becomes
///
/// ```text
/// 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
/// - `pos` - tile to start drawing at
/// - `start_tile_idx` - index of the meta tile's top left tile in the image
/// - `meta_tile_size` - size in tiles of the meta tile
/// - `count` - number of (horizontal, vertical) repeats of the meta tile
pub fn draw_bg_tiles(
    bg: &mut RegularBackground,
    tile_data: &TileData,
    pos: impl Into<Vector2D<i32>>,
    start_tile_idx: u16,
    meta_tile_size: impl Into<Vector2D<i32>>,
    count: impl Into<Vector2D<i32>>,
) {
    let pos = pos.into();
    let meta = meta_tile_size.into();
    let count = count.into();
    let stride = tile_data.width as i32;

    let mut row_start = start_tile_idx as i32;
    for meta_row in 0..meta.y {
        for meta_col in 0..meta.x {
            let setting: TileSetting = tile_data.tile_settings[(row_start + meta_col) as usize];
            let mut y = pos.y + meta_row;
            for _ in 0..count.y {
                let mut x = pos.x + meta_col;
                for _ in 0..count.x {
                    bg.set_tile(vec2(x, y), &tile_data.tiles, setting);
                    x += meta.x;
                }
                y += meta.y;
            }
        }
        row_start += stride;
    }
}