diff --git a/src/editor.rs b/src/editor.rs index 80f4512..fcea22e 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -12,7 +12,7 @@ use crate::{ draw_scaled_texture, draw_usize, get_free_id, get_scroll, level::Level, marble_engine::{ - board::Board, + board::{Board, ResizeDeltas}, pos::{Pos, PosInt}, tile::{Claim, Comparison, Direction, MathOp, MirrorType, OpenTile, PTile, Tile, WireType}, Machine, @@ -70,6 +70,14 @@ pub struct Editor { start_time: Instant, pasting_board: Option, draw_overlay: bool, + undo_history: Vec, + undo_index: usize, +} + +#[derive(Debug, Clone)] +enum Action { + SetTile(ResizeDeltas, Pos, Tile, Tile), + SetArea(ResizeDeltas, Pos, Board, Board), } #[derive(Debug, PartialEq)] @@ -150,9 +158,66 @@ impl Editor { start_time: Instant::now(), pasting_board: None, draw_overlay: true, + undo_history: Vec::new(), + undo_index: 0, } } + fn redo(&mut self) { + if self.undo_index >= self.undo_history.len() { + return; + } + let action = self.undo_history[self.undo_index].clone(); + self.do_action(action); + self.undo_index += 1; + } + + fn push_action(&mut self, action: Action) { + self.do_action(action.clone()); + self.undo_history.truncate(self.undo_index); + self.undo_history.push(action); + self.undo_index += 1; + } + + fn do_action(&mut self, action: Action) { + match action { + Action::SetTile(resize_delta, pos, _old, new) => { + self.resize_board(resize_delta); + self.source_board.set(pos, new); + } + Action::SetArea(_, _, _, _) => todo!(), + } + } + + fn undo(&mut self) { + if self.undo_index == 0 { + return; + } + self.undo_index -= 1; + let action = self.undo_history[self.undo_index].clone(); + match action { + Action::SetTile(resize_delta, pos, old, _new) => { + self.source_board.set(pos, old); + self.undo_resize_board(resize_delta); + } + Action::SetArea(_, _, _, _) => todo!(), + } + } + + fn resize_board(&mut self, deltas: ResizeDeltas) { + self.source_board.grow(&deltas); + let tile_size = TILE_TEXTURE_SIZE * self.zoom; + self.view_offset.x -= deltas.x_neg as f32 * tile_size; + self.view_offset.y -= deltas.y_neg as f32 * tile_size; + } + + fn undo_resize_board(&mut self, deltas: ResizeDeltas) { + self.source_board.shrink(&deltas); + let tile_size = TILE_TEXTURE_SIZE * self.zoom; + self.view_offset.x += deltas.x_neg as f32 * tile_size; + self.view_offset.y += deltas.y_neg as f32 * tile_size; + } + pub fn get_exit_state(&self) -> ExitState { self.exit_state } @@ -318,22 +383,39 @@ impl Editor { } } - fn set_tile(&mut self, mut pos: Pos, tile: Tile) { - let tile_size = TILE_TEXTURE_SIZE * self.zoom; - pos.x -= BOARD_MARGIN; - pos.y -= BOARD_MARGIN; - self.grow_board_and_update_view(&mut pos); - pos.x += BOARD_MARGIN * 2; - pos.y += BOARD_MARGIN * 2; - self.grow_board_and_update_view(&mut pos); - pos.x -= BOARD_MARGIN; - pos.y -= BOARD_MARGIN; - self.source_board.set(pos, tile); - if tile.is_blank() { - let (x, y) = self.source_board.trim_size(BOARD_MARGIN as usize); - self.view_offset.x += x as f32 * tile_size; - self.view_offset.y += y as f32 * tile_size; + fn set_tile(&mut self, pos: Pos, tile: Tile) { + let old_tile = self.source_board.get_or_blank(pos); + if old_tile == tile { + return; } + let width = self.source_board.width() as PosInt; + let height = self.source_board.height() as PosInt; + let resize = ResizeDeltas { + x_pos: if (pos.x + BOARD_MARGIN + 1) > width { + pos.x + BOARD_MARGIN + 1 - width + } else { + 0 + } as usize, + x_neg: if pos.x < BOARD_MARGIN { + BOARD_MARGIN - pos.x + } else { + 0 + } as usize, + y_pos: if (pos.y + BOARD_MARGIN + 1) > height { + pos.y + BOARD_MARGIN + 1 - height + } else { + 0 + } as usize, + y_neg: if pos.y < BOARD_MARGIN { + BOARD_MARGIN - pos.y + } else { + 0 + } as usize, + }; + let mut pos = pos; + pos.x += resize.x_neg as PosInt; + pos.y += resize.y_neg as PosInt; + self.push_action(Action::SetTile(resize, pos, old_tile, tile)); } pub fn update(&mut self, rl: &RaylibHandle) { @@ -417,12 +499,16 @@ impl Editor { } if self.sim_state == SimState::Editing { - if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) - && rl.is_key_pressed(KeyboardKey::KEY_V) - { - if let Ok(text) = rl.get_clipboard_text() { - let b = Board::parse(&text); - self.pasting_board = Some(b); + if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) { + if rl.is_key_pressed(KeyboardKey::KEY_V) { + if let Ok(text) = rl.get_clipboard_text() { + let b = Board::parse(&text); + self.pasting_board = Some(b); + } + } else if rl.is_key_pressed(KeyboardKey::KEY_Z) { + self.undo() + } else if rl.is_key_pressed(KeyboardKey::KEY_Y) { + self.redo(); } } } diff --git a/src/marble_engine/board.rs b/src/marble_engine/board.rs index 1b21540..2f10308 100644 --- a/src/marble_engine/board.rs +++ b/src/marble_engine/board.rs @@ -13,6 +13,14 @@ pub struct Board { height: usize, } +#[derive(Debug, Clone)] +pub struct ResizeDeltas { + pub x_pos: usize, + pub x_neg: usize, + pub y_pos: usize, + pub y_neg: usize, +} + impl Board { pub fn parse(source: &str) -> Self { let mut rows = Vec::new(); @@ -226,6 +234,34 @@ impl Board { (offset_x, offset_y) } + pub fn grow(&mut self, deltas: &ResizeDeltas) { + // dbg!(&deltas); + let new_width = self.width + deltas.x_neg + deltas.x_pos; + let new_height = self.height + deltas.y_neg + deltas.y_pos; + let mut new_board = Board::new_empty(new_width, new_height); + for x in 0..self.width { + for y in 0..self.height { + let tile = self.rows[y][x]; + new_board.rows[y + deltas.y_neg][x + deltas.x_neg] = tile; + } + } + *self = new_board; + } + + pub fn shrink(&mut self, deltas: &ResizeDeltas) { + // dbg!(&deltas); + let new_width = self.width - deltas.x_neg - deltas.x_pos; + let new_height = self.height - deltas.y_neg - deltas.y_pos; + let mut new_board = Board::new_empty(new_width, new_height); + for x in 0..new_width { + for y in 0..new_height { + let tile = self.rows[y + deltas.y_neg][x + deltas.x_neg]; + new_board.rows[y][x] = tile; + } + } + *self = new_board; + } + pub fn width(&self) -> usize { self.width }