diff --git a/assets/ui/main.blp b/assets/ui/main.blp index f30384d..f02784a 100644 --- a/assets/ui/main.blp +++ b/assets/ui/main.blp @@ -22,7 +22,16 @@ Adw.ApplicationWindow window { }; } - Adw.PreferencesPage { + Adw.StatusPage status_page { + icon-name: "image-loading-symbolic"; + title: "Loading data"; + + vexpand: true; + } + + Adw.PreferencesPage launcher_content { + visible: false; + Adw.PreferencesGroup { Gtk.Image { file: "assets/images/icon.png"; @@ -47,19 +56,13 @@ Adw.ApplicationWindow window { margin-top: 64; spacing: 8; - Adw.SplitButton launch_game { + Gtk.Button launch_game { label: "Launch"; hexpand: false; width-request: 200; styles ["suggested-action"] - - popover: Gtk.Popover { - Gtk.Button launch_game_debug { - label: "Launch in debug mode"; - } - }; } Gtk.Button open_preferences { diff --git a/src/lib/launcher/mod.rs b/src/lib/launcher/mod.rs new file mode 100644 index 0000000..2039478 --- /dev/null +++ b/src/lib/launcher/mod.rs @@ -0,0 +1 @@ +pub mod states; diff --git a/src/lib/launcher/states.rs b/src/lib/launcher/states.rs new file mode 100644 index 0000000..630ba7c --- /dev/null +++ b/src/lib/launcher/states.rs @@ -0,0 +1,89 @@ +use gtk4::{self as gtk, prelude::*}; +use libadwaita::{self as adw, prelude::*}; + +use anime_game_core::prelude::*; + +use crate::lib::config; + +#[derive(Debug, Clone)] +pub enum LauncherState { + Launch, + PatchAvailable(Patch), + + // Always contains `VersionDiff::Diff` + VoiceUpdateAvailable(VersionDiff), + + /// Always contains `VersionDiff::Outdated` + VoiceOutdated(VersionDiff), + + /// Always contains `VersionDiff::NotInstalled` + VoiceNotInstalled(VersionDiff), + + // Always contains `VersionDiff::Diff` + GameUpdateAvailable(VersionDiff), + + /// Always contains `VersionDiff::Outdated` + GameOutdated(VersionDiff), + + /// Always contains `VersionDiff::NotInstalled` + GameNotInstalled(VersionDiff) +} + +impl Default for LauncherState { + fn default() -> Self { + Self::Launch + } +} + +impl LauncherState { + pub fn get(status_page: Option) -> std::io::Result { + let config = config::get()?; + let game = Game::new(&config.game.path); + + if let Some(status_page) = &status_page { + status_page.set_description(Some("Updating game info...")); + } + + let diff = game.try_get_diff()?; + + Ok(match diff { + VersionDiff::Latest(_) => { + if let Some(status_page) = &status_page { + status_page.set_description(Some("Updating voice info...")); + } + + for voice_package in game.get_voice_packages()? { + if let Some(status_page) = &status_page { + status_page.set_description(Some(format!("Updating voice info ({})...", voice_package.locale().to_name()).as_str())); + } + + let diff = voice_package.try_get_diff()?; + + match diff { + VersionDiff::Latest(_) => continue, + VersionDiff::Diff { .. } => return Ok(Self::VoiceUpdateAvailable(diff)), + VersionDiff::Outdated { .. } => return Ok(Self::VoiceOutdated(diff)), + VersionDiff::NotInstalled { .. } => return Ok(Self::VoiceNotInstalled(diff)) + } + } + + if let Some(status_page) = &status_page { + status_page.set_description(Some("Updating patch info...")); + } + + let patch = Patch::try_fetch(config.patch.servers.clone())?; + + if patch.is_applied(&config.game.path)? { + Self::Launch + } + + else { + Self::PatchAvailable(patch) + } + }, + VersionDiff::Diff { .. } => Self::GameUpdateAvailable(diff), + VersionDiff::Outdated { .. } => Self::GameOutdated(diff), + VersionDiff::NotInstalled { .. } => Self::GameNotInstalled(diff) + }) + } +} diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 91a097a..c7a6b00 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -4,3 +4,4 @@ pub mod tasks; pub mod game; pub mod dxvk; pub mod wine; +pub mod launcher; diff --git a/src/ui/main.rs b/src/ui/main.rs index e404cf5..eb21098 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -14,6 +14,7 @@ use super::traits::toast_error::ToastError; use crate::lib::game; use crate::lib::tasks; +use crate::lib::launcher::states::LauncherState; /// This structure is used to describe widgets used in application /// @@ -26,9 +27,10 @@ pub struct AppWidgets { pub toast_overlay: adw::ToastOverlay, pub leaflet: adw::Leaflet, + pub status_page: adw::StatusPage, + pub launcher_content: adw::PreferencesPage, - pub launch_game: adw::SplitButton, - pub launch_game_debug: gtk::Button, + pub launch_game: gtk::Button, pub open_preferences: gtk::Button, pub launch_game_group: adw::PreferencesGroup, @@ -50,9 +52,10 @@ impl AppWidgets { toast_overlay: toast_overlay.clone(), leaflet: get_object(&builder, "leaflet")?, + status_page: get_object(&builder, "status_page")?, + launcher_content: get_object(&builder, "launcher_content")?, launch_game: get_object(&builder, "launch_game")?, - launch_game_debug: get_object(&builder, "launch_game_debug")?, open_preferences: get_object(&builder, "open_preferences")?, launch_game_group: get_object(&builder, "launch_game_group")?, @@ -76,7 +79,11 @@ impl AppWidgets { pub enum Actions { OpenPreferencesPage, PreferencesGoBack, - LaunchGame + PerformButtonEvent, + LaunchGame, + ShowProgressBar, + UpdateProgress { fraction: Rc, title: Rc }, + HideProgressBar } impl Actions { @@ -93,7 +100,9 @@ impl Actions { /// /// This must implement `Default` trait #[derive(Debug, Default, glib::Downgrade)] -pub struct Values; +pub struct Values { + state: Rc +} /// The main application structure /// @@ -125,6 +134,19 @@ impl App { // Bind app to the window result.widgets.window.set_application(Some(app)); + // Load initial launcher state + std::thread::spawn(clone!(@strong result => move || { + match LauncherState::get(Some(result.widgets.status_page.clone())) { + Ok(state) => { + result.set_state(state); + + result.widgets.status_page.set_visible(false); + result.widgets.launcher_content.set_visible(true); + }, + Err(err) => result.toast_error("Failed to get initial launcher state", err) + } + })); + Ok(result) } @@ -137,7 +159,7 @@ impl App { self.widgets.preferences_stack.preferences_go_back.connect_clicked(Actions::PreferencesGoBack.into_fn(&self)); // Launch game - self.widgets.launch_game.connect_clicked(Actions::LaunchGame.into_fn(&self)); + self.widgets.launch_game.connect_clicked(Actions::PerformButtonEvent.into_fn(&self)); self } @@ -152,10 +174,8 @@ impl App { let this = self.clone(); receiver.attach(None, move |action| { - let values = this.values.take(); - // Some debug output - println!("[main] [update] action: {:?}, values: {:?}", &action, &values); + println!("[main] [update] action: {:?}", &action); match action { Actions::OpenPreferencesPage => { @@ -175,15 +195,48 @@ impl App { this.widgets.leaflet.navigate(adw::NavigationDirection::Back); } + Actions::PerformButtonEvent => { + let values = this.values.take(); + let state = (*values.state).clone(); + + this.values.set(values); + + match state { + LauncherState::Launch => { + this.update(Actions::LaunchGame); + }, + LauncherState::PatchAvailable(_) => todo!(), + LauncherState::VoiceUpdateAvailable(_) => todo!(), + LauncherState::VoiceOutdated(_) => todo!(), + LauncherState::VoiceNotInstalled(_) => todo!(), + LauncherState::GameUpdateAvailable(_) => todo!(), + LauncherState::GameOutdated(_) => todo!(), + LauncherState::GameNotInstalled(_) => todo!() + } + } + Actions::LaunchGame => { // Display toast message if the game is failed to run if let Err(err) = game::run(false) { this.toast_error("Failed to run game", err); } } - } - this.values.set(values); + Actions::ShowProgressBar => { + this.widgets.launch_game_group.set_visible(false); + this.widgets.progress_bar_group.set_visible(true); + } + + Actions::UpdateProgress { fraction, title } => { + this.widgets.progress_bar.set_text(Some(title.as_str())); + this.widgets.progress_bar.set_fraction(*fraction); + } + + Actions::HideProgressBar => { + this.widgets.launch_game_group.set_visible(true); + this.widgets.progress_bar_group.set_visible(false); + } + } glib::Continue(true) }); @@ -211,6 +264,45 @@ impl App { pub fn show(&self) { self.widgets.window.show(); } + + pub fn set_state(&self, state: LauncherState) { + println!("[main] [set_state] state: {:?}", &state); + + match state { + LauncherState::Launch => { + self.widgets.launch_game.set_label("Launch"); + } + + LauncherState::PatchAvailable(_) => { + self.widgets.launch_game.set_label("Apply patch"); + } + + LauncherState::GameUpdateAvailable(_) => { + self.widgets.launch_game.set_label("Update"); + } + + LauncherState::VoiceUpdateAvailable(_) => { + self.widgets.launch_game.set_label("Update"); + } + + LauncherState::GameNotInstalled(_) => { + self.widgets.launch_game.set_label("Download"); + } + + LauncherState::VoiceNotInstalled(_) => { + self.widgets.launch_game.set_label("Download"); + } + + LauncherState::VoiceOutdated(_) => todo!(), + LauncherState::GameOutdated(_) => todo!() + } + + let mut values = self.values.take(); + + values.state = Rc::new(state); + + self.values.set(values); + } } impl ToastError for App { @@ -221,29 +313,3 @@ impl ToastError for App { unsafe impl Send for App {} unsafe impl Sync for App {} - -/* -pub enum AppState { - Launch, - Progress { - title: String, - progress: f64 - } -} - -pub fn update_state(&self, state: AppState) { - match state { - AppState::Launch => { - self.launch_game_group.set_visible(true); - self.progress_bar_group.set_visible(false); - }, - AppState::Progress { title, progress } => { - self.launch_game_group.set_visible(false); - self.progress_bar_group.set_visible(true); - - self.progress_bar.set_text(Some(&title)); - self.progress_bar.set_fraction(progress); - } - } -} -*/