mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2024-12-18 07:51:47 +03:00
Several changes
- added `add_action` function to add actions to widgets - added `MainApp::toast_error` method to show toast with some error - added `lib::game` mod with `run` function to run the game - added `lib::consts` mod with constant values like config file path - added `lib::config` mod to control config file - added warning subtitle to `Proton-GE` wine version selector
This commit is contained in:
parent
48d354cc4c
commit
1eed56e53a
10 changed files with 241 additions and 5 deletions
|
@ -10,4 +10,10 @@ build = "build.rs"
|
|||
[dependencies]
|
||||
gtk4 = "0.4"
|
||||
libadwaita = "0.1"
|
||||
|
||||
anime-game-core = { path = "anime-game-core", features = ["all"] }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.5.9"
|
||||
|
||||
dirs = "4.0.0"
|
||||
|
|
|
@ -18,8 +18,6 @@ Adw.ApplicationWindow window {
|
|||
orientation: vertical;
|
||||
hexpand: true;
|
||||
|
||||
name: "main";
|
||||
|
||||
Adw.HeaderBar {
|
||||
title-widget: Adw.WindowTitle {
|
||||
title: "An Anime Game Launcher";
|
||||
|
@ -72,6 +70,8 @@ Adw.ApplicationWindow window {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ToastOverlay toast_overlay {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -91,6 +91,7 @@ Adw.PreferencesPage general_page {
|
|||
|
||||
Adw.ExpanderRow {
|
||||
title: "Proton-GE";
|
||||
subtitle: "This version includes its own DXVK builds and you can use DXVK_ASYNC variable";
|
||||
|
||||
Adw.ActionRow {
|
||||
title: "7-16";
|
||||
|
|
88
src/lib/config.rs
Normal file
88
src/lib/config.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use std::collections::HashMap;
|
||||
use std::{fs::File, io::Read};
|
||||
use std::path::Path;
|
||||
use std::io::{Error, ErrorKind, Write};
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use super::consts::config_file;
|
||||
|
||||
pub fn get() -> Result<Config, Error> {
|
||||
match config_file() {
|
||||
Some(path) => {
|
||||
// Try to read config if the file exists
|
||||
if Path::new(&path).exists() {
|
||||
let mut file = File::open(path)?;
|
||||
let mut toml = String::new();
|
||||
|
||||
file.read_to_string(&mut toml)?;
|
||||
|
||||
match toml::from_str::<Config>(&toml) {
|
||||
Ok(toml) => Ok(toml),
|
||||
Err(err) => Err(Error::new(ErrorKind::InvalidData, format!("Failed to decode data from toml format: {}", err.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise create default config file
|
||||
else {
|
||||
update(Config::default())?;
|
||||
|
||||
Ok(Config::default())
|
||||
}
|
||||
},
|
||||
None => Err(Error::new(ErrorKind::NotFound, format!("Failed to get config file path")))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(config: Config) -> Result<(), Error> {
|
||||
match config_file() {
|
||||
Some(path) => {
|
||||
let mut file = File::create(&path)?;
|
||||
|
||||
match toml::to_string(&config) {
|
||||
Ok(toml) => {
|
||||
file.write_all(&mut toml.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
Err(err) => Err(Error::new(ErrorKind::InvalidData, format!("Failed to encode data into toml format: {}", err.to_string())))
|
||||
}
|
||||
},
|
||||
None => Err(Error::new(ErrorKind::NotFound, format!("Failed to get config file path")))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Config {
|
||||
pub paths: Paths,
|
||||
pub patch: Patch,
|
||||
pub wine: Wine
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Paths {
|
||||
pub game: String,
|
||||
pub patch: String
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Patch {
|
||||
pub hosts: Vec<String>
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Wine {
|
||||
pub prefix: String,
|
||||
pub executable: String,
|
||||
pub environment: HashMap<String, String>
|
||||
}
|
||||
|
||||
impl Default for Wine {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
prefix: String::new(),
|
||||
executable: String::new(),
|
||||
environment: HashMap::new()
|
||||
}
|
||||
}
|
||||
}
|
38
src/lib/consts.rs
Normal file
38
src/lib/consts.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
static mut LAUNCHER_DIR: Option<Option<String>> = None;
|
||||
static mut CONFIG_FILE: Option<Option<String>> = None;
|
||||
|
||||
pub fn launcher_dir() -> Option<String> {
|
||||
unsafe {
|
||||
match &LAUNCHER_DIR {
|
||||
Some(value) => value.clone(),
|
||||
None => {
|
||||
let value = match dirs::data_dir() {
|
||||
Some(dir) => Some(format!("{}/anime-game-launcher", dir.to_string_lossy())),
|
||||
None => None
|
||||
};
|
||||
|
||||
LAUNCHER_DIR = Some(value.clone());
|
||||
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_file() -> Option<String> {
|
||||
unsafe {
|
||||
match &CONFIG_FILE {
|
||||
Some(value) => value.clone(),
|
||||
None => {
|
||||
let value = match launcher_dir() {
|
||||
Some(dir) => Some(format!("{}/config.toml", dir)),
|
||||
None => None
|
||||
};
|
||||
|
||||
CONFIG_FILE = Some(value.clone());
|
||||
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
src/lib/game.rs
Normal file
22
src/lib/game.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use std::io::{Error, ErrorKind};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use super::config;
|
||||
|
||||
pub fn run() -> Result<(), Error> {
|
||||
let config = config::get()?;
|
||||
|
||||
if Path::new(&config.paths.game).exists() {
|
||||
return Err(Error::new(ErrorKind::Other, "Game is not installed"));
|
||||
}
|
||||
|
||||
Command::new(config.wine.executable)
|
||||
.env("WINEPREFIX", &config.wine.prefix)
|
||||
.envs(config.wine.environment)
|
||||
.current_dir(config.paths.game)
|
||||
.arg("launcher.bat")
|
||||
.output()?;
|
||||
|
||||
Ok(())
|
||||
}
|
3
src/lib/mod.rs
Normal file
3
src/lib/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod consts;
|
||||
pub mod config;
|
||||
pub mod game;
|
|
@ -2,6 +2,7 @@ use gtk4::{self as gtk, prelude::*};
|
|||
use libadwaita::{self as adw, prelude::*};
|
||||
|
||||
pub mod ui;
|
||||
pub mod lib;
|
||||
|
||||
use ui::MainApp;
|
||||
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
use gtk4::{self as gtk, prelude::*};
|
||||
use libadwaita::{self as adw, prelude::*};
|
||||
|
||||
use super::get_object;
|
||||
use super::{get_object, add_action};
|
||||
use super::preferences::PreferencesStack;
|
||||
|
||||
use crate::lib::game;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct App {
|
||||
pub window: adw::ApplicationWindow,
|
||||
pub leaflet: adw::Leaflet,
|
||||
pub launch_game: adw::SplitButton,
|
||||
pub open_preferences: gtk::Button
|
||||
pub open_preferences: gtk::Button,
|
||||
pub toast_overlay: adw::ToastOverlay
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
@ -21,7 +25,8 @@ impl App {
|
|||
window: get_object(&builder, "window")?,
|
||||
leaflet: get_object(&builder, "leaflet")?,
|
||||
launch_game: get_object(&builder, "launch_game")?,
|
||||
open_preferences: get_object(&builder, "open_preferences")?
|
||||
open_preferences: get_object(&builder, "open_preferences")?,
|
||||
toast_overlay: get_object(&builder, "toast_overlay")?
|
||||
};
|
||||
|
||||
// Add preferences page to the leaflet
|
||||
|
@ -35,9 +40,50 @@ impl App {
|
|||
leaflet.navigate(adw::NavigationDirection::Back);
|
||||
});
|
||||
|
||||
// Launch game
|
||||
let app_copy = result.clone();
|
||||
|
||||
result.launch_game.connect_clicked(move |_| {
|
||||
// Display toast message if the game is failed to run
|
||||
if let Err(err) = game::run() {
|
||||
app_copy.toast_error("Failed to run game", err);
|
||||
}
|
||||
});
|
||||
|
||||
// Bind app to the window
|
||||
result.window.set_application(Some(app));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Show toast with `toast` title and `See message` button
|
||||
///
|
||||
/// This button will show message dialog with error message
|
||||
pub fn toast_error(&self, toast: &str, err: std::io::Error) {
|
||||
let toast = adw::Toast::new(toast);
|
||||
|
||||
toast.set_button_label(Some("See message"));
|
||||
toast.set_action_name(Some("see-message.see-message"));
|
||||
|
||||
let window_copy = self.window.clone();
|
||||
|
||||
// Show error message in a dialog window
|
||||
add_action(&self.toast_overlay, "see-message", move || {
|
||||
let dialog = gtk::MessageDialog::new(
|
||||
Some(&window_copy),
|
||||
gtk::DialogFlags::all(),
|
||||
gtk::MessageType::Info,
|
||||
gtk::ButtonsType::Close,
|
||||
&err.to_string()
|
||||
);
|
||||
|
||||
dialog.connect_response(move |dialog, _| {
|
||||
dialog.close();
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
});
|
||||
|
||||
self.toast_overlay.add_toast(&toast);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use gtk4::{self as gtk, prelude::*};
|
||||
use libadwaita::{self as adw, prelude::*};
|
||||
|
||||
mod main;
|
||||
mod preferences;
|
||||
|
@ -12,3 +13,33 @@ pub fn get_object<T: IsA<gtk::glib::Object>>(builder: >k::Builder, name: &str)
|
|||
None => Err(format!("Failed to parse object '{}'", name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Add action to widget
|
||||
///
|
||||
/// All the actions needs to be in some group. This function creates new group with the name of the action.
|
||||
/// This means that to add action to some widget you need to speify `name.name` as its name
|
||||
///
|
||||
/// ## Example:
|
||||
///
|
||||
/// ```
|
||||
/// let toast = libadwaita::Toast::new("Example toast");
|
||||
///
|
||||
/// toast.set_button_label(Some("Example button"));
|
||||
/// toast.set_action_name(Some("example-button.example-button"));
|
||||
///
|
||||
/// add_action(&toast, "example-button", || {
|
||||
/// println!("Hello, World!");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn add_action<T: IsA<gtk::Widget>, F: Fn() + 'static>(obj: &T, name: &str, closure: F) {
|
||||
let action_group = adw::gio::SimpleActionGroup::new();
|
||||
let action = adw::gio::SimpleAction::new(name, None);
|
||||
|
||||
obj.insert_action_group(name, Some(&action_group));
|
||||
|
||||
action.connect_activate(move |_, _| {
|
||||
closure();
|
||||
});
|
||||
|
||||
action_group.add_action(&action);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue