diff --git a/src/ui/main/mod.rs b/src/ui/main/mod.rs index 6f910e3..101d9bf 100644 --- a/src/ui/main/mod.rs +++ b/src/ui/main/mod.rs @@ -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", diff --git a/src/ui/preferences/environment.rs b/src/ui/preferences/enhancements/environment.rs similarity index 54% rename from src/ui/preferences/environment.rs rename to src/ui/preferences/enhancements/environment.rs index c7ceb70..2e45c6b 100644 --- a/src/ui/preferences/environment.rs +++ b/src/ui/preferences/enhancements/environment.rs @@ -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, 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 = >k::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 = >k::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 = >k::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) { 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); diff --git a/src/ui/preferences/game.rs b/src/ui/preferences/enhancements/game.rs similarity index 67% rename from src/ui/preferences/game.rs rename to src/ui/preferences/enhancements/game.rs index 30c7e7f..2177798 100644 --- a/src/ui/preferences/game.rs +++ b/src/ui/preferences/enhancements/game.rs @@ -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, sessions_names: Vec, @@ -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 = >k::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 = >k::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 = >k::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) { 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::>(); diff --git a/src/ui/preferences/enhancements.rs b/src/ui/preferences/enhancements/mod.rs similarity index 81% rename from src/ui/preferences/enhancements.rs rename to src/ui/preferences/enhancements/mod.rs index 5371312..14c728b 100644 --- a/src/ui/preferences/enhancements.rs +++ b/src/ui/preferences/enhancements/mod.rs @@ -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 + gamescope: AsyncController, + game_page: AsyncController, + sandbox_page: AsyncController, + environment_page: AsyncController } #[derive(Debug)] pub enum EnhancementsAppMsg { SetGamescopeParent(adw::PreferencesWindow), - OpenGamescope + + OpenGamescope, + OpenMainPage, + OpenGameSettingsPage, + OpenSandboxSettingsPage, + OpenEnvironmentSettingsPage, + + Toast { + title: String, + description: Option + } } #[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 = >k::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 = >k::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 = >k::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) { + async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender) { 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(); + } } } } diff --git a/src/ui/preferences/sandbox.rs b/src/ui/preferences/enhancements/sandbox.rs similarity index 54% rename from src/ui/preferences/sandbox.rs rename to src/ui/preferences/enhancements/sandbox.rs index 1a9c474..73cf21a 100644 --- a/src/ui/preferences/sandbox.rs +++ b/src/ui/preferences/enhancements/sandbox.rs @@ -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); - 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, shared_paths: AsyncFactoryVecDeque, symlink_paths: AsyncFactoryVecDeque, @@ -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 = >k::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 = >k::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 = >k::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 = >k::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 = >k::Button { - add_css_class: "flat", + set_title_widget = &adw::WindowTitle { + set_title: &tr("sandbox") + }, - set_valign: gtk::Align::Center, + pack_start = >k::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 = >k::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 = >k::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 = >k::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 = >k::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 = >k::Button { - add_css_class: "flat", + #[wrap(Some)] + set_header_suffix = >k::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 = >k::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) { 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); diff --git a/src/ui/preferences/general/components.rs b/src/ui/preferences/general/components.rs index d7fbc51..62b8709 100644 --- a/src/ui/preferences/general/components.rs +++ b/src/ui/preferences/general/components.rs @@ -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 = >k::Button { set_icon_name: "go-previous-symbolic", diff --git a/src/ui/preferences/general/mod.rs b/src/ui/preferences/general/mod.rs index 34e3e9e..e829d9d 100644 --- a/src/ui/preferences/general/mod.rs +++ b/src/ui/preferences/general/mod.rs @@ -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"), diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index 213e6b4..9ce6499 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -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 = None; pub struct PreferencesApp { general: AsyncController, - enhancements: AsyncController, - game: AsyncController, - sandbox: AsyncController, - environment: AsyncController + enhancements: AsyncController } #[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!(); diff --git a/src/ui/preferences/mod.rs b/src/ui/preferences/mod.rs index 3ab11c5..bf7157e 100644 --- a/src/ui/preferences/mod.rs +++ b/src/ui/preferences/mod.rs @@ -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;