2025-02-07 04:17:11 +00:00
|
|
|
use egui::{Color32, Image, ImageSource, Response, Sense, TextureOptions, Ui, Widget};
|
|
|
|
|
2025-02-05 04:23:06 +00:00
|
|
|
pub const GENERIC_PALETTE: [u8; 4] = [0, 64, 128, 255];
|
|
|
|
|
2025-02-13 04:59:48 +00:00
|
|
|
pub fn shade(brt: u8, color: Color32) -> Color32 {
|
|
|
|
color.gamma_multiply(brt as f32 / 255.0)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn generic_palette(color: Color32) -> [Color32; 4] {
|
|
|
|
GENERIC_PALETTE.map(|brt| shade(brt, color))
|
|
|
|
}
|
|
|
|
|
2025-02-16 20:55:21 +00:00
|
|
|
pub const fn parse_palette(palette: u8) -> [u8; 4] {
|
2025-02-05 04:23:06 +00:00
|
|
|
[
|
2025-02-16 20:55:21 +00:00
|
|
|
0,
|
|
|
|
(palette >> 2) & 0x03,
|
|
|
|
(palette >> 4) & 0x03,
|
|
|
|
(palette >> 6) & 0x03,
|
2025-02-05 04:23:06 +00:00
|
|
|
]
|
|
|
|
}
|
2025-02-07 04:17:11 +00:00
|
|
|
|
2025-02-16 20:55:21 +00:00
|
|
|
pub const fn parse_shades(brts: &[u8; 8]) -> [u8; 4] {
|
|
|
|
[
|
|
|
|
0,
|
|
|
|
brts[0],
|
|
|
|
brts[2],
|
|
|
|
brts[0].saturating_add(brts[2]).saturating_add(brts[4]),
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn palette_colors(palette: u8, brts: &[u8; 8], color: Color32) -> [Color32; 4] {
|
|
|
|
let colors = parse_shades(brts).map(|s| shade(s, color));
|
|
|
|
parse_palette(palette).map(|p| colors[p as usize])
|
|
|
|
}
|
|
|
|
|
2025-02-15 05:28:37 +00:00
|
|
|
pub struct Object {
|
|
|
|
pub x: i16,
|
|
|
|
pub lon: bool,
|
|
|
|
pub ron: bool,
|
|
|
|
pub parallax: i16,
|
|
|
|
pub y: i16,
|
|
|
|
pub data: CellData,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Object {
|
|
|
|
pub fn parse(object: [u16; 4]) -> Self {
|
2025-02-15 17:56:58 +00:00
|
|
|
let x = ((object[0] & 0x03ff) << 6 >> 6) as i16;
|
|
|
|
let parallax = ((object[1] & 0x03ff) << 6 >> 6) as i16;
|
2025-02-15 05:28:37 +00:00
|
|
|
let lon = object[1] & 0x8000 != 0;
|
|
|
|
let ron = object[1] & 0x4000 != 0;
|
2025-02-15 17:56:58 +00:00
|
|
|
let y = (object[2] & 0x00ff) as i16;
|
2025-02-15 05:28:37 +00:00
|
|
|
// Y is stored as the bottom 8 bits of an i16,
|
|
|
|
// so only sign extend if it's out of range.
|
|
|
|
let y = if y > 224 { y << 8 >> 8 } else { y };
|
|
|
|
let data = CellData::parse(object[3]);
|
|
|
|
Self {
|
|
|
|
x,
|
|
|
|
lon,
|
|
|
|
ron,
|
|
|
|
parallax,
|
|
|
|
y,
|
|
|
|
data,
|
|
|
|
}
|
|
|
|
}
|
2025-02-15 17:56:58 +00:00
|
|
|
|
|
|
|
pub fn update(&self, source: &mut [u16; 4]) -> bool {
|
|
|
|
let mut changed = false;
|
|
|
|
|
|
|
|
let new_x = (self.x as u16 & 0x03ff) | (source[0] & 0xfc00);
|
|
|
|
changed |= source[0] != new_x;
|
|
|
|
source[0] = new_x;
|
|
|
|
|
|
|
|
let new_p = if self.lon { 0x8000 } else { 0x0000 }
|
|
|
|
| if self.ron { 0x4000 } else { 0x0000 }
|
|
|
|
| (self.parallax as u16 & 0x3ff)
|
|
|
|
| (source[1] & 0x3c00);
|
|
|
|
changed |= source[1] != new_p;
|
|
|
|
source[1] = new_p;
|
|
|
|
|
|
|
|
let new_y = (self.y as u16 & 0x00ff) | (source[2] & 0xff00);
|
|
|
|
changed |= source[2] != new_y;
|
|
|
|
source[2] = new_y;
|
|
|
|
|
|
|
|
if self.data.update(&mut source[3]) {
|
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
changed
|
|
|
|
}
|
2025-02-15 05:28:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct CellData {
|
|
|
|
pub palette_index: usize,
|
|
|
|
pub hflip: bool,
|
|
|
|
pub vflip: bool,
|
|
|
|
pub char_index: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CellData {
|
|
|
|
pub fn parse(cell: u16) -> Self {
|
|
|
|
let char_index = (cell & 0x7ff) as usize;
|
|
|
|
let vflip = cell & 0x1000 != 0;
|
|
|
|
let hflip = cell & 0x2000 != 0;
|
|
|
|
let palette_index = (cell >> 14) as usize;
|
|
|
|
Self {
|
|
|
|
char_index,
|
|
|
|
vflip,
|
|
|
|
hflip,
|
|
|
|
palette_index,
|
|
|
|
}
|
|
|
|
}
|
2025-02-15 17:56:58 +00:00
|
|
|
|
|
|
|
pub fn update(&self, source: &mut u16) -> bool {
|
|
|
|
let new_value = (self.palette_index as u16) << 14
|
|
|
|
| if self.hflip { 0x2000 } else { 0x0000 }
|
|
|
|
| if self.vflip { 0x1000 } else { 0x0000 }
|
|
|
|
| (self.char_index as u16 & 0x07ff)
|
|
|
|
| (*source & 0x0800);
|
|
|
|
let changed = *source != new_value;
|
|
|
|
*source = new_value;
|
|
|
|
changed
|
|
|
|
}
|
2025-02-13 04:59:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn read_char_row(
|
2025-02-15 21:14:03 +00:00
|
|
|
char: &[u16; 8],
|
2025-02-13 04:59:48 +00:00
|
|
|
hflip: bool,
|
|
|
|
vflip: bool,
|
|
|
|
row: usize,
|
|
|
|
) -> impl Iterator<Item = u8> {
|
|
|
|
let pixels = if vflip { char[7 - row] } else { char[row] };
|
|
|
|
(0..16).step_by(2).map(move |i| {
|
|
|
|
let pixel = if hflip { 14 - i } else { i };
|
|
|
|
((pixels >> pixel) & 0x3) as u8
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2025-02-07 04:17:11 +00:00
|
|
|
pub struct CharacterGrid<'a> {
|
|
|
|
source: ImageSource<'a>,
|
|
|
|
scale: f32,
|
|
|
|
show_grid: bool,
|
|
|
|
selected: Option<usize>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> CharacterGrid<'a> {
|
|
|
|
pub fn new(source: impl Into<ImageSource<'a>>) -> Self {
|
|
|
|
Self {
|
|
|
|
source: source.into(),
|
|
|
|
scale: 1.0,
|
|
|
|
show_grid: false,
|
|
|
|
selected: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_scale(self, scale: f32) -> Self {
|
|
|
|
Self { scale, ..self }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_grid(self, show_grid: bool) -> Self {
|
|
|
|
Self { show_grid, ..self }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_selected(self, selected: usize) -> Self {
|
|
|
|
Self {
|
|
|
|
selected: Some(selected),
|
|
|
|
..self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn show(self, ui: &mut Ui) -> Option<usize> {
|
|
|
|
let start_pos = ui.cursor().min;
|
|
|
|
let cell_size = 8.0 * self.scale;
|
|
|
|
|
|
|
|
let res = self.ui(ui);
|
|
|
|
|
|
|
|
let grid_width_cells = ((res.rect.max.x - res.rect.min.x) / cell_size).round() as usize;
|
|
|
|
|
|
|
|
if res.clicked() {
|
|
|
|
let click_pos = res.interact_pointer_pos()?;
|
|
|
|
let grid_pos = (click_pos - start_pos) / cell_size;
|
|
|
|
Some((grid_pos.y as usize * grid_width_cells) + grid_pos.x as usize)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Widget for CharacterGrid<'_> {
|
|
|
|
fn ui(self, ui: &mut Ui) -> Response {
|
|
|
|
let image = Image::new(self.source)
|
|
|
|
.fit_to_original_size(self.scale)
|
|
|
|
.texture_options(TextureOptions::NEAREST)
|
|
|
|
.sense(Sense::click());
|
|
|
|
let res = ui.add(image);
|
|
|
|
|
|
|
|
let cell_size = 8.0 * self.scale;
|
|
|
|
let grid_width_cells = ((res.rect.max.x - res.rect.min.x) / cell_size).round() as usize;
|
|
|
|
let grid_height_cells = ((res.rect.max.y - res.rect.min.y) / cell_size).round() as usize;
|
|
|
|
|
|
|
|
let painter = ui.painter_at(res.rect);
|
|
|
|
if self.show_grid {
|
|
|
|
let stroke = ui.style().visuals.widgets.noninteractive.fg_stroke;
|
|
|
|
for x in (1..grid_width_cells).map(|i| (i as f32) * cell_size) {
|
|
|
|
let p1 = (res.rect.min.x + x, res.rect.min.y).into();
|
|
|
|
let p2 = (res.rect.min.x + x, res.rect.max.y).into();
|
|
|
|
painter.line(vec![p1, p2], stroke);
|
|
|
|
}
|
|
|
|
for y in (1..grid_height_cells).map(|i| (i as f32) * cell_size) {
|
|
|
|
let p1 = (res.rect.min.x, res.rect.min.y + y).into();
|
|
|
|
let p2 = (res.rect.max.x, res.rect.min.y + y).into();
|
|
|
|
painter.line(vec![p1, p2], stroke);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(selected) = self.selected {
|
|
|
|
let x1 = (selected % grid_width_cells) as f32 * cell_size;
|
|
|
|
let x2 = x1 + cell_size;
|
|
|
|
let y1 = (selected / grid_width_cells) as f32 * cell_size;
|
|
|
|
let y2 = y1 + cell_size;
|
|
|
|
painter.line(
|
|
|
|
vec![
|
|
|
|
(res.rect.min + (x1, y1).into()),
|
|
|
|
(res.rect.min + (x2, y1).into()),
|
|
|
|
(res.rect.min + (x2, y2).into()),
|
|
|
|
(res.rect.min + (x1, y2).into()),
|
|
|
|
(res.rect.min + (x1, y1).into()),
|
|
|
|
],
|
|
|
|
ui.style().visuals.widgets.active.fg_stroke,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
res
|
|
|
|
}
|
|
|
|
}
|