Compare commits
10 commits
Author | SHA1 | Date | |
---|---|---|---|
1cc8e999b7 | |||
7d9d0a01f7 | |||
1eb7a34537 | |||
c8fc484e9c | |||
440cd7a759 | |||
2c7e844d00 | |||
80ab3b676e | |||
f0b878e93d | |||
509b577aaa | |||
62fcb538a6 |
13 changed files with 366 additions and 178 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -1,6 +1,23 @@
|
||||||
# Marble Machinations Change Log
|
# Marble Machinations Change Log
|
||||||
Game store page: https://crispypin.itch.io/marble-machinations
|
Game store page: https://crispypin.itch.io/marble-machinations
|
||||||
|
|
||||||
|
## v0.3.3 - 2025-04-21
|
||||||
|
### added
|
||||||
|
- option to display power direction while overlay is enabled
|
||||||
|
- show level name in end popup
|
||||||
|
|
||||||
|
### changed
|
||||||
|
- hide tick timing numbers by default
|
||||||
|
- when multiple I/O silos (or multiple directions of one) are activated in the same tick, they will all output the same value instead of pulling input in an arbitrary order
|
||||||
|
|
||||||
|
### fixed
|
||||||
|
- input bytes are consumed even if the marble can't be created because another one was taking its place
|
||||||
|
- keybindings activated even when typing in a text field, making especially renaming blueprints difficult
|
||||||
|
- grid rendering broken on right edge of the screen at some zoom levels and window sizes
|
||||||
|
- crash when saving config if no user dir exists
|
||||||
|
- bindings did not properly take into account order of pressing, so Shift+A and A+Shift were treated as the same thing
|
||||||
|
- after removing a binding that was a superset of another, the remaining one did not stop being blocked by the removed ones additional modifiers until another binding was added or edited
|
||||||
|
|
||||||
## v0.3.2 - 2025-04-14
|
## v0.3.2 - 2025-04-14
|
||||||
### added
|
### added
|
||||||
- "weird machines" chapter with levels for [deadfish](https://esolangs.org/wiki/Deadfish) and [brainfuck](https://esolangs.org/wiki/Brainfuck)
|
- "weird machines" chapter with levels for [deadfish](https://esolangs.org/wiki/Deadfish) and [brainfuck](https://esolangs.org/wiki/Brainfuck)
|
||||||
|
@ -10,7 +27,7 @@ Game store page: https://crispypin.itch.io/marble-machinations
|
||||||
### fixed
|
### fixed
|
||||||
- invalid action ids in the config file key bindings caused everything to revert to default.
|
- invalid action ids in the config file key bindings caused everything to revert to default.
|
||||||
- when start and stop are bound to the same thing (as by default), only start works
|
- when start and stop are bound to the same thing (as by default), only start works
|
||||||
- When two input bindings had the same trigger but one has a strict subset of the others modifiers, both would activate when the one with more modifiers was pressed. For example (Ctrl+S -> Save) would also trigger (S -> Wire Tool). Now, Shift+S will still trigger Wire Tool, unless Shift+S (or eg. Shift+Ctrl+S) is bound to something else.
|
- when two input bindings had the same trigger but one has a strict subset of the others modifiers, both would activate when the one with more modifiers was pressed. For example (Ctrl+S -> Save) would also trigger (S -> Wire Tool). Now, Shift+S will still trigger Wire Tool, unless Shift+S (or eg. Shift+Ctrl+S) is bound to something else.
|
||||||
|
|
||||||
## v0.3.1 - 2025-04-05
|
## v0.3.1 - 2025-04-05
|
||||||
### fixed
|
### fixed
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -213,7 +213,7 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "marble-machinations"
|
name = "marble-machinations"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arboard",
|
"arboard",
|
||||||
"raylib",
|
"raylib",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "marble-machinations"
|
name = "marble-machinations"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "marble-machinations"
|
default-run = "marble-machinations"
|
||||||
|
|
||||||
|
|
30
README.md
30
README.md
|
@ -7,30 +7,34 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
||||||
## todo
|
## todo
|
||||||
### meta
|
### meta
|
||||||
- engine tests
|
- engine tests
|
||||||
- blag post about marble movement logic
|
- blag post about marble movement logic?
|
||||||
|
- standardise terminology (cycle/step/tick)
|
||||||
### bugs
|
### bugs
|
||||||
- modifier-less bindings trigger when typing in a text box, makes renaming existing blueprints basically impossible
|
|
||||||
### features
|
### features
|
||||||
#### 0.3.x
|
#### 0.3.x
|
||||||
- more levels
|
- more levels
|
||||||
- packet routing?
|
- packet routing?
|
||||||
- game of life sim (width;height;steps;grid -> grid)
|
- game of life sim (width;height;steps;grid -> grid)
|
||||||
#### 0.4.0
|
- shrink button
|
||||||
|
#### 0.4.x
|
||||||
- UI layout engine
|
- UI layout engine
|
||||||
- global scale setting
|
- global scale multiplier, affected by window size
|
||||||
|
- background colour setting (requires color picker => after UI rework)
|
||||||
|
- light theme
|
||||||
#### unspecified
|
#### unspecified
|
||||||
- comments
|
- comments
|
||||||
- editing
|
- editing
|
||||||
- add to all intro levels
|
- add to all intro levels
|
||||||
- highlight regions with background colours
|
- highlight regions with background colours
|
||||||
- button + binding to flip selection that is being pasted
|
- button + binding to flip selection that is being pasted
|
||||||
- accessibility
|
- hotkeys for everything (no mouse needed to play)
|
||||||
- background colour setting (requires color picker => after UI rework)
|
- menu navigation (requires UI rework)
|
||||||
- hotkeys for everything (no mouse needed to play)
|
- speed up/down
|
||||||
- menu navigation (requires UI rework)
|
- keybinds for specific tool variants
|
||||||
- speed up/down
|
- grid cursor movement and placement
|
||||||
- grid cursor movement and placement
|
- grid zoom and pan
|
||||||
- grid zoom and pan
|
- config settings page categories (mostly for keybindings)
|
||||||
- UI: scroll output bytes
|
- UI: scroll output bytes
|
||||||
- timestamps in solutions and blueprints
|
- timestamps in solutions and blueprints
|
||||||
- display tool variant more clearly (it's not obvious there are more states)
|
- display tool variant more clearly (it's not obvious there are more states)
|
||||||
|
@ -38,11 +42,11 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
||||||
- font selection (probably a lot of work)
|
- font selection (probably a lot of work)
|
||||||
### online stuff
|
### online stuff
|
||||||
- store scores in server
|
- store scores in server
|
||||||
- validate solutions in server (with limits)
|
- validate solutions in server (with compute limits)
|
||||||
- show histograms
|
- show histograms
|
||||||
- author name in solutions and blueprints
|
- author name in solutions and blueprints
|
||||||
### undecided
|
### undecided
|
||||||
- option to skip (speed through with settable multiplier) first N stages, for when you are debugging something that happens in later stages
|
- option to skip (fast-forward through with settable multiplier) first N stages or cycles, for when you are debugging something that happens in later stages
|
||||||
- hide some tile tools in early levels to make it less overwhelming
|
- hide some tile tools in early levels to make it less overwhelming
|
||||||
- footprint score (tiles that were non-empty at any point in the run)
|
- footprint score (tiles that were non-empty at any point in the run)
|
||||||
- option to use 8-bit marbles?
|
- option to use 8-bit marbles?
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{input::Input, theme::FG_CHAPTER_TITLE, ui::text_button, util::Scroll, Globals};
|
use crate::{
|
||||||
|
input::Input,
|
||||||
|
theme::FG_CHAPTER_TITLE,
|
||||||
|
ui::{text_button, toggle_button},
|
||||||
|
util::Scroll,
|
||||||
|
Globals,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub input: Input,
|
pub input: Input,
|
||||||
|
#[serde(default)]
|
||||||
|
pub show_debug_timing: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub show_power_direction: bool,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
scroll_offset: u32,
|
scroll_offset: u32,
|
||||||
}
|
}
|
||||||
|
@ -25,20 +35,31 @@ impl Config {
|
||||||
Some(Scroll::Up) => self.scroll_offset = self.scroll_offset.saturating_sub(64),
|
Some(Scroll::Up) => self.scroll_offset = self.scroll_offset.saturating_sub(64),
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
let y = -(self.scroll_offset as i32);
|
let mut y = -(self.scroll_offset as i32) + 15;
|
||||||
|
d.draw_text("Settings", 16, y, 30, FG_CHAPTER_TITLE);
|
||||||
|
y += 40;
|
||||||
|
|
||||||
d.draw_text("Settings", 16, y + 16, 30, FG_CHAPTER_TITLE);
|
if text_button(d, &globals.mouse, 10, y, 80, "apply") {
|
||||||
|
|
||||||
if text_button(d, &globals.mouse, 10, y + 60, 80, "apply") {
|
|
||||||
return MenuReturn::StaySave;
|
return MenuReturn::StaySave;
|
||||||
}
|
}
|
||||||
if text_button(d, &globals.mouse, 100, y + 60, 80, "done") {
|
if text_button(d, &globals.mouse, 100, y, 80, "done") {
|
||||||
return MenuReturn::ReturnSave;
|
return MenuReturn::ReturnSave;
|
||||||
}
|
}
|
||||||
if text_button(d, &globals.mouse, 190, y + 60, 80, "cancel") {
|
if text_button(d, &globals.mouse, 190, y, 80, "cancel") {
|
||||||
return MenuReturn::ReturnCancel;
|
return MenuReturn::ReturnCancel;
|
||||||
}
|
}
|
||||||
|
y += 40;
|
||||||
|
|
||||||
|
let mut toggle = |value, text| {
|
||||||
|
toggle_button((d, &globals.mouse), 10, y, 30, 30, value);
|
||||||
|
d.draw_text(text, 50, y + 5, 20, Color::WHITE);
|
||||||
|
y += 40;
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle(&mut self.show_power_direction, "show power directions");
|
||||||
|
toggle(&mut self.show_debug_timing, "show debug timing");
|
||||||
|
|
||||||
|
// self.input.update(d);
|
||||||
self.input.draw_edit(d, globals, y);
|
self.input.draw_edit(d, globals, y);
|
||||||
MenuReturn::Stay
|
MenuReturn::Stay
|
||||||
}
|
}
|
||||||
|
|
213
src/editor.rs
213
src/editor.rs
|
@ -24,7 +24,7 @@ const HEADER_HEIGHT: i32 = 40;
|
||||||
const FOOTER_HEIGHT: i32 = 95;
|
const FOOTER_HEIGHT: i32 = 95;
|
||||||
const SIDEBAR_WIDTH: i32 = 200 + 32 * 2 + 5 * 4;
|
const SIDEBAR_WIDTH: i32 = 200 + 32 * 2 + 5 * 4;
|
||||||
const END_POPUP_WIDTH: i32 = 320;
|
const END_POPUP_WIDTH: i32 = 320;
|
||||||
const END_POPUP_HEIGHT: i32 = 225;
|
const END_POPUP_HEIGHT: i32 = 255;
|
||||||
|
|
||||||
const MAX_ZOOM: f32 = 8.;
|
const MAX_ZOOM: f32 = 8.;
|
||||||
const MIN_ZOOM: f32 = 0.25;
|
const MIN_ZOOM: f32 = 0.25;
|
||||||
|
@ -74,8 +74,8 @@ pub struct Editor {
|
||||||
undo_history: Vec<Action>,
|
undo_history: Vec<Action>,
|
||||||
undo_index: usize,
|
undo_index: usize,
|
||||||
// debug/profiling
|
// debug/profiling
|
||||||
step_time: u128,
|
step_time: usize,
|
||||||
max_step_time: u128,
|
max_step_time: usize,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,10 +85,10 @@ enum Action {
|
||||||
SetArea(ResizeDeltas, Pos, Board, Board),
|
SetArea(ResizeDeltas, Pos, Board, Board),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
enum Popup {
|
enum Popup {
|
||||||
None,
|
None,
|
||||||
Success,
|
Success(Score),
|
||||||
Failure,
|
Failure,
|
||||||
LevelInfo,
|
LevelInfo,
|
||||||
PauseMenu,
|
PauseMenu,
|
||||||
|
@ -316,7 +316,7 @@ impl Editor {
|
||||||
self.machine.step();
|
self.machine.step();
|
||||||
|
|
||||||
if let Some(i) = self.stage {
|
if let Some(i) = self.stage {
|
||||||
if matches!(self.popup, Popup::Failure | Popup::Success) {
|
if matches!(self.popup, Popup::Failure | Popup::Success(_)) {
|
||||||
self.popup = Popup::None;
|
self.popup = Popup::None;
|
||||||
self.dismissed_end = true;
|
self.dismissed_end = true;
|
||||||
}
|
}
|
||||||
|
@ -328,15 +328,16 @@ impl Editor {
|
||||||
self.total_steps += self.machine.step_count();
|
self.total_steps += self.machine.step_count();
|
||||||
self.reset_machine();
|
self.reset_machine();
|
||||||
} else {
|
} else {
|
||||||
self.popup = Popup::Success;
|
|
||||||
println!("completed in {:?}", self.start_time.elapsed());
|
println!("completed in {:?}", self.start_time.elapsed());
|
||||||
self.exit_state = ExitState::Save;
|
self.exit_state = ExitState::Save;
|
||||||
self.sim_state = SimState::Stepping;
|
self.sim_state = SimState::Stepping;
|
||||||
self.score = Some(Score {
|
let score = Score {
|
||||||
cycles: self.total_steps + self.machine.step_count(),
|
cycles: self.total_steps + self.machine.step_count(),
|
||||||
tiles: self.source_board.grid.count_tiles(),
|
tiles: self.source_board.grid.count_tiles(),
|
||||||
bounds_area: self.source_board.grid.used_bounds_area(),
|
bounds_area: self.source_board.grid.used_bounds_area(),
|
||||||
});
|
};
|
||||||
|
self.score = Some(score.clone());
|
||||||
|
self.popup = Popup::Success(score);
|
||||||
}
|
}
|
||||||
} else if !stage.output().as_bytes().starts_with(self.machine.output()) {
|
} else if !stage.output().as_bytes().starts_with(self.machine.output()) {
|
||||||
self.popup = Popup::Failure;
|
self.popup = Popup::Failure;
|
||||||
|
@ -439,7 +440,7 @@ impl Editor {
|
||||||
|
|
||||||
if globals.is_pressed(ActionId::ToggleMenu) {
|
if globals.is_pressed(ActionId::ToggleMenu) {
|
||||||
self.popup = match self.popup {
|
self.popup = match self.popup {
|
||||||
Popup::Success | Popup::Failure => {
|
Popup::Success(_) | Popup::Failure => {
|
||||||
self.dismissed_end = true;
|
self.dismissed_end = true;
|
||||||
Popup::None
|
Popup::None
|
||||||
}
|
}
|
||||||
|
@ -472,8 +473,8 @@ impl Editor {
|
||||||
.as_micros()
|
.as_micros()
|
||||||
.checked_div(steps_taken)
|
.checked_div(steps_taken)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.step_time = avg_step_time;
|
self.step_time = avg_step_time as usize;
|
||||||
self.max_step_time = avg_step_time.max(self.max_step_time);
|
self.max_step_time = self.step_time.max(self.max_step_time);
|
||||||
}
|
}
|
||||||
if globals.is_pressed(ActionId::StepSim) {
|
if globals.is_pressed(ActionId::StepSim) {
|
||||||
self.step_pressed()
|
self.step_pressed()
|
||||||
|
@ -574,27 +575,48 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_board(&self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
fn draw_board(&self, d: &mut RaylibDrawHandle, globals: &Globals) {
|
||||||
|
let draw_power = globals.config.show_power_direction && self.draw_overlay;
|
||||||
if self.sim_state == SimState::Editing {
|
if self.sim_state == SimState::Editing {
|
||||||
self.source_board
|
self.source_board.grid.draw(
|
||||||
.grid
|
d,
|
||||||
.draw(d, textures, self.view_offset, self.zoom);
|
&globals.textures,
|
||||||
|
self.view_offset,
|
||||||
|
self.zoom,
|
||||||
|
draw_power,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if self.machine.debug_subticks.is_empty() {
|
if self.machine.debug_subticks.is_empty() {
|
||||||
self.machine
|
self.machine.grid().draw(
|
||||||
.grid()
|
d,
|
||||||
.draw(d, textures, self.view_offset, self.zoom);
|
&globals.textures,
|
||||||
|
self.view_offset,
|
||||||
|
self.zoom,
|
||||||
|
draw_power,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let subframe = &self.machine.debug_subticks[self.machine.subtick_index];
|
let subframe = &self.machine.debug_subticks[self.machine.subtick_index];
|
||||||
subframe.grid.draw(d, textures, self.view_offset, self.zoom);
|
subframe.grid.draw(
|
||||||
|
d,
|
||||||
|
&globals.textures,
|
||||||
|
self.view_offset,
|
||||||
|
self.zoom,
|
||||||
|
draw_power,
|
||||||
|
);
|
||||||
if let Some(pos) = subframe.pos {
|
if let Some(pos) = subframe.pos {
|
||||||
let p = self.pos_to_screen(pos.to_vec());
|
let p = self.pos_to_screen(pos.to_vec());
|
||||||
d.draw_texture_ex(textures.get("selection"), p, 0., self.zoom, Color::ORANGE);
|
d.draw_texture_ex(
|
||||||
|
globals.get_tex("selection"),
|
||||||
|
p,
|
||||||
|
0.,
|
||||||
|
self.zoom,
|
||||||
|
Color::ORANGE,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.draw_overlay {
|
if self.draw_overlay {
|
||||||
self.machine
|
self.machine
|
||||||
.draw_marble_values(d, textures, self.view_offset, self.zoom);
|
.draw_marble_values(d, &globals.textures, self.view_offset, self.zoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.draw_overlay {
|
if self.draw_overlay {
|
||||||
|
@ -610,23 +632,25 @@ impl Editor {
|
||||||
let tile_size = TILE_TEXTURE_SIZE * self.zoom;
|
let tile_size = TILE_TEXTURE_SIZE * self.zoom;
|
||||||
let grid_spill_x = (self.view_offset.x).rem(tile_size) - tile_size;
|
let grid_spill_x = (self.view_offset.x).rem(tile_size) - tile_size;
|
||||||
let grid_spill_y = (self.view_offset.y).rem(tile_size) - tile_size;
|
let grid_spill_y = (self.view_offset.y).rem(tile_size) - tile_size;
|
||||||
for y in 0..=(d.get_screen_height() / tile_size as i32) {
|
let hlines = d.get_screen_height() / tile_size as i32 + 3;
|
||||||
|
let vlines = d.get_screen_width() / tile_size as i32 + 3;
|
||||||
|
for y in 0..hlines {
|
||||||
let y = y * tile_size as i32 + grid_spill_y as i32;
|
let y = y * tile_size as i32 + grid_spill_y as i32;
|
||||||
d.draw_line(0, y, d.get_screen_width(), y, FG_GRID);
|
d.draw_line(0, y, d.get_screen_width(), y, FG_GRID);
|
||||||
}
|
}
|
||||||
for x in 0..=(d.get_screen_width() / tile_size as i32) {
|
for x in 0..vlines {
|
||||||
let x = x * tile_size as i32 + grid_spill_x as i32;
|
let x = x * tile_size as i32 + grid_spill_x as i32;
|
||||||
d.draw_line(x, 0, x, d.get_screen_height(), FG_GRID);
|
d.draw_line(x, 0, x, d.get_screen_height(), FG_GRID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.draw_board(d, &globals.textures);
|
self.draw_board(d, globals);
|
||||||
self.board_overlay(d, &globals.textures);
|
self.board_overlay(d, globals);
|
||||||
self.draw_bottom_bar(d, globals);
|
self.draw_bottom_bar(d, globals);
|
||||||
self.draw_top_bar(d, globals);
|
self.draw_top_bar(d, globals);
|
||||||
|
|
||||||
if self.active_tool == Tool::Blueprint {
|
if self.active_tool == Tool::Blueprint {
|
||||||
self.draw_blueprint_sidebar(d, &globals.textures);
|
self.draw_blueprint_sidebar(d, globals);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mouse.update(d);
|
self.mouse.update(d);
|
||||||
|
@ -643,7 +667,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.popup {
|
match self.popup {
|
||||||
Popup::Success | Popup::Failure => {
|
Popup::Success(_) | Popup::Failure => {
|
||||||
self.draw_end_popup(d, &globals.textures);
|
self.draw_end_popup(d, &globals.textures);
|
||||||
}
|
}
|
||||||
Popup::LevelInfo => {
|
Popup::LevelInfo => {
|
||||||
|
@ -676,7 +700,7 @@ impl Editor {
|
||||||
self.tooltip.draw(d);
|
self.tooltip.draw(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_blueprint_sidebar(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
fn draw_blueprint_sidebar(&mut self, d: &mut RaylibDrawHandle, globals: &mut Globals) {
|
||||||
let sidebar_height = d.get_screen_height() - FOOTER_HEIGHT - HEADER_HEIGHT - 40;
|
let sidebar_height = d.get_screen_height() - FOOTER_HEIGHT - HEADER_HEIGHT - 40;
|
||||||
d.draw_rectangle(
|
d.draw_rectangle(
|
||||||
0,
|
0,
|
||||||
|
@ -700,7 +724,7 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(5, y),
|
(5, y),
|
||||||
textures.get("rubbish"),
|
globals.get_tex("rubbish"),
|
||||||
(&mut self.tooltip, "Delete"),
|
(&mut self.tooltip, "Delete"),
|
||||||
) {
|
) {
|
||||||
b.remove_file();
|
b.remove_file();
|
||||||
|
@ -711,7 +735,7 @@ impl Editor {
|
||||||
let mut text_selected = is_selected && self.blueprint_name_selected;
|
let mut text_selected = is_selected && self.blueprint_name_selected;
|
||||||
text_input(
|
text_input(
|
||||||
d,
|
d,
|
||||||
&self.mouse,
|
globals,
|
||||||
Rectangle::new(42., y as f32, 200., 32.),
|
Rectangle::new(42., y as f32, 200., 32.),
|
||||||
&mut b.name,
|
&mut b.name,
|
||||||
&mut text_selected,
|
&mut text_selected,
|
||||||
|
@ -730,7 +754,7 @@ impl Editor {
|
||||||
);
|
);
|
||||||
|
|
||||||
d.draw_texture_ex(
|
d.draw_texture_ex(
|
||||||
textures.get("blueprint"),
|
globals.get_tex("blueprint"),
|
||||||
Vector2::new((42 + 205) as f32, y as f32),
|
Vector2::new((42 + 205) as f32, y as f32),
|
||||||
0.,
|
0.,
|
||||||
2.,
|
2.,
|
||||||
|
@ -743,38 +767,35 @@ impl Editor {
|
||||||
fn draw_end_popup(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
fn draw_end_popup(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
||||||
let bounds = screen_centered_rect(d, END_POPUP_WIDTH, END_POPUP_HEIGHT);
|
let bounds = screen_centered_rect(d, END_POPUP_WIDTH, END_POPUP_HEIGHT);
|
||||||
let x = bounds.x as i32;
|
let x = bounds.x as i32;
|
||||||
let y = bounds.y as i32;
|
let mut y = bounds.y as i32 + 10;
|
||||||
d.draw_rectangle_rec(bounds, BG_DARK);
|
d.draw_rectangle_rec(bounds, BG_DARK);
|
||||||
if self.popup == Popup::Success {
|
if let Popup::Success(score) = &self.popup {
|
||||||
d.draw_text("Level Complete!", x + 45, y + 10, 30, Color::LIME);
|
d.draw_text("Level Complete!", x + 45, y, 30, Color::LIME);
|
||||||
if let Some(score) = &self.score {
|
y += 30;
|
||||||
d.draw_text("cycles", x + 15, y + 40, 20, Color::WHITE);
|
d.draw_text(self.level.name(), x + 10, y, 20, Color::GRAY);
|
||||||
draw_usize(d, textures, score.cycles, (x + 110, y + 40), 9, 2);
|
y += 30;
|
||||||
d.draw_text("tiles", x + 15, y + 80, 20, Color::WHITE);
|
d.draw_text("cycles", x + 15, y, 20, Color::WHITE);
|
||||||
draw_usize(d, textures, score.tiles, (x + 110, y + 80), 9, 2);
|
draw_usize(d, textures, score.cycles, (x + 110, y), 9, 2);
|
||||||
d.draw_text("bounds", x + 15, y + 120, 20, Color::WHITE);
|
y += 40;
|
||||||
draw_usize(d, textures, score.bounds_area, (x + 110, y + 120), 9, 2);
|
d.draw_text("tiles", x + 15, y, 20, Color::WHITE);
|
||||||
}
|
draw_usize(d, textures, score.tiles, (x + 110, y), 9, 2);
|
||||||
let y = y + 60;
|
y += 40;
|
||||||
if simple_button((d, &self.mouse), x + 10, y + 110, 140, 45) {
|
d.draw_text("bounds", x + 15, y, 20, Color::WHITE);
|
||||||
|
draw_usize(d, textures, score.bounds_area, (x + 110, y), 9, 2);
|
||||||
|
y += 40;
|
||||||
|
if simple_button((d, &self.mouse), x + 10, y, 140, 50) {
|
||||||
self.popup = Popup::None;
|
self.popup = Popup::None;
|
||||||
self.dismissed_end = true;
|
self.dismissed_end = true;
|
||||||
}
|
}
|
||||||
d.draw_text("continue\nediting", x + 15, y + 115, 20, Color::WHITE);
|
d.draw_text("continue\nediting", x + 15, y + 5, 20, Color::WHITE);
|
||||||
|
|
||||||
if simple_button(
|
if simple_button((d, &self.mouse), x + END_POPUP_WIDTH / 2 + 5, y, 140, 50) {
|
||||||
(d, &self.mouse),
|
|
||||||
x + END_POPUP_WIDTH / 2 + 5,
|
|
||||||
y + 110,
|
|
||||||
140,
|
|
||||||
45,
|
|
||||||
) {
|
|
||||||
self.exit_state = ExitState::ExitAndSave;
|
self.exit_state = ExitState::ExitAndSave;
|
||||||
}
|
}
|
||||||
d.draw_text(
|
d.draw_text(
|
||||||
"return to\nlevel list",
|
"return to\nlevel list",
|
||||||
x + END_POPUP_WIDTH / 2 + 10,
|
x + END_POPUP_WIDTH / 2 + 10,
|
||||||
y + 115,
|
y + 5,
|
||||||
20,
|
20,
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
|
@ -789,8 +810,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_top_bar(&mut self, d: &mut RaylibDrawHandle, globals: &Globals) {
|
fn draw_top_bar(&mut self, d: &mut RaylibDrawHandle, globals: &mut Globals) {
|
||||||
let textures = &globals.textures;
|
|
||||||
// background
|
// background
|
||||||
d.draw_rectangle(
|
d.draw_rectangle(
|
||||||
0,
|
0,
|
||||||
|
@ -803,7 +823,7 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(4, 4),
|
(4, 4),
|
||||||
textures.get("exit"),
|
globals.get_tex("exit"),
|
||||||
(&mut self.tooltip, "exit"),
|
(&mut self.tooltip, "exit"),
|
||||||
) {
|
) {
|
||||||
if self.exit_menu {
|
if self.exit_menu {
|
||||||
|
@ -816,7 +836,7 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(40, 4),
|
(40, 4),
|
||||||
textures.get("cancel"),
|
globals.get_tex("cancel"),
|
||||||
(&mut self.tooltip, "cancel"),
|
(&mut self.tooltip, "cancel"),
|
||||||
) {
|
) {
|
||||||
self.exit_menu = false;
|
self.exit_menu = false;
|
||||||
|
@ -824,7 +844,7 @@ impl Editor {
|
||||||
} else if tex32_button(
|
} else if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(40, 4),
|
(40, 4),
|
||||||
textures.get("save"),
|
globals.get_tex("save"),
|
||||||
(&mut self.tooltip, "save"),
|
(&mut self.tooltip, "save"),
|
||||||
) || globals.is_pressed(ActionId::Save)
|
) || globals.is_pressed(ActionId::Save)
|
||||||
{
|
{
|
||||||
|
@ -844,7 +864,7 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(150, 4),
|
(150, 4),
|
||||||
textures.get(undo_icon),
|
globals.get_tex(undo_icon),
|
||||||
(&mut self.tooltip, "Undo"),
|
(&mut self.tooltip, "Undo"),
|
||||||
) {
|
) {
|
||||||
self.undo()
|
self.undo()
|
||||||
|
@ -858,7 +878,7 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(186, 4),
|
(186, 4),
|
||||||
textures.get(redo_icon),
|
globals.get_tex(redo_icon),
|
||||||
(&mut self.tooltip, "Redo"),
|
(&mut self.tooltip, "Redo"),
|
||||||
) {
|
) {
|
||||||
self.redo()
|
self.redo()
|
||||||
|
@ -873,7 +893,7 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(223, 4),
|
(223, 4),
|
||||||
textures.get(overlay_btn_icon),
|
globals.get_tex(overlay_btn_icon),
|
||||||
(&mut self.tooltip, "Toggle overlay"),
|
(&mut self.tooltip, "Toggle overlay"),
|
||||||
) {
|
) {
|
||||||
self.draw_overlay = !self.draw_overlay;
|
self.draw_overlay = !self.draw_overlay;
|
||||||
|
@ -883,7 +903,7 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(260, 4),
|
(260, 4),
|
||||||
textures.get("pause"),
|
globals.get_tex("pause"),
|
||||||
(&mut self.tooltip, "Pause"),
|
(&mut self.tooltip, "Pause"),
|
||||||
) {
|
) {
|
||||||
self.sim_state = SimState::Stepping;
|
self.sim_state = SimState::Stepping;
|
||||||
|
@ -891,7 +911,7 @@ impl Editor {
|
||||||
} else if tex32_button(
|
} else if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(260, 4),
|
(260, 4),
|
||||||
textures.get("play"),
|
globals.get_tex("play"),
|
||||||
(&mut self.tooltip, "Start"),
|
(&mut self.tooltip, "Start"),
|
||||||
) {
|
) {
|
||||||
if self.sim_state == SimState::Editing {
|
if self.sim_state == SimState::Editing {
|
||||||
|
@ -904,7 +924,7 @@ impl Editor {
|
||||||
&& tex32_button(
|
&& tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(296, 4),
|
(296, 4),
|
||||||
textures.get("stop"),
|
globals.get_tex("stop"),
|
||||||
(&mut self.tooltip, "Stop"),
|
(&mut self.tooltip, "Stop"),
|
||||||
) {
|
) {
|
||||||
self.sim_state = SimState::Editing;
|
self.sim_state = SimState::Editing;
|
||||||
|
@ -914,14 +934,21 @@ impl Editor {
|
||||||
if tex32_button(
|
if tex32_button(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
(332, 4),
|
(332, 4),
|
||||||
textures.get("step"),
|
globals.get_tex("step"),
|
||||||
(&mut self.tooltip, "Step"),
|
(&mut self.tooltip, "Step"),
|
||||||
) {
|
) {
|
||||||
self.step_pressed();
|
self.step_pressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tooltip.add(368, 4, 48, 32, "Speed");
|
self.tooltip.add(368, 4, 48, 32, "Speed");
|
||||||
draw_usize(d, textures, 1 << self.sim_speed, (368, 4), SPEED_DIGITS, 1);
|
draw_usize(
|
||||||
|
d,
|
||||||
|
&globals.textures,
|
||||||
|
1 << self.sim_speed,
|
||||||
|
(368, 4),
|
||||||
|
SPEED_DIGITS,
|
||||||
|
1,
|
||||||
|
);
|
||||||
slider(
|
slider(
|
||||||
(d, &self.mouse),
|
(d, &self.mouse),
|
||||||
rect(368, 24, 48, 12),
|
rect(368, 24, 48, 12),
|
||||||
|
@ -931,20 +958,36 @@ impl Editor {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.tooltip.add(420, 4, 180, 32, "Steps");
|
self.tooltip.add(420, 4, 180, 32, "Steps");
|
||||||
draw_usize(d, textures, self.machine.step_count(), (420, 4), 9, 2);
|
draw_usize(
|
||||||
|
d,
|
||||||
|
&globals.textures,
|
||||||
|
self.machine.step_count(),
|
||||||
|
(420, 4),
|
||||||
|
9,
|
||||||
|
2,
|
||||||
|
);
|
||||||
if self.stage > Some(0) {
|
if self.stage > Some(0) {
|
||||||
self.tooltip.add(420, 44, 180, 32, "Total steps");
|
self.tooltip.add(420, 44, 180, 32, "Total steps");
|
||||||
let total_steps = self.total_steps + self.machine.step_count();
|
let total_steps = self.total_steps + self.machine.step_count();
|
||||||
draw_usize(d, textures, total_steps, (420, 44), 9, 2);
|
draw_usize(d, &globals.textures, total_steps, (420, 44), 9, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_usize(d, textures, self.step_time as usize, (260, 42), 9, 1);
|
if globals.config.show_debug_timing {
|
||||||
draw_usize(d, textures, self.max_step_time as usize, (260, 60), 9, 1);
|
draw_usize(d, &globals.textures, self.step_time, (260, 42), 9, 1);
|
||||||
#[cfg(debug_assertions)]
|
draw_usize(d, &globals.textures, self.max_step_time, (260, 60), 9, 1);
|
||||||
{
|
#[cfg(debug_assertions)]
|
||||||
draw_usize(d, textures, self.machine.subtick_index, (260, 80), 9, 1);
|
{
|
||||||
let subtick_count = self.machine.debug_subticks.len();
|
draw_usize(
|
||||||
draw_usize(d, textures, subtick_count, (260, 100), 9, 1);
|
d,
|
||||||
|
&globals.textures,
|
||||||
|
self.machine.subtick_index,
|
||||||
|
(260, 80),
|
||||||
|
9,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let subtick_count = self.machine.debug_subticks.len();
|
||||||
|
draw_usize(d, &globals.textures, subtick_count, (260, 100), 9, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.draw_text("input:", 603, 8, 10, Color::WHITE);
|
d.draw_text("input:", 603, 8, 10, Color::WHITE);
|
||||||
|
@ -967,7 +1010,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
if text_input(
|
if text_input(
|
||||||
d,
|
d,
|
||||||
&self.mouse,
|
globals,
|
||||||
Rectangle::new(input_x as f32, 5., (width - input_x - 5) as f32, 30.),
|
Rectangle::new(input_x as f32, 5., (width - input_x - 5) as f32, 30.),
|
||||||
&mut input_text,
|
&mut input_text,
|
||||||
&mut self.input_text_selected,
|
&mut self.input_text_selected,
|
||||||
|
@ -1021,7 +1064,7 @@ impl Editor {
|
||||||
hide_tile_tools = true;
|
hide_tile_tools = true;
|
||||||
text_input(
|
text_input(
|
||||||
d,
|
d,
|
||||||
&self.mouse,
|
globals,
|
||||||
Rectangle::new(100., footer_top + 10., 240., 30.),
|
Rectangle::new(100., footer_top + 10., 240., 30.),
|
||||||
&mut self.new_blueprint_name,
|
&mut self.new_blueprint_name,
|
||||||
&mut self.blueprint_name_selected,
|
&mut self.blueprint_name_selected,
|
||||||
|
@ -1317,7 +1360,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn board_overlay(&mut self, d: &mut RaylibDrawHandle, textures: &Textures) {
|
fn board_overlay(&mut self, d: &mut RaylibDrawHandle, globals: &Globals) {
|
||||||
let footer_top = (d.get_screen_height() - FOOTER_HEIGHT) as f32;
|
let footer_top = (d.get_screen_height() - FOOTER_HEIGHT) as f32;
|
||||||
|
|
||||||
let tile_size = TILE_TEXTURE_SIZE * self.zoom;
|
let tile_size = TILE_TEXTURE_SIZE * self.zoom;
|
||||||
|
@ -1336,7 +1379,9 @@ impl Editor {
|
||||||
offset.x -= offset.x.rem(tile_size);
|
offset.x -= offset.x.rem(tile_size);
|
||||||
offset.y -= offset.y.rem(tile_size);
|
offset.y -= offset.y.rem(tile_size);
|
||||||
offset += view_offset;
|
offset += view_offset;
|
||||||
board.grid.draw(d, textures, offset, self.zoom);
|
board
|
||||||
|
.grid
|
||||||
|
.draw(d, &globals.textures, offset, self.zoom, false);
|
||||||
board.draw_comments(d, offset, self.zoom);
|
board.draw_comments(d, offset, self.zoom);
|
||||||
if self.mouse.left_click() {
|
if self.mouse.left_click() {
|
||||||
let tile_pos = (self.mouse.pos() - self.view_offset) / tile_size;
|
let tile_pos = (self.mouse.pos() - self.view_offset) / tile_size;
|
||||||
|
@ -1350,7 +1395,7 @@ impl Editor {
|
||||||
if let Tool::Digits(Some(pos)) = &mut self.active_tool {
|
if let Tool::Digits(Some(pos)) = &mut self.active_tool {
|
||||||
let tile_screen_pos = pos.to_vec() * tile_size + self.view_offset;
|
let tile_screen_pos = pos.to_vec() * tile_size + self.view_offset;
|
||||||
d.draw_texture_ex(
|
d.draw_texture_ex(
|
||||||
textures.get("selection"),
|
globals.get_tex("selection"),
|
||||||
tile_screen_pos,
|
tile_screen_pos,
|
||||||
0.,
|
0.,
|
||||||
self.zoom,
|
self.zoom,
|
||||||
|
@ -1403,7 +1448,7 @@ impl Editor {
|
||||||
};
|
};
|
||||||
|
|
||||||
d.draw_texture_ex(
|
d.draw_texture_ex(
|
||||||
textures.get(tex),
|
globals.get_tex(tex),
|
||||||
tile_screen_pos,
|
tile_screen_pos,
|
||||||
0.,
|
0.,
|
||||||
self.zoom,
|
self.zoom,
|
||||||
|
@ -1473,7 +1518,9 @@ impl Editor {
|
||||||
offset.x -= offset.x.rem(tile_size);
|
offset.x -= offset.x.rem(tile_size);
|
||||||
offset.y -= offset.y.rem(tile_size);
|
offset.y -= offset.y.rem(tile_size);
|
||||||
offset += view_offset;
|
offset += view_offset;
|
||||||
bp.board.grid.draw(d, textures, offset, self.zoom);
|
bp.board
|
||||||
|
.grid
|
||||||
|
.draw(d, &globals.textures, offset, self.zoom, false);
|
||||||
bp.board.draw_comments(d, offset, self.zoom);
|
bp.board.draw_comments(d, offset, self.zoom);
|
||||||
}
|
}
|
||||||
if self.mouse.pos().x < SIDEBAR_WIDTH as f32 {
|
if self.mouse.pos().x < SIDEBAR_WIDTH as f32 {
|
||||||
|
|
109
src/input.rs
109
src/input.rs
|
@ -94,9 +94,11 @@ impl Default for Input {
|
||||||
bind_key(ActionId::TileGroupCompare, vec![], H);
|
bind_key(ActionId::TileGroupCompare, vec![], H);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
bindings,
|
action_bindings: bindings,
|
||||||
states: Default::default(),
|
action_states: [BindingState::Off; ActionId::SIZE],
|
||||||
|
key_states: [BindingState::Off; Button::SIZE],
|
||||||
editing_binding: None,
|
editing_binding: None,
|
||||||
|
in_text_edit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,9 +117,11 @@ type InputMap = BTreeMap<String, Vec<Binding>>;
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(from = "InputMap", into = "InputMap")]
|
#[serde(from = "InputMap", into = "InputMap")]
|
||||||
pub struct Input {
|
pub struct Input {
|
||||||
bindings: [Vec<Binding>; ActionId::SIZE],
|
action_bindings: [Vec<Binding>; ActionId::SIZE],
|
||||||
states: [BindingState; ActionId::SIZE],
|
action_states: [BindingState; ActionId::SIZE],
|
||||||
|
key_states: [BindingState; Button::SIZE],
|
||||||
editing_binding: Option<(ActionId, usize, BindingEdit)>,
|
editing_binding: Option<(ActionId, usize, BindingEdit)>,
|
||||||
|
pub in_text_edit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -128,8 +132,7 @@ enum BindingEdit {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn draw_edit(&mut self, d: &mut RaylibDrawHandle, globals: &mut Globals, y: i32) {
|
pub fn draw_edit(&mut self, d: &mut RaylibDrawHandle, globals: &mut Globals, mut y: i32) {
|
||||||
let mut y = y + 96;
|
|
||||||
if self.editing_binding.is_some() {
|
if self.editing_binding.is_some() {
|
||||||
globals.mouse.clear();
|
globals.mouse.clear();
|
||||||
}
|
}
|
||||||
|
@ -140,10 +143,14 @@ impl Input {
|
||||||
for action_index in 0..ActionId::SIZE {
|
for action_index in 0..ActionId::SIZE {
|
||||||
let action = ActionId::from_usize(action_index).unwrap();
|
let action = ActionId::from_usize(action_index).unwrap();
|
||||||
|
|
||||||
|
// if self.action_states[action_index] == BindingState::Pressed {
|
||||||
|
// d.draw_rectangle(200, y, 20, 20, Color::LIMEGREEN);
|
||||||
|
// }
|
||||||
d.draw_text(&format!("{action:?}"), 16, y, 20, Color::ORANGE);
|
d.draw_text(&format!("{action:?}"), 16, y, 20, Color::ORANGE);
|
||||||
for (binding_index, binding) in self.bindings[action_index].iter().enumerate() {
|
for (binding_index, binding) in self.action_bindings[action_index].iter().enumerate() {
|
||||||
if text_button(d, &globals.mouse, buttons_x, y, 80, "remove") {
|
if text_button(d, &globals.mouse, buttons_x, y, 80, "remove") {
|
||||||
self.bindings[action_index].remove(binding_index);
|
self.action_bindings[action_index].remove(binding_index);
|
||||||
|
self.update_modifier_blocks();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if text_button(d, &globals.mouse, buttons_x + 85, y, 45, "edit") {
|
if text_button(d, &globals.mouse, buttons_x + 85, y, 45, "edit") {
|
||||||
|
@ -159,7 +166,7 @@ impl Input {
|
||||||
d.draw_text(&modifiers, x, y + 5, 20, Color::LIGHTBLUE);
|
d.draw_text(&modifiers, x, y + 5, 20, Color::LIGHTBLUE);
|
||||||
x += 10 + d.measure_text(&modifiers, 20);
|
x += 10 + d.measure_text(&modifiers, 20);
|
||||||
//
|
//
|
||||||
let conflicts = conflicts(&self.bindings, binding, action);
|
let conflicts = conflicts(&self.action_bindings, binding, action);
|
||||||
if !conflicts.is_empty() {
|
if !conflicts.is_empty() {
|
||||||
let conflict_text = format!("also used by: {conflicts:?}");
|
let conflict_text = format!("also used by: {conflicts:?}");
|
||||||
d.draw_text(&conflict_text, x, y + 5, 20, Color::ORANGERED);
|
d.draw_text(&conflict_text, x, y + 5, 20, Color::ORANGERED);
|
||||||
|
@ -173,8 +180,11 @@ impl Input {
|
||||||
y += 32;
|
y += 32;
|
||||||
}
|
}
|
||||||
if text_button(d, &globals.mouse, buttons_x, y, 130, "add binding") {
|
if text_button(d, &globals.mouse, buttons_x, y, 130, "add binding") {
|
||||||
self.editing_binding =
|
self.editing_binding = Some((
|
||||||
Some((action, self.bindings[action_index].len(), BindingEdit::Init));
|
action,
|
||||||
|
self.action_bindings[action_index].len(),
|
||||||
|
BindingEdit::Init,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
y += 45;
|
y += 45;
|
||||||
}
|
}
|
||||||
|
@ -250,7 +260,7 @@ impl Input {
|
||||||
let text = format!("{:?} + {:?}", b.modifiers, b.trigger);
|
let text = format!("{:?} + {:?}", b.modifiers, b.trigger);
|
||||||
d.draw_text(&text, x + 5, y + 5, 20, colour);
|
d.draw_text(&text, x + 5, y + 5, 20, colour);
|
||||||
|
|
||||||
let conflicts = conflicts(&self.bindings, b, *action);
|
let conflicts = conflicts(&self.action_bindings, b, *action);
|
||||||
if !conflicts.is_empty() {
|
if !conflicts.is_empty() {
|
||||||
d.draw_text(
|
d.draw_text(
|
||||||
&format!("conflicts: {conflicts:?}"),
|
&format!("conflicts: {conflicts:?}"),
|
||||||
|
@ -263,7 +273,7 @@ impl Input {
|
||||||
}
|
}
|
||||||
if text_button(d, &globals.mouse, ok_btn_x, ok_btn_y, ok_btn_width, "ok") {
|
if text_button(d, &globals.mouse, ok_btn_x, ok_btn_y, ok_btn_width, "ok") {
|
||||||
if let BindingEdit::Releasing(binding) = edit_state {
|
if let BindingEdit::Releasing(binding) = edit_state {
|
||||||
let binding_list = &mut self.bindings[*action as usize];
|
let binding_list = &mut self.action_bindings[*action as usize];
|
||||||
if *binding_index < binding_list.len() {
|
if *binding_index < binding_list.len() {
|
||||||
binding_list[*binding_index] = binding.clone();
|
binding_list[*binding_index] = binding.clone();
|
||||||
} else {
|
} else {
|
||||||
|
@ -280,18 +290,10 @@ impl Input {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, rl: &RaylibHandle) {
|
pub fn update(&mut self, rl: &RaylibHandle) {
|
||||||
for i in 0..ActionId::SIZE {
|
for i in 0..Button::SIZE {
|
||||||
let bindings = &self.bindings[i];
|
let button = Button::from_usize(i).unwrap();
|
||||||
let mut is_active = false;
|
let state = &mut self.key_states[i];
|
||||||
for binding in bindings {
|
*state = if button.is_down(rl) {
|
||||||
if binding.modifiers.iter().all(|&m| m.is_down(rl))
|
|
||||||
&& !binding.blocking_modifiers.iter().any(|&m| m.is_down(rl))
|
|
||||||
{
|
|
||||||
is_active |= binding.trigger.is_down(rl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let state = &mut self.states[i];
|
|
||||||
*state = if is_active {
|
|
||||||
match state {
|
match state {
|
||||||
BindingState::Off | BindingState::Released => BindingState::Pressed,
|
BindingState::Off | BindingState::Released => BindingState::Pressed,
|
||||||
BindingState::Pressed | BindingState::Held => BindingState::Held,
|
BindingState::Pressed | BindingState::Held => BindingState::Held,
|
||||||
|
@ -303,31 +305,52 @@ impl Input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i in 0..ActionId::SIZE {
|
||||||
|
let bindings = &self.action_bindings[i];
|
||||||
|
let mut is_active = BindingState::Off;
|
||||||
|
if !self.in_text_edit {
|
||||||
|
for binding in bindings {
|
||||||
|
if binding
|
||||||
|
.modifiers
|
||||||
|
.iter()
|
||||||
|
.all(|&m| self.key_states[m as usize] == BindingState::Held)
|
||||||
|
&& binding
|
||||||
|
.blocking_modifiers
|
||||||
|
.iter()
|
||||||
|
.all(|&m| self.key_states[m as usize] == BindingState::Off)
|
||||||
|
{
|
||||||
|
is_active = is_active.or(self.key_states[binding.trigger as usize]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.action_states[i] = is_active;
|
||||||
|
}
|
||||||
|
self.in_text_edit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_pressed(&self, action: ActionId) -> bool {
|
pub fn is_pressed(&self, action: ActionId) -> bool {
|
||||||
self.states[action as usize] == BindingState::Pressed
|
self.action_states[action as usize] == BindingState::Pressed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_held(&self, action: ActionId) -> bool {
|
pub fn is_held(&self, action: ActionId) -> bool {
|
||||||
self.states[action as usize] == BindingState::Pressed
|
self.action_states[action as usize] == BindingState::Pressed
|
||||||
|| self.states[action as usize] == BindingState::Held
|
|| self.action_states[action as usize] == BindingState::Held
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_released(&self, action: ActionId) -> bool {
|
pub fn is_released(&self, action: ActionId) -> bool {
|
||||||
self.states[action as usize] == BindingState::Released
|
self.action_states[action as usize] == BindingState::Released
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Must be called after any binding has changed.
|
/// Must be called after any binding has changed.
|
||||||
/// Ensures a binding "S" is not triggered by "Ctrl+S", if "Ctrl+S" is bound to something else.
|
/// Ensures a binding "S" is not triggered by "Ctrl+S", if "Ctrl+S" is bound to something else.
|
||||||
pub fn update_modifier_blocks(&mut self) {
|
pub fn update_modifier_blocks(&mut self) {
|
||||||
for i in 0..ActionId::SIZE {
|
for i in 0..ActionId::SIZE {
|
||||||
let bindings = &self.bindings[i];
|
let bindings = &self.action_bindings[i];
|
||||||
for binding_index in 0..bindings.len() {
|
for binding_index in 0..bindings.len() {
|
||||||
let binding = &self.bindings[i][binding_index];
|
let binding = &self.action_bindings[i][binding_index];
|
||||||
let mut blocking_mods = Vec::new();
|
let mut blocking_mods = Vec::new();
|
||||||
for i in 0..ActionId::SIZE {
|
for i in 0..ActionId::SIZE {
|
||||||
let other_bindings = &self.bindings[i];
|
let other_bindings = &self.action_bindings[i];
|
||||||
for other_binding in other_bindings {
|
for other_binding in other_bindings {
|
||||||
if other_binding.trigger == binding.trigger {
|
if other_binding.trigger == binding.trigger {
|
||||||
for modifier in &other_binding.modifiers {
|
for modifier in &other_binding.modifiers {
|
||||||
|
@ -340,7 +363,7 @@ impl Input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.bindings[i][binding_index].blocking_modifiers = blocking_mods;
|
self.action_bindings[i][binding_index].blocking_modifiers = blocking_mods;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,13 +412,29 @@ pub struct Binding {
|
||||||
trigger: Button,
|
trigger: Button,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BindingState {
|
||||||
|
fn or(self, rhs: Self) -> Self {
|
||||||
|
use BindingState::*;
|
||||||
|
match (self, rhs) {
|
||||||
|
(Off, other) => other,
|
||||||
|
(other, Off) => other,
|
||||||
|
(Held, _) => Held,
|
||||||
|
(_, Held) => Held,
|
||||||
|
(Pressed, Pressed) => Pressed,
|
||||||
|
(Released, Released) => Released,
|
||||||
|
(Pressed, Released) => Held,
|
||||||
|
(Released, Pressed) => Held,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<InputMap> for Input {
|
impl From<InputMap> for Input {
|
||||||
fn from(map: InputMap) -> Self {
|
fn from(map: InputMap) -> Self {
|
||||||
let mut new = Self::default();
|
let mut new = Self::default();
|
||||||
for (action, loaded_bindings) in map {
|
for (action, loaded_bindings) in map {
|
||||||
let temp_json = format!("\"{action}\"");
|
let temp_json = format!("\"{action}\"");
|
||||||
if let Ok(action) = serde_json::from_str::<ActionId>(&temp_json) {
|
if let Ok(action) = serde_json::from_str::<ActionId>(&temp_json) {
|
||||||
new.bindings[action as usize] = loaded_bindings;
|
new.action_bindings[action as usize] = loaded_bindings;
|
||||||
} else {
|
} else {
|
||||||
println!("'{action}' is not a valid action id, bindings discarded");
|
println!("'{action}' is not a valid action id, bindings discarded");
|
||||||
}
|
}
|
||||||
|
@ -407,7 +446,7 @@ impl From<InputMap> for Input {
|
||||||
impl From<Input> for InputMap {
|
impl From<Input> for InputMap {
|
||||||
fn from(value: Input) -> Self {
|
fn from(value: Input) -> Self {
|
||||||
value
|
value
|
||||||
.bindings
|
.action_bindings
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, b)| {
|
.map(|(i, b)| {
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -1,6 +1,6 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::{read_dir, read_to_string, File},
|
fs::{create_dir_all, read_dir, read_to_string, File},
|
||||||
io::Write,
|
io::Write,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ impl Game {
|
||||||
let bounds = Rectangle::new(column_x as f32, y as f32, 220., 30.);
|
let bounds = Rectangle::new(column_x as f32, y as f32, 220., 30.);
|
||||||
if text_input(
|
if text_input(
|
||||||
d,
|
d,
|
||||||
&self.globals.mouse,
|
&mut self.globals,
|
||||||
bounds,
|
bounds,
|
||||||
&mut solution.name,
|
&mut solution.name,
|
||||||
&mut self.editing_solution_name,
|
&mut self.editing_solution_name,
|
||||||
|
@ -348,10 +348,15 @@ impl Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_config(&self) {
|
fn save_config(&self) {
|
||||||
|
_ = create_dir_all(userdata_dir());
|
||||||
let path = userdata_dir().join(CONFIG_FILE_NAME);
|
let path = userdata_dir().join(CONFIG_FILE_NAME);
|
||||||
let json = serde_json::to_string_pretty(&self.globals.config).unwrap();
|
let json = serde_json::to_string_pretty(&self.globals.config).unwrap();
|
||||||
let mut f = File::create(path).unwrap();
|
match File::create(path) {
|
||||||
f.write_all(json.as_bytes()).unwrap();
|
Ok(mut f) => {
|
||||||
|
_ = f.write_all(json.as_bytes());
|
||||||
|
}
|
||||||
|
Err(e) => println!("error saving config: {e}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub struct Machine {
|
||||||
// used across steps
|
// used across steps
|
||||||
powered: Vec<Pos>,
|
powered: Vec<Pos>,
|
||||||
// used within steps
|
// used within steps
|
||||||
new_marbles: Vec<(Pos, MarbleValue, Direction)>,
|
new_marbles: Vec<(Pos, MarbleValue, Direction, bool)>,
|
||||||
influenced_direction: Vec<(bool, DirInfluence)>,
|
influenced_direction: Vec<(bool, DirInfluence)>,
|
||||||
claim_positions: Vec<Pos>,
|
claim_positions: Vec<Pos>,
|
||||||
removed_marbles: Vec<usize>,
|
removed_marbles: Vec<usize>,
|
||||||
|
@ -216,20 +216,19 @@ impl Machine {
|
||||||
MathOp::Div => val_a.checked_div(val_b).unwrap_or_default(),
|
MathOp::Div => val_a.checked_div(val_b).unwrap_or_default(),
|
||||||
MathOp::Rem => val_a.checked_rem(val_b).unwrap_or_default(),
|
MathOp::Rem => val_a.checked_rem(val_b).unwrap_or_default(),
|
||||||
};
|
};
|
||||||
self.new_marbles.push((front_pos, value, dir));
|
self.new_marbles.push((front_pos, value, dir, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PTile::IO => {
|
PTile::IO => {
|
||||||
if front_tile == &Tile::BLANK && self.input_index < self.input.len()
|
if front_tile == &Tile::BLANK && self.input_index < self.input.len()
|
||||||
{
|
{
|
||||||
let value = self.input[self.input_index] as MarbleValue;
|
let value = self.input[self.input_index] as MarbleValue;
|
||||||
self.input_index += 1;
|
self.new_marbles.push((front_pos, value, dir, true));
|
||||||
self.new_marbles.push((front_pos, value, dir));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PTile::Silo => {
|
PTile::Silo => {
|
||||||
if front_tile == &Tile::BLANK {
|
if front_tile == &Tile::BLANK {
|
||||||
self.new_marbles.push((front_pos, 0, dir));
|
self.new_marbles.push((front_pos, 0, dir, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PTile::Flipper => {
|
PTile::Flipper => {
|
||||||
|
@ -321,9 +320,9 @@ impl Machine {
|
||||||
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
|
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
|
||||||
fn step_prepare_creating_marbles(&mut self) {
|
fn step_prepare_creating_marbles(&mut self) {
|
||||||
// #### new marbles ####
|
// #### new marbles ####
|
||||||
// self.claim_positions.clear(); // already drained
|
self.claim_positions.clear(); // already drained
|
||||||
// prepare creating the new marbles
|
// prepare creating the new marbles
|
||||||
for &(pos, _val, _dir) in &self.new_marbles {
|
for &(pos, _val, _dir, _is_input) in &self.new_marbles {
|
||||||
let Some(Tile::Open(OpenTile::Blank, claim)) = self.grid.get_mut(pos) else {
|
let Some(Tile::Open(OpenTile::Blank, claim)) = self.grid.get_mut(pos) else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
@ -336,15 +335,20 @@ impl Machine {
|
||||||
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
|
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
|
||||||
fn step_create_marbles(&mut self) {
|
fn step_create_marbles(&mut self) {
|
||||||
// create new marbles
|
// create new marbles
|
||||||
|
let mut advance_input = false;
|
||||||
// new marbles are past old_marbles index, so will not move this step
|
// new marbles are past old_marbles index, so will not move this step
|
||||||
for (pos, value, dir) in self.new_marbles.drain(..) {
|
for (pos, value, dir, is_input) in self.new_marbles.drain(..) {
|
||||||
let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.grid.get_mut(pos)
|
let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.grid.get_mut(pos)
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
advance_input |= is_input;
|
||||||
self.grid.set(pos, Tile::Marble { value, dir });
|
self.grid.set(pos, Tile::Marble { value, dir });
|
||||||
self.marbles.push(pos);
|
self.marbles.push(pos);
|
||||||
}
|
}
|
||||||
|
if advance_input {
|
||||||
|
self.input_index += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
|
#[cfg_attr(feature = "inline_less", inline(never), no_mangle)]
|
||||||
|
|
|
@ -285,16 +285,23 @@ impl Grid {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&self, d: &mut RaylibDrawHandle, textures: &Textures, offset: Vector2, scale: f32) {
|
pub fn draw(
|
||||||
|
&self,
|
||||||
|
d: &mut RaylibDrawHandle,
|
||||||
|
textures: &Textures,
|
||||||
|
offset: Vector2,
|
||||||
|
scale: f32,
|
||||||
|
power_directions: bool,
|
||||||
|
) {
|
||||||
let tile_size = (TILE_TEXTURE_SIZE * scale) as i32;
|
let tile_size = (TILE_TEXTURE_SIZE * scale) as i32;
|
||||||
|
|
||||||
let start_x = (-offset.x as i32) / tile_size - 1;
|
let start_x = (-offset.x as i32) / tile_size - 1;
|
||||||
let tile_width = d.get_screen_width() / tile_size + 2;
|
let tiles_width = d.get_screen_width() / tile_size + 3;
|
||||||
let start_y = (-offset.y as i32) / tile_size - 1;
|
let start_y = (-offset.y as i32) / tile_size - 1;
|
||||||
let tile_height = d.get_screen_height() / tile_size + 2;
|
let tiles_height = d.get_screen_height() / tile_size + 3;
|
||||||
|
|
||||||
for x in start_x..(start_x + tile_width) {
|
for x in start_x..(start_x + tiles_width) {
|
||||||
for y in start_y..(start_y + tile_height) {
|
for y in start_y..(start_y + tiles_height) {
|
||||||
let px = x * tile_size + offset.x as i32;
|
let px = x * tile_size + offset.x as i32;
|
||||||
let py = y * tile_size + offset.y as i32;
|
let py = y * tile_size + offset.y as i32;
|
||||||
if let Some(tile) = self.get((x, y).into()) {
|
if let Some(tile) = self.get((x, y).into()) {
|
||||||
|
@ -304,13 +311,13 @@ impl Grid {
|
||||||
}
|
}
|
||||||
let texture = textures.get(texname);
|
let texture = textures.get(texname);
|
||||||
draw_scaled_texture(d, texture, px, py, scale);
|
draw_scaled_texture(d, texture, px, py, scale);
|
||||||
#[cfg(debug_assertions)]
|
if power_directions {
|
||||||
// todo some in-game option to show power direction
|
if let Tile::Powerable(_, state) = &tile {
|
||||||
if let Tile::Powerable(_, state) = &tile {
|
for dir in Direction::ALL {
|
||||||
for dir in Direction::ALL {
|
if state.get_dir(dir) {
|
||||||
if state.get_dir(dir) {
|
let texture = textures.get(dir.debug_arrow_texture_name());
|
||||||
let texture = textures.get(dir.debug_arrow_texture_name());
|
draw_scaled_texture(d, texture, px, py, scale);
|
||||||
draw_scaled_texture(d, texture, px, py, scale);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub struct Solution {
|
||||||
pub score: Option<Score>,
|
pub score: Option<Score>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Score {
|
pub struct Score {
|
||||||
pub cycles: usize,
|
pub cycles: usize,
|
||||||
pub tiles: usize,
|
pub tiles: usize,
|
||||||
|
|
49
src/ui.rs
49
src/ui.rs
|
@ -1,6 +1,10 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{theme::*, util::draw_scaled_texture, util::MouseInput, util::Scroll, util::Textures};
|
use crate::{
|
||||||
|
theme::*,
|
||||||
|
util::{draw_scaled_texture, rect, MouseInput, Scroll, Textures},
|
||||||
|
Globals,
|
||||||
|
};
|
||||||
use raylib::prelude::*;
|
use raylib::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -156,6 +160,35 @@ impl Tooltip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_button(
|
||||||
|
(d, mouse): (&mut RaylibDrawHandle, &MouseInput),
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
val: &mut bool,
|
||||||
|
) {
|
||||||
|
let margin = 5;
|
||||||
|
let mouse_pos = mouse.pos();
|
||||||
|
let bounds = rect(x, y, width, height);
|
||||||
|
|
||||||
|
let hover = bounds.check_collision_point_rec(mouse_pos);
|
||||||
|
d.draw_rectangle(x, y, width, height, widget_bg(hover));
|
||||||
|
let pressed = hover && mouse.left_click();
|
||||||
|
if pressed {
|
||||||
|
*val = !*val;
|
||||||
|
}
|
||||||
|
if *val {
|
||||||
|
d.draw_rectangle(
|
||||||
|
x + margin,
|
||||||
|
y + margin,
|
||||||
|
width - margin * 2,
|
||||||
|
height - margin * 2,
|
||||||
|
Color::WHITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn simple_button(
|
pub fn simple_button(
|
||||||
(d, mouse): (&mut RaylibDrawHandle, &MouseInput),
|
(d, mouse): (&mut RaylibDrawHandle, &MouseInput),
|
||||||
x: i32,
|
x: i32,
|
||||||
|
@ -164,12 +197,7 @@ pub fn simple_button(
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mouse_pos = mouse.pos();
|
let mouse_pos = mouse.pos();
|
||||||
let bounds = Rectangle {
|
let bounds = rect(x, y, width, height);
|
||||||
x: x as f32,
|
|
||||||
y: y as f32,
|
|
||||||
width: width as f32,
|
|
||||||
height: height as f32,
|
|
||||||
};
|
|
||||||
let hover = bounds.check_collision_point_rec(mouse_pos);
|
let hover = bounds.check_collision_point_rec(mouse_pos);
|
||||||
let pressed = hover && mouse.left_click();
|
let pressed = hover && mouse.left_click();
|
||||||
d.draw_rectangle(x, y, width, height, widget_bg(hover));
|
d.draw_rectangle(x, y, width, height, widget_bg(hover));
|
||||||
|
@ -225,7 +253,7 @@ where
|
||||||
|
|
||||||
pub fn text_input(
|
pub fn text_input(
|
||||||
d: &mut RaylibDrawHandle,
|
d: &mut RaylibDrawHandle,
|
||||||
mouse: &MouseInput,
|
globals: &mut Globals,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
text: &mut String,
|
text: &mut String,
|
||||||
is_selected: &mut bool,
|
is_selected: &mut bool,
|
||||||
|
@ -262,12 +290,13 @@ pub fn text_input(
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
if editable && mouse.left_click() && (mouse.is_over(bounds) || *is_selected) {
|
if editable && globals.mouse.left_click() && (globals.mouse.is_over(bounds) || *is_selected) {
|
||||||
*is_selected = !*is_selected;
|
*is_selected = !*is_selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
if *is_selected {
|
if *is_selected {
|
||||||
if d.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
|
globals.config.input.in_text_edit = true;
|
||||||
|
if d.is_key_pressed(KeyboardKey::KEY_ESCAPE) || d.is_key_pressed(KeyboardKey::KEY_ENTER) {
|
||||||
*is_selected = false;
|
*is_selected = false;
|
||||||
}
|
}
|
||||||
if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) && !text.is_empty() {
|
if d.is_key_pressed(KeyboardKey::KEY_BACKSPACE) && !text.is_empty() {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use marble_machinations::marble_engine::{grid::Grid, Machine};
|
use marble_machinations::marble_engine::{grid::Grid, Machine};
|
||||||
|
|
||||||
fn no_input_test(steps: usize, output: &[u8], grid: &str) {
|
fn do_test(steps: usize, input: &[u8], output: &[u8], grid: &str) {
|
||||||
let mut engine = Machine::new_empty();
|
let mut engine = Machine::new_empty();
|
||||||
engine.set_grid(Grid::from_ascii(grid));
|
engine.set_grid(Grid::from_ascii(grid));
|
||||||
|
engine.set_input(input.to_owned());
|
||||||
for _ in 0..(steps - 1) {
|
for _ in 0..(steps - 1) {
|
||||||
engine.step();
|
engine.step();
|
||||||
}
|
}
|
||||||
|
@ -11,11 +12,25 @@ fn no_input_test(steps: usize, output: &[u8], grid: &str) {
|
||||||
assert_eq!(engine.output(), output, "expected output");
|
assert_eq!(engine.output(), output, "expected output");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn no_input_test(steps: usize, output: &[u8], grid: &str) {
|
||||||
|
do_test(steps, &[], output, grid)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn creating_marbles_cause_indirect_claim() {
|
fn creating_marbles_cause_indirect_claim() {
|
||||||
no_input_test(3, &[1], " o \n|-*-|\n| 1 |\n-B B-\n I\n");
|
no_input_test(3, &[1], " o \n|-*-|\n| 1 |\n-B B-\n I\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bug_input_consumed_when_marble_creation_blocked() {
|
||||||
|
do_test(
|
||||||
|
4,
|
||||||
|
&[1, 2, 3],
|
||||||
|
&[1, 1],
|
||||||
|
"# +-+\nIIo| I\n *+B\nII\n++*\n# #\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bug_overlapping_marble_creation_blocks_tile_forever() {
|
fn bug_overlapping_marble_creation_blocks_tile_forever() {
|
||||||
no_input_test(
|
no_input_test(
|
||||||
|
|
Loading…
Add table
Reference in a new issue