enter and exit solution editor
This commit is contained in:
parent
44b7d63cde
commit
c4381ac1a1
8 changed files with 195 additions and 41 deletions
|
@ -4,9 +4,8 @@
|
|||
logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
||||
|
||||
## todo
|
||||
level selection
|
||||
|
||||
solution saving & loading
|
||||
cleanup: unpowered texture names x_off -> x
|
||||
input/output display
|
||||
grow grid automatically while editing
|
||||
sim/speed control gui
|
||||
|
|
119
src/editor.rs
119
src/editor.rs
|
@ -9,9 +9,14 @@ use crate::{
|
|||
tile::{Direction, GateType, MathOp, MirrorType, PTile, Tile, WireType},
|
||||
Machine,
|
||||
},
|
||||
simple_button,
|
||||
solution::Solution,
|
||||
text_input, texture_option_button, Textures,
|
||||
};
|
||||
|
||||
const HEADER_HEIGHT: i32 = 40;
|
||||
const FOOTER_HEIGHT: i32 = 95;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Editor {
|
||||
source_board: Board,
|
||||
|
@ -31,6 +36,8 @@ pub struct Editor {
|
|||
input_text_selected: bool,
|
||||
sim_speed: f32,
|
||||
time_since_step: f32,
|
||||
exit_state: ExitState,
|
||||
exit_menu: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -53,10 +60,17 @@ enum SimState {
|
|||
Stepping,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ExitState {
|
||||
Dont,
|
||||
ExitAndSave,
|
||||
ExitNoSave,
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn new_sandbox() -> Self {
|
||||
pub fn new(solution: Solution, level: Level) -> Self {
|
||||
Self {
|
||||
source_board: Board::new_empty(16, 16),
|
||||
source_board: Board::parse(&solution.board),
|
||||
machine: Machine::new_empty(1),
|
||||
sim_state: SimState::Editing,
|
||||
view_offset: Vector2::zero(),
|
||||
|
@ -72,10 +86,28 @@ impl Editor {
|
|||
tool_menu_arrow: Direction::Right,
|
||||
tool_menu_mirror: MirrorType::Forward,
|
||||
tool_menu_wire: WireType::Vertical,
|
||||
level: Level::new_sandbox(),
|
||||
level,
|
||||
exit_state: ExitState::Dont,
|
||||
exit_menu: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_exit_state(&self) -> ExitState {
|
||||
self.exit_state
|
||||
}
|
||||
|
||||
pub fn level_id(&self) -> &str {
|
||||
&self.level.id()
|
||||
}
|
||||
|
||||
pub fn save(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn source_board(&self) -> &Board {
|
||||
&self.source_board
|
||||
}
|
||||
|
||||
pub fn load_board(&mut self, board: Board) {
|
||||
self.source_board = board;
|
||||
}
|
||||
|
@ -131,6 +163,9 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub fn input(&mut self, rl: &RaylibHandle) {
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||
self.sim_state = SimState::Editing
|
||||
}
|
||||
if self.sim_state == SimState::Running {
|
||||
self.time_since_step += rl.get_frame_time();
|
||||
if self.time_since_step > 1. / self.sim_speed {
|
||||
|
@ -149,9 +184,6 @@ impl Editor {
|
|||
}
|
||||
self.sim_state = SimState::Stepping;
|
||||
}
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||
self.sim_state = SimState::Editing;
|
||||
}
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
|
||||
match self.sim_state {
|
||||
SimState::Editing => {
|
||||
|
@ -175,6 +207,10 @@ impl Editor {
|
|||
if rl.is_mouse_button_pressed(MouseButton::MOUSE_BUTTON_RIGHT) {
|
||||
self.view_offset = Vector2::zero();
|
||||
}
|
||||
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_R) {
|
||||
self.rotate_tool(rl.is_key_down(KeyboardKey::KEY_LEFT_SHIFT));
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_board(&self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
||||
|
@ -192,18 +228,6 @@ impl Editor {
|
|||
|
||||
pub fn draw(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
||||
d.clear_background(Color::new(64, 64, 64, 255));
|
||||
self.draw_board(d, textures);
|
||||
|
||||
let height = d.get_screen_height();
|
||||
let footer_height = 95;
|
||||
let footer_top = (height - footer_height) as f32;
|
||||
d.draw_rectangle(
|
||||
0,
|
||||
height - footer_height,
|
||||
d.get_screen_width(),
|
||||
footer_height,
|
||||
Color::new(32, 32, 32, 255),
|
||||
);
|
||||
|
||||
let tile_size = (16 << self.zoom) as f32;
|
||||
let grid_spill_x = (self.view_offset.x).rem(tile_size) - tile_size;
|
||||
|
@ -213,13 +237,62 @@ impl Editor {
|
|||
grid_spill_x,
|
||||
grid_spill_y,
|
||||
d.get_screen_width() as f32 * 2.,
|
||||
height as f32 - grid_spill_y - footer_height as f32,
|
||||
d.get_screen_height() as f32 * 2.,
|
||||
),
|
||||
None,
|
||||
tile_size,
|
||||
1,
|
||||
);
|
||||
|
||||
self.draw_board(d, textures);
|
||||
self.board_overlay(d, textures);
|
||||
self.draw_bottom_bar(d, textures);
|
||||
self.draw_top_bar(d, textures);
|
||||
}
|
||||
|
||||
fn draw_top_bar(&mut self, d: &mut RaylibDrawHandle, _textures: &Textures) {
|
||||
// background
|
||||
d.draw_rectangle(
|
||||
0,
|
||||
0,
|
||||
d.get_screen_width(),
|
||||
HEADER_HEIGHT,
|
||||
Color::new(32, 32, 32, 255),
|
||||
);
|
||||
|
||||
if self.exit_menu {
|
||||
if simple_button(d, 5, 5, 80, 30) {
|
||||
self.exit_menu = false;
|
||||
}
|
||||
d.draw_text("cancel", 10, 10, 20, Color::WHITE);
|
||||
if simple_button(d, 90, 5, 80, 30) {
|
||||
self.exit_state = ExitState::ExitAndSave;
|
||||
}
|
||||
d.draw_text("save", 95, 10, 20, Color::WHITE);
|
||||
if simple_button(d, 175, 5, 80, 30) {
|
||||
self.exit_state = ExitState::ExitNoSave;
|
||||
}
|
||||
d.draw_text("revert", 180, 10, 20, Color::WHITE);
|
||||
} else {
|
||||
if simple_button(d, 5, 5, 80, 30) {
|
||||
self.exit_menu = true;
|
||||
}
|
||||
d.draw_text("exit", 10, 10, 20, Color::WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_bottom_bar(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
||||
let height = d.get_screen_height();
|
||||
let footer_top = (height - FOOTER_HEIGHT) as f32;
|
||||
// background
|
||||
d.draw_rectangle(
|
||||
0,
|
||||
height - FOOTER_HEIGHT,
|
||||
d.get_screen_width(),
|
||||
FOOTER_HEIGHT,
|
||||
Color::new(32, 32, 32, 255),
|
||||
);
|
||||
|
||||
d.gui_check_box(
|
||||
Rectangle::new(5., footer_top + 5., 25., 25.),
|
||||
Some(rstr!("output as text")),
|
||||
|
@ -297,12 +370,10 @@ impl Editor {
|
|||
&Tile::Powerable(PTile::Gate(self.tool_menu_gate), false).texture(),
|
||||
Tool::Gate,
|
||||
);
|
||||
|
||||
let is_shift = d.is_key_down(KeyboardKey::KEY_LEFT_SHIFT);
|
||||
if d.is_key_pressed(KeyboardKey::KEY_R) {
|
||||
self.rotate_tool(is_shift);
|
||||
}
|
||||
|
||||
fn board_overlay(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
||||
let footer_top = (d.get_screen_height() - FOOTER_HEIGHT) as f32;
|
||||
let mouse_pos = d.get_mouse_position();
|
||||
if self.sim_state == SimState::Editing {
|
||||
if let Tool::Digits(Some(pos)) = &mut self.active_tool {
|
||||
|
@ -334,7 +405,7 @@ impl Editor {
|
|||
pos.y += 1;
|
||||
}
|
||||
}
|
||||
if mouse_pos.y < footer_top {
|
||||
if mouse_pos.y < footer_top && mouse_pos.y > HEADER_HEIGHT as f32 {
|
||||
let tile_pos = (mouse_pos - self.view_offset) / (16 << self.zoom) as f32;
|
||||
let tile_pos = Vector2::new(tile_pos.x.floor(), tile_pos.y.floor());
|
||||
|
||||
|
|
10
src/level.rs
10
src/level.rs
|
@ -2,7 +2,7 @@ use serde::Deserialize;
|
|||
|
||||
use crate::marble_engine::board::Board;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Level {
|
||||
id: String,
|
||||
name: String,
|
||||
|
@ -36,8 +36,12 @@ impl Level {
|
|||
&self.description
|
||||
}
|
||||
|
||||
pub fn init_board(&self) -> Option<Board> {
|
||||
self.init_board.as_deref().map(Board::parse)
|
||||
// pub fn init_board(&self) -> Option<Board> {
|
||||
// self.init_board.as_deref().map(Board::parse)
|
||||
// }
|
||||
|
||||
pub fn init_board(&self)->Option<String>{
|
||||
self.init_board.clone()
|
||||
}
|
||||
|
||||
pub fn inputs(&self) -> &[u8] {
|
||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -11,7 +11,7 @@ mod marble_engine;
|
|||
mod solution;
|
||||
mod util;
|
||||
|
||||
use editor::Editor;
|
||||
use editor::{Editor, ExitState};
|
||||
use level::Level;
|
||||
use marble_engine::board::Board;
|
||||
use solution::Solution;
|
||||
|
@ -67,6 +67,16 @@ impl Game {
|
|||
if let Some(editor) = &mut self.open_editor {
|
||||
editor.input(&d);
|
||||
editor.draw(&mut d, &self.textures);
|
||||
match editor.get_exit_state() {
|
||||
ExitState::Dont => (),
|
||||
ExitState::ExitNoSave => self.open_editor = None,
|
||||
ExitState::ExitAndSave => {
|
||||
self.solutions.get_mut(editor.level_id()).unwrap()
|
||||
[self.selected_solution]
|
||||
.board = editor.source_board().to_string();
|
||||
self.open_editor = None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.draw(&mut d);
|
||||
}
|
||||
|
@ -165,7 +175,7 @@ impl Game {
|
|||
|
||||
if simple_button(d, level_list_width + 10, y, solution_entry_width, 30) {
|
||||
let n = solutions.len();
|
||||
solutions.push(Solution::new(level.id().to_owned(), n));
|
||||
solutions.push(Solution::new(&level, n));
|
||||
}
|
||||
d.draw_text(
|
||||
"new solution",
|
||||
|
@ -179,7 +189,7 @@ impl Game {
|
|||
let bounds = Rectangle {
|
||||
x: (level_list_width + 10 + solution_entry_width + 10) as f32,
|
||||
y: 60.,
|
||||
width: 240.,
|
||||
width: 220.,
|
||||
height: 30.,
|
||||
};
|
||||
text_input(
|
||||
|
@ -188,6 +198,12 @@ impl Game {
|
|||
&mut solution.name,
|
||||
&mut self.editing_solution_name,
|
||||
);
|
||||
|
||||
let button_x = level_list_width + solution_entry_width + 20;
|
||||
if simple_button(d, button_x, 100, 220, 30) {
|
||||
self.open_editor = Some(Editor::new(solution.clone(), level.clone()));
|
||||
}
|
||||
d.draw_text("edit", button_x + 5, 105, 20, Color::WHITE);
|
||||
}
|
||||
} else {
|
||||
self.solutions.insert(level.id().to_owned(), Vec::new());
|
||||
|
|
|
@ -63,6 +63,17 @@ impl Board {
|
|||
Board::new(rows)
|
||||
}
|
||||
|
||||
pub fn to_string(&self)->String{
|
||||
let mut out = String::new();
|
||||
for row in &self.rows{
|
||||
for tile in row{
|
||||
out.push(tile.to_char())
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn new_empty(width: usize, height: usize) -> Self {
|
||||
let rows = vec![vec![Tile::Blank; width]; height];
|
||||
Self {
|
||||
|
|
|
@ -104,6 +104,50 @@ impl Tile {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn to_char(self) -> char {
|
||||
match self {
|
||||
Tile::Blank => ' ',
|
||||
Tile::Block => '#',
|
||||
Tile::Marble { value: _, dir: _ } => 'o',
|
||||
Tile::Digit(n) => (b'0' + n) as char,
|
||||
Tile::Mirror(dir) => match dir {
|
||||
MirrorType::Forward => '/',
|
||||
MirrorType::Back => '\\',
|
||||
},
|
||||
Tile::Arrow(dir) => match dir {
|
||||
Direction::Up => '^',
|
||||
Direction::Down => 'v',
|
||||
Direction::Left => '<',
|
||||
Direction::Right => '>',
|
||||
},
|
||||
Tile::Powerable(tile, _) => match tile {
|
||||
PTile::Trigger => '*',
|
||||
PTile::Wire(wire) => match wire {
|
||||
WireType::Vertical => '|',
|
||||
WireType::Horizontal => '-',
|
||||
WireType::Cross => '+',
|
||||
},
|
||||
PTile::Gate(gate) => match gate {
|
||||
GateType::LessThan => 'L',
|
||||
GateType::GreaterThan => 'G',
|
||||
GateType::Equal => '=',
|
||||
GateType::NotEqual => '!',
|
||||
},
|
||||
PTile::Math(math) => match math {
|
||||
MathOp::Add => 'A',
|
||||
MathOp::Sub => 'S',
|
||||
MathOp::Mul => 'M',
|
||||
MathOp::Div => 'D',
|
||||
MathOp::Rem => 'R',
|
||||
},
|
||||
PTile::Bag => 'B',
|
||||
PTile::Flipper => 'F',
|
||||
PTile::Input => 'I',
|
||||
PTile::Output => 'P',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_blank(&self) -> bool {
|
||||
matches!(self, Tile::Blank)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
use crate::level::Level;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Solution {
|
||||
solution_id: String,
|
||||
// solution_id: String,
|
||||
level_id: String, // redundant?
|
||||
pub name: String,
|
||||
pub board: String,
|
||||
pub score: Option<Score>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Score {
|
||||
pub cycles: u32,
|
||||
pub tiles: u32,
|
||||
|
@ -17,16 +19,23 @@ pub struct Score {
|
|||
}
|
||||
|
||||
impl Solution {
|
||||
pub fn new(level_id: String, number: usize) -> Self {
|
||||
pub fn new(level: &Level, number: usize) -> Self {
|
||||
Self {
|
||||
solution_id: format!("solution_{number}"),
|
||||
level_id,
|
||||
// solution_id: format!("solution_{number}"),
|
||||
level_id: level.id().to_owned(),
|
||||
name: format!("Unnamed {number}"),
|
||||
board: " \n".repeat(20), // todo remove when auto resizing is implemented
|
||||
board: level
|
||||
.init_board()
|
||||
.unwrap_or_else(|| " \n".repeat(20)), // todo remove when auto resizing is implemented
|
||||
// score: Some(Score { cycles: 5, tiles: 88, area: 987 }),
|
||||
score: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn level_id(&self) -> &str {
|
||||
&self.level_id
|
||||
}
|
||||
|
||||
pub fn score_text(&self) -> String {
|
||||
if let Some(score) = &self.score {
|
||||
format!(
|
||||
|
|
|
@ -122,7 +122,7 @@ pub fn text_input(
|
|||
if d.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
||||
*is_selected = false;
|
||||
}
|
||||
if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) {
|
||||
if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) && !text.is_empty() {
|
||||
changed = true;
|
||||
text.pop();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue