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] #[watch]
set_sensitive: !model.disabled_buttons, set_sensitive: !model.disabled_buttons,
set_width_request: 48, set_width_request: 44,
add_css_class: "circular", add_css_class: "circular",
set_icon_name: "emblem-system-symbolic", set_icon_name: "emblem-system-symbolic",

View file

@ -4,6 +4,8 @@ use relm4::factory::*;
use adw::prelude::*; use adw::prelude::*;
use super::EnhancementsAppMsg;
use crate::i18n::tr; use crate::i18n::tr;
use crate::*; use crate::*;
@ -16,10 +18,10 @@ struct Variable {
#[relm4::factory(async)] #[relm4::factory(async)]
impl AsyncFactoryComponent for Variable { impl AsyncFactoryComponent for Variable {
type Init = (String, String); type Init = (String, String);
type Input = EnvironmentAppMsg; type Input = EnvironmentPageMsg;
type Output = EnvironmentAppMsg; type Output = EnvironmentPageMsg;
type CommandOutput = (); type CommandOutput = ();
type ParentInput = EnvironmentAppMsg; type ParentInput = EnvironmentPageMsg;
type ParentWidget = adw::PreferencesGroup; type ParentWidget = adw::PreferencesGroup;
view! { view! {
@ -33,7 +35,7 @@ impl AsyncFactoryComponent for Variable {
set_valign: gtk::Align::Center, set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| { 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>, variables: AsyncFactoryVecDeque<Variable>,
name_entry: adw::EntryRow, name_entry: adw::EntryRow,
@ -63,76 +65,95 @@ pub struct EnvironmentApp {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum EnvironmentAppMsg { pub enum EnvironmentPageMsg {
Add, Add,
Remove(DynamicIndex) Remove(DynamicIndex)
} }
#[relm4::component(async, pub)] #[relm4::component(async, pub)]
impl SimpleAsyncComponent for EnvironmentApp { impl SimpleAsyncComponent for EnvironmentPage {
type Init = (); type Init = ();
type Input = EnvironmentAppMsg; type Input = EnvironmentPageMsg;
type Output = (); type Output = EnhancementsAppMsg;
view! { view! {
adw::PreferencesPage { gtk::Box {
set_title: &tr("environment"), set_orientation: gtk::Orientation::Vertical,
set_icon_name: Some("document-properties-symbolic"),
add = &adw::PreferencesGroup { adw::HeaderBar {
set_title: &tr("game-command"), #[wrap(Some)]
set_description: Some(&tr("game-command-description")), set_title_widget = &adw::WindowTitle {
set_title: &tr("environment")
},
adw::EntryRow { pack_start = &gtk::Button {
set_title: "%command%", set_icon_name: "go-previous-symbolic",
set_text: CONFIG.game.command.as_ref().unwrap_or(&String::new()).trim(),
connect_changed => |entry| { connect_clicked[sender] => move |_| {
if let Ok(mut config) = Config::get() { sender.output(EnhancementsAppMsg::OpenMainPage).unwrap();
let command = entry.text().trim().to_string();
config.game.command = if command.is_empty() {
None
} else {
Some(command)
};
Config::update(config);
}
} }
} }
}, },
add = &adw::PreferencesGroup { adw::PreferencesPage {
set_title: &tr("new-variable"), set_title: &tr("environment"),
set_icon_name: Some("document-properties-symbolic"),
#[wrap(Some)] add = &adw::PreferencesGroup {
set_header_suffix = &gtk::Button { set_title: &tr("game-command"),
add_css_class: "flat", 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 { connect_changed => |entry| {
set_icon_name: "list-add-symbolic", if let Ok(mut config) = Config::get() {
set_label: &tr("add") 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] #[local_ref]
name_entry -> adw::EntryRow { add = variables -> adw::PreferencesGroup {}
set_title: &tr("name") }
},
#[local_ref]
value_entry -> adw::EntryRow {
set_title: &tr("value")
}
},
#[local_ref]
add = variables -> adw::PreferencesGroup {}
} }
} }
@ -166,7 +187,7 @@ impl SimpleAsyncComponent for EnvironmentApp {
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) { async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg { match msg {
EnvironmentAppMsg::Add => { EnvironmentPageMsg::Add => {
let name = self.name_entry.text().trim().to_string(); let name = self.name_entry.text().trim().to_string();
let value = self.value_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 Ok(mut config) = Config::get() {
if let Some(var) = self.variables.guard().get(index.current_index()) { if let Some(var) = self.variables.guard().get(index.current_index()) {
config.game.environment.remove(&var.key); 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::sessions::SessionsExt;
use anime_launcher_sdk::genshin::sessions::Sessions; use anime_launcher_sdk::genshin::sessions::Sessions;
use super::main::PreferencesAppMsg; use super::EnhancementsAppMsg;
use crate::i18n::tr; use crate::i18n::tr;
use crate::*; use crate::*;
@ -21,10 +21,10 @@ struct GameSession {
#[relm4::factory(async)] #[relm4::factory(async)]
impl AsyncFactoryComponent for GameSession { impl AsyncFactoryComponent for GameSession {
type Init = GameSession; type Init = GameSession;
type Input = GameAppMsg; type Input = GamePageMsg;
type Output = GameAppMsg; type Output = GamePageMsg;
type CommandOutput = (); type CommandOutput = ();
type ParentInput = GameAppMsg; type ParentInput = GamePageMsg;
type ParentWidget = adw::PreferencesGroup; type ParentWidget = adw::PreferencesGroup;
view! { view! {
@ -45,7 +45,7 @@ impl AsyncFactoryComponent for GameSession {
set_valign: gtk::Align::Center, set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| { 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, set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| { 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: AsyncFactoryVecDeque<GameSession>,
sessions_names: Vec<String>, sessions_names: Vec<String>,
@ -87,7 +87,7 @@ pub struct GameApp {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum GameAppMsg { pub enum GamePageMsg {
AddSession, AddSession,
UpdateSession(DynamicIndex), UpdateSession(DynamicIndex),
RemoveSession(DynamicIndex), RemoveSession(DynamicIndex),
@ -96,50 +96,69 @@ pub enum GameAppMsg {
} }
#[relm4::component(async, pub)] #[relm4::component(async, pub)]
impl SimpleAsyncComponent for GameApp { impl SimpleAsyncComponent for GamePage {
type Init = (); type Init = ();
type Input = GameAppMsg; type Input = GamePageMsg;
type Output = PreferencesAppMsg; type Output = EnhancementsAppMsg;
view! { view! {
adw::PreferencesPage { gtk::Box {
set_title: &tr("game"), set_orientation: gtk::Orientation::Vertical,
set_icon_name: Some("applications-games-symbolic"),
add = &adw::PreferencesGroup { adw::HeaderBar {
set_title: &tr("game-sessions"), #[wrap(Some)]
set_title_widget = &adw::WindowTitle {
set_title: &tr("game")
},
#[local_ref] pack_start = &gtk::Button {
sessions_combo -> adw::ComboRow { set_icon_name: "go-previous-symbolic",
set_title: &tr("active-sessions"),
set_subtitle: &tr("active-session-description"),
connect_selected_notify[sender] => move |row| { connect_clicked[sender] => move |_| {
if is_ready() { sender.output(EnhancementsAppMsg::OpenMainPage).unwrap();
sender.input(GameAppMsg::SetCurrent(row.selected())); }
}
},
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]
#[local_ref] add = sessions -> adw::PreferencesGroup {},
add = sessions -> adw::PreferencesGroup {}, }
} }
} }
@ -173,14 +192,14 @@ impl SimpleAsyncComponent for GameApp {
let widgets = view_output!(); let widgets = view_output!();
sender.input(GameAppMsg::UpdateCombo); sender.input(GamePageMsg::UpdateCombo);
AsyncComponentParts { model, widgets } 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 { match msg {
GameAppMsg::AddSession => { GamePageMsg::AddSession => {
let name = self.session_name_entry.text().trim().to_string(); let name = self.session_name_entry.text().trim().to_string();
if !name.is_empty() { if !name.is_empty() {
@ -194,46 +213,43 @@ impl SimpleAsyncComponent for GameApp {
description: None description: None
}); });
sender.input(GameAppMsg::UpdateCombo); sender.input(GamePageMsg::UpdateCombo);
} }
#[allow(unused_must_use)]
Err(err) => { Err(err) => {
sender.output(PreferencesAppMsg::Toast { sender.output(EnhancementsAppMsg::Toast {
title: tr("game-session-add-failed"), title: tr("game-session-add-failed"),
description: Some(err.to_string()) 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 Some(session) = self.sessions.guard().get(index.current_index()) {
if let Ok(config) = Config::get() { if let Ok(config) = Config::get() {
#[allow(unused_must_use)]
if let Err(err) = Sessions::update(session.name.clone(), config.get_wine_prefix_path()) { 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"), title: tr("game-session-update-failed"),
description: Some(err.to_string()) description: Some(err.to_string())
}); }).unwrap();
} }
} }
} }
} }
GameAppMsg::RemoveSession(index) => { GamePageMsg::RemoveSession(index) => {
if let Some(session) = self.sessions.guard().get(index.current_index()) { if let Some(session) = self.sessions.guard().get(index.current_index()) {
match Sessions::remove(&session.name) { match Sessions::remove(&session.name) {
Ok(()) => sender.input(GameAppMsg::UpdateCombo), Ok(()) => sender.input(GamePageMsg::UpdateCombo),
#[allow(unused_must_use)]
Err(err) => { Err(err) => {
sender.output(PreferencesAppMsg::Toast { sender.output(EnhancementsAppMsg::Toast {
title: tr("game-session-remove-failed"), title: tr("game-session-remove-failed"),
description: Some(err.to_string()) description: Some(err.to_string())
}); }).unwrap();
return; return;
} }
@ -243,32 +259,30 @@ impl SimpleAsyncComponent for GameApp {
self.sessions.guard().remove(index.current_index()); 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 Some(name) = self.sessions_names.get(id as usize) {
if let Ok(config) = Config::get() { if let Ok(config) = Config::get() {
#[allow(unused_must_use)]
if let Err(err) = Sessions::set_current(name.to_owned()) { 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"), title: tr("game-session-set-current-failed"),
description: Some(err.to_string()) description: Some(err.to_string())
}); }).unwrap();
// Prevent session applying // Prevent session applying
return; return;
} }
#[allow(unused_must_use)]
if let Err(err) = Sessions::apply(name.to_owned(), config.get_wine_prefix_path()) { 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"), title: tr("game-session-apply-failed"),
description: Some(err.to_string()) description: Some(err.to_string())
}); }).unwrap();
} }
} }
} }
} }
GameAppMsg::UpdateCombo => { GamePageMsg::UpdateCombo => {
let sessions = Sessions::get_sessions().unwrap_or_default(); let sessions = Sessions::get_sessions().unwrap_or_default();
self.sessions_names = sessions.sessions.into_keys().collect::<Vec<String>>(); 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::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::config::schema_blanks::prelude::*; use anime_launcher_sdk::config::schema_blanks::prelude::*;
use anime_launcher_sdk::is_available; 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::i18n::tr;
use crate::*; use crate::*;
use super::gamescope::*; use super::gamescope::*;
use super::main::PreferencesAppMsg;
pub struct EnhancementsApp { pub struct EnhancementsApp {
gamescope: AsyncController<GamescopeApp> gamescope: AsyncController<GamescopeApp>,
game_page: AsyncController<GamePage>,
sandbox_page: AsyncController<SandboxPage>,
environment_page: AsyncController<EnvironmentPage>
} }
#[derive(Debug)] #[derive(Debug)]
pub enum EnhancementsAppMsg { pub enum EnhancementsAppMsg {
SetGamescopeParent(adw::PreferencesWindow), SetGamescopeParent(adw::PreferencesWindow),
OpenGamescope
OpenGamescope,
OpenMainPage,
OpenGameSettingsPage,
OpenSandboxSettingsPage,
OpenEnvironmentSettingsPage,
Toast {
title: String,
description: Option<String>
}
} }
#[relm4::component(async, pub)] #[relm4::component(async, pub)]
impl SimpleAsyncComponent for EnhancementsApp { impl SimpleAsyncComponent for EnhancementsApp {
type Init = (); type Init = ();
type Input = EnhancementsAppMsg; type Input = EnhancementsAppMsg;
type Output = (); type Output = PreferencesAppMsg;
view! { view! {
#[root]
adw::PreferencesPage { adw::PreferencesPage {
set_title: &tr("enhancements"), set_title: &tr("enhancements"),
set_icon_name: Some("applications-graphics-symbolic"), 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 { add = &adw::PreferencesGroup {
set_title: &tr("wine"), 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( async fn init(
@ -511,15 +585,31 @@ impl SimpleAsyncComponent for EnhancementsApp {
let model = Self { let model = Self {
gamescope: GamescopeApp::builder() gamescope: GamescopeApp::builder()
.launch(()) .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!(); let widgets = view_output!();
AsyncComponentParts { model, widgets } 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 { match msg {
EnhancementsAppMsg::SetGamescopeParent(parent) => { EnhancementsAppMsg::SetGamescopeParent(parent) => {
self.gamescope.widget().set_transient_for(Some(&parent)); self.gamescope.widget().set_transient_for(Some(&parent));
@ -528,6 +618,41 @@ impl SimpleAsyncComponent for EnhancementsApp {
EnhancementsAppMsg::OpenGamescope => { EnhancementsAppMsg::OpenGamescope => {
self.gamescope.widget().present(); 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 anime_launcher_sdk::is_available;
use super::main::PreferencesAppMsg; use super::EnhancementsAppMsg;
use crate::i18n::tr; use crate::i18n::tr;
use crate::*; use crate::*;
@ -22,10 +22,10 @@ macro_rules! impl_directory {
#[relm4::factory(async)] #[relm4::factory(async)]
impl AsyncFactoryComponent for $name { impl AsyncFactoryComponent for $name {
type Init = (String, Option<String>); type Init = (String, Option<String>);
type Input = SandboxAppMsg; type Input = SandboxPageMsg;
type Output = SandboxAppMsg; type Output = SandboxPageMsg;
type CommandOutput = (); type CommandOutput = ();
type ParentInput = SandboxAppMsg; type ParentInput = SandboxPageMsg;
type ParentWidget = adw::PreferencesGroup; type ParentWidget = adw::PreferencesGroup;
view! { view! {
@ -66,11 +66,11 @@ macro_rules! impl_directory {
} }
} }
impl_directory!(PrivateDirectory, SandboxAppMsg::RemovePrivate); impl_directory!(PrivateDirectory, SandboxPageMsg::RemovePrivate);
impl_directory!(SharedDirectory, SandboxAppMsg::RemoveShared); impl_directory!(SharedDirectory, SandboxPageMsg::RemoveShared);
impl_directory!(SymlinkPath, SandboxAppMsg::RemoveSymlink); impl_directory!(SymlinkPath, SandboxPageMsg::RemoveSymlink);
pub struct SandboxApp { pub struct SandboxPage {
private_paths: AsyncFactoryVecDeque<PrivateDirectory>, private_paths: AsyncFactoryVecDeque<PrivateDirectory>,
shared_paths: AsyncFactoryVecDeque<SharedDirectory>, shared_paths: AsyncFactoryVecDeque<SharedDirectory>,
symlink_paths: AsyncFactoryVecDeque<SymlinkPath>, symlink_paths: AsyncFactoryVecDeque<SymlinkPath>,
@ -86,7 +86,7 @@ pub struct SandboxApp {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SandboxAppMsg { pub enum SandboxPageMsg {
AddPrivate, AddPrivate,
RemovePrivate(DynamicIndex), RemovePrivate(DynamicIndex),
@ -98,214 +98,233 @@ pub enum SandboxAppMsg {
} }
#[relm4::component(async, pub)] #[relm4::component(async, pub)]
impl SimpleAsyncComponent for SandboxApp { impl SimpleAsyncComponent for SandboxPage {
type Init = (); type Init = ();
type Input = SandboxAppMsg; type Input = SandboxPageMsg;
type Output = PreferencesAppMsg; type Output = EnhancementsAppMsg;
view! { view! {
adw::PreferencesPage { gtk::Box {
set_title: &tr("sandbox"), set_orientation: gtk::Orientation::Vertical,
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")),
adw::HeaderBar {
#[wrap(Some)] #[wrap(Some)]
set_header_suffix = &gtk::Button { set_title_widget = &adw::WindowTitle {
add_css_class: "flat", set_title: &tr("sandbox")
},
set_valign: gtk::Align::Center, pack_start = &gtk::Button {
set_icon_name: "go-previous-symbolic",
adw::ButtonContent { connect_clicked[sender] => move |_| {
set_icon_name: "list-add-symbolic", sender.output(EnhancementsAppMsg::OpenMainPage).unwrap();
set_label: &tr("add") }
}
},
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] add = &adw::PreferencesGroup {
shared_path_from_entry -> adw::EntryRow { set_title: &tr("private-directories"),
set_title: &tr("original-path") set_description: Some(&tr("private-directories-description")),
},
#[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] #[local_ref]
add_suffix = read_only_switch -> gtk::Switch { private_path_entry -> adw::EntryRow {
set_valign: gtk::Align::Center 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] #[local_ref]
add = shared_paths -> adw::PreferencesGroup {}, add = private_paths -> adw::PreferencesGroup {},
add = &adw::PreferencesGroup { add = &adw::PreferencesGroup {
set_title: &tr("symlinks"), set_title: &tr("shared-directories"),
set_description: Some(&tr("symlinks-description")), set_description: Some(&tr("shared-directories-description")),
#[wrap(Some)] #[wrap(Some)]
set_header_suffix = &gtk::Button { set_header_suffix = &gtk::Button {
add_css_class: "flat", add_css_class: "flat",
set_valign: gtk::Align::Center, set_valign: gtk::Align::Center,
adw::ButtonContent { adw::ButtonContent {
set_icon_name: "list-add-symbolic", set_icon_name: "list-add-symbolic",
set_label: &tr("add") 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] #[local_ref]
symlink_path_from_entry -> adw::EntryRow { add = shared_paths -> adw::PreferencesGroup {},
set_title: &tr("original-path")
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] #[local_ref]
symlink_path_to_entry -> adw::EntryRow { add = symlink_paths -> adw::PreferencesGroup {}
set_title: &tr("new-path") }
}
},
#[local_ref]
add = symlink_paths -> adw::PreferencesGroup {}
} }
} }
@ -376,7 +395,7 @@ impl SimpleAsyncComponent for SandboxApp {
async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) { async fn update(&mut self, msg: Self::Input, _sender: AsyncComponentSender<Self>) {
match msg { match msg {
SandboxAppMsg::AddPrivate => { SandboxPageMsg::AddPrivate => {
if let Ok(mut config) = Config::get() { if let Ok(mut config) = Config::get() {
let path = self.private_path_entry.text().trim().to_string(); 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 Ok(mut config) = Config::get() {
if let Some(var) = self.private_paths.guard().get(index.current_index()) { if let Some(var) = self.private_paths.guard().get(index.current_index()) {
config.sandbox.private.retain(|item| item != &var.from); 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() { if let Ok(mut config) = Config::get() {
let from = self.shared_path_from_entry.text().trim().to_string(); let from = self.shared_path_from_entry.text().trim().to_string();
let to = self.shared_path_to_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 Ok(mut config) = Config::get() {
if let Some(var) = self.shared_paths.guard().get(index.current_index()) { if let Some(var) = self.shared_paths.guard().get(index.current_index()) {
config.sandbox.mounts.read_only.remove(&var.from); 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() { if let Ok(mut config) = Config::get() {
let from = self.symlink_path_from_entry.text().trim().to_string(); let from = self.symlink_path_from_entry.text().trim().to_string();
let to = self.symlink_path_to_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 Ok(mut config) = Config::get() {
if let Some(var) = self.symlink_paths.guard().get(index.current_index()) { if let Some(var) = self.symlink_paths.guard().get(index.current_index()) {
config.sandbox.mounts.symlinks.remove(&var.from); 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::*;
use anime_launcher_sdk::components::wine::WincompatlibWine; use anime_launcher_sdk::components::wine::WincompatlibWine;
use super::GeneralAppMsg;
use crate::ui::components; use crate::ui::components;
use crate::ui::components::*; use crate::ui::components::*;
use super::GeneralAppMsg;
use crate::i18n::*; use crate::i18n::*;
use crate::*; use crate::*;
@ -58,6 +58,11 @@ impl SimpleAsyncComponent for ComponentsPage {
set_orientation: gtk::Orientation::Vertical, set_orientation: gtk::Orientation::Vertical,
adw::HeaderBar { adw::HeaderBar {
#[wrap(Some)]
set_title_widget = &adw::WindowTitle {
set_title: "Components"
},
pack_start = &gtk::Button { pack_start = &gtk::Button {
set_icon_name: "go-previous-symbolic", set_icon_name: "go-previous-symbolic",

View file

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

View file

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

View file

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