tarsila 0.1.1

GUI image editor for pixel art
use crate::wrapped_image::WrappedImage;
use crate::UiState;
use lapix::graphics;
use lapix::{Bitmap, FreeImage, Point, Position, Rect, Selection, Size};
use macroquad::prelude::Color as MqColor;
use macroquad::prelude::*;
use std::time::{SystemTime, UNIX_EPOCH};

const DASHED_LINE_SEGMENT: f32 = 5.;
const DASHED_LINE_ANIMATION_MS: u128 = 250;
const SPRSHEET_LINE_THICKNESS: f32 = 1.;
const SPRSHEET_LINE_COLOR: MqColor = BLACK;

#[derive(Debug, Copy, Clone)]
pub struct DrawContext {
    pub spritesheet: Size<u8>,
    pub scale: f32,
    pub canvas_pos: Position<f32>,
    pub camera: Position<f32>,
    pub canvas_size: Size<f32>,
    pub selection: Option<Selection>,
}

pub fn draw_texture_helper(texture: Texture2D, p: Position<f32>, scale: f32) {
    let w = texture.width();
    let h = texture.height();

    let params = DrawTextureParams {
        dest_size: Some(Vec2 {
            x: w * scale,
            y: h * scale,
        }),
        ..Default::default()
    };

    draw_texture_ex(texture, p.x, p.y, WHITE, params);
}

pub fn draw_animated_dashed_line(p1: Point<i32>, p2: Point<i32>) {
    let len = graphics::distance(p1, p2);
    let dist: Point<f32> = (p2 - p1).into();
    let segments = len / DASHED_LINE_SEGMENT;
    let (dx, dy) = (dist.x / segments, dist.y / segments);

    let iteration = (SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis()
        / DASHED_LINE_ANIMATION_MS
        % 2) as usize;

    for i in 0..(segments as usize) {
        let color = if i % 2 == iteration { BLACK } else { WHITE };
        draw_line(
            p1.x as f32 + i as f32 * dx,
            p1.y as f32 + i as f32 * dy,
            p1.x as f32 + (i as f32 + 1.) * dx,
            p1.y as f32 + (i as f32 + 1.) * dy,
            1.,
            color,
        );
    }
}

pub fn draw_animated_dashed_rect(rect: Rect<i32>) {
    draw_animated_dashed_line(rect.pos(), rect.top_right());
    draw_animated_dashed_line(rect.pos(), rect.bottom_left());
    draw_animated_dashed_line(rect.top_right(), rect.pos() + rect.size());
    draw_animated_dashed_line(rect.bottom_left(), rect.pos() + rect.size());
}

pub fn draw_free_image(
    ctx: DrawContext,
    img: &FreeImage<WrappedImage>,
    layer_opacity: u8,
    free_image_tex: Texture2D,
) {
    let w = img.texture.width() as f32;
    let h = img.texture.height() as f32;

    let x = ctx.canvas_pos.x - ctx.camera.x + img.rect.x as f32 * ctx.scale;
    let y = ctx.canvas_pos.y - ctx.camera.y + img.rect.y as f32 * ctx.scale;

    let params = DrawTextureParams {
        dest_size: Some(Vec2 {
            x: w * ctx.scale,
            y: h * ctx.scale,
        }),
        ..Default::default()
    };

    let color = [255, 255, 255, layer_opacity];
    macroquad::prelude::draw_texture_ex(free_image_tex, x, y, color.into(), params);
}

pub fn draw_selection(ctx: DrawContext, free_image: Option<&FreeImage<WrappedImage>>) {
    let rect = match ctx.selection {
        Some(Selection::FreeImage) => free_image.unwrap().rect,
        Some(Selection::Canvas(rect)) => rect,
        _ => return,
    };

    let p0 = ctx.canvas_pos - ctx.camera;
    let r = Rect {
        x: (p0.x + rect.x as f32 * ctx.scale) as i32,
        y: (p0.y + rect.y as f32 * ctx.scale) as i32,
        w: (rect.w as f32 * ctx.scale) as i32,
        h: (rect.h as f32 * ctx.scale) as i32,
    };
    draw_animated_dashed_rect(r);
}

pub fn draw_spritesheet_boundaries(ctx: DrawContext) {
    for i in 0..ctx.spritesheet.x {
        for j in 0..ctx.spritesheet.y {
            let p0 = ctx.canvas_pos - ctx.camera;
            let w = ctx.canvas_size.x / ctx.spritesheet.x as f32 * ctx.scale;
            let h = ctx.canvas_size.y / ctx.spritesheet.y as f32 * ctx.scale;
            let x = p0.x + i as f32 * w;
            let y = p0.y + j as f32 * h;

            macroquad::prelude::draw_rectangle_lines(
                x,
                y,
                w,
                h,
                SPRSHEET_LINE_THICKNESS,
                SPRSHEET_LINE_COLOR,
            );
        }
    }
}

pub fn draw_canvas(state: &UiState) {
    for i in 0..state.num_layers() {
        if !state.layer(i).visible() {
            continue;
        }

        let texture = state.layer_tex(i);
        let size = Size::new(texture.width(), texture.height());
        let p = state.canvas_pos() - state.camera();
        let scale = state.zoom();

        let params = DrawTextureParams {
            dest_size: Some(Vec2 {
                x: size.x * scale,
                y: size.y * scale,
            }),
            ..Default::default()
        };

        let color = [255, 255, 255, state.layer(i).opacity()];
        macroquad::prelude::draw_texture_ex(texture, p.x, p.y, color.into(), params);
    }
}