use crate::{
base::{
PROG_HOMEPAGE,
PROG_TITLE,
PROG_VERSION,
RGB,
PrependErrorString
},
fontset::CFont,
sys::{
Key,
System
}
};
use super::{
ERR_ALREADY_SET,
ERR_NOT_SET_CANNOT_RUN,
ERR_NOT_SET_CANNOT_FINISH,
Context,
Master
};
const MID: &str = "Meta";
const TEXT_COLOR: RGB = RGB{r: 0x40, g: 0xFF, b: 0x40};
const MENU_ITEM_ID_PREFIX: &str = "menu";
const MENU_ITEMS: [&str; 3] = ["resume", "help", "quit"]; const HELP_ID_PREFIX: &str = "help";
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum Screen {
Menu,
Help
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[allow(dead_code)]
enum Align {
Left,
Center,
Right
}
pub struct Terminal {
ch_height: i32,
ncols: i32,
nrows: i32,
x_padding: i32,
y_padding: i32,
buf: Vec<char>
}
pub enum MMeta {
Unset,
Set {
term: Terminal,
scr: Screen,
max_menu_item_name_nchars: i32,
sel_menu_item: String,
help_i_start: usize
}
}
impl Terminal {
fn init(Context{sys, fontset, ..}: &mut Context) -> Result<Self, String> {
let whitespace_img = System::make_text_image(fontset.get(CFont::Meta), " ", 0, TEXT_COLOR).pre_err("cannot render whitespace char of Meta font to calculate terminal dimensions")?;
let (ch_width, ch_height) = (whitespace_img.width(), whitespace_img.height());
let (ncols, nrows) = (sys.width() / ch_width, sys.height() / ch_height);
let (x_padding, y_padding) = ((sys.width() - ncols * ch_width) >> 1, (sys.height() - nrows * ch_height) >> 1);
let buf: Vec<char> = vec![' '; (ncols * nrows) as usize];
Ok(Self{ch_height, ncols, nrows, x_padding, y_padding, buf})
}
fn show(&mut self, Context{sys, fontset, ..}: &mut Context) {
if !sys.draw_locked() {
let mut offs: usize = 0;
let font = fontset.get(CFont::Meta);
for ty in 0..self.nrows {
let mut strow = String::new();
for _tx in 0..self.ncols {
strow.push(self.buf[offs]);
offs += 1;
}
let _ = sys.draw_text(font, strow, 0, TEXT_COLOR, 0, 0, - (sys.width() >> 1) + self.x_padding, - (sys.height() >> 1) + self.y_padding + self.ch_height * ty);
}
}
}
fn print(&mut self, text: impl ToString, x: i32, y: i32) {
if (y >= 0) && (y < self.nrows) {
let text = text.to_string();
for (i, ch) in text.chars().enumerate() {
let x = x + (i as i32);
if (x >= 0) && (x < self.ncols) {
self.buf[(y * self.ncols + x) as usize] = ch;
}
}
}
}
fn print_align(&mut self, text: impl ToString, y: i32, align: Align) {
let text_nchars = text.to_string().chars().count() as i32;
let x = match align {
Align::Left => 0,
Align::Center => (self.ncols - text_nchars) >> 1,
Align::Right => self.ncols - text_nchars
};
self.print(text, x, y);
}
#[allow(dead_code)]
fn print_fill_rect(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, fill: char) {
let (x1, y1, x2, y2) = (x1.min(x2), y1.min(y2), x1.max(x2), y1.max(y2));
let fill = format!("{}", fill);
for y in y1..=y2 {
for x in x1..=x2 {
self.print(&fill, x, y);
}
}
}
fn print_frame(&mut self, x1: i32, y1: i32, x2: i32, y2: i32) {
let (x1, y1, x2, y2) = (x1.min(x2), y1.min(y2), x1.max(x2), y1.max(y2));
self.print("┌", x1, y1); self.print("┐", x2, y1);
self.print("└", x1, y2); self.print("┘", x2, y2);
for x in (x1+1)..x2 {
self.print("─", x, y1); self.print("─", x, y2);
}
for y in (y1+1)..y2 {
self.print("│", x1, y); self.print("│", x2, y);
}
}
fn clear(&mut self) {
for ch in &mut self.buf {
*ch = ' ';
}
}
}
impl MMeta {
pub fn new() -> Box<Self> {
Box::new(Self::Unset)
}
fn pause_reset(&mut self, Context{sys, ether, ..}: &mut Context) {
ether.paused = true;
sys.pause_audio();
if let &mut Self::Set{ref mut scr, ref mut sel_menu_item, ..} = self {
*scr = Screen::Menu;
*sel_menu_item = String::from("resume");
}
}
fn menu(&mut self, Context{sys, lingua, ether, ..}: &mut Context) {
if let &mut Self::Set{ref mut term, ref mut scr, max_menu_item_name_nchars, ref mut sel_menu_item, ..} = self {
let mut i_sel = 0;
for (i, item) in MENU_ITEMS.iter().enumerate() {
let (i, item) = (i as i32, item.to_string());
let item_name = lingua.get1_or_echo(format!("{} {}", MENU_ITEM_ID_PREFIX, &item));
let x = term.ncols >> 1;
let y = (term.nrows >> 1) + 2 * (i - ((MENU_ITEMS.len() as i32) >> 1));
term.print_align(item_name, y, Align::Center);
if item.eq(sel_menu_item) { i_sel = i;
term.print_frame(x - (max_menu_item_name_nchars >> 1) - 3, y - 1, x + (max_menu_item_name_nchars >> 1) + 3, y + 1);
}
}
term.print_align(String::from(PROG_TITLE), 1, Align::Center);
term.print_align(format!("v{}", PROG_VERSION), term.nrows - 1, Align::Left);
term.print_align(format!("{}", PROG_HOMEPAGE), term.nrows - 1, Align::Right);
let mut d_i_sel: i32 = 0;
if sys.poll_keys(&[Key::Up, Key::Kp8]) {
d_i_sel = -1;
} else if sys.poll_keys(&[Key::Down, Key::Kp2]) {
d_i_sel = 1;
} else if sys.poll_keys(&[Key::Home, Key::Kp7, Key::PageUp, Key::Kp9]) {
d_i_sel = - (MENU_ITEMS.len() as i32);
} else if sys.poll_keys(&[Key::End, Key::Kp1, Key::PageDown, Key::Kp3]) {
d_i_sel = MENU_ITEMS.len() as i32;
}
if d_i_sel != 0 {
i_sel = (i_sel + d_i_sel).max(0).min((MENU_ITEMS.len() as i32) - 1);
*sel_menu_item = MENU_ITEMS[i_sel as usize].to_string();
}
if sys.poll_keys(&[Key::Enter, Key::KpEnter]) {
match sel_menu_item.as_str() {
"resume" => {
ether.meta = false;
},
"help" => {
*scr = Screen::Help;
},
"quit" => {
ether.quit = true;
}
_ => {}
}
} else if sys.poll_key(Key::Escape) {
ether.meta = false;
}
}
}
fn help(&mut self, Context{sys, lingua, ..}: &mut Context) {
if let &mut Self::Set{ref mut term, ref mut scr, ref mut help_i_start, ..} = self {
term.print_align(lingua.get1_or_echo(format!("{} title", HELP_ID_PREFIX)), 0, Align::Center);
let help_strs = lingua.get_or_echo(HELP_ID_PREFIX);
let page_size = (term.nrows - 4).max(1) as usize;
for i in 0..page_size {
let j = (*help_i_start) + i;
if j < help_strs.len() {
term.print_align(& help_strs[j], 2 + (i as i32), Align::Left);
}
}
term.print_align(lingua.get1_or_echo("help hint act"), term.nrows - 1, Align::Right);
if sys.poll_key(Key::Escape) {
*scr = Screen::Menu;
}
if sys.poll_key(Key::Up) && ((*help_i_start) > 0) {
*help_i_start -= 1;
}
if sys.poll_key(Key::Down) && (((*help_i_start) + page_size) < help_strs.len()) {
*help_i_start += 1;
}
}
}
}
impl Master for MMeta {
fn id(&self) -> String {
String::from(MID)
}
fn start(&mut self, ctx: &mut Context) -> Result<(), String> {
match self {
Self::Unset => {
ctx.lingua.load(&["meta"])?;
let term = Terminal::init(ctx)?;
let scr = Screen::Menu;
let max_menu_item_name_nchars = MENU_ITEMS.iter().fold(0, |max_nchars, s| max_nchars.max(ctx.lingua.get1_or_echo(format!("{} {}", MENU_ITEM_ID_PREFIX, s.to_string())).chars().count())) as i32;
let sel_menu_item = String::from("resume");
let help_i_start = 0;
*self = Self::Set {
term,
scr,
max_menu_item_name_nchars, sel_menu_item,
help_i_start
};
Ok(())
},
Self::Set{..} => Err(format!("{} {}", MID, ERR_ALREADY_SET))
}
}
fn run(&mut self, ctx: &mut Context) -> Result<(), String> {
ctx.lingua.set_prefix("meta");
if let Self::Unset = self {
return Err(format!("{} {}", MID, ERR_NOT_SET_CANNOT_RUN));
}
if !ctx.ether.paused { self.pause_reset(ctx);
}
if let &mut Self::Set{ref mut term, ..} = self {
term.clear();
}
let scr = match self {
&mut Self::Set{scr, ..} => scr,
_ => Screen::Menu
};
match scr {
Screen::Menu => self.menu(ctx),
Screen::Help => self.help(ctx)
};
if let &mut Self::Set{ref mut term, ..} = self {
let _ = ctx.sys.shade(0xFF); term.show(ctx);
}
ctx.sys.sync()?;
ctx.lingua.clear_prefix();
Ok(())
}
fn finish(&mut self, _ctx: &mut Context) -> Result<(), String> {
match self {
Self::Set{..} => {
Ok(())
},
Self::Unset => Err(format!("{} {}", MID, ERR_NOT_SET_CANNOT_FINISH))
}
}
}