implement key binding system
This commit is contained in:
parent
57512a4c6b
commit
70fd50d3bc
7 changed files with 410 additions and 57 deletions
|
@ -3,6 +3,7 @@ Game store page: https://crispypin.itch.io/marble-machinations
|
|||
|
||||
## [Unreleased]
|
||||
### added
|
||||
- configurable hotkeys (via file only, only some actions)
|
||||
- OS clipboard copy/paste, with fallback to old behavior when copying
|
||||
- in-grid text comments (not yet editable in-game)
|
||||
- changelog file
|
||||
|
|
|
@ -6,8 +6,11 @@ logic mostly like https://git.crispypin.cc/CrispyPin/marble
|
|||
### meta
|
||||
- itch page text
|
||||
- engine tests
|
||||
- blag post about marble movement logic
|
||||
### game
|
||||
- comments
|
||||
- editing
|
||||
- add to all intro levels
|
||||
- highlight regions with background colours
|
||||
- accessibility
|
||||
- ui scaling
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use raylib::ffi::KeyboardKey;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
hotkeys: Hotkeys,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Hotkeys {
|
||||
map: HashMap<String, Hotkey>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Hotkey {
|
||||
modifiers: Vec<u32>,
|
||||
trigger: Trigger,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub enum Trigger {
|
||||
Mouse(u32),
|
||||
Key(u32),
|
||||
}
|
|
@ -5,18 +5,19 @@ use std::{
|
|||
time::Instant,
|
||||
};
|
||||
|
||||
use arboard::Clipboard;
|
||||
use raylib::prelude::*;
|
||||
|
||||
use crate::{
|
||||
blueprint::Blueprint,
|
||||
board::Board,
|
||||
input::ActionId,
|
||||
level::Level,
|
||||
marble_engine::{grid::*, pos::*, tile::*, Machine},
|
||||
solution::*,
|
||||
theme::*,
|
||||
ui::*,
|
||||
util::*,
|
||||
Globals,
|
||||
};
|
||||
|
||||
const HEADER_HEIGHT: i32 = 40;
|
||||
|
@ -450,7 +451,7 @@ impl Editor {
|
|||
self.push_action(Action::SetTile(resize, pos, old_tile, tile));
|
||||
}
|
||||
|
||||
pub fn update(&mut self, rl: &RaylibHandle, clipboard: Option<&mut Clipboard>) {
|
||||
pub fn update(&mut self, rl: &RaylibHandle, globals: &mut Globals) {
|
||||
self.tooltip.init_frame(rl);
|
||||
self.mouse = MouseInput::get(rl);
|
||||
if self.popup != Popup::None {
|
||||
|
@ -495,7 +496,7 @@ impl Editor {
|
|||
self.step_time = avg_step_time;
|
||||
self.max_step_time = avg_step_time.max(self.max_step_time);
|
||||
}
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_SPACE) {
|
||||
if globals.input.is_pressed(ActionId::StepSim) {
|
||||
self.step_pressed()
|
||||
}
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
|
||||
|
@ -550,20 +551,20 @@ impl Editor {
|
|||
}
|
||||
|
||||
if self.sim_state == SimState::Editing {
|
||||
if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL) {
|
||||
if let Some(clipboard) = clipboard {
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_V) {
|
||||
if let Ok(text) = clipboard.get_text() {
|
||||
let b = Board::from_user_str(&text);
|
||||
self.pasting_board = Some(b);
|
||||
}
|
||||
if let Some(clipboard) = &mut globals.clipboard {
|
||||
if rl.is_key_down(KeyboardKey::KEY_LEFT_CONTROL)
|
||||
&& rl.is_key_pressed(KeyboardKey::KEY_V)
|
||||
{
|
||||
if let Ok(text) = clipboard.get_text() {
|
||||
let b = Board::from_user_str(&text);
|
||||
self.pasting_board = Some(b);
|
||||
}
|
||||
}
|
||||
if rl.is_key_pressed(KeyboardKey::KEY_Z) {
|
||||
self.undo();
|
||||
} else if rl.is_key_pressed(KeyboardKey::KEY_Y) {
|
||||
self.redo();
|
||||
}
|
||||
}
|
||||
if globals.input.is_pressed(ActionId::Undo) {
|
||||
self.undo();
|
||||
} else if globals.input.is_pressed(ActionId::Redo) {
|
||||
self.redo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -597,12 +598,7 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
d: &mut RaylibDrawHandle,
|
||||
textures: &Textures,
|
||||
clipboard: Option<&mut Clipboard>,
|
||||
) {
|
||||
pub fn draw(&mut self, d: &mut RaylibDrawHandle, textures: &Textures, globals: &mut Globals) {
|
||||
d.clear_background(BG_WORLD);
|
||||
|
||||
if self.draw_overlay && self.zoom >= 0.5 {
|
||||
|
@ -621,7 +617,7 @@ impl Editor {
|
|||
|
||||
self.draw_board(d, textures);
|
||||
self.board_overlay(d, textures);
|
||||
self.draw_bottom_bar(d, textures, clipboard);
|
||||
self.draw_bottom_bar(d, textures, globals);
|
||||
self.draw_top_bar(d, textures);
|
||||
|
||||
if self.active_tool == Tool::Blueprint {
|
||||
|
@ -999,7 +995,7 @@ impl Editor {
|
|||
&mut self,
|
||||
d: &mut RaylibDrawHandle,
|
||||
textures: &Textures,
|
||||
clipboard: Option<&mut Clipboard>,
|
||||
globals: &mut Globals,
|
||||
) {
|
||||
let height = d.get_screen_height();
|
||||
let footer_top = (height - FOOTER_HEIGHT) as f32;
|
||||
|
@ -1050,7 +1046,7 @@ impl Editor {
|
|||
&& d.is_key_down(KeyboardKey::KEY_LEFT_CONTROL))
|
||||
{
|
||||
let board = self.get_selected_as_board(selection);
|
||||
if let Some(clipboard) = clipboard {
|
||||
if let Some(clipboard) = &mut globals.clipboard {
|
||||
clipboard
|
||||
.set_text(serde_json::to_string(&board).unwrap())
|
||||
.unwrap();
|
||||
|
|
323
src/input.rs
Normal file
323
src/input.rs
Normal file
|
@ -0,0 +1,323 @@
|
|||
use std::{collections::HashMap, mem::transmute};
|
||||
|
||||
use raylib::{
|
||||
ffi::{KeyboardKey, MouseButton},
|
||||
RaylibHandle,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[repr(u8)]
|
||||
pub enum ActionId {
|
||||
StepSim,
|
||||
Undo,
|
||||
Redo,
|
||||
// just like in C, because this way doesn't need more depenedencies
|
||||
_ActionIdSize,
|
||||
}
|
||||
|
||||
impl Default for Input {
|
||||
fn default() -> Self {
|
||||
use KeyboardKey::*;
|
||||
let mut bindings = [(); ActionId::SIZE].map(|_| Vec::new());
|
||||
let mut bind_key = |action, mods, key| {
|
||||
bindings[action as usize] = vec![Binding {
|
||||
modifiers: mods,
|
||||
trigger: InputTrigger::Key(key),
|
||||
}];
|
||||
};
|
||||
bind_key(ActionId::StepSim, vec![], KEY_SPACE);
|
||||
bind_key(ActionId::Undo, vec![KEY_LEFT_CONTROL], KEY_Z);
|
||||
bind_key(ActionId::Redo, vec![KEY_LEFT_CONTROL], KEY_Y);
|
||||
|
||||
Self {
|
||||
bindings,
|
||||
states: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
enum BindingState {
|
||||
#[default]
|
||||
Off,
|
||||
Pressed,
|
||||
Held,
|
||||
Released,
|
||||
}
|
||||
|
||||
type InputMap = HashMap<ActionId, Vec<Binding>>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(from = "InputMap", into = "InputMap")]
|
||||
pub struct Input {
|
||||
bindings: [Vec<Binding>; ActionId::SIZE],
|
||||
states: [BindingState; ActionId::SIZE],
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn update(&mut self, rl: &RaylibHandle) {
|
||||
for i in 0..ActionId::SIZE {
|
||||
let bindings = &self.bindings[i];
|
||||
let mut is_active = false;
|
||||
for binding in bindings {
|
||||
if binding.modifiers.iter().all(|&m| rl.is_key_down(m)) {
|
||||
is_active |= match binding.trigger {
|
||||
InputTrigger::Mouse(btn) => rl.is_mouse_button_down(btn),
|
||||
InputTrigger::Key(key) => rl.is_key_down(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
let state = &mut self.states[i];
|
||||
*state = if is_active {
|
||||
match state {
|
||||
BindingState::Off | BindingState::Released => BindingState::Pressed,
|
||||
BindingState::Pressed | BindingState::Held => BindingState::Held,
|
||||
}
|
||||
} else {
|
||||
match state {
|
||||
BindingState::Off | BindingState::Released => BindingState::Off,
|
||||
BindingState::Pressed | BindingState::Held => BindingState::Released,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_pressed(&self, action: ActionId) -> bool {
|
||||
self.states[action as usize] == BindingState::Pressed
|
||||
}
|
||||
|
||||
pub fn is_held(&self, action: ActionId) -> bool {
|
||||
self.states[action as usize] == BindingState::Pressed
|
||||
|| self.states[action as usize] == BindingState::Held
|
||||
}
|
||||
|
||||
pub fn is_released(&self, action: ActionId) -> bool {
|
||||
self.states[action as usize] == BindingState::Released
|
||||
}
|
||||
}
|
||||
|
||||
impl ActionId {
|
||||
pub const SIZE: usize = Self::_ActionIdSize as usize;
|
||||
|
||||
fn from_usize(val: usize) -> Option<Self> {
|
||||
if val < Self::SIZE {
|
||||
Some(unsafe { transmute::<u8, ActionId>(val as u8) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(from = "BindingSerde", into = "BindingSerde")]
|
||||
pub struct Binding {
|
||||
modifiers: Vec<KeyboardKey>,
|
||||
trigger: InputTrigger,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum InputTrigger {
|
||||
Mouse(MouseButton),
|
||||
Key(KeyboardKey),
|
||||
}
|
||||
|
||||
// ###### everything below is for serialization of key bindings ######
|
||||
|
||||
impl From<InputMap> for Input {
|
||||
fn from(value: InputMap) -> Self {
|
||||
let mut new = Self::default();
|
||||
for (action, saved_bindings) in value {
|
||||
new.bindings[action as usize] = saved_bindings;
|
||||
}
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Input> for InputMap {
|
||||
fn from(value: Input) -> Self {
|
||||
value
|
||||
.bindings
|
||||
.iter()
|
||||
.enumerate()
|
||||
// for this to panic, the .bindings array would have to be larger than ActionId::SIZE
|
||||
.map(|(i, b)| (ActionId::from_usize(i).unwrap(), b.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct BindingSerde {
|
||||
modifiers: Vec<String>,
|
||||
trigger: InputTriggerSerde,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum InputTriggerSerde {
|
||||
Mouse(String),
|
||||
Key(String),
|
||||
}
|
||||
|
||||
impl From<BindingSerde> for Binding {
|
||||
fn from(value: BindingSerde) -> Self {
|
||||
Self {
|
||||
modifiers: value
|
||||
.modifiers
|
||||
.iter()
|
||||
.map(|s| key_string_to_enum(s))
|
||||
.collect(),
|
||||
trigger: value.trigger.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Binding> for BindingSerde {
|
||||
fn from(value: Binding) -> Self {
|
||||
Self {
|
||||
modifiers: value.modifiers.iter().map(|c| format!("{c:?}")).collect(),
|
||||
trigger: value.trigger.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InputTrigger> for InputTriggerSerde {
|
||||
fn from(value: InputTrigger) -> Self {
|
||||
match value {
|
||||
InputTrigger::Mouse(btn) => InputTriggerSerde::Mouse(format!("{btn:?}")),
|
||||
InputTrigger::Key(key) => InputTriggerSerde::Key(format!("{key:?}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InputTriggerSerde> for InputTrigger {
|
||||
fn from(value: InputTriggerSerde) -> Self {
|
||||
match value {
|
||||
InputTriggerSerde::Mouse(btn) => InputTrigger::Mouse(match btn.as_str() {
|
||||
"MOUSE_BUTTON_LEFT" => MouseButton::MOUSE_BUTTON_LEFT,
|
||||
"MOUSE_BUTTON_RIGHT" => MouseButton::MOUSE_BUTTON_RIGHT,
|
||||
"MOUSE_BUTTON_MIDDLE" => MouseButton::MOUSE_BUTTON_MIDDLE,
|
||||
"MOUSE_BUTTON_SIDE" => MouseButton::MOUSE_BUTTON_SIDE,
|
||||
"MOUSE_BUTTON_EXTRA" => MouseButton::MOUSE_BUTTON_EXTRA,
|
||||
"MOUSE_BUTTON_FORWARD" => MouseButton::MOUSE_BUTTON_FORWARD,
|
||||
"MOUSE_BUTTON_BACK" => MouseButton::MOUSE_BUTTON_BACK,
|
||||
_ => panic!("{btn} not a valid mouse button"),
|
||||
}),
|
||||
InputTriggerSerde::Key(key) => InputTrigger::Key(key_string_to_enum(key.as_str())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_string_to_enum(key: &str) -> KeyboardKey {
|
||||
match key {
|
||||
"KEY_NULL" => KeyboardKey::KEY_NULL,
|
||||
"KEY_APOSTROPHE" => KeyboardKey::KEY_APOSTROPHE,
|
||||
"KEY_COMMA" => KeyboardKey::KEY_COMMA,
|
||||
"KEY_MINUS" => KeyboardKey::KEY_MINUS,
|
||||
"KEY_PERIOD" => KeyboardKey::KEY_PERIOD,
|
||||
"KEY_SLASH" => KeyboardKey::KEY_SLASH,
|
||||
"KEY_ZERO" => KeyboardKey::KEY_ZERO,
|
||||
"KEY_ONE" => KeyboardKey::KEY_ONE,
|
||||
"KEY_TWO" => KeyboardKey::KEY_TWO,
|
||||
"KEY_THREE" => KeyboardKey::KEY_THREE,
|
||||
"KEY_FOUR" => KeyboardKey::KEY_FOUR,
|
||||
"KEY_FIVE" => KeyboardKey::KEY_FIVE,
|
||||
"KEY_SIX" => KeyboardKey::KEY_SIX,
|
||||
"KEY_SEVEN" => KeyboardKey::KEY_SEVEN,
|
||||
"KEY_EIGHT" => KeyboardKey::KEY_EIGHT,
|
||||
"KEY_NINE" => KeyboardKey::KEY_NINE,
|
||||
"KEY_SEMICOLON" => KeyboardKey::KEY_SEMICOLON,
|
||||
"KEY_EQUAL" => KeyboardKey::KEY_EQUAL,
|
||||
"KEY_A" => KeyboardKey::KEY_A,
|
||||
"KEY_B" => KeyboardKey::KEY_B,
|
||||
"KEY_C" => KeyboardKey::KEY_C,
|
||||
"KEY_D" => KeyboardKey::KEY_D,
|
||||
"KEY_E" => KeyboardKey::KEY_E,
|
||||
"KEY_F" => KeyboardKey::KEY_F,
|
||||
"KEY_G" => KeyboardKey::KEY_G,
|
||||
"KEY_H" => KeyboardKey::KEY_H,
|
||||
"KEY_I" => KeyboardKey::KEY_I,
|
||||
"KEY_J" => KeyboardKey::KEY_J,
|
||||
"KEY_K" => KeyboardKey::KEY_K,
|
||||
"KEY_L" => KeyboardKey::KEY_L,
|
||||
"KEY_M" => KeyboardKey::KEY_M,
|
||||
"KEY_N" => KeyboardKey::KEY_N,
|
||||
"KEY_O" => KeyboardKey::KEY_O,
|
||||
"KEY_P" => KeyboardKey::KEY_P,
|
||||
"KEY_Q" => KeyboardKey::KEY_Q,
|
||||
"KEY_R" => KeyboardKey::KEY_R,
|
||||
"KEY_S" => KeyboardKey::KEY_S,
|
||||
"KEY_T" => KeyboardKey::KEY_T,
|
||||
"KEY_U" => KeyboardKey::KEY_U,
|
||||
"KEY_V" => KeyboardKey::KEY_V,
|
||||
"KEY_W" => KeyboardKey::KEY_W,
|
||||
"KEY_X" => KeyboardKey::KEY_X,
|
||||
"KEY_Y" => KeyboardKey::KEY_Y,
|
||||
"KEY_Z" => KeyboardKey::KEY_Z,
|
||||
"KEY_LEFT_BRACKET" => KeyboardKey::KEY_LEFT_BRACKET,
|
||||
"KEY_BACKSLASH" => KeyboardKey::KEY_BACKSLASH,
|
||||
"KEY_RIGHT_BRACKET" => KeyboardKey::KEY_RIGHT_BRACKET,
|
||||
"KEY_GRAVE" => KeyboardKey::KEY_GRAVE,
|
||||
"KEY_SPACE" => KeyboardKey::KEY_SPACE,
|
||||
"KEY_ESCAPE" => KeyboardKey::KEY_ESCAPE,
|
||||
"KEY_ENTER" => KeyboardKey::KEY_ENTER,
|
||||
"KEY_TAB" => KeyboardKey::KEY_TAB,
|
||||
"KEY_BACKSPACE" => KeyboardKey::KEY_BACKSPACE,
|
||||
"KEY_INSERT" => KeyboardKey::KEY_INSERT,
|
||||
"KEY_DELETE" => KeyboardKey::KEY_DELETE,
|
||||
"KEY_RIGHT" => KeyboardKey::KEY_RIGHT,
|
||||
"KEY_LEFT" => KeyboardKey::KEY_LEFT,
|
||||
"KEY_DOWN" => KeyboardKey::KEY_DOWN,
|
||||
"KEY_UP" => KeyboardKey::KEY_UP,
|
||||
"KEY_PAGE_UP" => KeyboardKey::KEY_PAGE_UP,
|
||||
"KEY_PAGE_DOWN" => KeyboardKey::KEY_PAGE_DOWN,
|
||||
"KEY_HOME" => KeyboardKey::KEY_HOME,
|
||||
"KEY_END" => KeyboardKey::KEY_END,
|
||||
"KEY_CAPS_LOCK" => KeyboardKey::KEY_CAPS_LOCK,
|
||||
"KEY_SCROLL_LOCK" => KeyboardKey::KEY_SCROLL_LOCK,
|
||||
"KEY_NUM_LOCK" => KeyboardKey::KEY_NUM_LOCK,
|
||||
"KEY_PRINT_SCREEN" => KeyboardKey::KEY_PRINT_SCREEN,
|
||||
"KEY_PAUSE" => KeyboardKey::KEY_PAUSE,
|
||||
"KEY_F1" => KeyboardKey::KEY_F1,
|
||||
"KEY_F2" => KeyboardKey::KEY_F2,
|
||||
"KEY_F3" => KeyboardKey::KEY_F3,
|
||||
"KEY_F4" => KeyboardKey::KEY_F4,
|
||||
"KEY_F5" => KeyboardKey::KEY_F5,
|
||||
"KEY_F6" => KeyboardKey::KEY_F6,
|
||||
"KEY_F7" => KeyboardKey::KEY_F7,
|
||||
"KEY_F8" => KeyboardKey::KEY_F8,
|
||||
"KEY_F9" => KeyboardKey::KEY_F9,
|
||||
"KEY_F10" => KeyboardKey::KEY_F10,
|
||||
"KEY_F11" => KeyboardKey::KEY_F11,
|
||||
"KEY_F12" => KeyboardKey::KEY_F12,
|
||||
"KEY_LEFT_SHIFT" => KeyboardKey::KEY_LEFT_SHIFT,
|
||||
"KEY_LEFT_CONTROL" => KeyboardKey::KEY_LEFT_CONTROL,
|
||||
"KEY_LEFT_ALT" => KeyboardKey::KEY_LEFT_ALT,
|
||||
"KEY_LEFT_SUPER" => KeyboardKey::KEY_LEFT_SUPER,
|
||||
"KEY_RIGHT_SHIFT" => KeyboardKey::KEY_RIGHT_SHIFT,
|
||||
"KEY_RIGHT_CONTROL" => KeyboardKey::KEY_RIGHT_CONTROL,
|
||||
"KEY_RIGHT_ALT" => KeyboardKey::KEY_RIGHT_ALT,
|
||||
"KEY_RIGHT_SUPER" => KeyboardKey::KEY_RIGHT_SUPER,
|
||||
"KEY_KB_MENU" => KeyboardKey::KEY_KB_MENU,
|
||||
"KEY_KP_0" => KeyboardKey::KEY_KP_0,
|
||||
"KEY_KP_1" => KeyboardKey::KEY_KP_1,
|
||||
"KEY_KP_2" => KeyboardKey::KEY_KP_2,
|
||||
"KEY_KP_3" => KeyboardKey::KEY_KP_3,
|
||||
"KEY_KP_4" => KeyboardKey::KEY_KP_4,
|
||||
"KEY_KP_5" => KeyboardKey::KEY_KP_5,
|
||||
"KEY_KP_6" => KeyboardKey::KEY_KP_6,
|
||||
"KEY_KP_7" => KeyboardKey::KEY_KP_7,
|
||||
"KEY_KP_8" => KeyboardKey::KEY_KP_8,
|
||||
"KEY_KP_9" => KeyboardKey::KEY_KP_9,
|
||||
"KEY_KP_DECIMAL" => KeyboardKey::KEY_KP_DECIMAL,
|
||||
"KEY_KP_DIVIDE" => KeyboardKey::KEY_KP_DIVIDE,
|
||||
"KEY_KP_MULTIPLY" => KeyboardKey::KEY_KP_MULTIPLY,
|
||||
"KEY_KP_SUBTRACT" => KeyboardKey::KEY_KP_SUBTRACT,
|
||||
"KEY_KP_ADD" => KeyboardKey::KEY_KP_ADD,
|
||||
"KEY_KP_ENTER" => KeyboardKey::KEY_KP_ENTER,
|
||||
"KEY_KP_EQUAL" => KeyboardKey::KEY_KP_EQUAL,
|
||||
"KEY_BACK" => KeyboardKey::KEY_BACK,
|
||||
"KEY_VOLUME_UP" => KeyboardKey::KEY_VOLUME_UP,
|
||||
"KEY_VOLUME_DOWN" => KeyboardKey::KEY_VOLUME_DOWN,
|
||||
_ => panic!("{key} not a known key name"),
|
||||
}
|
||||
}
|
|
@ -1,9 +1,18 @@
|
|||
pub mod blueprint;
|
||||
pub mod board;
|
||||
pub mod editor;
|
||||
pub mod input;
|
||||
pub mod level;
|
||||
pub mod marble_engine;
|
||||
pub mod solution;
|
||||
pub mod theme;
|
||||
pub mod ui;
|
||||
pub mod util;
|
||||
|
||||
use arboard::Clipboard;
|
||||
use input::Input;
|
||||
|
||||
pub struct Globals {
|
||||
pub clipboard: Option<Clipboard>,
|
||||
pub input: Input,
|
||||
}
|
||||
|
|
61
src/main.rs
61
src/main.rs
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{read_dir, read_to_string},
|
||||
fs::{self, read_dir, read_to_string, File},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
use arboard::Clipboard;
|
||||
|
@ -16,6 +17,7 @@ use ui::{simple_option_button, tex32_button, text_button, text_input, ShapedText
|
|||
use util::*;
|
||||
|
||||
const TITLE_TEXT: &str = concat!("Marble Machinations v", env!("CARGO_PKG_VERSION"));
|
||||
const CONFIG_FILE_NAME: &str = "config.json";
|
||||
|
||||
struct Game {
|
||||
levels: Vec<LevelListEntry>,
|
||||
|
@ -28,7 +30,8 @@ struct Game {
|
|||
delete_solution: Option<usize>,
|
||||
editing_solution_name: bool,
|
||||
level_desc_text: ShapedText,
|
||||
clipboard: Option<Clipboard>,
|
||||
globals: Globals,
|
||||
show_settings: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -58,6 +61,14 @@ impl Game {
|
|||
|
||||
let levels = get_levels();
|
||||
let solutions = get_solutions();
|
||||
let config_path = userdata_dir().join(CONFIG_FILE_NAME);
|
||||
let input = fs::read_to_string(config_path)
|
||||
.ok()
|
||||
.and_then(|s| {
|
||||
println!("a");
|
||||
serde_json::from_str(&s).unwrap()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Self {
|
||||
levels,
|
||||
|
@ -70,18 +81,23 @@ impl Game {
|
|||
delete_solution: None,
|
||||
editing_solution_name: false,
|
||||
level_desc_text: ShapedText::new(20),
|
||||
clipboard: Clipboard::new()
|
||||
.map_err(|e| eprintln!("System clipboard error: {e}"))
|
||||
.ok(),
|
||||
globals: Globals {
|
||||
clipboard: Clipboard::new()
|
||||
.map_err(|e| eprintln!("System clipboard error: {e}"))
|
||||
.ok(),
|
||||
input,
|
||||
},
|
||||
show_settings: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self, rl: &mut RaylibHandle, thread: &RaylibThread) {
|
||||
while !rl.window_should_close() {
|
||||
let mut d = rl.begin_drawing(thread);
|
||||
self.globals.input.update(&d);
|
||||
if let Some(editor) = &mut self.open_editor {
|
||||
editor.update(&d, self.clipboard.as_mut());
|
||||
editor.draw(&mut d, &self.textures, self.clipboard.as_mut());
|
||||
editor.update(&d, &mut self.globals);
|
||||
editor.draw(&mut d, &self.textures, &mut self.globals);
|
||||
match editor.get_exit_state() {
|
||||
ExitState::Dont => (),
|
||||
ExitState::ExitAndSave => {
|
||||
|
@ -100,6 +116,8 @@ impl Game {
|
|||
solution.save();
|
||||
}
|
||||
}
|
||||
} else if self.show_settings {
|
||||
self.draw_settings(&mut d);
|
||||
} else {
|
||||
self.draw(&mut d);
|
||||
}
|
||||
|
@ -124,6 +142,16 @@ impl Game {
|
|||
let screen_height = d.get_screen_height();
|
||||
d.draw_rectangle(0, 0, level_list_width, screen_height, BG_MEDIUM);
|
||||
let mouse = MouseInput::get(d);
|
||||
if text_button(
|
||||
d,
|
||||
&mouse,
|
||||
d.get_screen_width() - 50,
|
||||
d.get_screen_height() - 40,
|
||||
40,
|
||||
"settings",
|
||||
) {
|
||||
self.show_settings = true;
|
||||
}
|
||||
|
||||
const ENTRY_SPACING: i32 = 65;
|
||||
let fit_on_screen = (d.get_screen_height() / ENTRY_SPACING) as usize;
|
||||
|
@ -313,6 +341,25 @@ impl Game {
|
|||
}
|
||||
tooltip.draw(d);
|
||||
}
|
||||
|
||||
fn draw_settings(&mut self, d: &mut RaylibDrawHandle) {
|
||||
d.clear_background(BG_DARK);
|
||||
let mouse = MouseInput::get(d);
|
||||
if text_button(d, &mouse, 5, 5, 50, "return") {
|
||||
self.show_settings = false;
|
||||
}
|
||||
if text_button(d, &mouse, 5, 45, 50, "save") {
|
||||
self.save_config();
|
||||
}
|
||||
}
|
||||
|
||||
fn save_config(&self) {
|
||||
let path = userdata_dir().join(CONFIG_FILE_NAME);
|
||||
// todo save more than just input
|
||||
let json = serde_json::to_string_pretty(&self.globals.input).unwrap();
|
||||
let mut f = File::create(path).unwrap();
|
||||
f.write_all(json.as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_levels() -> Vec<LevelListEntry> {
|
||||
|
|
Loading…
Add table
Reference in a new issue