use relm4::prelude::*; use relm4::component::*; use adw::prelude::*; use anime_launcher_sdk::config; use std::path::PathBuf; use crate::*; use crate::i18n::*; use super::main::*; pub struct DefaultPathsApp { show_additional: bool, launcher: PathBuf, runners: PathBuf, dxvks: PathBuf, prefix: PathBuf, game: PathBuf, patch: PathBuf, temp: PathBuf } #[derive(Debug, Clone)] pub enum Folders { Launcher, Runners, DXVK, Prefix, Game, Patch, Temp } #[derive(Debug, Clone)] pub enum DefaultPathsAppMsg { ToggleShowAdditional, ChoosePath(Folders), Continue, Exit } #[relm4::component(async, pub)] impl SimpleAsyncComponent for DefaultPathsApp { type Init = (); type Input = DefaultPathsAppMsg; type Output = FirstRunAppMsg; view! { adw::PreferencesPage { set_hexpand: true, add = &adw::PreferencesGroup { gtk::Label { set_label: "Choose default paths", add_css_class: "title-1" } }, add = &adw::PreferencesGroup { set_valign: gtk::Align::End, set_vexpand: true, adw::ActionRow { set_title: "Launcher folder", set_icon_name: Some("folder-symbolic"), set_activatable: true, #[watch] set_subtitle: model.launcher.to_str().unwrap(), connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Launcher) }, }, add = &adw::PreferencesGroup { set_valign: gtk::Align::Start, set_vexpand: true, adw::ActionRow { set_title: "I know what I'm doing", set_subtitle: "Show additional path selection settings. Do as I say...", add_suffix = >k::Switch { set_valign: gtk::Align::Center, connect_state_notify => DefaultPathsAppMsg::ToggleShowAdditional } }, }, add = &adw::PreferencesGroup { set_valign: gtk::Align::Center, set_vexpand: true, #[watch] set_visible: model.show_additional, adw::ActionRow { set_title: "Runners folder", set_icon_name: Some("folder-symbolic"), set_activatable: true, #[watch] set_subtitle: model.runners.to_str().unwrap(), connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Runners) }, adw::ActionRow { set_title: "DXVKs folder", set_icon_name: Some("folder-symbolic"), set_activatable: true, #[watch] set_subtitle: model.dxvks.to_str().unwrap(), connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::DXVK) }, adw::ActionRow { set_title: "Wine prefix folder", set_icon_name: Some("folder-symbolic"), set_activatable: true, #[watch] set_subtitle: model.prefix.to_str().unwrap(), connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Prefix) }, adw::ActionRow { set_title: "Game installation folder", set_icon_name: Some("folder-symbolic"), set_activatable: true, #[watch] set_subtitle: model.game.to_str().unwrap(), connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Game) }, adw::ActionRow { set_title: "Patch folder", set_icon_name: Some("folder-symbolic"), set_activatable: true, #[watch] set_subtitle: model.patch.to_str().unwrap(), connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Patch) }, adw::ActionRow { set_title: "Temp folder", set_icon_name: Some("folder-symbolic"), set_activatable: true, #[watch] set_subtitle: model.temp.to_str().unwrap(), connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Temp) }, }, add = &adw::PreferencesGroup { set_valign: gtk::Align::Center, set_vexpand: true, gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_halign: gtk::Align::Center, set_spacing: 8, gtk::Button { set_label: "Continue", add_css_class: "suggested-action", connect_clicked => DefaultPathsAppMsg::Continue }, gtk::Button { set_label: "Exit", connect_clicked => DefaultPathsAppMsg::Exit } } } } } async fn init( _init: Self::Init, root: Self::Root, _sender: AsyncComponentSender, ) -> AsyncComponentParts { let config = config::get().unwrap_or_default(); let model = Self { show_additional: false, launcher: LAUNCHER_FOLDER.to_path_buf(), runners: config.game.wine.builds, dxvks: config.game.dxvk.builds, prefix: config.game.wine.prefix, game: config.game.path, patch: config.patch.path, temp: config.launcher.temp.unwrap_or(PathBuf::from("/tmp")) }; let widgets = view_output!(); AsyncComponentParts { model, widgets } } async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender) { match msg { DefaultPathsAppMsg::ToggleShowAdditional => self.show_additional = !self.show_additional, DefaultPathsAppMsg::ChoosePath(folder) => { let result = rfd::AsyncFileDialog::new() .set_directory(&self.launcher) .pick_folder().await; if let Some(result) = result { let result = result.path().to_path_buf(); match folder { Folders::Launcher => { self.runners = result.join("runners"); self.dxvks = result.join("dxvks"); self.prefix = result.join("game"); self.game = result.join("game/drive_c/Program Files/Genshin Impact"); self.patch = result.join("patch"); self.temp = result.join("temp"); self.launcher = result; } Folders::Runners => self.runners = result, Folders::DXVK => self.dxvks = result, Folders::Prefix => self.prefix = result, Folders::Game => self.game = result, Folders::Patch => self.patch = result, Folders::Temp => self.temp = result } } } #[allow(unused_must_use)] DefaultPathsAppMsg::Continue => { match self.update_config() { Ok(_) => sender.output(Self::Output::ScrollToDownloadComponents), Err(err) => sender.output(Self::Output::Toast { title: tr("config-update-error"), description: Some(err.to_string()) }) }; } DefaultPathsAppMsg::Exit => { // TODO: relm4 has some function for it std::process::exit(0); } } } } impl DefaultPathsApp { pub fn update_config(&self) -> anyhow::Result<()> { let mut config = config::get()?; config.game.wine.builds = self.runners.clone(); config.game.dxvk.builds = self.dxvks.clone(); config.game.wine.prefix = self.prefix.clone(); config.game.path = self.game.clone(); config.patch.path = self.patch.clone(); config.launcher.temp = Some(self.temp.clone()); config::update_raw(config) } }