use raylib::{ color::Color, drawing::{RaylibDraw, RaylibDrawHandle}, math::Vector2, }; use serde::{Deserialize, Serialize}; use crate::{ marble_engine::{ grid::{Grid, ResizeDeltas}, pos::Pos, tile::Tile, }, theme::TILE_TEXTURE_SIZE, }; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Comment { text: String, x: i32, y: i32, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(from = "CompatBoard")] pub struct Board { pub grid: Grid, pub comments: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] enum CompatBoard { V1(String), V2 { grid: Grid, comments: Vec }, } impl From for Board { fn from(value: CompatBoard) -> Self { match value { CompatBoard::V1(string) => Self { grid: Grid::from_ascii(&string), comments: Vec::new(), }, CompatBoard::V2 { grid, comments } => Self { grid, comments }, } } } impl Default for Board { fn default() -> Self { Self { grid: Grid::new_single(Tile::BLANK), comments: Vec::new(), } } } impl Board { pub fn new(grid: Grid) -> Self { Self { grid, comments: Vec::new(), } } pub fn single_tile(tile: Tile) -> Self { Self { grid: Grid::new_single(tile), comments: Vec::new(), } } pub fn draw_comments(&self, d: &mut RaylibDrawHandle, offset: Vector2, scale: f32) { let tile_size = (TILE_TEXTURE_SIZE * scale) as i32; let font_size = 10 * (scale as i32).max(1); let line_space = 12 * (scale as i32).max(1); for comment in &self.comments { let x = comment.x * tile_size + offset.x as i32; let y = comment.y * tile_size + offset.y as i32; let y = y + (tile_size - font_size) / 2; // center vertically in the grid row for (i, line) in comment.text.lines().enumerate() { let y = y + line_space * i as i32; d.draw_text(line, x, y, font_size, Color::ORANGE); } } } pub fn get_rect(&self, pos: Pos, width: usize, height: usize) -> Self { let comments = self .comments .iter() .filter(|c| c.in_area(pos, (width, height))) .map(|c| { let mut c = c.clone(); c.x -= pos.x as i32; c.y -= pos.y as i32; c }) .collect(); Self { grid: self.grid.get_rect(pos, width, height), comments, } } pub fn paste_board(&mut self, pos: Pos, board: &Board) { // remove comments that are obscured by new board let mut i = 0; while i < self.comments.len() { if !self.comments[i].in_area(pos, board.grid.size()) { i += 1; } else { self.comments.remove(i); } } self.grid.paste_grid(pos, &board.grid); for c in &board.comments { let mut comment = c.clone(); comment.x += pos.x as i32; comment.y += pos.y as i32; self.add_comment(comment); } } pub fn add_comment(&mut self, comment: Comment) { if self.comments.iter().any(|c| c == &comment) { return; } self.comments.push(comment); } pub fn grow(&mut self, deltas: &ResizeDeltas) { self.grid.grow(deltas); for c in &mut self.comments { c.x += deltas.x_neg as i32; c.y += deltas.y_neg as i32; } } pub fn shrink(&mut self, deltas: &ResizeDeltas) { self.grid.shrink(deltas); for c in &mut self.comments { c.x -= deltas.x_neg as i32; c.y -= deltas.y_neg as i32; } } pub fn from_user_str(source: &str) -> Self { serde_json::from_str(source).unwrap_or_else(|_| Self { grid: Grid::from_ascii(source), comments: Vec::new(), }) } } impl Comment { fn in_area(&self, pos: Pos, (width, height): (usize, usize)) -> bool { self.x >= pos.x as i32 && self.y >= pos.y as i32 && self.x < pos.x as i32 + width as i32 && self.y < pos.y as i32 + height as i32 } }