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, } pub struct GameMapWidget<'a> { rules: &'a GameRules, default_empty_character: char, colored_cells: Vec, title: Option, legend: Option, yield_coordinates: Option>, chars: HashMap, } 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(mut self, title: D) -> Self { self.title = Some(title.to_string()); self } pub fn set_legend(mut self, legend: D) -> Self { self.legend = Some(legend.to_string()); self } pub fn set_yield_func(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; } } } }