1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
mod sprites;

use core::cmp;

use bitmap32::{BitBlock, BitMap};
use calc_common::{
    util::{SliceExt, TransmutableInto},
    DrawableCharacter, Point,
};

pub type ScreenBuffer = BitMap<u32, 256>;

#[inline(always)]
pub fn draw_text_at<C: TransmutableInto<DrawableCharacter>>(
    screen_buffer: &mut ScreenBuffer,
    text: &[C],
    x: u16,
    y: u16,
) {
    let text = C::transmute_slice_into(text);

    _draw_text_at(screen_buffer, text, x, y)
}

fn _draw_text_at(screen_buffer: &mut ScreenBuffer, text: &[DrawableCharacter], x: u16, y: u16) {
    let mut offset = 0;
    for char in text {
        draw_sprite_at(screen_buffer, *char, x + offset, y);
        offset += 5;
    }
}

pub fn draw_hline(screen_buffer: &mut ScreenBuffer, len: u16, x: u16, y: u16) {
    let screen_blocks: &mut [u32] = screen_buffer
        .get_blocks_mut((x as usize * 2)..)
        .unwrap_or(&mut []);

    let screen_blocks = if let Some(blocks) = screen_blocks.get_mut(0..(len as usize * 2)) {
        blocks
    } else {
        screen_blocks
    };

    let block_offset = (y / 32) % 2;
    let block_shift_amnt = y % 32;

    let mask = u32::UPSTREAM_ONE.wrapping_shift_downstream(block_shift_amnt as usize);

    for column in screen_blocks.chunks_exact_mut(2) {
        column[block_offset as usize] |= mask;
    }
}

pub const fn get_text_width<C: TransmutableInto<DrawableCharacter>>(text: &[C]) -> usize {
    text.len() * 5
}

#[inline(always)]
pub fn draw_sprite_at<T: Into<DrawableCharacter>>(
    screen_buffer: &mut ScreenBuffer,
    item: T,
    x: u16,
    y: u16,
) {
    let map = sprites::get_sprite(item.into());
    draw_map_at(screen_buffer, map, x, y);
}

pub fn draw_xor_rectangle(screen_buffer: &mut ScreenBuffer, p1: Point, p2: Point) {
    let top_left = Point {
        x: cmp::min(p1.x, p2.x).min(127),
        y: cmp::min(p1.y, p2.y).min(63),
    };
    let bottom_right = Point {
        x: cmp::max(p1.x, p2.x.min(128)),
        y: cmp::max(p1.y, p2.y).min(64),
    };

    let screen_blocks: &mut [u32] = screen_buffer.as_mut();
    let screen_blocks = unsafe {
        screen_blocks.get_unchecked_mut((top_left.x as usize * 2)..(bottom_right.x as usize * 2))
    };

    let top_mask = u32::MAX
        .checked_shl(u32::from(
            top_left.y + (32u8.saturating_sub(bottom_right.y)),
        ))
        .unwrap_or(0)
        .checked_shr(u32::from(top_left.y))
        .unwrap_or(0);

    let bottom_mask = u32::MAX
        .checked_shl(u32::from(
            top_left.y.saturating_sub(32) + 64u8.saturating_sub(bottom_right.y),
        ))
        .unwrap_or(0)
        >> (top_left.y.saturating_sub(32));

    for [top, bottom] in screen_blocks.uarray_chunks_mut() {
        *top ^= top_mask;
        *bottom ^= bottom_mask;
    }
}

/// Draws the on-screen portion of the map at (x, y) and returns true if and only if 0 <= `y` <= `64 - 8`
fn draw_map_at(screen_buffer: &mut ScreenBuffer, map: &BitMap<u8, 4>, x: u16, y: u16) -> bool {
    let screen_blocks: &mut [u32] = screen_buffer
        .get_blocks_mut((x as usize * 2)..)
        .unwrap_or(&mut []);

    let top_shift_amount = u32::from(y);
    let bottom_shift_amount = if let Some(s) = (64 - 8u16).checked_sub(y) {
        u32::from(s)
    } else {
        return false;
    };

    for ([top, bottom], map_column) in screen_blocks
        .uarray_chunks_mut::<2>()
        .zip(map.0.iter().copied())
    {
        let top_mask = (u32::from(map_column) << (32 - 8))
            .checked_shr(top_shift_amount)
            .unwrap_or(0);
        let bottom_mask = u32::from(map_column)
            .checked_shl(bottom_shift_amount)
            .unwrap_or(0);

        *top |= top_mask;
        *bottom |= bottom_mask;
    }

    true
}