feat(ui): moved sandbox, environment and game settings into separate pages

This commit is contained in:
Observer KRypt0n_ 2023-05-20 21:43:56 +02:00
parent 3a418126c2
commit e520d8678b
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
9 changed files with 512 additions and 352 deletions

View file

@ -482,7 +482,7 @@ impl SimpleComponent for App {
#[watch]
set_sensitive: !model.disabled_buttons,
set_width_request: 48,
set_width_request: 44,
add_css_class: "circular",
set_icon_name: "emblem-system-symbolic",

View file

@ -4,6 +4,8 @@ use relm4::factory::*;
use adw::prelude::*;
use super::EnhancementsAppMsg;
use crate::i18n::tr;
use crate::*;
@ -16,10 +18,10 @@ struct Variable {
#[relm4::factory(async)]
impl AsyncFactoryComponent for Variable {
type Init = (String, String);
type Input = EnvironmentAppMsg;
type Output = EnvironmentAppMsg;
type Input = EnvironmentPageMsg;
type Output = EnvironmentPageMsg;
type CommandOutput = ();
type ParentInput = EnvironmentAppMsg;
type ParentInput = EnvironmentPageMsg;
type ParentWidget = adw::PreferencesGroup;
view! {
@ -33,7 +35,7 @@ impl AsyncFactoryComponent for Variable {
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.output(EnvironmentAppMsg::Remove(index.clone()));
sender.output(EnvironmentPageMsg::Remove(index.clone()));
}
}
}
@ -55,7 +57,7 @@ impl AsyncFactoryComponent for Variable {
}
}
pub struct EnvironmentApp {
pub struct EnvironmentPage {
variables: AsyncFactoryVecDeque<Variable>,
name_entry: adw::EntryRow,
@ -63,76 +65,95 @@ pub struct EnvironmentApp {
}
#[derive(Debug, Clone)]
pub enum EnvironmentAppMsg {
pub enum EnvironmentPageMsg {
Add,
Remove(DynamicIndex)
}
#[relm4::component(async, pub)]
impl SimpleAsyncComponent for EnvironmentApp {
impl SimpleAsyncComponent for EnvironmentPage {
type Init = ();
type Input = EnvironmentAppMsg;
type Output = ();
type Input = EnvironmentPageMsg;
type Output = EnhancementsAppMsg;
view! {
adw::PreferencesPage {
set_title: &tr("environment"),
set_icon_name: Some("document-properties-symbolic"),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
add = &adw::PreferencesGroup {
set_title: &tr("game-command"),
set_description: Some(&tr("game-command-description")),
adw::HeaderBar {
#[wrap(Some)]
set_title_widget = &adw::WindowTitle {
set_title: &tr("environment")
},
adw::EntryRow {
set_title: "%command%",
set_text: CONFIG.game.command.as_ref().unwrap_or(&String::new()).trim(),
pack_start = &gtk::Button {
set_icon_name: "go-previous-symbolic",
connect_changed => |entry| {
if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string();
config.game.command = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
connect_clicked[sender] => move |_| {
sender.output(EnhancementsAppMsg::OpenMainPage).unwrap();
}
}
},
add = &adw::PreferencesGroup {
set_title: &tr("new-variable"),
adw::PreferencesPage {
set_title: &tr("environment"),
set_icon_name: Some("document-properties-symbolic"),
#[wrap(Some)]
set_header_suffix = &gtk::Button {
add_css_class: "flat",
add = &adw::PreferencesGroup {
set_title: &tr("game-command"),
set_description: Some(&tr("game-command-description")),
set_valign: gtk::Align::Center,
adw::EntryRow {
set_title: "%command%",
set_text: CONFIG.game.command.as_ref().unwrap_or(&String::new()).trim(),
adw::ButtonContent {
set_icon_name: "list-add-symbolic",
set_label: &tr("add")
connect_changed => |entry| {
if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string();
config.game.command = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
}
}
},
add = &adw::PreferencesGroup {
set_title: &tr("new-variable"),
#[wrap(Some)]
set_header_suffix = &gtk::Button {
add_css_class: "flat",
set_valign: gtk::Align::Center,
adw::ButtonContent {
set_icon_name: "list-add-symbolic",
set_label: &tr("add")
},
connect_clicked => EnvironmentPageMsg::Add
},
connect_clicked => EnvironmentAppMsg::Add
#[local_ref]
name_entry -> adw::EntryRow {
set_title: &tr("name")
},
#[local_ref]
value_entry -> adw::EntryRow {
set_title: &tr("value")
}
},
#[local_ref]
name_entry -> adw::EntryRow {
set_title: &tr("name")
},
#[local_ref]
value_entry -> adw::EntryRow {
set_title: &tr("value")
}
},
#[local_ref]
add = variables -> adw::PreferencesGroup {}
add = variables -> adw::PreferencesGroup {}
}
}
}
@ -166,7 +187,7 @@ impl SimpleAsyncComponent for EnvironmentApp {
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg {
EnvironmentAppMsg::Add => {
EnvironmentPageMsg::Add => {
let name = self.name_entry.text().trim().to_string();
let value = self.value_entry.text().trim().to_string();
@ -184,7 +205,7 @@ impl SimpleAsyncComponent for EnvironmentApp {
}
}
EnvironmentAppMsg::Remove(index) => {
EnvironmentPageMsg::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);

View file

@ -7,7 +7,7 @@ use adw::prelude::*;
use anime_launcher_sdk::sessions::SessionsExt;
use anime_launcher_sdk::genshin::sessions::Sessions;
use super::main::PreferencesAppMsg;
use super::EnhancementsAppMsg;
use crate::i18n::tr;
use crate::*;
@ -21,10 +21,10 @@ struct GameSession {
#[relm4::factory(async)]
impl AsyncFactoryComponent for GameSession {
type Init = GameSession;
type Input = GameAppMsg;
type Output = GameAppMsg;
type Input = GamePageMsg;
type Output = GamePageMsg;
type CommandOutput = ();
type ParentInput = GameAppMsg;
type ParentInput = GamePageMsg;
type ParentWidget = adw::PreferencesGroup;
view! {
@ -45,7 +45,7 @@ impl AsyncFactoryComponent for GameSession {
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.output(GameAppMsg::UpdateSession(index.clone()));
sender.output(GamePageMsg::UpdateSession(index.clone()));
}
},
@ -58,7 +58,7 @@ impl AsyncFactoryComponent for GameSession {
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.output(GameAppMsg::RemoveSession(index.clone()));
sender.output(GamePageMsg::RemoveSession(index.clone()));
}
}
}
@ -77,7 +77,7 @@ impl AsyncFactoryComponent for GameSession {
}
}
pub struct GameApp {
pub struct GamePage {
sessions: AsyncFactoryVecDeque<GameSession>,
sessions_names: Vec<String>,
@ -87,7 +87,7 @@ pub struct GameApp {
}
#[derive(Debug, Clone)]
pub enum GameAppMsg {
pub enum GamePageMsg {
AddSession,
UpdateSession(DynamicIndex),
RemoveSession(DynamicIndex),
@ -96,50 +96,69 @@ pub enum GameAppMsg {
}
#[relm4::component(async, pub)]
impl SimpleAsyncComponent for GameApp {
impl SimpleAsyncComponent for GamePage {
type Init = ();
type Input = GameAppMsg;
type Output = PreferencesAppMsg;
type Input = GamePageMsg;
type Output = EnhancementsAppMsg;
view! {
adw::PreferencesPage {
set_title: &tr("game"),
set_icon_name: Some("applications-games-symbolic"),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
add = &adw::PreferencesGroup {
set_title: &tr("game-sessions"),
adw::HeaderBar {
#[wrap(Some)]
set_title_widget = &adw::WindowTitle {
set_title: &tr("game")
},
#[local_ref]
sessions_combo -> adw::ComboRow {
set_title: &tr("active-sessions"),
set_subtitle: &tr("active-session-description"),
pack_start = &gtk::Button {
set_icon_name: "go-previous-symbolic",
connect_selected_notify[sender] => move |row| {
if is_ready() {
sender.input(GameAppMsg::SetCurrent(row.selected()));
connect_clicked[sender] => move |_| {
sender.output(EnhancementsAppMsg::OpenMainPage).unwrap();
}
}
},
adw::PreferencesPage {
set_title: &tr("game"),
set_icon_name: Some("applications-games-symbolic"),
add = &adw::PreferencesGroup {
set_title: &tr("game-sessions"),
#[local_ref]
sessions_combo -> adw::ComboRow {
set_title: &tr("active-sessions"),
set_subtitle: &tr("active-session-description"),
connect_selected_notify[sender] => move |row| {
if is_ready() {
sender.input(GamePageMsg::SetCurrent(row.selected()));
}
}
}
}
},
add = &adw::PreferencesGroup {
#[local_ref]
session_name_entry -> adw::EntryRow {
set_title: &tr("name"),
add_suffix = &gtk::Button {
set_icon_name: "list-add-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
},
connect_clicked => GameAppMsg::AddSession
add = &adw::PreferencesGroup {
#[local_ref]
session_name_entry -> adw::EntryRow {
set_title: &tr("name"),
add_suffix = &gtk::Button {
set_icon_name: "list-add-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked => GamePageMsg::AddSession
}
}
}
},
#[local_ref]
add = sessions -> adw::PreferencesGroup {},
},
#[local_ref]
add = sessions -> adw::PreferencesGroup {},
}
}
}
@ -173,14 +192,14 @@ impl SimpleAsyncComponent for GameApp {
let widgets = view_output!();
sender.input(GameAppMsg::UpdateCombo);
sender.input(GamePageMsg::UpdateCombo);
AsyncComponentParts { model, widgets }
}
async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender<Self>) {
match msg {
GameAppMsg::AddSession => {
GamePageMsg::AddSession => {
let name = self.session_name_entry.text().trim().to_string();
if !name.is_empty() {
@ -194,46 +213,43 @@ impl SimpleAsyncComponent for GameApp {
description: None
});
sender.input(GameAppMsg::UpdateCombo);
sender.input(GamePageMsg::UpdateCombo);
}
#[allow(unused_must_use)]
Err(err) => {
sender.output(PreferencesAppMsg::Toast {
sender.output(EnhancementsAppMsg::Toast {
title: tr("game-session-add-failed"),
description: Some(err.to_string())
});
}).unwrap();
}
}
}
}
}
GameAppMsg::UpdateSession(index) => {
GamePageMsg::UpdateSession(index) => {
if let Some(session) = self.sessions.guard().get(index.current_index()) {
if let Ok(config) = Config::get() {
#[allow(unused_must_use)]
if let Err(err) = Sessions::update(session.name.clone(), config.get_wine_prefix_path()) {
sender.output(PreferencesAppMsg::Toast {
sender.output(EnhancementsAppMsg::Toast {
title: tr("game-session-update-failed"),
description: Some(err.to_string())
});
}).unwrap();
}
}
}
}
GameAppMsg::RemoveSession(index) => {
GamePageMsg::RemoveSession(index) => {
if let Some(session) = self.sessions.guard().get(index.current_index()) {
match Sessions::remove(&session.name) {
Ok(()) => sender.input(GameAppMsg::UpdateCombo),
Ok(()) => sender.input(GamePageMsg::UpdateCombo),
#[allow(unused_must_use)]
Err(err) => {
sender.output(PreferencesAppMsg::Toast {
sender.output(EnhancementsAppMsg::Toast {
title: tr("game-session-remove-failed"),
description: Some(err.to_string())
});
}).unwrap();
return;
}
@ -243,32 +259,30 @@ impl SimpleAsyncComponent for GameApp {
self.sessions.guard().remove(index.current_index());
}
GameAppMsg::SetCurrent(id) => {
GamePageMsg::SetCurrent(id) => {
if let Some(name) = self.sessions_names.get(id as usize) {
if let Ok(config) = Config::get() {
#[allow(unused_must_use)]
if let Err(err) = Sessions::set_current(name.to_owned()) {
sender.output(PreferencesAppMsg::Toast {
sender.output(EnhancementsAppMsg::Toast {
title: tr("game-session-set-current-failed"),
description: Some(err.to_string())
});
}).unwrap();
// Prevent session applying
return;
}
#[allow(unused_must_use)]
if let Err(err) = Sessions::apply(name.to_owned(), config.get_wine_prefix_path()) {
sender.output(PreferencesAppMsg::Toast {
sender.output(EnhancementsAppMsg::Toast {
title: tr("game-session-apply-failed"),
description: Some(err.to_string())
});
}).unwrap();
}
}
}
}
GameAppMsg::UpdateCombo => {
GamePageMsg::UpdateCombo => {
let sessions = Sessions::get_sessions().unwrap_or_default();
self.sessions_names = sessions.sessions.into_keys().collect::<Vec<String>>();

View file

@ -5,37 +5,102 @@ use adw::prelude::*;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::config::schema_blanks::prelude::*;
use anime_launcher_sdk::is_available;
pub mod game;
pub mod sandbox;
pub mod environment;
use game::*;
use sandbox::*;
use environment::*;
use crate::i18n::tr;
use crate::*;
use super::gamescope::*;
use super::main::PreferencesAppMsg;
pub struct EnhancementsApp {
gamescope: AsyncController<GamescopeApp>
gamescope: AsyncController<GamescopeApp>,
game_page: AsyncController<GamePage>,
sandbox_page: AsyncController<SandboxPage>,
environment_page: AsyncController<EnvironmentPage>
}
#[derive(Debug)]
pub enum EnhancementsAppMsg {
SetGamescopeParent(adw::PreferencesWindow),
OpenGamescope
OpenGamescope,
OpenMainPage,
OpenGameSettingsPage,
OpenSandboxSettingsPage,
OpenEnvironmentSettingsPage,
Toast {
title: String,
description: Option<String>
}
}
#[relm4::component(async, pub)]
impl SimpleAsyncComponent for EnhancementsApp {
type Init = ();
type Input = EnhancementsAppMsg;
type Output = ();
type Output = PreferencesAppMsg;
view! {
#[root]
adw::PreferencesPage {
set_title: &tr("enhancements"),
set_icon_name: Some("applications-graphics-symbolic"),
add = &adw::PreferencesGroup {
set_title: "Options",
adw::ActionRow {
set_title: "Game settings",
set_subtitle: "Manage in-game settings and account session",
add_suffix = &gtk::Image {
set_icon_name: Some("go-next-symbolic")
},
set_activatable: true,
connect_activated => EnhancementsAppMsg::OpenGameSettingsPage
},
adw::ActionRow {
set_title: "Sandbox settings",
set_subtitle: "Run the game in a bubblewrap sandbox, similar to what Flatpak does",
add_suffix = &gtk::Image {
set_icon_name: Some("go-next-symbolic")
},
set_activatable: true,
connect_activated => EnhancementsAppMsg::OpenSandboxSettingsPage
},
adw::ActionRow {
set_title: "Environment settings",
set_subtitle: "Specify environment variables and game launching command",
add_suffix = &gtk::Image {
set_icon_name: Some("go-next-symbolic")
},
set_activatable: true,
connect_activated => EnhancementsAppMsg::OpenEnvironmentSettingsPage
}
},
add = &adw::PreferencesGroup {
set_title: &tr("wine"),
@ -498,7 +563,16 @@ impl SimpleAsyncComponent for EnhancementsApp {
}
},
}
}
},
#[local_ref]
game_page -> gtk::Box {},
#[local_ref]
sandbox_page -> gtk::Box {},
#[local_ref]
environment_page -> gtk::Box {}
}
async fn init(
@ -511,15 +585,31 @@ impl SimpleAsyncComponent for EnhancementsApp {
let model = Self {
gamescope: GamescopeApp::builder()
.launch(())
.detach()
.detach(),
game_page: GamePage::builder()
.launch(())
.forward(sender.input_sender(), std::convert::identity),
sandbox_page: SandboxPage::builder()
.launch(())
.forward(sender.input_sender(), std::convert::identity),
environment_page: EnvironmentPage::builder()
.launch(())
.forward(sender.input_sender(), std::convert::identity)
};
let game_page = model.game_page.widget();
let sandbox_page = model.sandbox_page.widget();
let environment_page = model.environment_page.widget();
let widgets = view_output!();
AsyncComponentParts { model, widgets }
}
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender<Self>) {
match msg {
EnhancementsAppMsg::SetGamescopeParent(parent) => {
self.gamescope.widget().set_transient_for(Some(&parent));
@ -528,6 +618,41 @@ impl SimpleAsyncComponent for EnhancementsApp {
EnhancementsAppMsg::OpenGamescope => {
self.gamescope.widget().present();
}
EnhancementsAppMsg::OpenMainPage => unsafe {
PREFERENCES_WINDOW.as_ref()
.unwrap_unchecked()
.widget()
.close_subpage();
}
EnhancementsAppMsg::OpenGameSettingsPage => unsafe {
PREFERENCES_WINDOW.as_ref()
.unwrap_unchecked()
.widget()
.present_subpage(self.game_page.widget());
}
EnhancementsAppMsg::OpenSandboxSettingsPage => unsafe {
PREFERENCES_WINDOW.as_ref()
.unwrap_unchecked()
.widget()
.present_subpage(self.sandbox_page.widget());
}
EnhancementsAppMsg::OpenEnvironmentSettingsPage => unsafe {
PREFERENCES_WINDOW.as_ref()
.unwrap_unchecked()
.widget()
.present_subpage(self.environment_page.widget());
}
EnhancementsAppMsg::Toast { title, description } => {
sender.output(PreferencesAppMsg::Toast {
title,
description
}).unwrap();
}
}
}
}

View file

@ -6,7 +6,7 @@ use adw::prelude::*;
use anime_launcher_sdk::is_available;
use super::main::PreferencesAppMsg;
use super::EnhancementsAppMsg;
use crate::i18n::tr;
use crate::*;
@ -22,10 +22,10 @@ macro_rules! impl_directory {
#[relm4::factory(async)]
impl AsyncFactoryComponent for $name {
type Init = (String, Option<String>);
type Input = SandboxAppMsg;
type Output = SandboxAppMsg;
type Input = SandboxPageMsg;
type Output = SandboxPageMsg;
type CommandOutput = ();
type ParentInput = SandboxAppMsg;
type ParentInput = SandboxPageMsg;
type ParentWidget = adw::PreferencesGroup;
view! {
@ -66,11 +66,11 @@ macro_rules! impl_directory {
}
}
impl_directory!(PrivateDirectory, SandboxAppMsg::RemovePrivate);
impl_directory!(SharedDirectory, SandboxAppMsg::RemoveShared);
impl_directory!(SymlinkPath, SandboxAppMsg::RemoveSymlink);
impl_directory!(PrivateDirectory, SandboxPageMsg::RemovePrivate);
impl_directory!(SharedDirectory, SandboxPageMsg::RemoveShared);
impl_directory!(SymlinkPath, SandboxPageMsg::RemoveSymlink);
pub struct SandboxApp {
pub struct SandboxPage {
private_paths: AsyncFactoryVecDeque<PrivateDirectory>,
shared_paths: AsyncFactoryVecDeque<SharedDirectory>,
symlink_paths: AsyncFactoryVecDeque<SymlinkPath>,
@ -86,7 +86,7 @@ pub struct SandboxApp {
}
#[derive(Debug, Clone)]
pub enum SandboxAppMsg {
pub enum SandboxPageMsg {
AddPrivate,
RemovePrivate(DynamicIndex),
@ -98,214 +98,233 @@ pub enum SandboxAppMsg {
}
#[relm4::component(async, pub)]
impl SimpleAsyncComponent for SandboxApp {
impl SimpleAsyncComponent for SandboxPage {
type Init = ();
type Input = SandboxAppMsg;
type Output = PreferencesAppMsg;
type Input = SandboxPageMsg;
type Output = EnhancementsAppMsg;
view! {
adw::PreferencesPage {
set_title: &tr("sandbox"),
set_icon_name: Some("folder-symbolic"),
set_sensitive: is_available("bwrap"),
add = &adw::PreferencesGroup {
set_title: &tr("sandbox"),
set_description: Some(&tr("sandbox-description")),
adw::ActionRow {
set_title: &tr("enable-sandboxing"),
set_subtitle: &tr("enable-sandboxing-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.sandbox.enabled,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.sandbox.enabled = switch.state();
Config::update(config);
}
}
}
}
},
adw::ActionRow {
set_title: &tr("hide-home-directory"),
set_subtitle: &tr("hide-home-directory-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.sandbox.isolate_home,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.sandbox.isolate_home = switch.state();
Config::update(config);
}
}
}
}
},
adw::EntryRow {
set_title: &tr("hostname"),
set_text: CONFIG.sandbox.hostname.as_ref().unwrap_or(&String::new()).trim(),
connect_changed => |entry| {
if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string();
config.sandbox.hostname = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
}
},
adw::EntryRow {
set_title: &tr("additional-arguments"),
set_text: CONFIG.sandbox.args.as_ref().unwrap_or(&String::new()).trim(),
connect_changed => |entry| {
if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string();
config.sandbox.args = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
},
add_suffix = &gtk::Button {
set_icon_name: "dialog-information-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender] => move |_| {
if let Err(err) = open::that("https://man.archlinux.org/man/bwrap.1") {
sender.output(PreferencesAppMsg::Toast {
title: tr("documentation-url-open-failed"),
description: Some(err.to_string())
}).unwrap();
}
}
}
}
},
add = &adw::PreferencesGroup {
set_title: &tr("private-directories"),
set_description: Some(&tr("private-directories-description")),
#[local_ref]
private_path_entry -> adw::EntryRow {
set_title: &tr("path"),
add_suffix = &gtk::Button {
set_icon_name: "list-add-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked => SandboxAppMsg::AddPrivate
}
}
},
#[local_ref]
add = private_paths -> adw::PreferencesGroup {},
add = &adw::PreferencesGroup {
set_title: &tr("shared-directories"),
set_description: Some(&tr("shared-directories-description")),
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
adw::HeaderBar {
#[wrap(Some)]
set_header_suffix = &gtk::Button {
add_css_class: "flat",
set_title_widget = &adw::WindowTitle {
set_title: &tr("sandbox")
},
set_valign: gtk::Align::Center,
pack_start = &gtk::Button {
set_icon_name: "go-previous-symbolic",
adw::ButtonContent {
set_icon_name: "list-add-symbolic",
set_label: &tr("add")
connect_clicked[sender] => move |_| {
sender.output(EnhancementsAppMsg::OpenMainPage).unwrap();
}
}
},
adw::PreferencesPage {
set_title: &tr("sandbox"),
set_icon_name: Some("folder-symbolic"),
set_sensitive: is_available("bwrap"),
add = &adw::PreferencesGroup {
set_title: &tr("sandbox"),
set_description: Some(&tr("sandbox-description")),
adw::ActionRow {
set_title: &tr("enable-sandboxing"),
set_subtitle: &tr("enable-sandboxing-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.sandbox.enabled,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.sandbox.enabled = switch.state();
Config::update(config);
}
}
}
}
},
connect_clicked => SandboxAppMsg::AddShared
adw::ActionRow {
set_title: &tr("hide-home-directory"),
set_subtitle: &tr("hide-home-directory-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.sandbox.isolate_home,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.sandbox.isolate_home = switch.state();
Config::update(config);
}
}
}
}
},
adw::EntryRow {
set_title: &tr("hostname"),
set_text: CONFIG.sandbox.hostname.as_ref().unwrap_or(&String::new()).trim(),
connect_changed => |entry| {
if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string();
config.sandbox.hostname = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
}
},
adw::EntryRow {
set_title: &tr("additional-arguments"),
set_text: CONFIG.sandbox.args.as_ref().unwrap_or(&String::new()).trim(),
connect_changed => |entry| {
if let Ok(mut config) = Config::get() {
let command = entry.text().trim().to_string();
config.sandbox.args = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
},
add_suffix = &gtk::Button {
set_icon_name: "dialog-information-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender] => move |_| {
if let Err(err) = open::that("https://man.archlinux.org/man/bwrap.1") {
sender.output(EnhancementsAppMsg::Toast {
title: tr("documentation-url-open-failed"),
description: Some(err.to_string())
}).unwrap();
}
}
}
}
},
#[local_ref]
shared_path_from_entry -> adw::EntryRow {
set_title: &tr("original-path")
},
#[local_ref]
shared_path_to_entry -> adw::EntryRow {
set_title: &tr("new-path")
},
adw::ActionRow {
set_title: &tr("read-only"),
set_subtitle: &tr("read-only-description"),
add = &adw::PreferencesGroup {
set_title: &tr("private-directories"),
set_description: Some(&tr("private-directories-description")),
#[local_ref]
add_suffix = read_only_switch -> gtk::Switch {
set_valign: gtk::Align::Center
private_path_entry -> adw::EntryRow {
set_title: &tr("path"),
add_suffix = &gtk::Button {
set_icon_name: "list-add-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked => SandboxPageMsg::AddPrivate
}
}
}
},
},
#[local_ref]
add = shared_paths -> adw::PreferencesGroup {},
#[local_ref]
add = private_paths -> adw::PreferencesGroup {},
add = &adw::PreferencesGroup {
set_title: &tr("symlinks"),
set_description: Some(&tr("symlinks-description")),
add = &adw::PreferencesGroup {
set_title: &tr("shared-directories"),
set_description: Some(&tr("shared-directories-description")),
#[wrap(Some)]
set_header_suffix = &gtk::Button {
add_css_class: "flat",
#[wrap(Some)]
set_header_suffix = &gtk::Button {
add_css_class: "flat",
set_valign: gtk::Align::Center,
set_valign: gtk::Align::Center,
adw::ButtonContent {
set_icon_name: "list-add-symbolic",
set_label: &tr("add")
adw::ButtonContent {
set_icon_name: "list-add-symbolic",
set_label: &tr("add")
},
connect_clicked => SandboxPageMsg::AddShared
},
connect_clicked => SandboxAppMsg::AddSymlink
#[local_ref]
shared_path_from_entry -> adw::EntryRow {
set_title: &tr("original-path")
},
#[local_ref]
shared_path_to_entry -> adw::EntryRow {
set_title: &tr("new-path")
},
adw::ActionRow {
set_title: &tr("read-only"),
set_subtitle: &tr("read-only-description"),
#[local_ref]
add_suffix = read_only_switch -> gtk::Switch {
set_valign: gtk::Align::Center
}
}
},
#[local_ref]
symlink_path_from_entry -> adw::EntryRow {
set_title: &tr("original-path")
add = shared_paths -> adw::PreferencesGroup {},
add = &adw::PreferencesGroup {
set_title: &tr("symlinks"),
set_description: Some(&tr("symlinks-description")),
#[wrap(Some)]
set_header_suffix = &gtk::Button {
add_css_class: "flat",
set_valign: gtk::Align::Center,
adw::ButtonContent {
set_icon_name: "list-add-symbolic",
set_label: &tr("add")
},
connect_clicked => SandboxPageMsg::AddSymlink
},
#[local_ref]
symlink_path_from_entry -> adw::EntryRow {
set_title: &tr("original-path")
},
#[local_ref]
symlink_path_to_entry -> adw::EntryRow {
set_title: &tr("new-path")
}
},
#[local_ref]
symlink_path_to_entry -> adw::EntryRow {
set_title: &tr("new-path")
}
},
#[local_ref]
add = symlink_paths -> adw::PreferencesGroup {}
add = symlink_paths -> adw::PreferencesGroup {}
}
}
}
@ -376,7 +395,7 @@ impl SimpleAsyncComponent for SandboxApp {
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg {
SandboxAppMsg::AddPrivate => {
SandboxPageMsg::AddPrivate => {
if let Ok(mut config) = Config::get() {
let path = self.private_path_entry.text().trim().to_string();
@ -392,7 +411,7 @@ impl SimpleAsyncComponent for SandboxApp {
}
}
SandboxAppMsg::RemovePrivate(index) => {
SandboxPageMsg::RemovePrivate(index) => {
if let Ok(mut config) = Config::get() {
if let Some(var) = self.private_paths.guard().get(index.current_index()) {
config.sandbox.private.retain(|item| item != &var.from);
@ -404,7 +423,7 @@ impl SimpleAsyncComponent for SandboxApp {
}
},
SandboxAppMsg::AddShared => {
SandboxPageMsg::AddShared => {
if let Ok(mut config) = Config::get() {
let from = self.shared_path_from_entry.text().trim().to_string();
let to = self.shared_path_to_entry.text().trim().to_string();
@ -435,7 +454,7 @@ impl SimpleAsyncComponent for SandboxApp {
}
}
SandboxAppMsg::RemoveShared(index) => {
SandboxPageMsg::RemoveShared(index) => {
if let Ok(mut config) = Config::get() {
if let Some(var) = self.shared_paths.guard().get(index.current_index()) {
config.sandbox.mounts.read_only.remove(&var.from);
@ -448,7 +467,7 @@ impl SimpleAsyncComponent for SandboxApp {
}
},
SandboxAppMsg::AddSymlink => {
SandboxPageMsg::AddSymlink => {
if let Ok(mut config) = Config::get() {
let from = self.symlink_path_from_entry.text().trim().to_string();
let to = self.symlink_path_to_entry.text().trim().to_string();
@ -466,7 +485,7 @@ impl SimpleAsyncComponent for SandboxApp {
}
}
SandboxAppMsg::RemoveSymlink(index) => {
SandboxPageMsg::RemoveSymlink(index) => {
if let Ok(mut config) = Config::get() {
if let Some(var) = self.symlink_paths.guard().get(index.current_index()) {
config.sandbox.mounts.symlinks.remove(&var.from);

View file

@ -9,11 +9,11 @@ use anime_launcher_sdk::wincompatlib::prelude::*;
use anime_launcher_sdk::components::*;
use anime_launcher_sdk::components::wine::WincompatlibWine;
use super::GeneralAppMsg;
use crate::ui::components;
use crate::ui::components::*;
use super::GeneralAppMsg;
use crate::i18n::*;
use crate::*;
@ -58,6 +58,11 @@ impl SimpleAsyncComponent for ComponentsPage {
set_orientation: gtk::Orientation::Vertical,
adw::HeaderBar {
#[wrap(Some)]
set_title_widget = &adw::WindowTitle {
set_title: "Components"
},
pack_start = &gtk::Button {
set_icon_name: "go-previous-symbolic",

View file

@ -168,7 +168,7 @@ impl SimpleAsyncComponent for GeneralApp {
view! {
#[root]
main_page = adw::PreferencesPage {
adw::PreferencesPage {
set_title: &tr("general"),
set_icon_name: Some("applications-system-symbolic"),

View file

@ -15,18 +15,12 @@ use crate::i18n::tr;
use super::general::*;
use super::enhancements::*;
use super::game::*;
use super::sandbox::*;
use super::environment::*;
pub static mut PREFERENCES_WINDOW: Option<adw::PreferencesWindow> = None;
pub struct PreferencesApp {
general: AsyncController<GeneralApp>,
enhancements: AsyncController<EnhancementsApp>,
game: AsyncController<GameApp>,
sandbox: AsyncController<SandboxApp>,
environment: AsyncController<EnvironmentApp>
enhancements: AsyncController<EnhancementsApp>
}
#[derive(Debug, Clone)]
@ -71,9 +65,6 @@ impl SimpleAsyncComponent for PreferencesApp {
add = model.general.widget(),
add = model.enhancements.widget(),
add = model.game.widget(),
add = model.sandbox.widget(),
add = model.environment.widget(),
connect_close_request[sender] => move |_| {
if let Err(err) = Config::flush() {
@ -102,19 +93,7 @@ impl SimpleAsyncComponent for PreferencesApp {
enhancements: EnhancementsApp::builder()
.launch(())
.detach(),
game: GameApp::builder()
.launch(())
.detach(),
sandbox: SandboxApp::builder()
.launch(())
.forward(sender.input_sender(), std::convert::identity),
environment: EnvironmentApp::builder()
.launch(())
.detach()
.forward(sender.input_sender(), std::convert::identity)
};
let widgets = view_output!();

View file

@ -1,7 +1,4 @@
pub mod main;
pub mod general;
pub mod enhancements;
pub mod game;
pub mod sandbox;
pub mod environment;
pub mod gamescope;