zretro/src/gui.rs

216 lines
7.7 KiB
Rust

use super::Result;
use std::fs::File;
use std::io::Read;
use std::collections::HashMap;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::surface::{Surface, SurfaceRef};
use sdl2::image::ImageRWops;
use sdl2::rect::{Rect, Point};
use regex::Regex;
pub const BG_COLOR: Color = Color { r: 65, g: 44, b: 130, a: 255 };
pub const MOUSE_PNG: &'static [u8] = include_bytes!("zmouse.png");
pub const MOUSE_SHADOW_PNG: &'static [u8] = include_bytes!("zmouse-shadow.png");
pub struct SurfaceFont<'a> {
char_rects: HashMap<String, Rect>,
pub surf: Surface<'a>,
line_height: u32,
}
impl<'a> SurfaceFont<'a> {
pub fn load_lifont() -> Result<SurfaceFont<'a>> {
let surf = surface_asset(include_bytes!("lifont.png"))?;
let surf_rect = surf.rect();
let canvas = surf.into_canvas()?;
let mut char_rects = HashMap::new();
let mut src_rect = Rect::new(0, 0, 8, 8);
for name in include_str!("lifont.txt").lines() {
let pixel_bytes = canvas.read_pixels(src_rect, PixelFormatEnum::ARGB1555)?;
let pixel_shorts = unsafe { std::slice::from_raw_parts(
pixel_bytes.as_ptr() as *const u16,
pixel_bytes.len() / 2,
)};
let mut pixel_points = Vec::new();
for y in 0..8 {
for x in 0..8 {
if (pixel_shorts[y * 8 + x] & 0x8000) != 0 {
pixel_points.push(Point::new(x as i32, y as i32));
}
}
}
let crop = Rect::from_enclose_points(&pixel_points, None)
.unwrap_or(Rect::new(0, 0, 8, 8));
let mut rect = src_rect;
rect.set_x(crop.x() + src_rect.x());
rect.set_width(crop.width());
char_rects.insert(name.to_string(), rect);
// position for next char
src_rect.set_x(src_rect.x() + 8);
if !surf_rect.contains_rect(src_rect) {
src_rect.set_x(0);
src_rect.set_y(src_rect.y() + 8);
}
}
Ok(SurfaceFont {
char_rects,
surf: canvas.into_surface(),
line_height: 8,
})
}
pub fn load_zfont() -> Result<SurfaceFont<'a>> {
const FONT_ROWS: usize = 16;
const FONT_COLS: usize = 16;
const FONT_MAX_W: usize = 8;
const FONT_MAX_H: usize = 8;
let mut zfont_txt = String::new();
// max 8x8 fonts, 16*16=256 characters
let mut canvas = Surface::new(
(FONT_MAX_W * FONT_COLS) as u32,
(FONT_MAX_H * FONT_ROWS) as u32,
PixelFormatEnum::ARGB1555,
)?.into_canvas()?;
canvas.set_draw_color(Color { r: 255, g: 255, b: 255, a: 255 });
File::open("zfont.txt")
.and_then(|mut f| {
f.read_to_string(&mut zfont_txt).map(|_| {})
})
.unwrap_or_else(|_| {
zfont_txt = String::from(include_str!("zfont.txt"));
});
let lines: Vec<_> = zfont_txt.lines().map(str::trim).collect();
let mut char_rects = HashMap::new();
let mut max_height = 0;
let mut line_num = 0;
while line_num < lines.len() {
let char_hdr = lines[line_num];
if char_hdr == "EOF" {
break;
}
let r = Regex::new(r"^; (.*) 0x([0123456789abcdefABCDEF]{2})( \^\^)?$").unwrap();
let groups = r.captures(char_hdr).ok_or_else(||
format!("zfont.txt:{} character descriptor invalid:\n\t{}", line_num, char_hdr)
)?;
let mut name = groups.get(1)
.ok_or_else(|| format!("zfont.txt:{} missing character name:\n\t{}", line_num, char_hdr))?
.as_str()
.to_string();
let hex_match = groups.get(2)
.ok_or_else(|| format!("zfont.txt:{} missing hex code:\n\t{}", line_num, char_hdr))?;
let index = usize::from_str_radix(hex_match.as_str(), 16)?;
if char_rects.len() != index {
return Err(format!(
"zfont.txt:{} code 0x{} out of order (expecting {:x})",
line_num, index, char_rects.len()
).into());
}
let x0 = ((index % FONT_COLS) * FONT_MAX_W) as i32;
let y0 = ((index / FONT_COLS) * FONT_MAX_H) as i32;
let mut y = y0;
let mut points = Vec::new();
for row in &lines[line_num + 1..=(line_num + FONT_MAX_H).min(lines.len() - 1)] {
if let Some(';') = row.chars().next() {
break;
}
if *row == "EOF" {
break;
}
let mut x = x0;
if let Ok(mut row_bits) = u16::from_str_radix(row, 2) {
row_bits <<= 16 - FONT_MAX_W;
while row_bits != 0 {
if (row_bits & (1 << 15)) != 0 {
let point = Point::new(x, y);
points.push(point);
canvas.draw_point(point)?;
}
row_bits <<= 1;
x += 1;
}
} else {
return Err(format!("zfont.txt:{} invalid bit map in character {:?}", line_num, name).into())
}
y += 1;
}
let height = (y - y0) as u32;
if height > max_height {
max_height = height;
}
if points.len() == 0 {
name = " ".to_string();
}
let mut bounding = Rect::from_enclose_points(&points, None)
.unwrap_or(Rect::new(0, 0, height, height));
bounding.set_height(height);
bounding.set_y(y0);
char_rects.insert(name, bounding);
line_num += 1 + height as usize;
}
Ok(SurfaceFont {
char_rects,
surf: canvas.into_surface(),
line_height: max_height,
})
}
pub fn blit_text(&self, target: &mut impl AsMut<SurfaceRef>, text: impl AsRef<str>, dest_rect: Rect) -> Result<()> {
let target = target.as_mut();
let text = text.as_ref();
let mut cursor_x = dest_rect.x();
let mut cursor_y = dest_rect.y();
for i in 0..text.len() {
let c = (&text[i..=i]).to_uppercase();
if c == "\n" {
cursor_x = dest_rect.x();
cursor_y += self.line_height as i32 + 1;
} else if let Some(src_char_rect) = self.char_rects.get(&c) {
let mut dst_char_rect = src_char_rect.clone();
dst_char_rect.set_x(cursor_x);
dst_char_rect.set_y(cursor_y);
if dest_rect.contains_rect(dst_char_rect) {
cursor_x += src_char_rect.width() as i32 + 1;
self.surf.blit(*src_char_rect, target, dst_char_rect)?;
} else {
return Err("out of bounds text rendering".into());
}
}
}
Ok(())
}
}
pub fn surface_asset(asset: &'static [u8]) -> Result<Surface<'static>> {
let rwops = sdl2::rwops::RWops::from_bytes(asset)?;
let surf_shortlived = rwops.load_png()?;
// there's a nonsemantic inheritance of lifetime from ImageRWops::load_png. TODO: pr
unsafe {
let ptr = surf_shortlived.raw();
std::mem::forget(surf_shortlived);
Ok(Surface::from_ll(ptr))
}
}
pub enum GuiState {
Menus,
Game
}
impl GuiState {
pub fn toggle(&mut self) {
match self {
GuiState::Menus => *self = GuiState::Game,
GuiState::Game => *self = GuiState::Menus,
}
}
}