devotee 0.1.47

Visualization engine
Documentation
use std::collections::HashSet;

use devotee::app;
use devotee::app::config;
use devotee::app::context::Context;
use devotee::app::input::event;
use devotee::app::input::Input;
use devotee::app::root::Root;
use devotee::app::setup;
use devotee::app::window::Window;
use devotee::util::vector::Vector;
use devotee::visual::canvas::Canvas;
use devotee::visual::color;
use devotee::visual::prelude::*;
use devotee::winit;
use devotee_backend_softbuffer::SoftbufferBackend;

fn main() {
    let init_config = setup::Builder::<Config>::new()
        .with_render_target(Canvas::with_resolution(FourBits::Black, 128, 128))
        .with_input(Default::default())
        .with_root_constructor(|_| Default::default())
        .with_title("mandelbrot")
        .with_scale(4);
    let app = app::App::<_, SoftbufferBackend>::with_setup(init_config).unwrap();

    app.run();
}

struct Config;

impl config::Config for Config {
    type Root = Mandelbrot;
    type Converter = Converter;
    type Input = CustomInput;
    type RenderTarget = Canvas<FourBits>;

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

    fn background_color() -> FourBits {
        0.into()
    }
}

struct Mandelbrot {
    scale: f64,
    center: Vector<f64>,
}

impl Default for Mandelbrot {
    fn default() -> Self {
        Self {
            scale: 0.5,
            center: Vector::new(0.0, 0.0),
        }
    }
}

impl Root<Config> for Mandelbrot {
    fn update(&mut self, update: &mut Context<Config>) {
        let delta = update.delta().as_secs_f64();

        if update.input().is_pressed(Button::In) {
            self.scale -= delta;
        }
        if update.input().is_pressed(Button::Out) {
            self.scale += delta;
        }

        let scale = 2.0_f64.powf(self.scale);
        if update.input().is_pressed(Button::Left) {
            *self.center.x_mut() += delta / scale;
        }
        if update.input().is_pressed(Button::Right) {
            *self.center.x_mut() -= delta / scale;
        }
        if update.input().is_pressed(Button::Up) {
            *self.center.y_mut() += delta / scale;
        }
        if update.input().is_pressed(Button::Down) {
            *self.center.y_mut() -= delta / scale;
        }

        if update.input().just_pressed(Button::Quit) {
            update.shutdown();
        }
    }

    fn render(&self, render: &mut Canvas<FourBits>) {
        let scale = 2.0_f64.powf(self.scale);
        let width = render.width();
        let height = render.height();
        for x in 0..width {
            for y in 0..height {
                let x0 = (x - width / 2) as f64 / width as f64 / scale - self.center.x();
                let y0 = (y - height / 2) as f64 / height as f64 / scale - self.center.y();
                let mut px = 0.0;
                let mut py = 0.0;
                let mut iteration: u8 = 0;
                while px * px + py * py < 4.0 && iteration < 32 {
                    (px, py) = (px * px - py * py + x0, 2.0 * px * py + y0);
                    iteration += 1;
                }
                if let Some(p) = render.pixel_mut((x, y).into()) {
                    *p = (iteration % 16).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 {
    #[inline]
    fn mix(self, other: Self) -> Self {
        other
    }
}

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

#[derive(Default)]
struct CustomInput {
    is_pressed: HashSet<Button>,
    was_pressed: HashSet<Button>,
}

#[derive(Clone, Copy, Hash, PartialEq, Eq)]
enum Button {
    Quit,
    Left,
    Right,
    Up,
    Down,
    In,
    Out,
}

impl TryFrom<Option<event::VirtualKeyCode>> for Button {
    type Error = ();

    fn try_from(value: Option<event::VirtualKeyCode>) -> Result<Self, Self::Error> {
        match value.ok_or(())? {
            event::VirtualKeyCode::Escape => Ok(Button::Quit),
            event::VirtualKeyCode::Left => Ok(Button::Left),
            event::VirtualKeyCode::Right => Ok(Button::Right),
            event::VirtualKeyCode::Up => Ok(Button::Up),
            event::VirtualKeyCode::Down => Ok(Button::Down),
            event::VirtualKeyCode::Z => Ok(Button::In),
            event::VirtualKeyCode::X => Ok(Button::Out),
            _ => Err(()),
        }
    }
}

impl CustomInput {
    pub fn is_pressed(&self, button: Button) -> bool {
        self.is_pressed.contains(&button)
    }

    pub fn just_pressed(&self, button: Button) -> bool {
        self.is_pressed.contains(&button) && !self.was_pressed.contains(&button)
    }
}

impl<Bck> Input<Bck> for CustomInput {
    fn next_frame(&mut self) {
        self.was_pressed = self.is_pressed.clone();
    }

    fn consume_window_event<'a>(
        &mut self,
        event: winit::event::WindowEvent<'a>,
        _window: &Window,
        _back: &Bck,
    ) -> Option<winit::event::WindowEvent<'a>> {
        match event {
            winit::event::WindowEvent::KeyboardInput { input, .. } => {
                if let Ok(button) = input.virtual_keycode.try_into() {
                    match input.state {
                        event::ElementState::Pressed => {
                            self.is_pressed.insert(button);
                        }
                        event::ElementState::Released => {
                            self.is_pressed.remove(&button);
                        }
                    }
                }
                None
            }
            event => Some(event),
        }
    }
}