allow collapsing chapters in level list
This commit is contained in:
parent
69fe8546f5
commit
522a027f7a
3 changed files with 89 additions and 77 deletions
|
@ -2,6 +2,8 @@
|
|||
Game store page: https://crispypin.itch.io/marble-machinations
|
||||
|
||||
## [unreleased]
|
||||
### added
|
||||
- click to collapse chapters in level list
|
||||
### fixed
|
||||
- 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.
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ use crate::board::Board;
|
|||
pub struct Chapter {
|
||||
pub title: String,
|
||||
pub levels: Vec<Level>,
|
||||
#[serde(default = "default_true")]
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
|
@ -81,3 +83,7 @@ impl Stage {
|
|||
&self.output
|
||||
}
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
108
src/main.rs
108
src/main.rs
|
@ -10,7 +10,7 @@ use marble_machinations::*;
|
|||
|
||||
use config::Config;
|
||||
use editor::{Editor, ExitState};
|
||||
use level::{Chapter, Level};
|
||||
use level::Chapter;
|
||||
use solution::Solution;
|
||||
use theme::*;
|
||||
use ui::{simple_option_button, tex32_button, text_button, text_input, ShapedText, Tooltip};
|
||||
|
@ -19,11 +19,11 @@ use util::*;
|
|||
const TITLE_TEXT: &str = concat!("Marble Machinations v", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
struct Game {
|
||||
levels: Vec<LevelListEntry>,
|
||||
level_scroll: usize,
|
||||
chapters: Vec<Chapter>,
|
||||
level_scroll: i32,
|
||||
solutions: HashMap<String, Vec<Solution>>,
|
||||
open_editor: Option<Editor>,
|
||||
selected_level: usize,
|
||||
selected_level: (usize, usize),
|
||||
selected_solution: usize,
|
||||
delete_solution: Option<usize>,
|
||||
editing_solution_name: bool,
|
||||
|
@ -32,12 +32,6 @@ struct Game {
|
|||
edit_settings: Option<Config>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LevelListEntry {
|
||||
Level(Level),
|
||||
Chapter(String, usize),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let (mut rl, thread) = raylib::init().resizable().title(TITLE_TEXT).build();
|
||||
rl.set_target_fps(60);
|
||||
|
@ -52,15 +46,15 @@ fn main() {
|
|||
|
||||
impl Game {
|
||||
fn new(rl: &mut RaylibHandle, thread: &RaylibThread) -> Self {
|
||||
let levels = get_levels();
|
||||
let chapters = get_chapters();
|
||||
let solutions = get_solutions();
|
||||
|
||||
Self {
|
||||
levels,
|
||||
chapters,
|
||||
level_scroll: 0,
|
||||
solutions,
|
||||
open_editor: None,
|
||||
selected_level: 0,
|
||||
selected_level: (0, 0),
|
||||
selected_solution: 0,
|
||||
delete_solution: None,
|
||||
editing_solution_name: false,
|
||||
|
@ -147,38 +141,49 @@ impl Game {
|
|||
|
||||
const ENTRY_SPACING: i32 = 65;
|
||||
let fit_on_screen = (d.get_screen_height() / ENTRY_SPACING) as usize;
|
||||
let max_scroll = self.levels.len().saturating_sub(fit_on_screen);
|
||||
let max_scroll = self
|
||||
.chapters
|
||||
.iter()
|
||||
.map(|c| 1 + if c.visible { c.levels.len() } else { 0 })
|
||||
.sum::<usize>()
|
||||
.saturating_sub(fit_on_screen) as i32
|
||||
* ENTRY_SPACING;
|
||||
if self.globals.mouse.pos().x < level_list_width as f32 {
|
||||
if self.globals.mouse.scroll() == Some(Scroll::Down) && self.level_scroll < max_scroll {
|
||||
self.level_scroll += 1;
|
||||
self.level_scroll += ENTRY_SPACING;
|
||||
}
|
||||
if self.globals.mouse.scroll() == Some(Scroll::Up) && self.level_scroll > 0 {
|
||||
self.level_scroll -= 1;
|
||||
self.level_scroll -= ENTRY_SPACING;
|
||||
}
|
||||
}
|
||||
|
||||
for (row_index, level_index) in (self.level_scroll..self.levels.len()).enumerate() {
|
||||
let level = &mut self.levels[level_index];
|
||||
let y = 10 + row_index as i32 * ENTRY_SPACING;
|
||||
let bounds = Rectangle {
|
||||
x: 5.,
|
||||
y: y as f32 - 5.,
|
||||
width: level_list_width as f32 - 10.,
|
||||
height: ENTRY_SPACING as f32 - 5.,
|
||||
};
|
||||
let mut y = 10 - self.level_scroll;
|
||||
for (chapter_i, chapter) in self.chapters.iter_mut().enumerate() {
|
||||
let bounds = rect(5, y - 5, level_list_width - 10, ENTRY_SPACING - 5);
|
||||
d.draw_rectangle_rec(bounds, BG_DARK);
|
||||
d.draw_text(&chapter.title, 10, y, 30, FG_CHAPTER_TITLE);
|
||||
let subtitle = format!("{} levels", chapter.levels.len());
|
||||
d.draw_text(&subtitle, 10, y + 30, 20, Color::WHITE);
|
||||
y += ENTRY_SPACING;
|
||||
let clicked_this =
|
||||
self.globals.mouse.left_click() && self.globals.mouse.is_over(bounds);
|
||||
match level {
|
||||
LevelListEntry::Chapter(title, level_count) => {
|
||||
d.draw_rectangle_rec(bounds, BG_DARK);
|
||||
d.draw_text(title, 10, y, 30, FG_CHAPTER_TITLE);
|
||||
let subtitle = format!("{level_count} levels");
|
||||
d.draw_text(&subtitle, 10, y + 30, 20, Color::WHITE);
|
||||
|
||||
if clicked_this {
|
||||
chapter.visible = !chapter.visible;
|
||||
}
|
||||
LevelListEntry::Level(level) => {
|
||||
if clicked_this && self.selected_level != level_index {
|
||||
|
||||
if !chapter.visible {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (level_index, level) in chapter.levels.iter().enumerate() {
|
||||
let bounds = rect(5, y - 5, level_list_width - 10, ENTRY_SPACING - 5);
|
||||
let clicked_this =
|
||||
self.globals.mouse.left_click() && self.globals.mouse.is_over(bounds);
|
||||
|
||||
if clicked_this && self.selected_level != (chapter_i, level_index) {
|
||||
self.editing_solution_name = false;
|
||||
self.selected_level = level_index;
|
||||
self.selected_level = (chapter_i, level_index);
|
||||
self.selected_solution = 0;
|
||||
self.delete_solution = None;
|
||||
// select the last solution of the level, if there is one
|
||||
|
@ -186,7 +191,10 @@ impl Game {
|
|||
self.selected_solution = solutions.len().saturating_sub(1);
|
||||
}
|
||||
}
|
||||
d.draw_rectangle_rec(bounds, widget_bg(self.selected_level == level_index));
|
||||
d.draw_rectangle_rec(
|
||||
bounds,
|
||||
widget_bg(self.selected_level == (chapter_i, level_index)),
|
||||
);
|
||||
|
||||
let mut title_color = Color::WHITE;
|
||||
if let Some(solutions) = self.solutions.get(level.id()) {
|
||||
|
@ -207,11 +215,15 @@ impl Game {
|
|||
Color::LIGHTGRAY
|
||||
};
|
||||
d.draw_text(&subtext, 10, y + 30, 20, subtext_color);
|
||||
}
|
||||
y += ENTRY_SPACING;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(LevelListEntry::Level(level)) = self.levels.get(self.selected_level) {
|
||||
if let Some(level) = self
|
||||
.chapters
|
||||
.get(self.selected_level.0)
|
||||
.and_then(|c| c.levels.get(self.selected_level.1))
|
||||
{
|
||||
d.draw_text(level.name(), level_list_width + 10, 10, 40, Color::CYAN);
|
||||
d.draw_text(level.id(), level_list_width + 10, 50, 10, Color::GRAY);
|
||||
|
||||
|
@ -343,7 +355,7 @@ impl Game {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_levels() -> Vec<LevelListEntry> {
|
||||
fn get_chapters() -> Vec<Chapter> {
|
||||
let mut chapters = Vec::<Chapter>::new();
|
||||
for d in read_dir("levels").unwrap().flatten() {
|
||||
if let Ok(text) = read_to_string(d.path()) {
|
||||
|
@ -354,12 +366,6 @@ fn get_levels() -> Vec<LevelListEntry> {
|
|||
}
|
||||
chapters.sort_unstable_by_key(|c| c.title.clone());
|
||||
|
||||
let mut level_list = Vec::new();
|
||||
for c in chapters {
|
||||
level_list.push(LevelListEntry::Chapter(c.title, c.levels.len()));
|
||||
level_list.extend(c.levels.into_iter().map(LevelListEntry::Level));
|
||||
}
|
||||
|
||||
// user levels
|
||||
let mut custom_levels = Vec::new();
|
||||
let custom_level_dir = userdata_dir().join("levels");
|
||||
|
@ -372,15 +378,13 @@ fn get_levels() -> Vec<LevelListEntry> {
|
|||
}
|
||||
}
|
||||
}
|
||||
level_list.push(LevelListEntry::Chapter(
|
||||
"Custom levels".into(),
|
||||
custom_levels.len(),
|
||||
));
|
||||
for l in custom_levels {
|
||||
level_list.push(LevelListEntry::Level(l));
|
||||
}
|
||||
chapters.push(Chapter {
|
||||
title: "Custom levels".into(),
|
||||
levels: custom_levels,
|
||||
visible: true,
|
||||
});
|
||||
|
||||
level_list
|
||||
chapters
|
||||
}
|
||||
|
||||
fn get_solutions() -> HashMap<String, Vec<Solution>> {
|
||||
|
|
Loading…
Add table
Reference in a new issue