feat(ui): added environment settings page

Added Environment settings page where you can specify command which
will be used to run the game, and manage
its environment variables
This commit is contained in:
Observer KRypt0n_ 2023-02-26 19:15:18 +02:00
parent 6f794947b9
commit 3b40ce75e6
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
9 changed files with 240 additions and 14 deletions

View file

@ -0,0 +1,7 @@
environment = Environment
game-command = Game command
game-command-description = Command used to launch the game. Placeholder %command% is generated automatically by the launcher. For example: gamemoderun '%command%'
new-variable = New variable
name = Name
value = Value
add = Add

View file

@ -0,0 +1,7 @@
environment = Environment
game-command = Game command
game-command-description = Command used to launch the game. Placeholder %command% is generated automatically by the launcher. For example: gamemoderun '%command%'
new-variable = New variable
name = Name
value = Value
add = Add

View file

@ -0,0 +1,7 @@
environment = Окружение
game-command = Команда запуска
game-command-description = Команда, используемая для запуска игры. Ключ %command% генерируется лаунчером автоматически. Например: gamemoderun '%command%'
new-variable = Новая переменная
name = Имя
value = Значение
add = Добавить

View file

@ -151,7 +151,7 @@ fn main() {
", BACKGROUND_FILE.to_string_lossy())); ", BACKGROUND_FILE.to_string_lossy()));
// Set UI language // Set UI language
let lang = config::get().unwrap().launcher.language.parse().expect("Wrong language format used in config"); let lang = CONFIG.launcher.language.parse().expect("Wrong language format used in config");
i18n::set_lang(lang).expect("Failed to set launcher language"); i18n::set_lang(lang).expect("Failed to set launcher language");

View file

@ -200,20 +200,18 @@ impl SimpleAsyncComponent for DefaultPathsApp {
root: Self::Root, root: Self::Root,
_sender: AsyncComponentSender<Self>, _sender: AsyncComponentSender<Self>,
) -> AsyncComponentParts<Self> { ) -> AsyncComponentParts<Self> {
let config = config::get().unwrap_or_default();
let model = Self { let model = Self {
show_additional: false, show_additional: false,
launcher: LAUNCHER_FOLDER.to_path_buf(), launcher: LAUNCHER_FOLDER.to_path_buf(),
runners: config.game.wine.builds, runners: CONFIG.game.wine.builds.clone(),
dxvks: config.game.dxvk.builds, dxvks: CONFIG.game.dxvk.builds.clone(),
prefix: config.game.wine.prefix, prefix: CONFIG.game.wine.prefix.clone(),
game: config.game.path, game: CONFIG.game.path.clone(),
patch: config.patch.path, patch: CONFIG.patch.path.clone(),
#[allow(clippy::or_fun_call)] #[allow(clippy::or_fun_call)]
temp: config.launcher.temp.unwrap_or(PathBuf::from("/tmp")) temp: CONFIG.launcher.temp.clone().unwrap_or(PathBuf::from("/tmp"))
}; };
let widgets = view_output!(); let widgets = view_output!();

View file

@ -411,8 +411,4 @@ impl SimpleAsyncComponent for EnhancementsApp {
AsyncComponentParts { model, widgets } AsyncComponentParts { model, widgets }
} }
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
tracing::debug!("Called enhancements settings event: {:?}", msg);
}
} }

View file

@ -0,0 +1,203 @@
use relm4::factory::AsyncFactoryVecDeque;
use relm4::prelude::*;
use relm4::component::*;
use relm4::factory::*;
use adw::prelude::*;
use anime_launcher_sdk::config;
use crate::i18n::tr;
use crate::*;
#[derive(Debug)]
struct Variable {
key: String,
value: String
}
#[relm4::factory(async)]
impl AsyncFactoryComponent for Variable {
type Init = (String, String);
type Input = EnvironmentMsg;
type Output = EnvironmentMsg;
type CommandOutput = ();
type ParentInput = EnvironmentMsg;
type ParentWidget = adw::PreferencesGroup;
view! {
root = adw::ActionRow {
set_title: &self.key,
set_subtitle: &self.value,
add_suffix = &gtk::Button {
set_icon_name: "user-trash-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.input(EnvironmentMsg::Remove(index.clone()));
}
}
}
}
fn output_to_parent_input(output: Self::Output) -> Option<Self::ParentInput> {
Some(output)
}
async fn init_model(
init: Self::Init,
_index: &DynamicIndex,
_sender: AsyncFactorySender<Self>,
) -> Self {
Self {
key: init.0,
value: init.1
}
}
async fn update(&mut self, msg: Self::Input, sender: AsyncFactorySender<Self>) {
sender.output(msg);
}
}
pub struct EnvironmentApp {
variables: AsyncFactoryVecDeque<Variable>,
name: gtk::Entry,
value: gtk::Entry
}
#[derive(Debug, Clone)]
pub enum EnvironmentMsg {
Add,
Remove(DynamicIndex)
}
#[relm4::component(async, pub)]
impl SimpleAsyncComponent for EnvironmentApp {
type Init = ();
type Input = EnvironmentMsg;
type Output = ();
view! {
adw::PreferencesPage {
set_title: &tr("environment"),
set_icon_name: Some("document-properties"),
add = &adw::PreferencesGroup {
set_title: &tr("game-command"),
set_description: Some(&tr("game-command-description")),
adw::EntryRow {
set_title: "%command%",
set_text: CONFIG.game.command.as_ref().unwrap_or(&String::new()),
connect_changed => |entry| {
if let Ok(mut config) = config::get() {
let command = entry.text().to_string();
config.game.command = if command.is_empty() {
None
} else {
Some(command)
};
config::update(config);
}
}
}
},
add = &adw::PreferencesGroup {
set_title: &tr("new-variable"),
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 8,
#[local_ref]
name -> gtk::Entry {
set_placeholder_text: Some(&tr("name"))
},
#[local_ref]
value -> gtk::Entry {
set_placeholder_text: Some(&tr("value")),
set_hexpand: true
}
},
gtk::Button {
set_label: &tr("add"),
set_margin_top: 8,
set_halign: gtk::Align::Start,
connect_clicked => EnvironmentMsg::Add
}
},
#[local_ref]
add = variables -> adw::PreferencesGroup {}
}
}
async fn init(
_init: Self::Init,
root: Self::Root,
sender: AsyncComponentSender<Self>,
) -> AsyncComponentParts<Self> {
tracing::info!("Initializing environment settings");
let mut model = Self {
variables: AsyncFactoryVecDeque::new(adw::PreferencesGroup::new(), sender.input_sender()),
name: gtk::Entry::new(),
value: gtk::Entry::new()
};
for (name, value) in &CONFIG.game.environment {
model.variables.guard().push_back((name.clone(), value.clone()));
}
let variables = model.variables.widget();
let name = &model.name;
let value = &model.value;
let widgets = view_output!();
AsyncComponentParts { model, widgets }
}
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg {
EnvironmentMsg::Add => {
if let Ok(mut config) = config::get() {
let name = self.name.text().to_string();
let value = self.value.text().to_string();
config.game.environment.insert(name.clone(), value.clone());
config::update(config);
self.variables.guard().push_back((name, value));
}
}
EnvironmentMsg::Remove(index) => {
if let Ok(mut config) = config::get() {
if let Some(var) = self.variables.guard().get(index.current_index()) {
config.game.environment.remove(&var.key);
config::update(config);
}
self.variables.guard().remove(index.current_index());
}
}
}
}
}

View file

@ -12,12 +12,14 @@ use crate::i18n::tr;
use super::general::*; use super::general::*;
use super::enhancements::*; use super::enhancements::*;
use super::environment::*;
pub static mut PREFERENCES_WINDOW: Option<adw::PreferencesWindow> = None; pub static mut PREFERENCES_WINDOW: Option<adw::PreferencesWindow> = None;
pub struct PreferencesApp { pub struct PreferencesApp {
general: AsyncController<GeneralApp>, general: AsyncController<GeneralApp>,
enhancements: AsyncController<EnhancementsApp> enhancements: AsyncController<EnhancementsApp>,
environment: AsyncController<EnvironmentApp>
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -53,6 +55,7 @@ impl SimpleAsyncComponent for PreferencesApp {
add = model.general.widget(), add = model.general.widget(),
add = model.enhancements.widget(), add = model.enhancements.widget(),
add = model.environment.widget(),
connect_close_request[sender] => move |_| { connect_close_request[sender] => move |_| {
if let Err(err) = anime_launcher_sdk::config::flush() { if let Err(err) = anime_launcher_sdk::config::flush() {
@ -80,6 +83,10 @@ impl SimpleAsyncComponent for PreferencesApp {
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
enhancements: EnhancementsApp::builder() enhancements: EnhancementsApp::builder()
.launch(())
.detach(),
environment: EnvironmentApp::builder()
.launch(()) .launch(())
.detach() .detach()
}; };

View file

@ -1,3 +1,4 @@
pub mod main; pub mod main;
pub mod general; pub mod general;
pub mod enhancements; pub mod enhancements;
pub mod environment;