nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use crate::tui::ecs::components::TermColor;
use crate::tui::ecs::world::{LABEL, POSITION, SPRITE, TILEMAP, World as TuiWorld};

#[derive(Clone)]
pub struct GridCell {
    pub character: char,
    pub foreground: TermColor,
    pub background: TermColor,
}

impl Default for GridCell {
    fn default() -> Self {
        Self {
            character: ' ',
            foreground: TermColor::White,
            background: TermColor::Black,
        }
    }
}

pub struct CellGrid {
    pub columns: u16,
    pub rows: u16,
    pub cells: Vec<GridCell>,
}

impl CellGrid {
    pub fn new(columns: u16, rows: u16) -> Self {
        let size = columns as usize * rows as usize;
        Self {
            columns,
            rows,
            cells: vec![GridCell::default(); size],
        }
    }

    pub fn clear(&mut self) {
        for cell in &mut self.cells {
            *cell = GridCell::default();
        }
    }

    pub fn set(&mut self, column: i32, row: i32, cell: GridCell) {
        if column >= 0 && column < self.columns as i32 && row >= 0 && row < self.rows as i32 {
            let index = row as usize * self.columns as usize + column as usize;
            self.cells[index] = cell;
        }
    }

    pub fn get(&self, column: u16, row: u16) -> &GridCell {
        let index = row as usize * self.columns as usize + column as usize;
        &self.cells[index]
    }
}

enum DrawableKind {
    Sprite,
    Label,
    Tilemap,
}

pub fn build_grid_from_tui_world(tui_world: &TuiWorld, grid: &mut CellGrid) {
    grid.clear();

    let camera = tui_world.resources.camera;

    let mut drawables: Vec<(i32, freecs::Entity, DrawableKind)> = Vec::new();

    for entity in tui_world.query_entities(POSITION | SPRITE) {
        let has_label = tui_world.get_label(entity).is_some();
        let has_tilemap = tui_world.get_tilemap(entity).is_some();
        if has_label || has_tilemap {
            continue;
        }
        let visible = tui_world
            .get_visibility(entity)
            .is_none_or(|visibility| visibility.visible);
        if !visible {
            continue;
        }
        let z = tui_world.get_z_index(entity).map_or(0, |z_index| z_index.0);
        drawables.push((z, entity, DrawableKind::Sprite));
    }

    for entity in tui_world.query_entities(POSITION | LABEL) {
        let visible = tui_world
            .get_visibility(entity)
            .is_none_or(|visibility| visibility.visible);
        if !visible {
            continue;
        }
        let z = tui_world.get_z_index(entity).map_or(0, |z_index| z_index.0);
        drawables.push((z, entity, DrawableKind::Label));
    }

    for entity in tui_world.query_entities(POSITION | TILEMAP) {
        let visible = tui_world
            .get_visibility(entity)
            .is_none_or(|visibility| visibility.visible);
        if !visible {
            continue;
        }
        let z = tui_world.get_z_index(entity).map_or(0, |z_index| z_index.0);
        drawables.push((z, entity, DrawableKind::Tilemap));
    }

    drawables.sort_by_key(|(z, _, _)| *z);

    for (_, entity, kind) in &drawables {
        let Some(position) = tui_world.get_position(*entity) else {
            continue;
        };

        let screen_column = (position.column - camera.offset_column).round() as i32;
        let screen_row = (position.row - camera.offset_row).round() as i32;

        match kind {
            DrawableKind::Sprite => {
                let Some(sprite) = tui_world.get_sprite(*entity) else {
                    continue;
                };
                grid.set(
                    screen_column,
                    screen_row,
                    GridCell {
                        character: sprite.character,
                        foreground: sprite.foreground,
                        background: sprite.background,
                    },
                );
            }
            DrawableKind::Label => {
                let Some(label) = tui_world.get_label(*entity) else {
                    continue;
                };
                for (char_index, character) in label.text.chars().enumerate() {
                    grid.set(
                        screen_column + char_index as i32,
                        screen_row,
                        GridCell {
                            character,
                            foreground: label.foreground,
                            background: label.background,
                        },
                    );
                }
            }
            DrawableKind::Tilemap => {
                let Some(tilemap) = tui_world.get_tilemap(*entity) else {
                    continue;
                };
                for tile_row in 0..tilemap.height {
                    for tile_column in 0..tilemap.width {
                        let cell = &tilemap.cells[tile_row * tilemap.width + tile_column];
                        if cell.character == '\0' {
                            continue;
                        }
                        grid.set(
                            screen_column + tile_column as i32,
                            screen_row + tile_row as i32,
                            GridCell {
                                character: cell.character,
                                foreground: cell.foreground,
                                background: cell.background,
                            },
                        );
                    }
                }
            }
        }
    }
}