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, pub surf: Surface<'a>, line_height: u32, } impl<'a> SurfaceFont<'a> { pub fn load_lifont() -> Result> { 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> { 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, text: impl AsRef, 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> { 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, } } }