205 lines
6.1 KiB
Rust
205 lines
6.1 KiB
Rust
use std::collections::HashMap;
|
|
use std::fmt::Display;
|
|
|
|
use tui::buffer::Buffer;
|
|
use tui::layout::Rect;
|
|
use tui::style::{Color, Style};
|
|
use tui::widgets::{BorderType, Widget};
|
|
|
|
use sea_battle_backend::data::{Coordinates, GameRules, PlayConfiguration};
|
|
|
|
use crate::ui_screens::utils::centered_rect_size;
|
|
|
|
pub struct ColoredCells {
|
|
pub color: Color,
|
|
pub cells: Vec<Coordinates>,
|
|
}
|
|
|
|
pub struct GameMapWidget<'a> {
|
|
rules: &'a GameRules,
|
|
default_empty_character: char,
|
|
colored_cells: Vec<ColoredCells>,
|
|
title: Option<String>,
|
|
legend: Option<String>,
|
|
yield_coordinates: Option<Box<dyn 'a + FnMut(Coordinates, Rect)>>,
|
|
chars: HashMap<Coordinates, char>,
|
|
}
|
|
|
|
impl<'a> GameMapWidget<'a> {
|
|
pub fn new(rules: &'a GameRules) -> Self {
|
|
Self {
|
|
rules,
|
|
default_empty_character: '.',
|
|
colored_cells: vec![],
|
|
title: None,
|
|
legend: None,
|
|
yield_coordinates: None,
|
|
chars: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn set_default_empty_char(mut self, c: char) -> Self {
|
|
self.default_empty_character = c;
|
|
self
|
|
}
|
|
|
|
pub fn add_colored_cells(mut self, c: ColoredCells) -> Self {
|
|
self.colored_cells.push(c);
|
|
self
|
|
}
|
|
|
|
pub fn set_title<D: Display>(mut self, title: D) -> Self {
|
|
self.title = Some(title.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn set_legend<D: Display>(mut self, legend: D) -> Self {
|
|
self.legend = Some(legend.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn set_yield_func<F>(mut self, func: F) -> Self
|
|
where
|
|
F: 'a + FnMut(Coordinates, Rect),
|
|
{
|
|
self.yield_coordinates = Some(Box::new(func));
|
|
self
|
|
}
|
|
|
|
pub fn set_char(mut self, coordinates: Coordinates, c: char) -> Self {
|
|
self.chars.insert(coordinates, c);
|
|
self
|
|
}
|
|
|
|
pub fn set_char_no_overwrite(mut self, coordinates: Coordinates, c: char) -> Self {
|
|
self.chars.entry(coordinates).or_insert(c);
|
|
self
|
|
}
|
|
|
|
pub fn grid_size(&self) -> (u16, u16) {
|
|
let w = self.rules.map_width as u16 * 2 + 1;
|
|
let h = self.rules.map_height as u16 * 2 + 1;
|
|
|
|
(w, h)
|
|
}
|
|
|
|
pub fn estimated_size(&self) -> (u16, u16) {
|
|
let (w, mut h) = self.grid_size();
|
|
|
|
if self.title.is_some() {
|
|
h += 2;
|
|
}
|
|
|
|
if let Some(l) = &self.legend {
|
|
h += 1 + l.split('\n').count() as u16;
|
|
}
|
|
|
|
(w, h)
|
|
}
|
|
}
|
|
|
|
impl<'a> Widget for GameMapWidget<'a> {
|
|
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
|
let alphabet = PlayConfiguration::default().ordinate_alphabet;
|
|
|
|
let symbols = BorderType::line_symbols(BorderType::Plain);
|
|
|
|
let mut start_y = area.y;
|
|
|
|
// Render title
|
|
if let Some(title) = &self.title {
|
|
let x = centered_rect_size(title.len() as u16, 1, &area).x;
|
|
buf.set_string(x, start_y, title, Style::default());
|
|
start_y += 2;
|
|
}
|
|
|
|
// Paint game grid
|
|
for y in 0..(self.rules.map_height + 1) {
|
|
// Header (ordinate)
|
|
if y < self.rules.map_height {
|
|
buf.get_mut(area.x, start_y + 2 + (y as u16 * 2))
|
|
.set_char(alphabet.chars().nth(y).unwrap());
|
|
}
|
|
|
|
for x in 0..(self.rules.map_width + 1) {
|
|
let coordinates = Coordinates::new(x as i32, y as i32);
|
|
|
|
// Header (abscissa)
|
|
if x < self.rules.map_width {
|
|
buf.set_string(
|
|
area.x + 2 + (x as u16 * 2) - (x as u16) / 10,
|
|
start_y,
|
|
x.to_string(),
|
|
Style::default(),
|
|
);
|
|
}
|
|
|
|
let o_x = 1 + area.x + (x as u16 * 2);
|
|
let o_y = 1 + start_y + (y as u16 * 2);
|
|
|
|
let color = self
|
|
.colored_cells
|
|
.iter()
|
|
.find(|c| c.cells.contains(&coordinates));
|
|
|
|
buf.get_mut(o_x, o_y).set_symbol(match (x, y) {
|
|
(0, 0) => symbols.top_left,
|
|
|
|
(x, 0) if x == self.rules.map_width => symbols.top_right,
|
|
(0, y) if y == self.rules.map_height => symbols.bottom_left,
|
|
|
|
(0, _) => symbols.vertical_right,
|
|
(_, 0) => symbols.horizontal_down,
|
|
|
|
(x, y) if x == self.rules.map_width && y == self.rules.map_height => {
|
|
symbols.bottom_right
|
|
}
|
|
|
|
(x, _) if x == self.rules.map_width => symbols.vertical_left,
|
|
(_, y) if y == self.rules.map_height => symbols.horizontal_up,
|
|
|
|
_ => symbols.cross,
|
|
});
|
|
|
|
if x < self.rules.map_width {
|
|
buf.get_mut(o_x + 1, o_y).set_symbol(symbols.horizontal);
|
|
}
|
|
|
|
if y < self.rules.map_height {
|
|
buf.get_mut(o_x, o_y + 1).set_symbol(symbols.vertical);
|
|
}
|
|
|
|
if x < self.rules.map_width && y < self.rules.map_height {
|
|
let cell = buf.get_mut(o_x + 1, o_y + 1).set_char(
|
|
*self
|
|
.chars
|
|
.get(&coordinates)
|
|
.unwrap_or(&self.default_empty_character),
|
|
);
|
|
|
|
if let Some(c) = color {
|
|
cell.set_bg(c.color);
|
|
}
|
|
|
|
if let Some(f) = self.yield_coordinates.as_mut() {
|
|
f(coordinates, Rect::new(o_x + 1, o_y + 1, 1, 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
start_y += self.grid_size().1;
|
|
|
|
// Paint legend (if any)
|
|
if let Some(legend) = &self.legend {
|
|
start_y += 1;
|
|
for line in legend.split('\n') {
|
|
let center_rect = centered_rect_size(line.len() as u16, 1, &area);
|
|
let x = center_rect.x;
|
|
buf.set_string(x, start_y, line, Style::default());
|
|
start_y += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|