add undo/redo for single tile placement/erasing

This commit is contained in:
Crispy 2024-12-16 21:45:50 +01:00
parent 6d8bfa03b0
commit 5b6113780a
2 changed files with 144 additions and 22 deletions

View file

@ -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<Board>,
draw_overlay: bool,
undo_history: Vec<Action>,
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();
}
}
}

View file

@ -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
}