diff --git a/src/main.rs b/src/main.rs index 058bf99..49cbc7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,10 @@ use anime_launcher_sdk::config; pub mod i18n; pub mod ui; +mod prettify_bytes; + +pub use prettify_bytes::prettify_bytes; + pub const APP_ID: &str = "moe.launcher.an-anime-game-launcher-gtk"; pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const APP_DEBUG: bool = cfg!(debug_assertions); @@ -38,8 +42,16 @@ fn main() { tracing::info!("Set UI language to {}", i18n::LANG); } - // Run the app + // Create the app let app = RelmApp::new("moe.launcher.an-anime-game-launcher"); + // Set global css + relm4::set_global_css(" + progressbar > text { + margin-bottom: 4px; + } + "); + + // Run the app app.run::(()); } diff --git a/src/prettify_bytes.rs b/src/prettify_bytes.rs new file mode 100644 index 0000000..516eb71 --- /dev/null +++ b/src/prettify_bytes.rs @@ -0,0 +1,17 @@ +pub fn prettify_bytes(bytes: u64) -> String { + if bytes > 1024 * 1024 * 1024 { + format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0) + } + + else if bytes > 1024 * 1024 { + format!("{:.2} MB", bytes as f64 / 1024.0 / 1024.0) + } + + else if bytes > 1024 { + format!("{:.2} KB", bytes as f64 / 1024.0) + } + + else { + format!("{:.2} B", bytes) + } +} diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index 7316768..6e93fc5 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -1,10 +1,12 @@ pub mod list; pub mod group; pub mod version; +pub mod progress_bar; pub use list::*; pub use group::*; pub use version::*; +pub use progress_bar::*; use anime_launcher_sdk::components::*; diff --git a/src/ui/components/progress_bar.rs b/src/ui/components/progress_bar.rs new file mode 100644 index 0000000..7f877f6 --- /dev/null +++ b/src/ui/components/progress_bar.rs @@ -0,0 +1,130 @@ +use relm4::prelude::*; + +use adw::prelude::*; + +use anime_launcher_sdk::anime_game_core::installer::installer::Update as InstallerUpdate; + +use crate::prettify_bytes; + +pub struct ProgressBar { + pub fraction: f64, + pub caption: Option, + + /// e.g. (53.21 MB, 10 GB) + pub downloaded: Option<(String, String)>, + + pub visible: bool +} + +#[derive(Debug)] +pub enum AppMsg { + Reset, + UpdateCaption(Option), + + /// (current bytes, total bytes) + UpdateProgress(u64, u64), + + UpdateFromState(InstallerUpdate), + SetVisible(bool) +} + +#[relm4::component(pub)] +impl SimpleComponent for ProgressBar { + type Init = (Option, bool); + type Input = AppMsg; + type Output = (); + + view! { + progress_bar = gtk::ProgressBar { + set_valign: gtk::Align::Center, + + #[watch] + set_visible: model.visible, + + #[watch] + set_fraction: model.fraction, + + #[watch] + set_show_text: model.caption.is_some(), + + #[watch] + set_text: match model.caption.as_ref() { + Some(caption) => Some({ + if let Some((curr, total)) = &model.downloaded { + // caption.push_str(&format!(": {:.2}% ({curr} of {total})", model.fraction)); + } + + caption + }), + None => None + } + } + } + + fn init( + init: Self::Init, + root: &Self::Root, + _sender: ComponentSender, + ) -> ComponentParts { + let model = ProgressBar { + fraction: 0.0, + caption: init.0, + downloaded: None, + visible: init.1 + }; + + let widgets = view_output!(); + + ComponentParts { model, widgets } + } + + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { + tracing::debug!("Called components list event: {:?}", msg); + + match msg { + AppMsg::Reset => { + self.fraction = 0.0; + self.downloaded = None; + self.caption = None; + } + + AppMsg::UpdateCaption(caption) => self.caption = caption, + + AppMsg::UpdateProgress(curr, total) => { + self.fraction = curr as f64 / total as f64; + + self.downloaded = Some(( + prettify_bytes(curr), + prettify_bytes(total) + )); + } + + // TODO: add translation + AppMsg::UpdateFromState(state) => { + match state { + InstallerUpdate::CheckingFreeSpace(_) => self.caption = Some(String::from("Checking free space...")), + InstallerUpdate::DownloadingStarted(_) => self.caption = Some(String::from("Downloading...")), + InstallerUpdate::UnpackingStarted(_) => self.caption = Some(String::from("Unpacking...")), + + InstallerUpdate::DownloadingProgress(curr, total) | + InstallerUpdate::UnpackingProgress(curr, total) => { + self.fraction = curr as f64 / total as f64; + + self.downloaded = Some(( + prettify_bytes(curr), + prettify_bytes(total) + )); + } + + InstallerUpdate::DownloadingFinished => tracing::info!("Downloading finished"), + InstallerUpdate::UnpackingFinished => tracing::info!("Unpacking finished"), + + InstallerUpdate::DownloadingError(err) => tracing::error!("Downloading error: {:?}", err), + InstallerUpdate::UnpackingError(err) => tracing::error!("Unpacking error: {:?}", err) + } + } + + AppMsg::SetVisible(visible) => self.visible = visible + } + } +} diff --git a/src/ui/components/version.rs b/src/ui/components/version.rs index cf70532..ee6ac23 100644 --- a/src/ui/components/version.rs +++ b/src/ui/components/version.rs @@ -5,17 +5,17 @@ use adw::prelude::*; use gtk::glib::clone; +use super::progress_bar::AppMsg as ProgressBarMsg; + use anime_launcher_sdk::config; use anime_launcher_sdk::anime_game_core::installer::installer::*; use std::path::PathBuf; -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum VersionState { Downloaded, - Loading, - Downloading(u64, u64), - Unpacking(u64, u64), + Downloading, NotDownloaded } @@ -28,13 +28,16 @@ pub struct ComponentVersion { pub download_folder: PathBuf, pub show_recommended_only: bool, - pub state: VersionState + pub state: VersionState, + + pub progress_bar: Controller } #[derive(Debug)] pub enum AppMsg { ShowRecommendedOnly(bool), - PerformAction + PerformAction, + SetState(VersionState) } #[relm4::component(pub)] @@ -62,6 +65,9 @@ impl SimpleComponent for ComponentVersion { add_css_class: "flat", set_valign: gtk::Align::Center, + #[watch] + set_visible: model.state != VersionState::Downloading, + connect_clicked => AppMsg::PerformAction } } @@ -81,7 +87,11 @@ impl SimpleComponent for ComponentVersion { download_folder: init.1, show_recommended_only: true, - state: VersionState::NotDownloaded + state: VersionState::NotDownloaded, + + progress_bar: super::ProgressBar::builder() + .launch((None, false)) + .detach() }; // Set default component state @@ -91,12 +101,17 @@ impl SimpleComponent for ComponentVersion { VersionState::NotDownloaded }; + // Set progress bar width + model.progress_bar.widgets().progress_bar.set_width_request(200); + let widgets = view_output!(); + widgets.row.add_suffix(model.progress_bar.widget()); + ComponentParts { model, widgets } } - fn update(&mut self, msg: Self::Input, _sender: ComponentSender) { + fn update(&mut self, msg: Self::Input, sender: ComponentSender) { tracing::debug!("Called component version [{}] event: {:?} (state = {:?})", self.title, msg, self.state); match msg { @@ -108,6 +123,9 @@ impl SimpleComponent for ComponentVersion { let path = self.download_folder.join(&self.name); if path.exists() { + // To hide main button while it's deleting compontent's folder + self.state = VersionState::Downloading; + // todo std::fs::remove_dir_all(path).expect("Failed to delete component"); @@ -124,15 +142,33 @@ impl SimpleComponent for ComponentVersion { installer.set_temp_folder(temp); } - // self.state = VersionState::Loading; + self.state = VersionState::Downloading; - // todo sus + let progress_bar_sender = self.progress_bar.sender().clone(); + + #[allow(unused_must_use)] std::thread::spawn(clone!(@strong self.download_folder as download_folder => move || { - installer.install(download_folder, |status| { - match status { - Update::UnpackingFinished | Update::UnpackingError(_) => println!("sus"), - _ => (), + progress_bar_sender.send(ProgressBarMsg::Reset); + progress_bar_sender.send(ProgressBarMsg::SetVisible(true)); + + installer.install(download_folder, move |state| { + match &state { + Update::UnpackingFinished | + Update::DownloadingError(_) | + Update::UnpackingError(_) => { + progress_bar_sender.send(ProgressBarMsg::SetVisible(false)); + + sender.input(AppMsg::SetState(if let Update::UnpackingFinished = &state { + VersionState::Downloaded + } else { + VersionState::NotDownloaded + })); + }, + + _ => () } + + progress_bar_sender.send(ProgressBarMsg::UpdateFromState(state)); }); })); } @@ -141,6 +177,8 @@ impl SimpleComponent for ComponentVersion { _ => () } } + + AppMsg::SetState(state) => self.state = state } } }