devotee 0.1.47

Visualization engine
Documentation
use std::f64::consts;
use std::time::Duration;

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::visual::color;
use devotee::visual::prelude::*;
use devotee::visual::sprite::Sprite;
use devotee_backend_softbuffer::SoftbufferBackend;
use rodio::source::{SineWave, Source};

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

    app.run();
}

struct Config;

impl config::Config for Config {
    type Root = Twister;
    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
    }
}

#[derive(Default)]
struct Twister {
    rotation: f64,
    twist: f64,
}

impl Root<Config> for Twister {
    fn update(&mut self, update: &mut Context<Config>) {
        if update.input().keys().just_pressed(VirtualKeyCode::Escape) {
            update.shutdown();
        }
        let delta = update.delta().as_secs_f64();
        if update.input().keys().is_pressed(VirtualKeyCode::Left) {
            self.rotation += delta;
        }
        if update.input().keys().is_pressed(VirtualKeyCode::Right) {
            self.rotation -= delta;
        }
        if update.input().keys().just_pressed(VirtualKeyCode::Space) {
            self.rotation = 0.0;
            self.twist = 0.0;

            update.sound_system_mut().map(|s| {
                s.play(Box::new(
                    SineWave::new(500.0).take_duration(Duration::from_secs_f64(0.05)),
                ))
            });
        }
        self.twist += delta;
    }

    fn render(&self, render: &mut Sprite<FourBits, 128, 128>) {
        let mut render = render.painter();
        render.clear(0.into());
        let resolution_x = render.width();
        let resolution_y = render.height();
        let rotation = 2.0 * self.rotation;
        let center = resolution_y as f64 / 2.0;

        render.rect_f(
            (resolution_x / 6, resolution_y / 6),
            (5 * resolution_x / 6, 5 * resolution_y / 6),
            draw(14.into()),
        );

        render.rect_f(
            (resolution_x / 4, resolution_y / 4),
            (3 * resolution_x / 4, 3 * resolution_y / 4),
            draw(15.into()),
        );

        let twist = 4.0 * consts::PI * (consts::FRAC_PI_4 * self.twist).cos();
        let width = 16.0;

        for x in 0..resolution_x {
            let twist = x as f64 / resolution_x as f64 * twist + rotation;
            let y1 = (f64::sin(twist) * width + center) as i32;
            let y2 = (f64::sin(twist + consts::FRAC_PI_2) * width + center) as i32;
            let y3 = (f64::sin(twist + consts::PI) * width + center) as i32;
            let y4 = (f64::sin(twist - consts::FRAC_PI_2) * width + center) as i32;

            if y1 < y2 {
                render.line((x, y1), (x, y2), draw(1.into()));
            }
            if y2 < y3 {
                render.line((x, y2), (x, y3), draw(2.into()));
            }
            if y3 < y4 {
                render.line((x, y3), (x, y4), draw(3.into()));
            }
            if y4 < y1 {
                render.line((x, y4), (x, y1), draw(4.into()));
            }
        }

        for y in 0..resolution_y {
            let twist = y as f64 / resolution_y as f64 * twist + rotation;
            let x1 = (f64::sin(twist) * width + center) as i32;
            let x2 = (f64::sin(twist + consts::FRAC_PI_2) * width + center) as i32;
            let x3 = (f64::sin(twist + consts::PI) * width + center) as i32;
            let x4 = (f64::sin(twist - consts::FRAC_PI_2) * width + center) as i32;

            if x1 < x2 {
                render.line((x1, y), (x2, y), draw(5.into()));
            }
            if x2 < x3 {
                render.line((x2, y), (x3, y), draw(6.into()));
            }
            if x3 < x4 {
                render.line((x3, y), (x4, y), draw(7.into()));
            }
            if x4 < x1 {
                render.line((x4, y), (x1, y), draw(8.into()));
            }
        }
    }
}

#[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,
        }
    }
}

impl color::Color for FourBits {
    fn mix(self, other: Self) -> Self {
        match other {
            FourBits::Black => self,
            value => value,
        }
    }
}

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 0x00000000;
        }
        {
            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,
            }
        }
    }
}