game picker

This commit is contained in:
Rph :3 2025-04-17 00:39:10 +02:00
parent 89f08ff580
commit 165a9fc139
No known key found for this signature in database
5 changed files with 243 additions and 14 deletions

171
Cargo.lock generated
View File

@ -165,6 +165,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]] [[package]]
name = "android_system_properties" name = "android_system_properties"
version = "0.1.5" version = "0.1.5"
@ -689,6 +695,29 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "chrono-humanize"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b"
dependencies = [
"chrono",
]
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.0" version = "5.4.0"
@ -1041,6 +1070,20 @@ dependencies = [
"egui", "egui",
] ]
[[package]]
name = "egui_extras"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624659a2e972a46f4d5f646557906c55f1cd5a0836eddbe610fdf1afba1b4226"
dependencies = [
"ahash",
"egui",
"enum-map",
"log",
"mime_guess2",
"profiling",
]
[[package]] [[package]]
name = "egui_glow" name = "egui_glow"
version = "0.31.1" version = "0.31.1"
@ -1074,6 +1117,27 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
[[package]]
name = "enum-map"
version = "2.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9"
dependencies = [
"enum-map-derive",
"serde",
]
[[package]]
name = "enum-map-derive"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "enumflags2" name = "enumflags2"
version = "0.7.11" version = "0.7.11"
@ -1539,6 +1603,30 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "1.5.0" version = "1.5.0"
@ -1937,6 +2025,24 @@ dependencies = [
"paste", "paste",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess2"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54028747dfea8e8bf00d3c2d4e83cf023c1accfd5d436335456e9864940cb85"
dependencies = [
"mime",
"phf",
"phf_shared",
"unicase",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.5" version = "0.8.5"
@ -2446,6 +2552,50 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand 0.8.5",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn",
"unicase",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
"unicase",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.10" version = "1.1.10"
@ -2942,6 +3092,12 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -3249,12 +3405,15 @@ name = "tundlebool"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono",
"chrono-humanize",
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
"eframe", "eframe",
"egui", "egui",
"egui-modal", "egui-modal",
"egui_alignments", "egui_alignments",
"egui_extras",
"env_logger", "env_logger",
"hex", "hex",
"keyvalues-parser", "keyvalues-parser",
@ -3299,6 +3458,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.18"
@ -3798,6 +3963,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.2.0" version = "0.2.0"

View File

@ -21,6 +21,9 @@ hex = "0.4.3"
egui_alignments = "0.3.4" egui_alignments = "0.3.4"
log = "0.4.27" log = "0.4.27"
egui-modal = { path = "./egui-modal" } egui-modal = { path = "./egui-modal" }
chrono-humanize = "0.2.3"
chrono = "0.4.40"
egui_extras = "0.31.1"
[workspace] [workspace]
members = ["egui-modal"] members = ["egui-modal"]

View File

@ -2,7 +2,7 @@ use std::{f32::consts::E, path::PathBuf, sync::Arc, thread, time::Duration};
use crossbeam_channel::Receiver; use crossbeam_channel::Receiver;
use egui::{mutex::Mutex}; use egui::{mutex::Mutex};
use log::debug; use log::{debug, info};
use crate::{config::{self, ConfigProvider}, omori_locator}; use crate::{config::{self, ConfigProvider}, omori_locator};
@ -10,7 +10,7 @@ use crate::{config::{self, ConfigProvider}, omori_locator};
pub enum UiState { pub enum UiState {
Loading, Loading,
KeyRequired(String), KeyRequired(String),
PickGame(Result<PathBuf, String>, Vec<PathBuf>), PickGame(Result<PathBuf, String>, Vec<(PathBuf, u64)>),
Error(String) Error(String)
} }
@ -20,7 +20,9 @@ pub struct UiStateHolder {
} }
pub enum UiEvent { pub enum UiEvent {
SetKey(Vec<u8>) SetKey(Vec<u8>),
UsePath(PathBuf),
UseSteamPath
} }
pub struct AppThread { pub struct AppThread {
@ -49,7 +51,7 @@ impl AppThread {
} }
} }
fn pick_game(&mut self, provider: &ConfigProvider) -> anyhow::Result<()> { fn pick_game(&mut self, provider: &mut ConfigProvider) -> anyhow::Result<PathBuf> {
let steam_location = match omori_locator::get_omori_path() { let steam_location = match omori_locator::get_omori_path() {
Ok(l) => Ok(l), Ok(l) => Ok(l),
Err(e) => Err(String::from(format!("{:#}", e))) Err(e) => Err(String::from(format!("{:#}", e)))
@ -59,7 +61,20 @@ impl AppThread {
self.commit(UiState::PickGame(steam_location.clone(), location_history.clone())); self.commit(UiState::PickGame(steam_location.clone(), location_history.clone()));
Ok(()) loop {
match self.ui_event_channel.recv()? {
UiEvent::UsePath(path) => {
provider.set_game_path_used(&path)?;
self.commit(UiState::Loading);
return Ok(path);
},
UiEvent::UseSteamPath => {
self.commit(UiState::Loading);
return Ok(steam_location.expect("The steam location was not set even if the UI told us to use it. This should never happen"));
}
_ => {}
}
}
} }
pub fn create(state_holder: &UiStateHolder, context: &egui::Context, ui_event_channel: &Receiver<UiEvent>) -> AppThread { pub fn create(state_holder: &UiStateHolder, context: &egui::Context, ui_event_channel: &Receiver<UiEvent>) -> AppThread {
@ -87,7 +102,8 @@ impl AppThread {
config_provider.set_key(&self.decryption_key)?; config_provider.set_key(&self.decryption_key)?;
self.pick_game(&config_provider); let game_path = self.pick_game(&mut config_provider)?;
info!("Will use {:?}", game_path);
Ok(()) Ok(())
} }

View File

@ -66,11 +66,11 @@ impl ConfigProvider {
Ok(()) Ok(())
} }
pub fn get_game_paths(&self) -> anyhow::Result<Vec<PathBuf>> { pub fn get_game_paths(&self) -> anyhow::Result<Vec<(PathBuf, u64)>> {
let mut paths: Vec<(&PathBuf, &u64)> = self.config.recently_used_game_paths.iter().collect(); let mut paths: Vec<(&PathBuf, &u64)> = self.config.recently_used_game_paths.iter().collect();
paths.sort_by(|a, b| a.1.cmp(b.1)); paths.sort_by(|a, b| a.1.cmp(b.1).reverse());
Ok(paths.iter().map(|v| v.0.clone()).collect()) Ok(paths.iter().map(|v| (v.0.clone(), *v.1)).collect())
} }
pub fn set_game_path_used(&mut self, which: &PathBuf) -> anyhow::Result<()> { pub fn set_game_path_used(&mut self, which: &PathBuf) -> anyhow::Result<()> {

View File

@ -2,13 +2,14 @@ mod omori_locator;
mod app_logic; mod app_logic;
mod config; mod config;
use std::{sync::Arc, thread, time::Duration}; use std::{sync::Arc, thread, time::{Duration, SystemTime}};
use app_logic::{AppThread, UiEvent, UiState, UiStateHolder}; use app_logic::{AppThread, UiEvent, UiState, UiStateHolder};
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use eframe::egui; use eframe::egui;
use egui::{mutex::{Mutex, RwLock}, Align, Layout, RichText, ThemePreference}; use egui::{mutex::{Mutex, RwLock}, Align, Layout, RichText, ThemePreference};
use egui_alignments::{center_horizontal, center_vertical, top_horizontal, Aligner}; use egui_alignments::{center_horizontal, center_vertical, top_horizontal, Aligner};
use egui_extras::{Column, TableBuilder};
use egui_modal::Modal; use egui_modal::Modal;
use sha2::Digest; use sha2::Digest;
@ -39,6 +40,9 @@ fn main() -> anyhow::Result<()> {
eframe::run_native("TundleBool", options, Box::new(|cc| { eframe::run_native("TundleBool", options, Box::new(|cc| {
cc.egui_ctx.set_theme(ThemePreference::System); cc.egui_ctx.set_theme(ThemePreference::System);
cc.egui_ctx.style_mut(|style| {
style.interaction.selectable_labels = false;
});
let mut app_thread = AppThread::create(&app_state, &cc.egui_ctx, &receiver); let mut app_thread = AppThread::create(&app_state, &cc.egui_ctx, &receiver);
@ -164,7 +168,9 @@ impl eframe::App for Application {
Ok(path) => { Ok(path) => {
ui.label(RichText::new(format!("Found game at:\n{}", path.display())).italics()); ui.label(RichText::new(format!("Found game at:\n{}", path.display())).italics());
ui.separator(); ui.separator();
ui.button(RichText::new("Use Steam version").size(16.0)); if ui.button(RichText::new("Use Steam version").size(16.0)).clicked() {
self.sender.send(UiEvent::UseSteamPath).expect("Failed to send");
}
}, },
Err(e) => { Err(e) => {
ui.label(RichText::new(format!("Failed to find game:\n{}", e)).color(ui.visuals().error_fg_color)); ui.label(RichText::new(format!("Failed to find game:\n{}", e)).color(ui.visuals().error_fg_color));
@ -175,11 +181,44 @@ impl eframe::App for Application {
columns[1].vertical(|ui| { columns[1].vertical(|ui| {
ui.label(RichText::new("Custom").size(24.0)); ui.label(RichText::new("Custom").size(24.0));
if ui.button(RichText::new("Pick game location").size(16.0)).clicked() { if ui.button(RichText::new("Pick game location").size(16.0)).clicked() {
rfd::FileDialog::new().pick_folder(); match rfd::FileDialog::new().pick_folder() {
invalid_path_modal.open(); Some(path) => {
if omori_locator::validate_omori_installation(&path) {
self.sender.send(UiEvent::UsePath(path)).expect("Failed to send");
} else {
invalid_path_modal.open();
}
},
None => {}
}
} }
ui.separator(); ui.separator();
if others.len() > 0 { if others.len() > 0 {
ui.label("History of game locations");
TableBuilder::new(ui)
.striped(true)
.column(Column::remainder())
.column(Column::auto())
.body(|body| {
body.rows(20.0, others.len(), |mut row| {
let item = &others[row.index()];
let dt =
(item.1 as i64) -
(SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).expect("Failed to get the time").as_secs() as i64);
let dt = chrono::Duration::from(chrono::TimeDelta::seconds(dt as i64));
row.col(|ui| {
ui.label(format!("{}", item.0.display()));
});
row.col(|ui| {
ui.label(format!("{}", chrono_humanize::HumanTime::from(dt)));
});
});
});
} else { } else {
ui.label("Once you use a custom location, it will be remembered here."); ui.label("Once you use a custom location, it will be remembered here.");