1use std::collections::HashMap;
2
3use crate::color::{color_blend, Color};
4
5pub const CHAR_CORNER_NW: u16 = 218;
7pub const CHAR_CORNER_SW: u16 = 192;
8pub const CHAR_CORNER_SE: u16 = 217;
9pub const CHAR_CORNER_NE: u16 = 191;
10pub const CHAR_LINE_H: u16 = 196;
11pub const CHAR_LINE_V: u16 = 179;
12pub const CHAR_SUBP_NW: u16 = 226;
14pub const CHAR_SUBP_NE: u16 = 227;
15pub const CHAR_SUBP_N: u16 = 228;
16pub const CHAR_SUBP_SE: u16 = 229;
17pub const CHAR_SUBP_DIAG: u16 = 230;
18pub const CHAR_SUBP_E: u16 = 231;
19pub const CHAR_SUBP_SW: u16 = 232;
20
21#[derive(Copy, Clone)]
22pub enum TextAlign {
23 Left,
24 Right,
25 Center,
26}
27
28pub struct Console {
30 width: u32,
31 height: u32,
32 pot_width: u32,
34 pot_height: u32,
35 ascii: Vec<u32>,
36 back: Vec<Color>,
37 fore: Vec<Color>,
38 colors: HashMap<String, Color>,
39 color_stack: Vec<Color>,
40}
41
42impl Console {
43 pub fn new(width: u32, height: u32) -> Self {
46 let mut back = Vec::new();
47 let mut fore = Vec::new();
48 let mut ascii = Vec::new();
49 let mut pot_width = 1;
50 let mut pot_height = 1;
51 while pot_width < width {
52 pot_width *= 2;
53 }
54 while pot_height < height {
55 pot_height *= 2;
56 }
57 for _ in 0..(pot_width * pot_height) as usize {
58 back.push((0, 0, 0, 255));
59 fore.push((255, 255, 255, 255));
60 ascii.push(' ' as u32);
61 }
62 Self {
63 width,
64 height,
65 ascii,
66 back,
67 fore,
68 pot_width,
69 pot_height,
70 colors: HashMap::new(),
71 color_stack: Vec::new(),
72 }
73 }
74 pub fn resize(&mut self, width: u32, height: u32) {
76 self.width = width;
77 self.height = height;
78 let mut pot_width = 1;
79 let mut pot_height = 1;
80 while pot_width < width {
81 pot_width *= 2;
82 }
83 while pot_height < height {
84 pot_height *= 2;
85 }
86 self.pot_height = pot_height;
87 self.pot_width = pot_width;
88 self.back.clear();
89 self.fore.clear();
90 self.ascii.clear();
91 for _ in 0..(pot_width * pot_height) as usize {
92 self.back.push((0, 0, 0, 255));
93 self.fore.push((255, 255, 255, 255));
94 self.ascii.push(' ' as u32);
95 }
96 }
97 pub fn register_color(&mut self, name: &str, value: Color) {
107 self.colors.insert(name.to_owned(), value);
108 }
109 pub fn get_width(&self) -> u32 {
110 self.width
111 }
112 pub fn get_height(&self) -> u32 {
113 self.height
114 }
115 pub fn get_size(&self) -> (u32, u32) {
116 (self.width, self.height)
117 }
118 pub fn get_pot_width(&self) -> u32 {
119 self.pot_width
120 }
121 pub fn get_pot_height(&self) -> u32 {
122 self.pot_height
123 }
124 pub fn borrow_ascii(&self) -> &Vec<u32> {
126 &self.ascii
127 }
128 pub fn borrow_foreground(&self) -> &Vec<Color> {
130 &self.fore
131 }
132 pub fn borrow_background(&self) -> &Vec<Color> {
134 &self.back
135 }
136 pub fn borrow_mut_ascii(&mut self) -> &mut Vec<u32> {
138 &mut self.ascii
139 }
140 pub fn borrow_mut_foreground(&mut self) -> &mut Vec<Color> {
142 &mut self.fore
143 }
144 pub fn borrow_mut_background(&mut self) -> &mut Vec<Color> {
146 &mut self.back
147 }
148 pub fn get_back(&self, x: i32, y: i32) -> Option<Color> {
150 if self.check_coords(x, y) {
151 return Some(self.unsafe_get_back(x, y));
152 }
153 None
154 }
155 pub fn get_fore(&self, x: i32, y: i32) -> Option<Color> {
157 if self.check_coords(x, y) {
158 return Some(self.unsafe_get_fore(x, y));
159 }
160 None
161 }
162 pub fn get_ascii(&self, x: i32, y: i32) -> Option<u16> {
164 if self.check_coords(x, y) {
165 return Some(self.unsafe_get_ascii(x, y));
166 }
167 None
168 }
169 pub fn unsafe_get_back(&self, x: i32, y: i32) -> Color {
171 let off = self.offset(x, y);
172 self.back[off]
173 }
174 pub fn unsafe_get_fore(&self, x: i32, y: i32) -> Color {
176 let off = self.offset(x, y);
177 self.fore[off]
178 }
179 pub fn unsafe_get_ascii(&self, x: i32, y: i32) -> u16 {
181 let off = self.offset(x, y);
182 self.ascii[off] as u16
183 }
184 fn offset(&self, x: i32, y: i32) -> usize {
185 x as usize + y as usize * self.pot_width as usize
186 }
187 fn check_coords(&self, x: i32, y: i32) -> bool {
188 (x as u32) < self.width && (y as u32) < self.height
189 }
190 pub fn ascii(&mut self, x: i32, y: i32, ascii: u16) {
199 if self.check_coords(x, y) {
200 self.unsafe_ascii(x, y, ascii);
201 }
202 }
203 pub fn fore(&mut self, x: i32, y: i32, col: Color) {
205 if self.check_coords(x, y) {
206 self.unsafe_fore(x, y, col);
207 }
208 }
209 pub fn back(&mut self, x: i32, y: i32, col: Color) {
211 if self.check_coords(x, y) {
212 self.unsafe_back(x, y, col);
213 }
214 }
215 pub fn unsafe_ascii(&mut self, x: i32, y: i32, ascii: u16) {
217 let off = self.offset(x, y);
218 self.ascii[off] = u32::from(ascii);
219 }
220 pub fn unsafe_fore(&mut self, x: i32, y: i32, col: Color) {
222 let off = self.offset(x, y);
223 self.fore[off] = col;
224 }
225 pub fn unsafe_back(&mut self, x: i32, y: i32, col: Color) {
227 let off = self.offset(x, y);
228 self.back[off] = col;
229 }
230 pub fn clear(&mut self, fore: Option<Color>, back: Option<Color>, fillchar: Option<u16>) {
232 let w = self.width;
233 let h = self.height;
234 self.area(0, 0, w, h, fore, back, fillchar);
235 }
236 pub fn print_color(
250 &mut self,
251 x: i32,
252 y: i32,
253 text: &str,
254 align: TextAlign,
255 back: Option<Color>,
256 ) {
257 let mut cury = y;
258 self.color_stack.clear();
259 for line in text.to_owned().split('\n') {
260 self.print_line_color(x, cury, line, align, back);
261 cury += 1;
262 }
263 }
264
265 pub fn text_color_len(text: &str) -> usize {
277 let mut text_len = 0;
278 for color_span in text.to_owned().split("#[") {
279 if color_span.is_empty() {
280 continue;
281 }
282 let mut col_text = color_span.split(']');
283 let col_name = col_text.next().unwrap();
284 if let Some(text_span) = col_text.next() {
285 text_len += text_span.chars().count();
286 } else {
287 text_len += col_name.chars().count();
288 }
289 }
290 text_len
291 }
292
293 fn get_color_spans(&mut self, text: &str, text_len: &mut i32) -> Vec<(Color, String)> {
294 let mut spans: Vec<(Color, String)> = Vec::new();
295 *text_len = 0;
296 let mut fore = *self.color_stack.last().unwrap_or(&(255, 255, 255, 255));
297 for color_span in text.to_owned().split("#[") {
298 if color_span.is_empty() {
299 continue;
300 }
301 let mut col_text = color_span.splitn(2, ']');
302 let col_name = col_text.next().unwrap();
303 if let Some(text_span) = col_text.next() {
304 if let Some(color) = self.colors.get(col_name) {
305 fore = *color;
306 self.color_stack.push(fore);
307 } else {
308 self.color_stack.pop();
309 fore = *self.color_stack.last().unwrap_or(&(255, 255, 255, 255));
310 }
311 spans.push((fore, text_span.to_owned()));
312 *text_len += text_span.chars().count() as i32;
313 } else {
314 spans.push((fore, col_name.to_owned()));
315 *text_len += col_name.chars().count() as i32;
316 }
317 }
318 spans
319 }
320
321 fn print_line_color(
322 &mut self,
323 x: i32,
324 y: i32,
325 text: &str,
326 align: TextAlign,
327 back: Option<Color>,
328 ) {
329 let mut str_len = 0;
330 let spans = self.get_color_spans(text, &mut str_len);
331 let mut ix = match align {
332 TextAlign::Left => x,
333 TextAlign::Right => x - str_len + 1,
334 TextAlign::Center => x - str_len / 2,
335 };
336 for (color, span) in spans {
337 self.print_line(ix, y, &span, TextAlign::Left, Some(color), back);
338 ix += span.chars().count() as i32;
339 }
340 }
341 pub fn print(
344 &mut self,
345 x: i32,
346 y: i32,
347 text: &str,
348 align: TextAlign,
349 fore: Option<Color>,
350 back: Option<Color>,
351 ) {
352 let mut cury = y;
353 for line in text.to_owned().split('\n') {
354 self.print_line(x, cury, line, align, fore, back);
355 cury += 1;
356 }
357 }
358 fn print_line(
359 &mut self,
360 x: i32,
361 y: i32,
362 text: &str,
363 align: TextAlign,
364 fore: Option<Color>,
365 back: Option<Color>,
366 ) {
367 let stext = text.to_owned();
368 let mut str_len = stext.chars().count() as i32;
369 let mut start = 0;
370 let mut ix = match align {
371 TextAlign::Left => x,
372 TextAlign::Right => x - str_len + 1,
373 TextAlign::Center => x - str_len / 2,
374 };
375 if ix < 0 {
376 str_len += ix;
377 start -= ix;
378 ix = 0;
379 }
380 if ix + str_len > self.width as i32 {
381 str_len = self.width as i32 - ix;
382 }
383 let mut chars = stext.chars().skip(start as usize);
384 for _ in 0..str_len {
385 let ch = chars.next();
386 self.cell(ix, y, Some(ch.unwrap() as u16), fore, back);
387 ix += 1;
388 }
389 }
390 pub fn rectangle(
392 &mut self,
393 x: i32,
394 y: i32,
395 w: u32,
396 h: u32,
397 fore: Option<Color>,
398 back: Option<Color>,
399 fill: Option<u16>,
400 ) {
401 let right = x + (w as i32) - 1;
402 let down = y + (h as i32) - 1;
403 self.cell(x, y, Some(CHAR_CORNER_NW), fore, back);
404 self.cell(right, down, Some(CHAR_CORNER_SE), fore, back);
405 self.cell(right, y, Some(CHAR_CORNER_NE), fore, back);
406 self.cell(x, down, Some(CHAR_CORNER_SW), fore, back);
407 if (y as u32) < self.height {
408 self.area(x + 1, y, w - 2, 1, fore, back, Some(CHAR_LINE_H));
409 }
410 if (down as u32) < self.height {
411 self.area(x + 1, down, w - 2, 1, fore, back, Some(CHAR_LINE_H));
412 }
413 if (x as u32) < self.width {
414 self.area(x, y + 1, 1, h - 2, fore, back, Some(CHAR_LINE_V));
415 }
416 if (right as u32) < self.width {
417 self.area(right, y + 1, 1, h - 2, fore, back, Some(CHAR_LINE_V));
418 }
419 if fill.is_some() {
420 self.area(x + 1, y + 1, w - 2, h - 2, fore, back, fill);
421 }
422 }
423 pub fn area(
425 &mut self,
426 x: i32,
427 y: i32,
428 w: u32,
429 h: u32,
430 fore: Option<Color>,
431 back: Option<Color>,
432 fillchar: Option<u16>,
433 ) {
434 let right = x + (w as i32);
435 let down = y + (h as i32);
436 if let Some(fillchar) = fillchar {
437 for iy in y.max(0)..down.min(self.height as i32) {
438 let off = iy * self.pot_width as i32;
439 for ix in x.max(0)..right.min(self.width as i32) {
440 self.ascii[(off + ix) as usize] = u32::from(fillchar);
441 }
442 }
443 }
444 if let Some(fore) = fore {
445 for iy in y.max(0)..down.min(self.height as i32) {
446 let off = iy * self.pot_width as i32;
447 for ix in x.max(0)..right.min(self.width as i32) {
448 self.fore[(off + ix) as usize] = fore;
449 }
450 }
451 }
452 if let Some(back) = back {
453 for iy in y.max(0)..down.min(self.height as i32) {
454 let off = iy * self.pot_width as i32;
455 for ix in x.max(0)..right.min(self.width as i32) {
456 self.back[(off + ix) as usize] = back;
457 }
458 }
459 }
460 }
461 pub fn cell(
463 &mut self,
464 x: i32,
465 y: i32,
466 ascii: Option<u16>,
467 fore: Option<Color>,
468 back: Option<Color>,
469 ) {
470 if self.check_coords(x, y) {
471 let off = self.offset(x, y);
472 if let Some(ascii) = ascii {
473 self.ascii[off] = u32::from(ascii);
474 }
475 if let Some(fore) = fore {
476 self.fore[off] = fore;
477 }
478 if let Some(back) = back {
479 self.back[off] = back;
480 }
481 }
482 }
483 pub fn blit(
488 &self,
489 x: i32,
490 y: i32,
491 destination: &mut Console,
492 fore_alpha: f32,
493 back_alpha: f32,
494 key_color: Option<Color>,
495 ) {
496 self.blit_ex(
497 0,
498 0,
499 self.width as i32,
500 self.height as i32,
501 destination,
502 x,
503 y,
504 fore_alpha,
505 back_alpha,
506 key_color,
507 );
508 }
509 pub fn blit_ex(
512 &self,
513 xsrc: i32,
514 ysrc: i32,
515 wsrc: i32,
516 hsrc: i32,
517 destination: &mut Console,
518 xdst: i32,
519 ydst: i32,
520 fore_alpha: f32,
521 back_alpha: f32,
522 key_color: Option<Color>,
523 ) {
524 for y in 0..hsrc - ysrc {
525 let off = (y + ysrc) * self.pot_width as i32;
526 let doff = (y + ydst) * destination.pot_width as i32;
527 for x in 0..wsrc - xsrc {
528 if self.check_coords(xsrc + x, ysrc + y)
529 && destination.check_coords(xdst + x, ydst + y)
530 {
531 let src_idx = (off + x + xsrc) as usize;
532 let dest_idx = (doff + x + xdst) as usize;
533 let src_back = self.back[src_idx];
534 let dst_back = destination.back[dest_idx];
535 if back_alpha > 0.0 {
536 let back = self.back[src_idx];
537 if let Some(key) = key_color {
538 if key == back {
539 continue;
540 }
541 }
542 destination.back[dest_idx] = color_blend(dst_back, src_back, back_alpha);
543 }
544 if fore_alpha > 0.0 {
545 let src_fore = self.fore[src_idx];
546 let dst_fore = destination.fore[dest_idx];
547 let src_char = self.ascii[src_idx];
548 let dst_char = destination.ascii[dest_idx];
549 let dst_back = destination.back[dest_idx];
550 if fore_alpha < 1.0 {
551 if src_char == ' ' as u32 {
552 destination.fore[dest_idx] =
553 color_blend(dst_fore, src_back, back_alpha);
554 } else if dst_char == ' ' as u32 {
555 destination.ascii[dest_idx] = src_char;
556 destination.fore[dest_idx] =
557 color_blend(dst_back, src_fore, fore_alpha);
558 } else if dst_char == src_char {
559 destination.fore[dest_idx] =
560 color_blend(dst_fore, src_fore, fore_alpha);
561 } else if fore_alpha < 0.5 {
562 destination.fore[dest_idx] =
563 color_blend(dst_fore, dst_back, fore_alpha * 2.0);
564 } else {
565 destination.ascii[dest_idx] = src_char;
566 destination.fore[dest_idx] =
567 color_blend(dst_back, src_fore, (fore_alpha - 0.5) * 2.0);
568 }
569 } else {
570 destination.fore[dest_idx] = src_fore;
571 destination.ascii[dest_idx] = src_char;
572 }
573 }
574 }
575 }
576 }
577 }
578}