move menu work, start on preliminary save system
This commit is contained in:
Generated
+106
@@ -184,6 +184,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "app_dirs2"
|
||||
version = "2.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7e7b35733e3a8c1ccb90385088dd5b6eaa61325cb4d1ad56e683b5224ff352e"
|
||||
dependencies = [
|
||||
"jni 0.21.1",
|
||||
"ndk-context",
|
||||
"winapi",
|
||||
"xdg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
@@ -2269,6 +2281,37 @@ version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"defmt-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||
dependencies = [
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
@@ -3141,6 +3184,59 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"jiff-core",
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
|
||||
dependencies = [
|
||||
"jiff-core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb-platform"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
|
||||
dependencies = [
|
||||
"jiff-tzdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
@@ -5090,8 +5186,12 @@ dependencies = [
|
||||
name = "tt_net_test_vibeless"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"app_dirs2",
|
||||
"bevy",
|
||||
"derive_more",
|
||||
"jiff",
|
||||
"ron",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6078,6 +6178,12 @@ version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23"
|
||||
|
||||
[[package]]
|
||||
name = "xdg"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon-dl"
|
||||
version = "0.4.2"
|
||||
|
||||
+5
-1
@@ -4,8 +4,12 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.19"
|
||||
app_dirs2 = "2.5.5"
|
||||
bevy = { version = "0.19", features = ["system_clipboard"] }
|
||||
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
|
||||
jiff = { version = "0.2.35", features = ["serde"] }
|
||||
ron = "0.12.2"
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
||||
+4
-1
@@ -2,8 +2,11 @@ use bevy::{camera::ScalingMode, color::palettes::css::GREEN, prelude::*};
|
||||
use main_menu::MainMenuPlugin;
|
||||
use networking::NetworkingPlugin;
|
||||
|
||||
use crate::save::SavePlugin;
|
||||
|
||||
mod main_menu;
|
||||
mod networking;
|
||||
mod save;
|
||||
|
||||
/// We target this 16:9 resolution to make things easier.
|
||||
pub const WINDOW_TARGET_WIDTH_U32: u32 = 3840;
|
||||
@@ -22,7 +25,7 @@ pub enum AppState {
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((DefaultPlugins, MainMenuPlugin, NetworkingPlugin))
|
||||
.add_plugins((DefaultPlugins, MainMenuPlugin, NetworkingPlugin, SavePlugin))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, debug_border)
|
||||
.insert_resource(ClearColor(Color::srgb(0.07, 0.33, 0.45)))
|
||||
|
||||
+110
-19
@@ -1,4 +1,8 @@
|
||||
use crate::save::get_saves;
|
||||
use crate::{AppState, WINDOW_TARGET_HEIGHT, WINDOW_TARGET_WIDTH};
|
||||
use bevy::color::palettes::tailwind::SLATE_900;
|
||||
use bevy::input_focus::tab_navigation::TabIndex;
|
||||
use bevy::text::{EditableText, TextCursorStyle};
|
||||
use bevy::ui_widgets::Button;
|
||||
use bevy::{prelude::*, ui_widgets::Activate};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
@@ -9,6 +13,7 @@ impl Plugin for MainMenuPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(OnEnter(AppState::Menu), setup_menu)
|
||||
.add_systems(OnExit(AppState::Menu), cleanup_menu)
|
||||
// .add_systems(Startup, enable_ui_debug)
|
||||
.add_systems(Update, (menu_redraw).run_if(in_state(AppState::Menu)))
|
||||
.add_observer(button_activate_observer)
|
||||
.add_systems(Update, (scale_ui, style_ui_elements));
|
||||
@@ -78,6 +83,7 @@ pub enum MenuLayer {
|
||||
Root,
|
||||
Play,
|
||||
LoadSave,
|
||||
NewGame,
|
||||
Browse,
|
||||
DirectConnect,
|
||||
Settings,
|
||||
@@ -110,16 +116,31 @@ impl MenuLayer {
|
||||
menu_button("Direct Connect", Some(MenuLayer::DirectConnect)),
|
||||
]
|
||||
}),
|
||||
MenuLayer::LoadSave => Box::new(bsn! {
|
||||
menu_layer(layer)
|
||||
MenuLayer::LoadSave => {
|
||||
// TODO: this needs to be on an IO thread
|
||||
let saves = get_saves().unwrap_or_default();
|
||||
let entries: Vec<_> = saves
|
||||
.into_iter()
|
||||
.map(|m| menu_button(m.save_name, None))
|
||||
.collect();
|
||||
|
||||
Box::new(bsn! {
|
||||
menu_layer(layer)
|
||||
ChildOf(menu_root)
|
||||
Children [
|
||||
{entries}
|
||||
(
|
||||
menu_button("New Game", Some(MenuLayer::NewGame))
|
||||
InteractionColors::GOOD
|
||||
),
|
||||
]
|
||||
})
|
||||
}
|
||||
MenuLayer::NewGame => Box::new(bsn! {
|
||||
full_menu_layer(layer)
|
||||
ChildOf(menu_root)
|
||||
Children [
|
||||
menu_button("Save 1", None),
|
||||
menu_button("Save 2", None),
|
||||
(
|
||||
menu_button("New Game", None)
|
||||
InteractionColors::GOOD
|
||||
),
|
||||
{new_game_menu()}
|
||||
]
|
||||
}),
|
||||
MenuLayer::Browse => Box::new(bsn! {
|
||||
@@ -170,6 +191,13 @@ struct LastMenu(Vec<MenuLayer>);
|
||||
#[derive(Resource, Debug, Clone, PartialEq, Eq, Hash, Deref, DerefMut)]
|
||||
struct CurrentMenu(Vec<MenuLayer>);
|
||||
|
||||
// fn enable_ui_debug(mut debug_options: ResMut<GlobalUiDebugOptions>) {
|
||||
// debug_options.enabled = true;
|
||||
// debug_options.outline_border_box = true;
|
||||
// debug_options.outline_padding_box = true;
|
||||
// debug_options.outline_content_box = true;
|
||||
// }
|
||||
|
||||
fn setup_menu(mut commands: Commands) {
|
||||
commands.insert_resource(CurrentMenu(vec![MenuLayer::Root]));
|
||||
commands.insert_resource(LastMenu(vec![]));
|
||||
@@ -233,6 +261,70 @@ fn menu_redraw(
|
||||
}
|
||||
}
|
||||
|
||||
fn new_game_menu() -> impl SceneList {
|
||||
bsn_list![
|
||||
(
|
||||
Text("New Game")
|
||||
Node {
|
||||
align_self: AlignSelf::Center,
|
||||
}
|
||||
TextFont {
|
||||
// font: FontSourceTemplate::Handle("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: px(100.0),
|
||||
}
|
||||
TextColor(Color::srgb(0.9, 0.9, 0.9))
|
||||
TextShadow
|
||||
),
|
||||
(
|
||||
Text("Save Name")
|
||||
Node {
|
||||
margin: UiRect::axes(px(12), px(0)),
|
||||
}
|
||||
TextFont {
|
||||
// font: FontSourceTemplate::Handle("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: px(66.0),
|
||||
}
|
||||
TextColor(Color::srgb(0.9, 0.9, 0.9))
|
||||
TextShadow
|
||||
),
|
||||
(
|
||||
#NewGameTextField
|
||||
Node {
|
||||
border: Val::all(px(6)),
|
||||
padding: Val::all(px(12)),
|
||||
border_radius: BorderRadius::all(px(12)),
|
||||
}
|
||||
// BorderColor::from(SLATE_800)
|
||||
BorderColor::from(SLATE_900)
|
||||
EditableText {
|
||||
cursor_width: 0.05,
|
||||
}
|
||||
TextLayout::no_wrap()
|
||||
TextFont {
|
||||
font_size: FontSize::Px(66.),
|
||||
}
|
||||
TextCursorStyle {
|
||||
color: Color::BLACK
|
||||
}
|
||||
TabIndex(0)
|
||||
BackgroundColor(Color::srgb(0.48, 0.84, 0.87))
|
||||
),
|
||||
(
|
||||
Node {
|
||||
margin: UiRect::top(px(48)),
|
||||
}
|
||||
Children [
|
||||
menu_button("Create", None)
|
||||
Node {
|
||||
margin: UiRect::left(Val::Auto),
|
||||
}
|
||||
InteractionColors::GOOD
|
||||
on(|event: On<Activate>, q_text:| { info!("create!") })
|
||||
]
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn menu_layer(layer: usize) -> impl Scene {
|
||||
let name = format!("MenuLayer{}", layer);
|
||||
bsn! {
|
||||
@@ -242,8 +334,6 @@ fn menu_layer(layer: usize) -> impl Scene {
|
||||
width: percent(100.0 / N_LAYERS as f32),
|
||||
height: percent(100),
|
||||
flex_direction: FlexDirection::Column,
|
||||
// align_items: AlignItems::Start,
|
||||
align_items: AlignItems::Baseline,
|
||||
justify_content: JustifyContent::FlexEnd,
|
||||
row_gap: px(100),
|
||||
}
|
||||
@@ -251,21 +341,22 @@ fn menu_layer(layer: usize) -> impl Scene {
|
||||
}
|
||||
|
||||
fn full_menu_layer(layer: usize) -> impl Scene {
|
||||
let name = format!("FullMenuLayer{}", layer);
|
||||
bsn! {
|
||||
menu_layer(layer)
|
||||
Name(name)
|
||||
Layer(layer)
|
||||
Node {
|
||||
width: Val::Auto,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
flex_grow: 1.,
|
||||
border_radius: BorderRadius::all(px(24)),
|
||||
padding: Val::all(px(24)),
|
||||
}
|
||||
BackgroundColor(Color::srgba(0.8, 0.1, 0.1, 0.7))
|
||||
BackgroundColor(Color::srgb(0.4, 0.4, 0.4))
|
||||
}
|
||||
}
|
||||
|
||||
fn menu_button(label: &str, next_layer: Option<MenuLayer>) -> impl Scene {
|
||||
let next_layer_component = next_layer
|
||||
.map(|n| Box::new(bsn! { NextLayer(n) }) as Box<dyn Scene>)
|
||||
.unwrap_or(Box::new(bsn! {}));
|
||||
|
||||
fn menu_button(label: impl Into<String>, next_layer: Option<MenuLayer>) -> impl Scene {
|
||||
bsn! {
|
||||
Button
|
||||
Node {
|
||||
@@ -277,7 +368,7 @@ fn menu_button(label: &str, next_layer: Option<MenuLayer>) -> impl Scene {
|
||||
align_items: AlignItems::Center,
|
||||
margin: UiRect::all(px(4)),
|
||||
}
|
||||
next_layer_component
|
||||
{next_layer.map(|l| template_value(NextLayer(l)))}
|
||||
BorderColor::from(Color::BLACK)
|
||||
InteractionColors::DEFAULT
|
||||
Interaction
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
use app_dirs2::{AppInfo, app_dir};
|
||||
use bevy::prelude::*;
|
||||
use jiff::Zoned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const APP_INFO: AppInfo = AppInfo {
|
||||
name: "OfficeWarper",
|
||||
author: "MarinoDev",
|
||||
};
|
||||
|
||||
#[derive(Resource, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ActiveGameSave {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Message, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SaveMessage;
|
||||
|
||||
pub struct SavePlugin;
|
||||
|
||||
/// The metadata stored with each game save
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct GameSaveMetadata {
|
||||
pub save_name: String,
|
||||
pub version: String,
|
||||
pub timestamp: Zoned,
|
||||
}
|
||||
|
||||
impl Plugin for SavePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_message::<SaveMessage>()
|
||||
.add_systems(PostUpdate, handle_saves);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_saves(
|
||||
mut save_messages: MessageReader<SaveMessage>,
|
||||
active_game_save: Option<Res<ActiveGameSave>>,
|
||||
) {
|
||||
for _save in save_messages.read() {
|
||||
let Some(ref active_game_save) = active_game_save else {
|
||||
warn!("Save requested, but no active game save exists!");
|
||||
return;
|
||||
};
|
||||
|
||||
info!("Saving!");
|
||||
let save_subpath = format!("saves/{}", active_game_save.name);
|
||||
let save_path = match app_dir(app_dirs2::AppDataType::UserData, &APP_INFO, &*save_subpath) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to get the save path for \"{}\": {e}",
|
||||
active_game_save.name
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = std::fs::write(save_path.join("version.txt"), "v0.0.1") {
|
||||
error!("Error when writing the game version: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Game saved!");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_saves() -> Result<Vec<GameSaveMetadata>, std::io::Error> {
|
||||
let saves_path = match app_dir(app_dirs2::AppDataType::UserData, &APP_INFO, "saves") {
|
||||
Ok(p) => p,
|
||||
Err(app_dirs2::AppDirsError::Io(e)) => {
|
||||
error!("Failed to get path to the saves: {e}",);
|
||||
return Err(e);
|
||||
}
|
||||
Err(app_dirs2::AppDirsError::NotSupported) => {
|
||||
error!("The app data directory is not known on this platform!");
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Unsupported,
|
||||
"the app data directory is not known on this platform",
|
||||
));
|
||||
}
|
||||
Err(app_dirs2::AppDirsError::InvalidAppInfo) => {
|
||||
// :/
|
||||
panic!("APP_INFO invalid!");
|
||||
}
|
||||
};
|
||||
|
||||
let dir = match std::fs::read_dir(saves_path) {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
error!("Failed to read the saves path: {e}");
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
// TODO: do a meta.ron file instead of name.txt
|
||||
let save_folders = dir
|
||||
.filter_map(|entry| {
|
||||
entry
|
||||
.ok()
|
||||
.filter(|e| e.file_type().is_ok_and(|t| t.is_dir()))
|
||||
})
|
||||
.map(|dir| dir.path());
|
||||
|
||||
let mut saves = vec![];
|
||||
for save_folder in save_folders {
|
||||
let meta_raw = match std::fs::read_to_string(save_folder.join("meta.ron")) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("Failed to read meta for save \"{save_folder:?}\"");
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
let meta: GameSaveMetadata = match ron::from_str(&meta_raw) {
|
||||
Err(e) => {
|
||||
error!("Error when deserializing metadata for save \"{save_folder:?}\": {e}");
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e));
|
||||
}
|
||||
Ok(m) => m,
|
||||
};
|
||||
saves.push(meta);
|
||||
}
|
||||
|
||||
Ok(saves)
|
||||
}
|
||||
Reference in New Issue
Block a user