ltrender 0.1.3

a diff based terminal rendering engine
Documentation
use common_stdx::{Point, Rect};

use crate::{
    DrawError, SpriteEntry, SpriteId, SpriteRegistry,
    terminal_buffer::{BasicDrawCreator, Drawable, drawable::SinglePointed},
    update_interval_handler::UpdateIntervalCreator,
};

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum VideoLoopType {
    Loop,
    NoLoop,
    Boomerang,
    KillOnFinish,
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum VideoSpeed {
    Fps(u16),
    MillisecondsPerFrame(u16),
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum FrameIdent {
    FirstFrame,
    SpecificFrame(u16),
    LastFrame,
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub enum AnimationInfo {
    Image {
        frame: FrameIdent,
    },
    Video {
        loop_type: VideoLoopType,
        speed: VideoSpeed,
        start_frame: FrameIdent,
        end_frame: FrameIdent,
    },
}
impl Default for AnimationInfo {
    fn default() -> Self {
        AnimationInfo::Video {
            loop_type: VideoLoopType::Loop,
            speed: VideoSpeed::Fps(1),
            start_frame: FrameIdent::FirstFrame,
            end_frame: FrameIdent::LastFrame,
        }
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct SpriteDrawable {
    pub position: Point<i32>,
    pub sprite_id: SpriteId,
    pub last_state_change: std::time::Instant,
    pub animation_type: AnimationInfo,
}

impl SinglePointed for SpriteDrawable {
    fn position(&self) -> Point<i32> {
        self.position
    }
    fn set_position(&mut self, p: Point<i32>) {
        self.position = p;
    }
}

impl SpriteDrawable {
    fn get_frame_idx(&self, ident: &FrameIdent, sprite: &SpriteEntry) -> usize {
        match ident {
            FrameIdent::FirstFrame => 0,
            FrameIdent::SpecificFrame(idx) => *idx as usize,
            FrameIdent::LastFrame => sprite.info.size().0 - 1,
        }
    }
    fn draw_frame(
        &self,
        sprite: &SpriteEntry,
        frame_idx: usize,
        size: (usize, usize),
        bd_creator: &mut BasicDrawCreator,
    ) -> Result<(), DrawError> {
        let frame_data =
            sprite
                .info
                .get_frame_flat(frame_idx)
                .ok_or(DrawError::SpriteFrameNotFound(
                    self.sprite_id,
                    frame_idx as u16,
                ))?;

        let origin_x = self.position.x;
        let origin_y = self.position.y;

        for (i, chr) in frame_data.iter().enumerate() {
            let dx = (i % size.0) as i32;
            let dy = (i / size.0) as i32;

            let abs_x = origin_x.saturating_add(dx);
            let abs_y = origin_y.saturating_add(dy);

            bd_creator.draw_char((abs_x, abs_y), *chr);
        }
        Ok(())
    }

    fn get_frames_since_last_update(&self, speed: &VideoSpeed) -> u16 {
        let elapsed_ms = self.last_state_change.elapsed().as_millis();
        match speed {
            VideoSpeed::Fps(fps) => ((elapsed_ms * (*fps as u128)) / 1000) as u16,
            VideoSpeed::MillisecondsPerFrame(ms) => (elapsed_ms / (*ms as u128)) as u16,
        }
    }
}

impl Drawable for SpriteDrawable {
    fn size(&self, sprites: &SpriteRegistry) -> Result<(u16, u16), DrawError> {
        let sprite = sprites
            .get(&self.sprite_id)
            .ok_or(DrawError::SpriteNotFound(self.sprite_id))?;

        let size = sprite.info.size();
        Ok((size.1 as u16, size.2 as u16))
    }
    fn as_single_pointed_mut(&mut self) -> Option<&mut dyn SinglePointed> {
        Some(self)
    }
    fn draw(&mut self, sprites: &SpriteRegistry) -> Result<BasicDrawCreator, DrawError> {
        let sprite = sprites
            .get(&self.sprite_id)
            .ok_or(DrawError::SpriteNotFound(self.sprite_id))?;

        let size = sprite.info.size();
        let mut bd_creator = BasicDrawCreator::new_with_capacity(size.0 * size.1);
        match &self.animation_type {
            AnimationInfo::Image { frame } => {
                let frame_idx = self.get_frame_idx(frame, sprite);

                self.draw_frame(sprite, frame_idx, (size.1, size.2), &mut bd_creator)?;
            }
            AnimationInfo::Video {
                loop_type,
                speed,
                start_frame,
                end_frame,
            } => {
                let sprite = sprites
                    .get(&self.sprite_id)
                    .ok_or(DrawError::SpriteNotFound(self.sprite_id))?;

                let real_start_frame = self.get_frame_idx(start_frame, sprite);
                let real_end_frame = self.get_frame_idx(end_frame, sprite);

                let amount_of_frames_since_start = self.get_frames_since_last_update(speed);
                let total_frame_range = real_end_frame - real_start_frame + 1;

                // if current_frame_in_range = None, nothing will be drawn
                let current_frame_in_range = match &loop_type {
                    VideoLoopType::Loop => {
                        Some(amount_of_frames_since_start % total_frame_range as u16)
                    }
                    VideoLoopType::NoLoop => {
                        if amount_of_frames_since_start >= total_frame_range as u16 {
                            Some((total_frame_range - 1) as u16)
                        } else {
                            Some(amount_of_frames_since_start)
                        }
                    }
                    VideoLoopType::KillOnFinish => {
                        if amount_of_frames_since_start >= total_frame_range as u16 {
                            None
                        } else {
                            Some(amount_of_frames_since_start)
                        }
                    }

                    VideoLoopType::Boomerang => {
                        unimplemented!("fuck this")
                    }
                };
                if let Some(frame_in_range) = current_frame_in_range {
                    let frame_to_draw = real_start_frame + frame_in_range as usize;
                    let size = sprite.info.size();
                    self.draw_frame(sprite, frame_to_draw, (size.1, size.2), &mut bd_creator)?;
                }
            }
        }
        Ok(bd_creator)
    }

    fn bounding_iv(&self, sprites: &SpriteRegistry) -> Option<UpdateIntervalCreator> {
        let size = sprites
            .get(&self.sprite_id)
            .map(|s| s.size())
            .unwrap_or((0, 0, 0));
        let mut c = UpdateIntervalCreator::new();
        c.register_redraw_region(Rect {
            p1: self.position,
            p2: Point {
                x: self.position.x + size.1 as i32,
                y: self.position.y + size.2 as i32,
            },
        });
        Some(c)
    }
}