216 lines
7.7 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|