devotee 0.1.47

Visualization engine
Documentation
use std::collections::HashMap;
use std::f64::consts::{FRAC_PI_2, PI};

use devotee::app;
use devotee::app::config;
use devotee::app::context::Context;
use devotee::app::input::key_mouse::{KeyMouse, VirtualKeyCode};
use devotee::app::root::Root;
use devotee::app::setup;
use devotee::util::vector::Vector;
use devotee::visual::color;
use devotee::visual::prelude::*;
use devotee::visual::sprite::Sprite;
use devotee_backend_softbuffer::SoftbufferBackend;

fn main() {
    let init_config = setup::Builder::<Config>::new()
        .with_render_target(Sprite::with_color(FourBits::Black))
        .with_input(Default::default())
        .with_root_constructor(|_| TextApp::new())
        .with_title("text")
        .with_scale(2);
    let app = app::App::<_, SoftbufferBackend>::with_setup(init_config).unwrap();

    app.run();
}

struct Config;

impl config::Config for Config {
    type Root = TextApp;
    type Converter = Converter;
    type Input = KeyMouse;
    type RenderTarget = Sprite<FourBits, 128, 128>;

    fn converter() -> Self::Converter {
        Converter { transparent: None }
    }

    fn background_color() -> FourBits {
        FourBits::Black
    }
}

struct TextApp {
    counter: f64,
    moving: bool,
    ease: (f64, f64),
    font: HashMap<char, Sprite<u8, 4, 6>>,
    angle: f64,
}

impl TextApp {
    fn new() -> Self {
        let counter = 0.0;
        // FIXME: replace with array::zip of chars and symbols arrays when
        // stabilized.
        let font = HashMap::from([
            (
                '0',
                symbol([[1, 1, 1], [1, 0, 1], [1, 0, 1], [1, 0, 1], [1, 1, 1]]),
            ),
            (
                '1',
                symbol([[1, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0], [1, 1, 1]]),
            ),
            (
                '2',
                symbol([[1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 0, 0], [1, 1, 1]]),
            ),
            (
                '3',
                symbol([[1, 1, 1], [0, 0, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1]]),
            ),
            (
                '4',
                symbol([[1, 0, 1], [1, 0, 1], [1, 1, 1], [0, 0, 1], [0, 0, 1]]),
            ),
            (
                '5',
                symbol([[1, 1, 1], [1, 0, 0], [1, 1, 1], [0, 0, 1], [1, 1, 1]]),
            ),
            (
                '6',
                symbol([[1, 1, 1], [1, 0, 0], [1, 1, 1], [1, 0, 1], [1, 1, 1]]),
            ),
            (
                '7',
                symbol([[1, 1, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]]),
            ),
            (
                '8',
                symbol([[1, 1, 1], [1, 0, 1], [1, 1, 1], [1, 0, 1], [1, 1, 1]]),
            ),
            (
                '9',
                symbol([[1, 1, 1], [1, 0, 1], [1, 1, 1], [0, 0, 1], [0, 0, 1]]),
            ),
            (
                '.',
                symbol([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 1, 0]]),
            ),
            (
                '-',
                symbol([[0, 0, 0], [0, 0, 0], [1, 1, 1], [0, 0, 0], [0, 0, 0]]),
            ),
            (
                '\n',
                symbol([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]),
            ),
        ]);
        let moving = false;
        let ease = (0.0, FRAC_PI_2);
        let angle = 0.0;
        Self {
            counter,
            moving,
            font,
            ease,
            angle,
        }
    }
}

fn symbol(data: [[u8; 3]; 5]) -> Sprite<u8, 4, 6> {
    let data = data.map(|line| [line[0], line[1], line[2], 0]);
    let data = [data[0], data[1], data[2], data[3], data[4], [0, 0, 0, 0]];
    Sprite::with_data(data)
}

impl Root<Config> for TextApp {
    fn update(&mut self, update: &mut Context<Config>) {
        if update.input().keys().just_pressed(VirtualKeyCode::Escape) {
            update.shutdown();
        }
        if update.input().keys().just_pressed(VirtualKeyCode::Z) && !self.moving {
            self.moving = true;
            self.counter = 0.0;
        }

        if self.moving {
            self.counter += update.delta().as_secs_f64();

            if self.counter >= 1.0 {
                self.moving = false;
                self.angle = self.ease.1;
                self.ease.0 = self.ease.1;
                self.ease.1 += FRAC_PI_2;
            } else {
                let t = -((PI * self.counter).cos() - 1.0) / 2.0;
                self.angle = self.ease.0 + (self.ease.1 - self.ease.0) * t;
            }
        }
    }

    fn render(&self, render: &mut Sprite<FourBits, 128, 128>) {
        let color = FourBits::White;

        let mut render = render.painter();
        render.set_offset(Vector::new(64, 64));

        render.clear(0.into());
        render.line((-16, 0), (16, 0), paint(FourBits::Red));
        render.line((0, -16), (0, 16), paint(FourBits::Green));
        render.text((0, 0), printer(), &self.font, "0", |_, _, p, _, _, o| {
            if o == 0 {
                p
            } else {
                color
            }
        });

        let cos = self.angle.cos();
        let sin = self.angle.sin();

        let x = (32.0 * cos) as i32;
        let y = -(32.0 * sin) as i32;

        render.line((0, 0), (x, y), paint(FourBits::Beige));

        render.text(
            (x, y),
            printer(),
            &self.font,
            &format!("{:.3}\n{:.3}", cos, sin),
            |_, _, p, _, _, o| {
                if o == 0 {
                    p
                } else {
                    color
                }
            },
        );
    }
}

#[derive(Copy, Clone, PartialEq)]
enum FourBits {
    Black,
    DarkBlue,
    Eggplant,
    DarkGreen,
    Brown,
    DirtyGray,
    Gray,
    White,
    Red,
    Orange,
    Yellow,
    Green,
    LightBlue,
    Purple,
    Pink,
    Beige,
}

impl From<u8> for FourBits {
    #[inline]
    fn from(value: u8) -> Self {
        match value {
            0 => FourBits::Black,
            1 => FourBits::DarkBlue,
            2 => FourBits::Eggplant,
            3 => FourBits::DarkGreen,
            4 => FourBits::Brown,
            5 => FourBits::DirtyGray,
            6 => FourBits::Gray,
            7 => FourBits::White,
            8 => FourBits::Red,
            9 => FourBits::Orange,
            10 => FourBits::Yellow,
            11 => FourBits::Green,
            12 => FourBits::LightBlue,
            13 => FourBits::Purple,
            14 => FourBits::Pink,
            15 => FourBits::Beige,
            _ => FourBits::Black,
        }
    }
}

struct Converter {
    transparent: Option<FourBits>,
}

impl color::Converter for Converter {
    type Palette = FourBits;
    #[inline]
    fn convert(&self, color: &Self::Palette) -> u32 {
        if matches!(&self.transparent, Some(transparent) if *transparent == *color) {
            return 0x0000000000;
        }
        {
            match color {
                FourBits::Black => 0x00000000,
                FourBits::DarkBlue => 0x001d2b53,
                FourBits::Eggplant => 0x007e2553,
                FourBits::DarkGreen => 0x00008751,
                FourBits::Brown => 0x00ab5236,
                FourBits::DirtyGray => 0x005f574f,
                FourBits::Gray => 0x00c2c3c7,
                FourBits::White => 0x00fff1e8,
                FourBits::Red => 0x00ff004d,
                FourBits::Orange => 0x00ffa300,
                FourBits::Yellow => 0x00ffec27,
                FourBits::Green => 0x0000e436,
                FourBits::LightBlue => 0x0029adff,
                FourBits::Purple => 0x0083769c,
                FourBits::Pink => 0x00ff77a8,
                FourBits::Beige => 0x00ffccaa,
            }
        }
    }
}