use std::collections::HashMap;
use color::{color_blend, Color};
pub const CHAR_CORNER_NW: u16 = 218;
pub const CHAR_CORNER_SW: u16 = 192;
pub const CHAR_CORNER_SE: u16 = 217;
pub const CHAR_CORNER_NE: u16 = 191;
pub const CHAR_LINE_H: u16 = 196;
pub const CHAR_LINE_V: u16 = 179;
pub const CHAR_SUBP_NW: u16 = 226;
pub const CHAR_SUBP_NE: u16 = 227;
pub const CHAR_SUBP_N: u16 = 228;
pub const CHAR_SUBP_SE: u16 = 229;
pub const CHAR_SUBP_DIAG: u16 = 230;
pub const CHAR_SUBP_E: u16 = 231;
pub const CHAR_SUBP_SW: u16 = 232;
#[derive(Copy, Clone)]
pub enum TextAlign {
Left,
Right,
Center,
}
pub struct Console {
width: u32,
height: u32,
pot_width: u32,
pot_height: u32,
ascii: Vec<u32>,
back: Vec<Color>,
fore: Vec<Color>,
colors: HashMap<String, Color>,
color_stack: Vec<Color>,
}
impl Console {
pub fn new(width: u32, height: u32) -> Self {
let mut back = Vec::new();
let mut fore = Vec::new();
let mut ascii = Vec::new();
let mut pot_width = 1;
let mut pot_height = 1;
while pot_width < width {
pot_width *= 2;
}
while pot_height < height {
pot_height *= 2;
}
for _ in 0..(pot_width * pot_height) as usize {
back.push((0, 0, 0, 255));
fore.push((255, 255, 255, 255));
ascii.push(' ' as u32);
}
Self {
width,
height,
ascii,
back,
fore,
pot_width,
pot_height,
colors: HashMap::new(),
color_stack: Vec::new(),
}
}
pub fn resize(&mut self, width: u32, height: u32) {
self.width = width;
self.height = height;
let mut pot_width = 1;
let mut pot_height = 1;
while pot_width < width {
pot_width *= 2;
}
while pot_height < height {
pot_height *= 2;
}
self.pot_height = pot_height;
self.pot_width = pot_width;
self.back.clear();
self.fore.clear();
self.ascii.clear();
for _ in 0..(pot_width * pot_height) as usize {
self.back.push((0, 0, 0, 255));
self.fore.push((255, 255, 255, 255));
self.ascii.push(' ' as u32);
}
}
pub fn register_color(&mut self, name: &str, value: Color) {
self.colors.insert(name.to_owned(), value);
}
pub fn get_width(&self) -> u32 {
self.width
}
pub fn get_height(&self) -> u32 {
self.height
}
pub fn get_pot_width(&self) -> u32 {
self.pot_width
}
pub fn get_pot_height(&self) -> u32 {
self.pot_height
}
pub fn borrow_ascii(&self) -> &Vec<u32> {
&self.ascii
}
pub fn borrow_foreground(&self) -> &Vec<Color> {
&self.fore
}
pub fn borrow_background(&self) -> &Vec<Color> {
&self.back
}
pub fn borrow_mut_ascii(&mut self) -> &mut Vec<u32> {
&mut self.ascii
}
pub fn borrow_mut_foreground(&mut self) -> &mut Vec<Color> {
&mut self.fore
}
pub fn borrow_mut_background(&mut self) -> &mut Vec<Color> {
&mut self.back
}
pub fn get_back(&self, x: i32, y: i32) -> Option<Color> {
if self.check_coords(x, y) {
return Some(self.unsafe_get_back(x, y));
}
None
}
pub fn get_fore(&self, x: i32, y: i32) -> Option<Color> {
if self.check_coords(x, y) {
return Some(self.unsafe_get_fore(x, y));
}
None
}
pub fn get_ascii(&self, x: i32, y: i32) -> Option<u16> {
if self.check_coords(x, y) {
return Some(self.unsafe_get_ascii(x, y));
}
None
}
pub fn unsafe_get_back(&self, x: i32, y: i32) -> Color {
let off = self.offset(x, y);
self.back[off]
}
pub fn unsafe_get_fore(&self, x: i32, y: i32) -> Color {
let off = self.offset(x, y);
self.fore[off]
}
pub fn unsafe_get_ascii(&self, x: i32, y: i32) -> u16 {
let off = self.offset(x, y);
self.ascii[off] as u16
}
fn offset(&self, x: i32, y: i32) -> usize {
x as usize + y as usize * self.pot_width as usize
}
fn check_coords(&self, x: i32, y: i32) -> bool {
(x as u32) < self.width && (y as u32) < self.height
}
pub fn ascii(&mut self, x: i32, y: i32, ascii: u16) {
if self.check_coords(x, y) {
self.unsafe_ascii(x, y, ascii);
}
}
pub fn fore(&mut self, x: i32, y: i32, col: Color) {
if self.check_coords(x, y) {
self.unsafe_fore(x, y, col);
}
}
pub fn back(&mut self, x: i32, y: i32, col: Color) {
if self.check_coords(x, y) {
self.unsafe_back(x, y, col);
}
}
pub fn unsafe_ascii(&mut self, x: i32, y: i32, ascii: u16) {
let off = self.offset(x, y);
self.ascii[off] = ascii as u32;
}
pub fn unsafe_fore(&mut self, x: i32, y: i32, col: Color) {
let off = self.offset(x, y);
self.fore[off] = col;
}
pub fn unsafe_back(&mut self, x: i32, y: i32, col: Color) {
let off = self.offset(x, y);
self.back[off] = col;
}
pub fn clear(&mut self, fore: Option<Color>, back: Option<Color>, fillchar: Option<u16>) {
let w = self.width;
let h = self.height;
self.area(0, 0, w, h, fore, back, fillchar);
}
pub fn print_color(
&mut self,
x: i32,
y: i32,
text: &str,
align: TextAlign,
back: Option<Color>,
) {
let mut cury = y;
self.color_stack.clear();
for line in text.to_owned().split("\n") {
self.print_line_color(x, cury, line, align, back);
cury += 1;
}
}
fn get_color_spans(&mut self, text: &str, text_len: &mut i32) -> Vec<(Color, String)> {
let mut spans: Vec<(Color, String)> = Vec::new();
*text_len = 0;
let mut fore = *self.color_stack.last().unwrap_or(&(255, 255, 255, 255));
for color_span in text.to_owned().split("#[") {
if color_span.len() == 0 {
continue;
}
let mut col_text = color_span.split("]");
let col_name = col_text.next().unwrap();
if let Some(text_span) = col_text.next() {
if let Some(color) = self.colors.get(col_name) {
fore = *color;
self.color_stack.push(fore);
} else {
self.color_stack.pop();
fore = *self.color_stack.last().unwrap_or(&(255, 255, 255, 255));
}
spans.push((fore, text_span.to_owned()));
*text_len += text_span.chars().count() as i32;
} else {
spans.push((fore, col_name.to_owned()));
*text_len += col_name.chars().count() as i32;
}
}
spans
}
fn print_line_color(
&mut self,
x: i32,
y: i32,
text: &str,
align: TextAlign,
back: Option<Color>,
) {
let mut str_len = 0;
let spans = self.get_color_spans(text, &mut str_len);
let mut ix = match align {
TextAlign::Left => x,
TextAlign::Right => (x - str_len + 1),
TextAlign::Center => (x - str_len / 2),
};
for (color, span) in spans {
self.print_line(ix, y, &span, TextAlign::Left, Some(color), back);
ix += span.chars().count() as i32;
}
}
pub fn print(
&mut self,
x: i32,
y: i32,
text: &str,
align: TextAlign,
fore: Option<Color>,
back: Option<Color>,
) {
let mut cury = y;
for line in text.to_owned().split("\n") {
self.print_line(x, cury, line, align, fore, back);
cury += 1;
}
}
fn print_line(
&mut self,
x: i32,
y: i32,
text: &str,
align: TextAlign,
fore: Option<Color>,
back: Option<Color>,
) {
let stext = text.to_owned();
let mut str_len = stext.chars().count() as i32;
let mut start = 0;
let mut ix = match align {
TextAlign::Left => x,
TextAlign::Right => (x - str_len + 1),
TextAlign::Center => (x - str_len / 2),
};
if ix < 0 {
str_len += ix;
start -= ix;
ix = 0;
}
if ix + str_len > self.width as i32 {
str_len = self.width as i32 - ix;
}
let mut chars = stext.chars().skip(start as usize);
for _ in 0..str_len {
let ch = chars.next();
self.cell(ix, y, Some(ch.unwrap() as u16), fore, back);
ix += 1;
}
}
pub fn rectangle(
&mut self,
x: i32,
y: i32,
w: u32,
h: u32,
fore: Option<Color>,
back: Option<Color>,
fill: Option<u16>,
) {
let right = x + (w as i32) - 1;
let down = y + (h as i32) - 1;
self.cell(x, y, Some(CHAR_CORNER_NW), fore, back);
self.cell(right, down, Some(CHAR_CORNER_SE), fore, back);
self.cell(right, y, Some(CHAR_CORNER_NE), fore, back);
self.cell(x, down, Some(CHAR_CORNER_SW), fore, back);
if (y as u32) < self.height {
self.area(x + 1, y, w - 2, 1, fore, back, Some(CHAR_LINE_H));
}
if (down as u32) < self.height {
self.area(x + 1, down, w - 2, 1, fore, back, Some(CHAR_LINE_H));
}
if (x as u32) < self.width {
self.area(x, y + 1, 1, h - 2, fore, back, Some(CHAR_LINE_V));
}
if (right as u32) < self.width {
self.area(right, y + 1, 1, h - 2, fore, back, Some(CHAR_LINE_V));
}
if fill.is_some() {
self.area(x + 1, y + 1, w - 2, h - 2, fore, back, fill);
}
}
pub fn area(
&mut self,
x: i32,
y: i32,
w: u32,
h: u32,
fore: Option<Color>,
back: Option<Color>,
fillchar: Option<u16>,
) {
let right = x + (w as i32);
let down = y + (h as i32);
if let Some(fillchar) = fillchar {
for iy in y.max(0)..down.min(self.height as i32) {
let off = iy * self.pot_width as i32;
for ix in x.max(0)..right.min(self.width as i32) {
self.ascii[(off + ix) as usize] = fillchar as u32;
}
}
}
if let Some(fore) = fore {
for iy in y.max(0)..down.min(self.height as i32) {
let off = iy * self.pot_width as i32;
for ix in x.max(0)..right.min(self.width as i32) {
self.fore[(off + ix) as usize] = fore;
}
}
}
if let Some(back) = back {
for iy in y.max(0)..down.min(self.height as i32) {
let off = iy * self.pot_width as i32;
for ix in x.max(0)..right.min(self.width as i32) {
self.back[(off + ix) as usize] = back;
}
}
}
}
pub fn cell(
&mut self,
x: i32,
y: i32,
ascii: Option<u16>,
fore: Option<Color>,
back: Option<Color>,
) {
if self.check_coords(x, y) {
let off = self.offset(x, y);
if let Some(ascii) = ascii {
self.ascii[off] = ascii as u32;
}
if let Some(fore) = fore {
self.fore[off] = fore;
}
if let Some(back) = back {
self.back[off] = back;
}
}
}
pub fn blit(
&self,
x: i32,
y: i32,
destination: &mut Console,
fore_alpha: f32,
back_alpha: f32,
key_color: Option<Color>,
) {
self.blit_ex(
0,
0,
self.width as i32,
self.height as i32,
destination,
x,
y,
fore_alpha,
back_alpha,
key_color,
);
}
pub fn blit_ex(
&self,
xsrc: i32,
ysrc: i32,
wsrc: i32,
hsrc: i32,
destination: &mut Console,
xdst: i32,
ydst: i32,
fore_alpha: f32,
back_alpha: f32,
key_color: Option<Color>,
) {
for y in 0..hsrc - ysrc {
let off = (y + ysrc) * self.pot_width as i32;
let doff = (y + ydst) * destination.pot_width as i32;
for x in 0..wsrc - xsrc {
if self.check_coords(xsrc + x, ysrc + y) {
if destination.check_coords(xdst + x, ydst + y) {
let src_idx = (off + x + xsrc) as usize;
let dest_idx = (doff + x + xdst) as usize;
let src_back = self.back[src_idx];
let dst_back = destination.back[dest_idx];
if back_alpha > 0.0 {
let mut back = self.back[src_idx];
if let Some(key) = key_color {
if key == back {
continue;
}
}
destination.back[dest_idx] =
color_blend(&dst_back, &src_back, back_alpha);
}
if fore_alpha > 0.0 {
let src_fore = self.fore[src_idx];
let dst_fore = destination.fore[dest_idx];
let src_char = self.ascii[src_idx];
let dst_char = destination.ascii[dest_idx];
let dst_back = destination.back[dest_idx];
if fore_alpha < 1.0 {
if src_char == ' ' as u32 {
destination.fore[dest_idx] =
color_blend(&dst_fore, &src_back, back_alpha);
} else if dst_char == ' ' as u32 {
destination.ascii[dest_idx] = src_char;
destination.fore[dest_idx] =
color_blend(&dst_back, &src_fore, fore_alpha);
} else if dst_char == src_char {
destination.fore[dest_idx] =
color_blend(&dst_fore, &src_fore, fore_alpha);
} else {
if fore_alpha < 0.5 {
destination.fore[dest_idx] =
color_blend(&dst_fore, &dst_back, fore_alpha * 2.0);
} else {
destination.ascii[dest_idx] = src_char;
destination.fore[dest_idx] = color_blend(
&dst_back,
&src_fore,
(fore_alpha - 0.5) * 2.0,
);
}
}
} else {
destination.fore[dest_idx] = src_fore;
destination.ascii[dest_idx] = src_char;
}
}
}
}
}
}
}
}