mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2024-12-18 16:01: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]
|
[dependencies]
|
||||||
gtk4 = "0.4"
|
gtk4 = "0.4"
|
||||||
libadwaita = "0.1"
|
libadwaita = "0.1"
|
||||||
|
|
||||||
anime-game-core = { path = "anime-game-core", features = ["all"] }
|
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;
|
orientation: vertical;
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
|
|
||||||
name: "main";
|
|
||||||
|
|
||||||
Adw.HeaderBar {
|
Adw.HeaderBar {
|
||||||
title-widget: Adw.WindowTitle {
|
title-widget: Adw.WindowTitle {
|
||||||
title: "An Anime Game Launcher";
|
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 {
|
Adw.ExpanderRow {
|
||||||
title: "Proton-GE";
|
title: "Proton-GE";
|
||||||
|
subtitle: "This version includes its own DXVK builds and you can use DXVK_ASYNC variable";
|
||||||
|
|
||||||
Adw.ActionRow {
|
Adw.ActionRow {
|
||||||
title: "7-16";
|
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::*};
|
use libadwaita::{self as adw, prelude::*};
|
||||||
|
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
pub mod lib;
|
||||||
|
|
||||||
use ui::MainApp;
|
use ui::MainApp;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
use gtk4::{self as gtk, prelude::*};
|
use gtk4::{self as gtk, prelude::*};
|
||||||
use libadwaita::{self as adw, prelude::*};
|
use libadwaita::{self as adw, prelude::*};
|
||||||
|
|
||||||
use super::get_object;
|
use super::{get_object, add_action};
|
||||||
use super::preferences::PreferencesStack;
|
use super::preferences::PreferencesStack;
|
||||||
|
|
||||||
|
use crate::lib::game;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub window: adw::ApplicationWindow,
|
pub window: adw::ApplicationWindow,
|
||||||
pub leaflet: adw::Leaflet,
|
pub leaflet: adw::Leaflet,
|
||||||
pub launch_game: adw::SplitButton,
|
pub launch_game: adw::SplitButton,
|
||||||
pub open_preferences: gtk::Button
|
pub open_preferences: gtk::Button,
|
||||||
|
pub toast_overlay: adw::ToastOverlay
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
@ -21,7 +25,8 @@ impl App {
|
||||||
window: get_object(&builder, "window")?,
|
window: get_object(&builder, "window")?,
|
||||||
leaflet: get_object(&builder, "leaflet")?,
|
leaflet: get_object(&builder, "leaflet")?,
|
||||||
launch_game: get_object(&builder, "launch_game")?,
|
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
|
// Add preferences page to the leaflet
|
||||||
|
@ -35,9 +40,50 @@ impl App {
|
||||||
leaflet.navigate(adw::NavigationDirection::Back);
|
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
|
// Bind app to the window
|
||||||
result.window.set_application(Some(app));
|
result.window.set_application(Some(app));
|
||||||
|
|
||||||
Ok(result)
|
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 gtk4::{self as gtk, prelude::*};
|
||||||
|
use libadwaita::{self as adw, prelude::*};
|
||||||
|
|
||||||
mod main;
|
mod main;
|
||||||
mod preferences;
|
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))
|
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